对提供的 CrackmeTest.apk 进行逆向分析,程序含有反调试机制(加壳),通过静态补丁反反调试(去壳),再动态调试获取其中密码。
目录
环境
基础
实验内容
静态分析
动态分析
反反调试
再动态调试
补充
环境
环境:Windows11,java环境。
工具:IDA_Pro_v7.0 , AndroidKiller_V1.2 , 雷神模拟器,adb工具。
基础
壳目的:反调试、反静态分析、反篡改、许可证管理。
壳种类:压缩壳、加密壳(防止逆向)
当一个Android应用加载一个原生库System.loadLibrary("crackme")时,Java层调用 System.loadLibrary,然后加载libcrackme.so动态链接,Android Native库加载执行的三个级别如下:
系统级:-> libdl.so ->linker
外壳级:.init ->.init_array -> JNI_OnLoad
应用级:Java_com_xxx函数(SecurityCheck)
可在以上位置设置加壳,实验样本是加在JNI_OnLoad的简单保护壳(“弱壳”),外壳级,对JNI_OnLoad下断点。
若 JNI_OnLoad 被加壳,应在以下位置设断点:
-
.init 、.init_array
-
系统级.so中的初始化函数(例如在libc.so的fopen、strcmp,或者链接器的__dl_init函数)
壳保护机制:在 libcrackme.so 的 JNI_OnLoad函数中,通过ptrace进行自我跟踪检测。如果发现TracerPid不为0(即程序正在被调试),就立即终止进程。
Android APK 文件结构:
AndroidManifest.xml:应用“身份证”和“权限申请单”
resources.arsc:资源索引表,将所有资源ID映射到具体内容
res:资源目录,存放具体资源文件
res/values/strings.xml:保存所有文本字符串
res/values/public.xml:固定资源ID的映射关系
Classes.dex:应用代码逻辑,由Java编译成
Lib/ 原生库目录:存放.so文件,C/C++编译的本地库
实验内容
静态分析
-
初始在模拟器中运行程序,随意输入密码显示验证码校验失败。
-
使用 AndroidKiller 反编译 APK,查看AndroidMainfest.xml中的activity类,找到入口Activity(MainActivity),以及在res-values-strings.xml和public.xml搜索成功提示等,未找到字段。
-
使用 jadx-gui 分析 Java 代码,查看MainActtivity和ResultActivity的java代码,确定密码校验的关键函数securityCheck。
-
用IDA静态分析libcrackme.so文件定位到securityCheck函数,对其分析可知apppwd即加密后off_628C(初始"aWojiushidaan"),与inputpwd即用户输入的密码比较,相等则校验成功。
- 确定securityCheck偏移地址:0x000011A8,JNI_OnLoad偏移地址00001B9C。
动态分析
-
adb连接模拟器后将android_server上传后运行失败,换成android_x86_server后运行成功,开始监听,并将23946端口转发。
- 模拟器开启root权限,开启adb远程调试权限,在IDA中附加进程com.mytest0.crackme开始远程调试。
- 搜segement找libcrackme.so发现无可执行的.so文件,modules搜索crackme得到的入口地址:0B244000
-
入口地址加上securityCheck的偏移地址000011A8得到绝对地址: F0B2451A8,搜索绝对地址添加断点调试。
-
发现屏幕显示FFFFFFFF,程序有反调试机制,查看ptrace确认TracerPid非零。
反反调试
-
调试模式启动程序,重新打开IDA调试挂载的程序。
-
libcrackme.so的基地址0B244000+JNI_OnLoad偏移地址00001B9C得到绝对地址0B245B9C,跳转到这里添加断点逐步调试。
-
发现到对应相对地址00001C58处退出,BLX R7这个调用最终会执行ptrace检测,如果发现被调试就退出,静态调试修改libcrackme.so对应十六进制文件,修改该指令为空指令。
-
将修改后的.so文件替换原来的.so文件,用AndroidKiller重新编译打包,再次上传,重新调试。
再动态调试
再次挂载程序,IDA附加进程,基地址0B245000(动态链接,每次都会变),加上securityCheck函数偏移地址000011A8得到绝对地址0B2451A8添加断点调试,随意输入密码放入寄存器,一直F8执行,直到0B2451A8处发现R1中存放输入的密码,与R3中真正的密码比较,查看内存中R3寄存器处的对应值得到真实的密码aiyou,bucuoo。
补充
静态脱壳(适用于简单压缩壳),有些压缩壳的算法是公开的,可以直接用专门的脱壳工具(如UPX -d)进行解压,恢复出原始文件,但对加密壳完全无效。
动态脱壳是最主流的方法
- 加载程序(壳开始执行)
- 执行壳的解密代码(绕过反调试)
- 等待解密完成(定位原始程序的真正入口OEP)
- 抓取内存镜像(Dump Memory)
- 修复文件(内存镜像重建输入表/节区)
- 得到脱壳后的原生程序