一次 CTF 之旅

有好几周没有发文章了,具体原因是上上周末去了团建,上周末主要在研读《Android 软件安全与逆向分析》一书。我报名参加了公司第一次 CTF 比赛,结果很幸运组队时抱上了几个大腿进了决赛。本着绝不坑队友的原则,精心准备了一把。结果最后还是坑了,我那道 Android 逆向的题还是没有做出来。虽然比赛已经结束,但心有不甘,这周末又继续搞了两天这题,代码逆向和解题思路都搞的差不多,但是似乎 Native 层的代码理解有一点点偏差。昨晚 11 点多不得已向比赛组委会出题大神请教,解题方法居然没有我想的复杂,我还是太 Naive 了。

首先介绍一下什么是 CTF。

CTF(Capture The Flag)夺旗赛,在网络安全领域中指的是网络安全技术人员之间进行技术竞技的一种比赛形式。CTF 起源于 1996 年 DEFCON 全球黑客大会,以代替之前黑客们通过互相发起真实攻击进行技术比拼的方式。

这次是公司第一次举办 CTF 的比赛。而我在这之前,对 CTF 也是了解甚少。组织这次比赛的是公司 T5 大神所在的实验室,本着学习的态度,就尝试报了名。刚好队里缺 Android 逆向的小伙伴,所有我就专攻 Android 题,其他题交给其他的小伙伴。

由于是第一次举办,采用了比较初级的「解题模式」。主要分为几类:隐写、逆向、Web、Mobile。举个隐写的例子就明白了。比如让你在一个浏览器页面里找藏在里面的 Flag,只要查看源码:

<p>
找到第一个 Flag!
<!-- Flag{WelcomeToCTF} -->
</p>

WelcomeToCTF 就是其中的 Flag。当然,这种属于最初级的题,更多的隐写 Flag 会藏在更深的地方,比如图片、MP3、PDF 等等文件里,你需要做各种数据抽离,找出其中的不同,最终发现写在里面的隐藏内容。

对 CTF 有兴趣的同学可以自行去了解更多,这里不一一介绍了。一是我本身也专研不深,二是这次比赛我只搞了 Mobile 题。所以,接下来就只聊一聊 Mobile 题。

Mobile 题主要是 Android 的逆向,Crack 破解或者是找出其中漏洞。同样举个简单的例子,比如破解一个需要序列号的 APP,通过逆向,你找到它里面判断序列号是否正确的地方,把判断条件改反,或者修改判断条件,让它永远返回 True。这样就简单的绕过了验证。

而实际情况会比这种复杂的多。所以要掌握好 Android 逆向,需要了解 Android 虚拟机的原理及 Dalvik 指令集,熟悉 DEX 文件的格式及反汇编语言 smali。能读懂和理解 ARM 汇编(寄存器和各种寻址)。由于 Java 层的破解相对容易,很多 APP 会把关键功能放在 Android NDK 的 so 里。所以,分析反汇编的 so 代码也是必须要掌握的。

Android 逆向主要分静态分析和动态调试。静态分析就是查看逆向的代码,找出其他的关键所在,然后修改代码,重新打包、签名,得到最终结果。动态调试是指使用工具动态调试 APP,可在运行时下断点,查看或修改寄存器的值,Dump 一段内存等等。

常见的 Android 加壳过程就是通过 so 加载一个二进制文件,然后解密出真正的 DEX 文件。通过加壳,把真实的业务代码隐藏了起来,Java 层的逆向你看不到真实的业务逻辑代码,因为只有一个壳。破解的思路也很简单,动态调试 APP,运行到解密完 DEX 的地方(或者是定位到关键函数,比如加载新 DEX 的地方),将 DEX 文件所在的内存 Dump 出来,然后再去分析 Dump 出来的真实 DEX 文件。

魔高一尺道高一丈。既然动态调试可以这么容易把隐藏的 DEX Dump 出来,那么反破解的方法就是加入「反调试」的逻辑。比如,不断检查 App 的 TracerID,一旦发现有人在调试它,就启动自毁程序。于是,动态调试的难度又增加了,这时候就需要用到「反反调试」。找出其中「反调试」的代码,通过静态分析,想尽一切办法绕过反调试。

