一句话总结:APK 就是一个 zip,解压后是 dex 字节码、清单文件、资源表和 so 库;逆向的关键不是记住多少工具,而是知道「哪个问题该用哪把工具」——看结构和资源用 apktool、读 Java 逻辑用 jadx、改完要重打包才碰 smali、native 层换成 Ghidra 或 Frida。
站里的逆向文章全是 Windows 侧:PE 结构、x64dbg、加壳、Ghidra。Android 是完全另一套,但道理是相通的,先搞清楚文件怎么组织,再谈怎么分析。
这篇按「文件结构 → 三把工具 → 改一个东西 → 动态验证」的顺序走,所有演示对象是自己写的测试 App,不去碰别人的商业应用。
APK 解开是什么
APK 本质是 zip,unzip -l 就能看:
classes.dex # Dalvik 字节码,真正的代码在这
AndROIdManifest.xml # 二进制格式的清单:权限、组件、入口 Activity
resources.arsc # 资源索引表:字符串、颜色、尺寸
res/ # 资源文件本体
lib/arm64-v8a/*.so # native 库,C/C++ 编译产物
Meta-INF/ # 签名信息
assets/ # 原始资源,不参与编译
三个文件值得先看:
AndroidManifest.xml 决定了这个 App 的攻击面:哪些 Activity 是 exported=true(可被外部调起)、申请了什么权限、有没有 debuggable 标记、备份是否允许。很多安全问题在这个文件里一眼就能看出来,根本不用读代码。
classes.dex 是代码本体。一个 App 可以有多个 dex(classes2.dex、classes3.dex),大型应用很容易超过 65536 方法数上限而分包。
lib/ 下面的 so 是 native 层。核心加密、反调试、协议签名通常都在这,Java 层只是壳。
三把工具,各管一段
| 工具 | 输入 | 输出 | 什么时候用 |
|---|---|---|---|
| apktool | APK | 可读的 smali + 资源 | 看清单、改资源、改逻辑后重打包 |
| jadx | APK / dex | Java 伪代码 | 读逻辑,最快的入口 |
| Ghidra / IDA | so 文件 | 反汇编 + 伪代码 | native 层分析 |
分工很清楚:读逻辑用 jadx,改东西用 apktool,native 层换 Ghidra。 新手最容易犯的错是一上手就用 apktool 看 smali,然后被几千行 invoke-virtual 劝退。
图 1:同一个 APK 里三块内容,对应三种工具和三种问题
命令都很短:
# 反编译成 Java 伪代码(读逻辑)
jadx -d out_jadx app.apk
# 解包成 smali + 资源(改东西)
apktool d app.apk -o out_apktool
# 改完之后重打包
apktool b out_apktool -o patched.apk
jadx 打开后先看 AndroidManifest.xml 里声明的入口 Activity,顺着包名往下点。这个顺序比「全局搜索关键词」靠谱得多——搜索命中的往往是混淆过的无用代码。
smali 长什么样,需要会到什么程度
smali 是 Dalvik 字节码的文本表示。不要求会写,但要能读三个东西:
.method public isVip()Z
.locals 1
iget-object v0, p0, Lcom/example/app/User;->vipFlag:Ljava/lang/Boolean;
invoke-virtual {v0}, Ljava/lang/Boolean;->booleanValue()Z
move-result v0
return v0
.endmethod
看懂这四行需要三个知识点:
.method ... Z 括号后是返回类型。Z 是 boolean,V 是 void,I 是 int,Ljava/lang/String; 是对象。
p0 是 this。 实例方法的第一个参数寄存器是 p0,静态方法没有 p0。
invoke-virtual 调实例方法,invoke-static 调静态方法,move-result 接返回值。 这三个组合覆盖了绝大部分逻辑。
要改一个判断,找到那个 if-eqz / if-nez 指令,改成无条件跳转或者改成相反的判断就行:
# 原来:v0 为 0(false)才跳走
if-eqz v0, :cond_0
# 改成:永远跳走
goto :cond_0
# 或者改成:v0 非 0 才跳走(逻辑反转)
if-nez v0, :cond_0
改完必须重打包并重新签名,否则装不上:
keytool -genkey -v -keystore test.keystore -alias test \
-keyalg RSA -keysize 2048 -validity 10000
apksigner sign --ks test.keystore --out signed.apk patched.apk
apksigner verify signed.apk
动态验证:Frida 接上 Android
静态看完不确定的地方,用 Frida 跑一遍最快。Android 上的用法和 Windows 差别很大,它 hook 的是 Java 方法,不是内存地址:
Java.perform(function () {
var User = Java.use("com.example.app.User");
// hook 实例方法,直接改返回值
User.isVip.implementation = function () {
var real = this.isVip(); // 先调原方法,便于对比
console.log("[hook] isVip 原值: " + real);
return true; // 强行返回 true
};
// hook 带参数的方法,看入参
var Login = Java.use("com.example.app.Login");
Login.checkPassword.oveRLoad('java.lang.String').implementation = function (pwd) {
console.log("[hook] 密码入参: " + pwd);
return this.checkPassword(pwd);
};
});
启动方式两种:
# 方式一:App 已安装,附着上去
frida -U -f com.example.app -l hook.js # -f 冷启动
frida -U com.example.app -l hook.js # 附着已运行进程
# 方式二:提前注入(应对启动即检测的场景)
frida -U -f com.example.app --no-pause -l hook.js
Java.use 找类的速度取决于类是否已加载,如果 hook 不生效,通常是类还没被 load,加个 Java.enumerateLoadedClasses 打印一下确认。动态 hook 的通用思路和 Frida 那篇是通的,那边讲的是 native 层。
加固:第一道现实的门槛
真实 App 大概率加了壳。国内常见的有 360 加固、梆梆、腾讯乐固,特征比较明显:
- 解包后
classes.dex极小(几 KB),真正的代码在assets/下的加密文件里 lib/里有陌生的 so(如libjiagu.so、libshella.so)- Manifest 里的 Application 类指向壳的类,不是真实业务类
这时候 jadx 打开基本是空的。要么脱壳,要么换动态路线(直接 hook 运行时已解密的类)。 动态路线对新手更友好:壳能加密文件,但运行时类必须解密加载,Java.enumerateLoadedClasses 就能看到真实类名。
脱壳是另一个话题,本篇不展开。
五个坑,按命中率排
坑 1:一上手就读 smali。 几千行字节码会直接劝退。先用 jadx 读 Java 伪代码,确定要改哪一行,再回到 smali 定位。
坑 2:改完忘记签名。 apktool b 出来的 APK 是未签名的,直接装会报「解析包时出现问题」。必须 apksigner 签一遍。
坑 3:签名和原包冲突。 覆盖安装要求签名一致,改过的包签名必然不同,得先卸载原 App。卸载会清数据,测试机上提前备份。
坑 4:只改 Java 层。 很多校验在 native 层做(so 里算签名、比对完整性)。Java 改完运行还是失败,先怀疑 so。
坑 5:把 debuggable 当万能钥匙。 加 android:debuggable="true" 能挂调试器,但很多 App 启动时会检测这个标记并退出。这个改法只在测试环境有效。
边界
这篇的技术全部用于自己写的 App 和一个开源练习样本。分析别人的商业应用、绕过付费或授权,在国内有明确的判例,不是「学习用途」能免责的事。
合法用途同样明确:分析自己公司的 App 有没有暴露组件、检查第三方 SDK 偷偷做了什么、验证加固是否生效、做恶意样本分析。这些场景里 APK 逆向是必需技能,不需要绕弯子。
想练手的话,自己写一个带登录和 VIP 判断的 Demo App,编译出来再逆回去,完整走一遍比看十篇教程有用。整个学习顺序可以对照逆向路线图。
参考资料
- 参考资料

发表评论
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。