5、静态分析工具实战:JADX与GDA反编译器使用技巧、代码混淆与反混淆初步
好,咱们今天聊点实在的。静态分析,说白了就是不动手跑代码,直接看源码。你可能会问:“App都加壳了,我看什么?” 嗯,别急。加壳只是第一道门,脱壳之后,你面对的就是一堆DEX文件。这时候,JADX和GDA就是你的左膀右臂。
我个人习惯,先拿JADX扫一遍整体结构,再用GDA做深度分析。为什么?JADX的树形目录清晰,适合看逻辑;GDA的交叉引用强,适合追数据流。今天我就把这两个工具的使用技巧,以及代码混淆的初步对抗方法,一次性讲透。
5.1 JADX:快速定位核心逻辑
JADX是我用得最多的反编译器。它能把DEX直接转成可读的Java代码,而且支持搜索、书签、反混淆。我在项目中遇到过好几次,客户说“这个App逻辑很简单”,结果一打开JADX,发现几千个类……嗯,这时候搜索功能就救命了。
5.1.1 常用操作技巧
- 全局搜索(Ctrl+Shift+F):搜字符串、类名、方法名。比如搜“http://”或“api”,快速定位网络请求。
- 跳转到声明(F4):看到某个方法调用,直接跳过去看实现。
- 反混淆开关:在菜单栏勾选“反混淆”,JADX会自动重命名混淆后的类和方法。虽然不能100%还原,但至少能看出点规律。
- 导出Gradle项目:File -> Save as Gradle project,直接导出可编译的工程。我经常用这个功能做二次开发或动态调试。
5.1.2 代码示例:定位关键方法
假设我们要找登录逻辑。在JADX里搜“login”或“password”,你会看到类似这样的代码:
public class a implements b {
public String c(String str) {
// 实际是加密密码
return d.a(str);
}
}
你看,类名和方法名都被混淆了。但通过搜索字符串“password”,我们能找到调用点。这时候右键 -> “查找用例”,就能看到谁调用了这个方法。这就是静态分析的威力——不用跑代码,就能画出调用链。
5.2 GDA:深度数据流分析
GDA是我最近两年才用顺手的工具。它比JADX强在哪?交叉引用和寄存器分析。JADX适合看逻辑,GDA适合看数据怎么流动的。我记得有一次分析一个恶意App,JADX里看到的代码全是乱序的,但GDA的CFG(控制流图)直接帮我理清了执行路径。
5.2.1 GDA核心功能
| 功能 | 说明 | 我的用法 |
|---|---|---|
| 交叉引用(Xref) | 查看某个方法被谁调用,调用了谁 | 追加密函数的调用链 |
| 寄存器分析 | 显示每个寄存器的赋值和引用 | 分析混淆后的字符串拼接 |
| 控制流图(CFG) | 图形化展示代码执行路径 | 理解混淆后的跳转逻辑 |
| DEX编辑器 | 直接修改DEX字节码 | 临时patch掉反调试 |
5.2.2 实战:用GDA分析字符串混淆
很多App会把关键字符串拆成多个片段,运行时拼接。比如:
String s1 = "aGVsbG8="; // base64编码的"hello"
String s2 = "d29ybGQ="; // base64编码的"world"
String result = new String(Base64.decode(s1 + s2, 0));
在GDA里,你可以右键点击字符串变量,选择“查看交叉引用”,就能看到这个字符串在哪里被使用。然后顺着寄存器追踪,找到拼接和decode的逻辑。我曾经用这个方法,从一个金融App里挖出了硬编码的AES密钥。
5.3 代码混淆与反混淆初步
混淆,说白了就是让代码变得“不像人写的”。常见的混淆手段有:
- 名称混淆:类名、方法名变成a、b、c
- 控制流混淆:插入垃圾代码、打乱执行顺序
- 字符串加密:关键字符串运行时解密
- 反射调用:通过字符串动态调用方法
你想想看,如果代码里全是“a.b(c)”这种调用,你怎么分析?嗯,这时候就需要反混淆了。
5.3.1 手动反混淆技巧
我一般不依赖自动化工具,因为混淆规则千变万化。我的做法是:
- 先看入口点:比如Application的onCreate,或者Activity的onCreate。从这里开始,顺着调用链往下走。
- 关注字符串解密函数:如果看到类似“decryptString”或“getString”的方法,八成是解密函数。把它单独拎出来,写个脚本跑一遍,还原所有字符串。
- 利用JADX的反混淆插件:JADX有个“Deobfuscation”插件,能自动重命名混淆类。虽然效果一般,但至少能把“a”、“b”改成“Class1”、“Class2”,方便阅读。
核心思路:混淆只是增加了阅读难度,没有改变代码的逻辑。只要你能找到关键的数据流和控制流,混淆就是一层窗户纸。
5.3.2 实战:反混淆一个字符串加密函数
假设我们在JADX里看到这样的代码:
public static String a(String str) {
char[] cArr = str.toCharArray();
for (int i = 0; i < cArr.length; i++) {
cArr[i] = (char) (cArr[i] ^ 0x7F);
}
return new String(cArr);
}
这明显是一个异或解密函数。我们只需要找到所有调用“a”的地方,把加密后的字符串提取出来,然后用同样的算法解密。我一般写个Python脚本:
def decrypt(s):
return ''.join(chr(ord(c) ^ 0x7F) for c in s)
# 从JADX里复制出来的加密字符串
encrypted = ["\u0080\u0081\u0082", "\u0083\u0084\u0085"]
for e in encrypted:
print(decrypt(e))
你看,几行代码就把所有字符串还原了。这就是反混淆的入门操作。
5.4 知识体系图
下面这张图,是我自己总结的静态分析知识体系。你可以把它当作一个检查清单,看看自己掌握了哪些。
这张图里,JADX和GDA是左右护法,混淆对抗是核心目标。你不需要一次性掌握所有功能,但至少要知道:遇到什么场景,该用哪个工具。
5.5 避坑指南
最后,分享几个我踩过的坑:
- 别迷信自动化反混淆:我曾经用某个工具一键反混淆,结果把正常的类名也改了,导致代码完全不可读。手动分析+脚本辅助,才是正道。
- 注意DEX版本:JADX和GDA对Android 10以上的新DEX格式支持不够好。如果遇到解析失败,试试用Android SDK里的d8工具重新打包。
- 别忽略资源文件:很多关键信息藏在AndroidManifest.xml或res/values/strings.xml里。比如,我曾在strings.xml里找到过硬编码的API密钥。
公众号:蓝海资料掘金营,微信deep3321