We are apologize for the inconvenience but you need to download
more modern browser in order to be able to browse our page

Download Safari
Download Safari
Download Chrome
Download Chrome
Download Firefox
Download Firefox
Download IE 10+
Download IE 10+

LCTF 2017 BeRealDriver 非官方Writeup

最近忙里偷闲,在成山的作业中挤出时间来打LCTF超心塞啊(来快来表扬本宝宝一下)

然后我作为逆向选手,自然是挑了一道题开始啃。当时全场总共三道逆向题,其他队员已经在做了两个,留下一个BeRealDriver

那自然我就选这个了。然而很不幸,这个是全场最难的逆向题,然后我,一下做了12小时。

0x01 初步分析

10:20 刚起床拿到题之后,我先拖进了IDA进行初步分析,程序被stripped了,但是导入函数的名称仍然存在。

IDA中g++的FLIRT库非常少,所以根本匹配不到相应的函数。大概看了一下main函数,也没有太长。不过由于大量使用类,反编译结果难以理解。同时特别高的优化导致程序中硬编码了许多不知所云的常量,让人有些头晕(啊啊啊快扶我起来)

不过我们有OpenCV的完整文档和全部源码。决定开始硬啃。

10:40 困惑ing,暂时去看了别的题

11:30 决定进行调试

想都不想就直接apt install,结果发现apt源里的opencv完全处于broken状态,根本没法安装。

网上都说要下载源码编译,但是感觉我实在是懒,不想手动下载编译。于是进行了半个多小时的搜索,尝试了install-opencv.sh,然后毫无疑问失败了。接着利用脚本下载好的源码继续配置并编译,然而还是报错。。我初步猜测是版本的问题。

又进行了半个小时的搜索。。

终于,我屈服了,开始下载github的release

然而。。联通的垃圾网络每次下载到70MB的时候会重置链接,导致下载中断…..换代理重试几次后,我决定用我的服务器做一下中转,先下到我的服务器上,然后再下载到我的电脑上,于是opencv终于下载完了。

14:00 继续静态分析

等着编译、下载的时间里,我开始尝试还原整个算法流程。

初步发现程序会释放两个图片文件,利用IDA的导出功能写到文件查看后,发现是很让人匪夷所思的图片。

图片高达1.8M,看了一下是2000*6000的巨大图片(难怪4k屏都看不全)

然后另一张图只有49k

然后呢程序会通过cv::imread读取释放的文件,然后开始操作。

但是呢,我看到的程序是这样的。中间都是类的初始化操作。由于优化开的太高,大量类的构造函数被内联展开,导致类之间的边界很模糊,难以分析。

这时候就得祭出大招1:连蒙带猜

v57和v58在栈帧中是相邻的两个变量,很自然让人联想到两个变量同属于一个类,v57被初始化为一个数,v58在上面被operator=为v157,而v157正是我们读入的图片mat。

猜出这个之后就简单了许多,只需要找到相邻的变量即可找到背后对应的数据。

然后就是出现了虚函数。没法调试怎么确定虚函数呢。

这时候就得祭出大招2:快速匹配已知函数

通过cv::Mat::zeros可以看出v157是一个mat的instance,那么从Mat这个类的文档里面翻虚函数,注意到最后一个数是0xFFFFFFFF,很容易联想到-1,直接搜索-1即可看到

观察发现参数基本吻合,那么自然就是assignTo了。

接着发现同一个位置被多次重复使用为不同类型的变量

这时候就得祭出大招3:按块分析

观察到v63先试被用作Mat,又是被用作Size。既然编译器敢于这样直接重用空间,那么说明这个变量的生命周期已经结束。只需要在中间把程序分为两块分析就可以了。

15:20 初步完成分析

大概总结了一下算法

解释一下就是:

输入的mapOffset文件与第一张图异或后做一下变换,opOffset与第二张图异或,两个结果要有90%的相似度。

接着opOffset要与一系列的方块做比较,分别有不同的准确度要求。

0x02 深入分析

发现img1的结果要与img2的结果做比较,然而当时看图片的时候,第二张图不管是从大小上看还是从图案上面看都让人觉得很小对不对QAQ,于是以为只有600*1800左右的像素的x(忘记自己是4k屏了啊摔)

