混淆后的代码为什么难读
ProGuard/R8 混淆后,类名方法名全变成 a.b.c、a.a.a——不同类同名、同类不同方法同名,全局搜索基本报废。但混淆只改名字,改不了结构:字符串常量、API 调用关系、继承体系都还在。反混淆的核心思路就是用结构特征替代名字特征。
技巧一:字符串是永不混淆的路标
无论怎么混淆,字符串常量必须保留(运行时要真用)。逆向第一步永远是搜字符串:
# 在 jadx 里全局搜(Text search)
"sign" → 找签名相关
"token" → 找鉴权相关
"AES/CBC/PKCS5Padding" → 直接定位加密代码
"/api/" → 找接口定义
"deviceId" → 找设备指纹搜到字符串后按 X(交叉引用)查谁在用——使用点往往就是业务逻辑所在,类名再混淆也无所谓。
技巧二:从入口顺藤摸瓜
混淆改不了 AndroidManifest.xml(系统要按原名找组件)。从 Manifest 里的入口 Activity 开始:
- Manifest 找到主 Activity(即使类名混淆,路径是明确的)
- 进 onCreate 看它初始化了什么
- 网络层、加密层都会在启动链上露面
四大组件、Application 入口都是同理——Manifest 是混淆代码的地图。
技巧三:批量重命名,自己建立秩序
jadx 支持重命名(N 键),且重命名会保存到项目文件。分析时养成习惯:
- 认出
a.b.c是签名工具类 → 重命名SignUtils - 认出
a.a(String)是 md5 → 重命名md5
半小时后,你面对的就不再是乱码而是自己命名的清晰结构。重命名的另一个好处:交叉引用会跟着更新,越分析越清晰。
技巧四:用 API 调用反查业务类
想找"谁在做网络请求",别搜类名,搜 API:
# 搜 OkHttp 的使用
okhttp3.Request.Builder → 查这个类的交叉引用 → 所有构造请求的地方
# 搜加解密 API
javax.crypto.Cipher → 交叉引用 → 所有加解密点
# 搜 SharedPreferences
getSharedPreferences → 找本地存储的 token/配置系统 API 类名不会被混淆,它们是锚点,业务类是挂在锚点上的藤蔓。
技巧五:对付 R8 的激进优化
R8 比 ProGuard 更狠:内联短方法、删除未用代码、合并类。表现为:
- 方法找不到:可能被内联了。搜方法内的特征字符串,在调用方里找展开的逻辑
- 类消失了:可能被合并。看继承关系,找"胖得异常"的类
- switch 变 if:枚举/常量的 switch 被优化。jadx 的 deobf 模式(Preferences → Deobfuscation)能缓解一部分
技巧六:jadx 命令行批量导出
GUI 适合交互分析,批量处理用命令行:
# 导出全部源码供 grep
jadx -d out_dir target.apk
# 然后上 ripgrep
rg -l "Cipher.getInstance" out_dir/
rg "getSign|makeSign|calcSign" out_dir/ -A 3配合脚本批量分析几十个 APK 时,这条流水线比 GUI 点来点去快一百倍。
精髓:反混淆的心法
- 名字是假的,结构是真的——字符串、API 调用、Manifest 入口永不混淆
- 先找锚点再顺藤摸瓜,别试图通读代码
- 边分析边重命名,分析成果即时固化
- GUI 交互 + 命令行批量,按场景选工具
总结
- 字符串搜索 + 交叉引用是第一板斧
- Manifest 入口是混淆代码的地图
- 系统 API 是锚点,业务类挂在其上
- 重命名建立秩序,R8 优化用特征字符串破解
交流微信:run1255