当前位置:首页 > 逆向工程 > 正文

APK 逆向入门:解包、smali 与三把工具的分工

约 5632 字 · 预计阅读 15 分钟
一句话总结: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 层只是壳。

三把工具,各管一段

工具 输入 输出 什么时候用
apktoolAPK可读的 smali + 资源看清单、改资源、改逻辑后重打包
jadxAPK / dexJava 伪代码读逻辑,最快的入口
Ghidra / IDAso 文件反汇编 + 伪代码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,编译出来再逆回去,完整走一遍比看十篇教程有用。整个学习顺序可以对照逆向路线图。

参考资料

目录导航
  • APK 解开是什么
  • 三把工具,各管一段
  • smali 长什么样,需要会到什么程度
  • 动态验证:Frida 接上 Android
  • 加固:第一道现实的门槛
  • 五个坑,按命中率排
  • 边界
    • 参考资料
  • 相关文章:

  • Frida 动态注入入门:hook 一个函数、改一个返回值2026-10-30 00:35:08
  • 发表评论

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