于是就开始了无限的困惑。。。想了一会,正好去安装opencv。

15:40 与出题人第一次交流,说了我的困惑,

然而并没有得到解答,只得到了“很接近”的指示。。

15:50 继续尝试编译OpenCV

由于实验室项目,我替换了libtiff的版本,导致了在链接的时候,程序找不到tiff库的符号。

尝试apt remove再apt install,无果,于是发现完全没法继续编译。

当即决定直接恢复快照。

IDA方面继续分析doSth,发现使用Canny寻边,然后分别生成了两组点存入vector中,之后使用fillPoly填充,初步推测填充颜色为(255,0,0)。。然而并没能分析出来算法的原理

16:30 OpenCV第一次编译成功

开心的我立刻make install,发现程序还是跑不起来,接着就发现我编译的是之前的脚本下载下来的OpenCV 3.2。

17:00 重整旗鼓,OpenCV 3.3编译完成

我又按下了make install,发现程序还是跑不起来,不过这回缺的是aruco库。开始Googling,并没有查出结果,怀疑是自己配置的时候有误,重新编译*3并install后,仍然失败。。。

顺便开始学习opencv-python的各种用法。

18:00 发现需要OpenCV的extra库

Google后发现ArUco在opencv-contrib库里面,而且opencv-contrib需要在编译opencv的时候配置进去,(心态崩了),继续下载opencv-contrib,用巨慢的速度下载中。。

顺便开始学习opencv-python的各种用法。发现了各种

18:30 重新配置并编译

继续搞,按照README.md中的指示define了相应的变量,心想终于要完了,然后make install后,发现程序开始少freetype(心态更崩了),再回去Google一下

18:45 找到FreeType module的安装指南

发现需要安装harfbuzz和freetype,一波搜索+补全之后得知ubuntu下的包名是libfreetype6-dev和libharfbuzz-dev。赶紧安装重新编译,还是少,发现自己忘记重新configure了,赶紧重新编译,make install

19:00 编译成功,开始跑路

19:30 跑路途中开始调试

终于可以用IDA调试了,迅速dump处doSth后的mat,利用以下的python语句转为文件

import numpy as np,cv2

f = open(“123”,’rb’)

a = f.read()

cv.imwrite(‘123.jpg’, np.array([ord(a[i]) for i in xrange(36000000)]).reshape(6000,2000,3))

打开后发现是纯黑白的图片,中间有一个白道。结合提示的https://en.wikipedia.org/wiki/Lane_departure_warning_system,立刻想到是寻路并标白的算法。于是发现填充颜色是(255,255,255)(之前的分析出错了啊啊啊OAO)

然后想到把路变宽,程序直接segfault

20:10 想到把路标变弯

暴力用ps的Ctrl+T一段一段拼出来轨道

发现程序还是出现segfault

0:40 与出题人交流,怀疑是限制道路宽度

还是segfault

1:00 与出题人提供的道路图比较+暴打出题人

怀疑是有连续性要求,于是

2:00 出题人去睡觉了,开始赶作业x

9:00 作业做完,开始睡觉

12:00 怀疑上方的界限没有正确修补

懒得修改了,直接用出题人给的没异或的文件进行异或,发现仍然不对

16:00 与出题人联调

Beyond Compare发现两张图片基本吻合,然而程序仍然segfault

出题人说程序找不到右车道

16:20 心态彻底崩了,用出题人的异或后的文件提交,得到了flag

LCTF{We1c0m3_to_ldw_wor1d_And_is_th3_malf0rmed_fi13_1337_en0ugh_f0r_me}

16:35 发现自己忘记将opOffset文件保存为灰度

仍然失败,出题人调试后发现仍然不是opOffset的锅

17:00 双方表示自己都不能解释为什么

该题目难度确实较高。(弃疗了23333)

0x03 总结

这题总历时长达31小时,

除去划水or摸鱼的时间,至少这道题做了15个小时,可以说是我打CTF以来用时最久的逆向题。

不过确实认识了Silver这个可爱的小伙伴~

由他所出的BeRealDriver和NuclearBomb都是相当优秀的思路。

我呢也在之前相应的研究过CUDA和OpenCV,没想到仍然没能成功解出。看着队友把队伍拉到了第一,不禁让人有些愧疚x