这其中过程说的似乎轻描淡写,实际操作起来却并非那么容易。我体验一段时间之后的感觉是,首先基础要扎实,上看得了 Java,下看得了 smali,C++,arm 汇编,其次,工具使用要熟练。已经很少有人徒手破解了,因为有很多现成的工具可用。比如:APKTool、jd-gui、IDA Pro…… 其中的 IDA Pro 堪称神器,熟练掌握 IDA Pro 几乎成功了一半(瞎说的,因为我不是很熟)。第三,要有敏锐的思路,分析出其中的关键路径,寻出破解的方法。

基础方面,我恶补了《Android 软件安全与逆向分析》一书。写的确实好,讲的非常系统,入门的首选。我买的纸质书,看完的感受是看此类书还是纸质的比较好。比赛结束后又入了一本《漏洞战争》,最新出炉的安全大作,作者就是文章开头我提到的这次 CTF 组委会出题的大神。

实践部分我做的就不好了,工具的不熟练使用成了我的一大瓶颈。首先是环境准备部分,我就入了一个大坑。搞 Android 逆向,使用手机真机调试是必不可少的,而且手机必须是 root 过的,同时必须准备好多个 Android API 版本的手机(因为有的题的 APK 对 API Level 有要求。)别人已经在破解调试了,而题目的 APP 在我的手机上正常跑起来却是一个问题。(汗~)

当然,也是可以使用模拟器的。这又要说到我遇到的另外一个大坑,因为习惯了 Genymotion 模拟器(性能很好),然后发现动态调试使用的 android_server 在 Genymotion 里根本跑不起来。于是当初我就放弃了模拟器方案,最后在这周末的时候发现,使用 Google 原生的模拟器,选择 arm 版本的 SDK 就可以了!(哭~)

当然,这都没什么。还有一个更大的坑。因为比赛我带的是我的 Mac,而破解工具大多数是在 Windows 里运行的。所以我就选择在 Mac 里装 Windows 虚拟机(VirtualBox),然后就掉坑里了。默认的 NAT 方式,虚拟机里是不能访问宿主机的,所以根本没办法进行动态调试。比赛之前我早有考虑,所以简单的切换到 Bridge 模式就可以解决。然而我还是太 Naive 了,公司里是不允许用 Bridge 方式的,使用后根本就上不了网。

我不太可能把 Mac 再装个 Windows 双系统,而且我也不会那样做。最后,为了能在虚拟机里使用 IDA Pro 动态调试 Android,我折腾出如下方法:虚拟机同时设置 NAT 模式和 Host-Only 模式,NAT 模式是为了能够上网,Host-Only 模式是为了能访问宿主机器。有了 Host-Only 模式,可以访问到宿主机了,但是还是没办法直接连接到 android_server 的调试端口。

原理是这样的,android_server 的调试端口 23946 是在手机上开启的,为了在 PC 上能连接这个端口,所有的教程都教你需要输入如下指令:

adb forward tcp:23946 tcp:23946

即,将 PC 机上的 23946 端口的数据全部转发到手机的 23946 端口上。在虚拟机的 IDA Pro 里,我直接设置主机的 IP 和端口,是无法连接上的,因为主机只负责转发来自 127.0.0.1 的 23946 端口的数据。于是,我拿出了 nginx 杀手锏,开启了一个监听端口 23950,然后配置成自动转发到本地的 127.0.0.1:23946 上:

stream {
    upstream dbg_socket {
        hash $remote_addr consistent;
        server 127.0.0.1:23946;
    }

    server {
       listen 23950;
       proxy_connect_timeout 180s;
       proxy_timeout 3600s;
       proxy_pass dbg_socket;
    }
}

然后在 IDA Pro 里设主机的 IP 和端口 23950,it works!

第一次参加 CTF 的我真是有点伤不起啊。如果再来一次,我会告诉自己,带上几台真机,放下你的 Mac,老老实实的带个 Windows 本子去吧。

至于解题思路方面,需要平时多关注一些安全相关的文章,同时要有开放性的思维。

这就是我这次 CTF 之旅的血泪史,虽然最后我的成绩不尽人意,但还是收获满满。主要是队友给力,通过比赛,认识了一群 Nice、聪明、激情又好学的朋友,你可以想象我的队友在刚刷出题目 5 秒钟就告诉我们解出来了吗?这就是 CTF!

微信扫一扫交流

作者:CoderZh
微信关注:hacker-thinking (一个程序员的思考)
本文出处:https://blog.coderzh.com/2016/07/11/a-ctf-tour/
文章版权归本人所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。