第一次接触AliCrackme这个样本时,我就被它精巧的反调试设计吸引了。这个来自2014年阿里安全挑战赛的题目,虽然年代有些久远,但其中涉及的反调试技术至今仍具有典型意义。记得当时我在真机调试时,刚附加进程程序就闪退,这种挫败感反而激起了我的斗志。
要完整分析这个样本,我们需要准备以下环境:
建议先使用jadx查看APK的Java层逻辑。这个Crackme的验证逻辑很清晰 - 用户输入密码后,会调用libcrackme.so中的securityCheck()函数进行校验。重点在于这个native函数,它被各种反调试机制保护着,我们需要像剥洋葱一样层层破解。
当我按照常规流程启动android_server,用IDA附加进程时,发现点击运行按钮(F9)后程序立即闪退。这种情况在逆向加固应用时很常见,说明存在某种反调试检测。
通过adb logcat查看日志,能看到类似"Detected debugger, exiting..."的提示。更直接的证据是查看进程的TracerPid值:
bash复制adb shell
su
cat /proc/`pidof com.yaotong.crackme`/status | grep TracerPid
果然显示有调试进程附加。这就是典型的ptrace反调试,原理是利用Linux内核会为被调试进程设置TracerPid的特性。
关键问题是:这个检测代码放在哪里?通过分析so的加载流程,我们知道有两个关键节点:
经验告诉我,这类检测通常放在JNI_OnLoad中。但常规调试方式已经来不及拦截这个函数了,我们需要特殊手段。
要让程序停在JNI_OnLoad处,需要使用调试模式启动APP:
bash复制adb shell am start -D -n com.yaotong.crackme/.MainActivity
这时APP会显示"Waiting For Debugger"。此时IDA附加进程后,还需要用jdb恢复执行:
bash复制adb forward tcp:8700 jdwp:<pid>
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
这个技巧让我成功在JNI_OnLoad入口下了断点。单步跟踪发现,程序通过pthread_create创建了检测线程,不断检查TracerPid值。
最直接的破解方法是修改so文件中的检测代码。找到关键跳转指令BLX R7,在Hex View中将其改为00 00 00 00(ARM nop指令)。记得用IDA的Patch功能保存修改到原so文件。
这里有个细节:修改后的so需要重新打包到APK中。我习惯用AndroidKiller工具完成重打包和签名:
绕过反调试后,终于可以安心分析securityCheck()了。在伪代码中看到有趣的逻辑:
c复制if (strcmp(input, v6) == 0) {
// 验证通过
}
v6这个变量指向off_628C地址,但静态分析时这里显示为null。这正是需要动态调试的原因 - 某些值只在运行时确定。
我在strcmp调用处下断点,运行后输入测试字符串"1234"。当断点触发时,查看寄存器发现R2寄存器保存着真正的flag:"aiyou,bucuoyoo"。
有些Crackme会使用字符串加密,这时需要跟踪解密过程。在这个样本中,虽然字符串是明文的,但建议练习如何通过xrefs追踪字符串的加载过程:
每次修改APK的debuggable属性很麻烦,我更喜欢一劳永逸的方法 - 修改系统的ro.debuggable属性。这需要root权限:
bash复制adb push mprop /data/local/tmp
adb shell chmod 755 /data/local/tmp/mprop
adb shell /data/local/tmp/mprop ro.debuggable 1
重启后需要重新设置。这个方法让所有APP都可调试,适合逆向分析环境。
除了TracerPid检测,常见的反调试还有:
对抗这些检测需要更精细的调试技巧,比如修改端口号、隐藏调试器特征等。建议在掌握基础后,再学习这些进阶内容。
经过这次完整的逆向过程,我总结了几个关键点:
记得第一次成功获取flag时,那种成就感至今难忘。逆向工程就是这样,遇到问题->分析原因->找到解决方案的循环,正是技术提升的过程。建议新手从这个案例入手,逐步挑战更复杂的样本。