简介:面向需要从安卓安装包中提取图标、布局、XML 配置等资源的逆向学习者或测试人员,这份 Mac 环境下的 Apktool 工具合集可以开箱即用。压缩包内共有三个文件,包含可执行 Java 库、macOS 下的启动脚本以及 Markdown 格式的中文使用说明,整体约十兆,结构精简但覆盖了解包、查看、回编译的核心链路。借助启动脚本可免去手动配置依赖的麻烦,快速完成 APK 反编译与资源提取;说明文档中除常用参数外,还整理了权限设置、输出目录、常见异常报错等实操要点,能帮助新手减少踩坑。资源同时适合从事 Android 应用分析、合规检测、界面素材提取或竞品样式研究的技术人员,既可作为入门学习材料,也可作为日常逆向工具箱中的常备组件。目前已有两千八百余人次学习下载,口碑可见。
1. APK 解压出来全是乱码,apktool 才是真正能动手的入口
想改一个 APK 的图标、应用名、版本号,或者单纯想看它的布局是怎么写的,大多数人的第一反应是用解压工具把 APK 解开。解开之后发现 AndroidManifest.xml 打不开,布局文件一片乱码,还以为自己下载的文件坏了。APK 里的 XML 不是普通文本,Android 在编译阶段就把它们压成了二进制格式,这时候该拿出来的工具就是 apktool。
apktool 是安卓逆向里绕不开的命令行工具,作用是做 APK 的解码与回编:解码时,把二进制 XML 还原成可读文本,把 dex 反汇编成 smali 目录;改完之后,再把整个目录编回一个 APK。配合签名工具,就能完成一条完整的拆包、修改、重新打包链路。
这篇文章适合两类人,一是做测试和适配的,被劣质文档和不透明参数折腾过;二是想改第三方包做本地化或功能裁剪的。文章不背官方手册,只讲实际用过的命令、参数边界和血泪经验,新手能跟着跑通,熟手可以跳过前置章节直接看避坑部分。标题里的 apktool 工具和使用说明,正文里都会落到能直接敲的命令行上。后面的章节就从原理和安装说起。
2. 先弄懂 apktool 的原理再动手:它到底解了什么、怎么装
2.1 APK 不只是 zip:二进制 XML 和 resources.arsc 才是主战场
把 APK 当 zip 解开其实没有错,APK 确实是一个 zip 容器。Android 打包时,aapt 会把 AndroidManifest.xml 和所有布局、菜单 XML 转成一种二进制 AXML 格式,目的是让系统在运行时更快地解析,也顺带让普通解压工具读出来变成乱码。所以用解压软件打开 APK,唯一能直接看的是 assets、raw 这类没经过编译的文件,真正定义应用行为的 XML 全是二进制。
resources.arsc 是更核心的文件,它是一张全局资源索引表,把所有资源的 ID、类型、名称、路径压缩在一坨二进制里。系统安装应用时靠它查资源,修改 APK 时也绕不开它。apktool 做的事情本质上就是把这两块黑匣子打开:AXML 还原成文本 XML,arsc 还原成 res/ 目录和 res/values/ 下的可读 XML 文件。
dex 文件也需要特别说明。classes.dex 是 Java/Kotlin 字节码,直接打开基本没法读。apktool 内部调用 baksmali 把 dex 反汇编成 smali,一种可读的中间汇编语言,每个 Java 类对应一个 .smali 文件,方法、字段、指令都在里面。改 smali 就是改代码逻辑,这是 apktool 另一个看家本领。
2.2 装 apktool:Windows、macOS、Linux 各自的配法
apktool 是个 jar 包,本质跑在 Java 上,所以第一步先确认机器有 Java 环境。我一般习惯装 JDK 8 或更高的版本,然后用 java -version 验证一下:
java -version # 输出的版本是 1.8.x 或 11.x、17.x 都可以注意别用太老的 JRE,新版 apktool 对 JDK 版本有底线要求,装太老版本会在启动时直接报 UnsupportedClassVersionError。
Windows 上常见做法是去 apktool 官网下载两个东西:apktool.jar 和一个包装脚本 apktool.bat。把 jar 放进一个固定目录,比如 D:\tools\apktool,把 bat 也放进去,再把该目录加进 PATH。这样在任意路径下都能敲 apktool 命令。如果你用 chocolatey,也可以直接 choco install apktool,省去手工配置路径的麻烦:
choco install apktool -ymacOS 用户最简单的是用 Homebrew,一条命令装完,Java 依赖也会自动处理:
brew install apktool如果不想让 brew 替你管理,也可以自己下 jar,然后写一个 alias 指向 java -jar 的完整命令。Linux 上同理,Ubuntu/Debian 系可以 apt install apktool,但仓库里的版本经常滞后,新 APK 的解码兼容性不如官方发布版。我一般会到官方 GitHub Release 页面下最新的 jar 包,配合一个小脚本用:
java -jar apktool.jar "$@"这样能始终跟着上游版本走,遇到新版 Android 编译出来的 APK 不会因为工具太老而翻车。
2.3 验证安装:版本号与第一条解码命令
装完先验证版本,输出正常就说明 Java 和 jar 都被正确找到了:
apktool --version然后随便找个 APK 试解码。第一次跑可以选一个自己写的测试应用,或者一个体积小、没加固的第三方应用:
apktool d demo.apk -o demo-decoded这条命令把 demo.apk 解码到 demo-decoded 目录。命令结束后,目录里应该能看到 AndroidManifest.xml、res、smali 这些关键内容。能看到就说明工具链通了。如果这一步都报错,优先检查 Java 版本和路径,跟 APK 本身关系不大。
提示:apktool 只管解码和回编,不做签名。重打包之后必须用 apksigner 或类似工具补上签名,否则手机装不上。这一步放在第 4 章专门讲。
3. 反编译实操:apktool d 的参数、输出目录与资源过滤
3.1 一次完整解码:命令写法与 -o、-f 两个高频参数
apktool d 是日常最高频的命令,完整写法是 apktool decode [选项] <apk路径>。最常用的几个选项先列出来,后面再逐个展开:
apktool d input.apk -o output_dir -f-o 指定输出目录。不写的时候,apktool 会在当前目录生成一个跟 APK 同名的目录。我习惯强制指定输出目录,因为跑批量任务时同名目录容易互相覆盖。-f 的作用是强制覆盖已存在的输出目录。如果不加 -f 且目录已存在,工具会停下来问你是否删除,交互式脚本里这种停顿特别烦人,批处理时务必加上。
有一点要注意:-f 会把已有目录整个删掉再重建,如果上一次解码后手工改过内容,加 -f 会把这些改动全部清空。所以我的习惯是每个 APK 单独一个工作目录,确认改完了再执行回编,不在同一个目录反复折腾。
3.2 输出目录拆解:smali、res、original 各自的工作
跑完上一条命令,输出目录长这样:
demo-decoded/ ├── AndroidManifest.xml ├── apktool.yml ├── assets/ ├── original/ ├── res/ ├── smali/ └── unknown/逐个说作用。AndroidManifest.xml 是还原成文本的清单文件,包名、权限、四大组件、版本号都写在这里,直接改。apktool.yml 是这次解码的元信息,记录版本号、资源配置、framework 版本号,回编时靠它恢复资源上下文,手工改 versionCode 时这里是一个入口。res/ 目录下是全部资源和还原出来的 XML 文本,布局、颜色、字符串都在这里。smali/ 目录按包名分目录,里面是反汇编出来的代码,比如 smali/com/example/app/MainActivity.smali,改逻辑翻它。original/ 目录保存的是 APK 原始签名和元数据,重打包后这里通常会被忽略,不作为安装依据。
assets/ 和 unknown/ 基本原样保留,assets 一般放业务文件,unknown 放工具识别不了的杂项文件。拿到一个陌生 APK,我的观察顺序永远是先看 AndroidManifest.xml 确认入口和权限,再看 res/ 找资源和布局,最后才下钻到 smali/ 改逻辑。
3.3 按需解码:-s 只看资源、-r 只碰代码、--no-assets 提速
很多时候不需要完整解码。只想换图标、改应用名,用 -s 跳过 smali 反汇编,解码速度会快一大截:
apktool d app.apk -o res-only -s-s 意味着不动 dex,输出目录里不会出现 smali/。配合资源修改,这一条命令是最快的路径。
反过来,只想分析代码逻辑、不想被一堆资源文件拖慢,用 -r 跳过资源解码:
apktool d app.apk -o code-only -r这时 res/ 目录不输出,代码相关的内容仍在。还有--no-assets,它只跳过 assets/ 的解码。遇到 APK 的 assets 里塞了几百 MB 视频或模型文件,而你又根本不关心这部分时,这个参数能把解码时间从几分钟压到几秒。
3.4 高频参数速查表
下表整理了 d 命令里值得记住的选项,方便复制到笔记里对照:
| 参数 | 作用 | 典型场景 |
|---|---|---|
| -o | 指定输出目录 | 批处理时隔离不同 APK |
| -f | 强制覆盖已有目录 | 重复解码同一 APK |
| -s | 不反编译 dex | 只改资源时提速 |
| -r | 不处理资源 | 只看代码逻辑 |
| --no-assets | 跳过 assets | assets 体积大、无关时 |
| -t | 指定 framework 标签 | 需要自定义 framework 时 |
| --use-aapt2 | 强制用 aapt2 解析资源 | 老旧 APK 解码出错时的退路 |
其中 -t 和--use-aapt2属于进阶参数,平时用不太到,但实际碰上 framework-res.apk 相关的解码报错时,这两个值得加进去试。遇到 arsc 解析失败时,先换--use-aapt2再考虑换版本,这已经是社区里的标准排查顺序了。
4. 修改、回编、签名:把 APK 完整走一遍的落地链路
4.1 修改入口:清单文件里的应用名、图标和版本号
解码完成后,第一处值得下手的通常是 AndroidManifest.xml。找 application 节点,改 android:label 换应用名,改 android:icon 换图标路径。图标文件本身在 res/ 对应 mipmap 或 drawable 目录里,直接替换同名文件即可。
版本号要改两个地方:AndroidManifest.xml 里的 android:versionCode 和 android:versionName,以及 apktool.yml 里的 versionCode 和 versionName。只改清单文件的话,回编后 apktool.yml 的信息会被重新写回清单,版本号要同时保证一致,才不会出现安装时版本降级的报错。
如果要动代码逻辑,则要进 smali/。比如想让某个方法直接返回,不去执行后面的网络请求,就把方法体末尾改成:
return-void比如想强制某个返回 boolean 的方法永远为 true,把方法里的 return v0 等改成:
const/4 v0, 0x1 return v0这两段是最常见的 smali 改法。虽然简单,但要记住:smali 是有寄存器概念的,改完了和原有的数据流对不上,回编时 dex 校验就过不了。最稳妥的做法是一次只改一个方法,立刻回编验证,不要一口气改几十处再一起编,那样报错时定位会很痛苦。
4.2 回编译:apktool b 的参数与产物
代码改完、资源换完,执行回编:
apktool b demo-decoded -o rebuild.apkb 是 build 的意思,后接解码出来的目录。不写 -o 的话,产物会生成在 demo-decoded/dist/ 下,我一般显式指定输出文件路径。
回编过程分两步:内部会先调用 aapt2(老版本默认 aapt1)把 res/ 下面的资源重新编译并生成资源索引,然后把 smali/ 重新汇编成 dex。绝大多数回编失败都发生在资源阶段:可能是改了 XML 的格式不对,也可能改了图片导致资源类型不匹配,还有可能是原 APK 做过资源混淆,回编时 ID 映射对不上。
回编产物 rebuild.apk 此时还是一个未签名的包。直接 adb install 会报 INSTALL_PARSE_FAILED_NO_CERTIFICATES,这也是新手最常见的翻车现场。接下来的签名步骤不能省。
4.3 签名链路:keytool 生成密钥、zipalign 对齐、apksigner 签名
apktool 从某个版本开始就不再内置签名功能,所以签名必须靠 Android SDK 自带的工具链完成。标准流程是三步:先用 keytool 生成一个自签名证书,再用 zipalign 做内存对齐,最后用 apksigner 把签名写进 APK。
keytool -genkeypair -v -keystore my.keystore -alias mykey -keyalg RSA -keysize 2048 -validity 10000 zipalign -p -f 4 rebuild.apk aligned.apk apksigner sign --ks my.keystore --ks-key-alias mykey --out signed.apk aligned.apkkeytool 过程中会让你输入组织信息,随意填写即可,密码要记住,后面两条命令要用。zipalign 的 -p 参数表示对 .so 文件用页面对齐,4 表示 4 字节对齐,这是 Android 的标准做法;apksigner 的 --ks 指定密钥库,--ks-key-alias 指定别名,签名时按提示输入密钥库密码和密钥密码。
zipalign 和 apksigner 都放在 Android SDK 的 build-tools 目录里,路径形如 $ANDROID_HOME/build-tools/<版本号>/。如果找不到,说明 build-tools 没装全,去 SDK Manager 里装一份。
签名完成后,再用 apksigner verify 验证一遍:
apksigner verify --verbose signed.apk输出了 signing lineage 和 v2 签名信息就算通过。这条链路过一次之后,可以把 keytool、zipalign、apksigner 三步包成一个脚本,后面每次重打包只改脚本里的文件名。
5. 避坑指南:APK 解码回编最常见的五个翻车现场
5.1 解码直接报 AndrolibException:arsc 解不开
现象:apktool d 跑到一半,控制台抛出 brut.androlib.AndrolibException: Could not decode arsc file,或者直接提示 Failed to load framework resources。原因大概率是 APK 用了新版 Android(比如 13/14)里引入的资源格式,而本地的 apktool 版本太老,解析不了;也有小概率是 APK 被厂商特殊处理过资源表。
解决:优先把 apktool 升到最新版再试;升完仍然失败,尝试加--use-aapt2强制用新版资源编译器重解析;实在不行,把这个 APK 放进 GitHub issues 里搜,通常会有某个特定版本能解它。这类问题的本质是版本兼容性,不是操作问题,别在这上面反复折腾自己的命令写法。
5.2 回编时 resource not found:资源混淆与 public 映射的锅
现象:apktool b 时报 error: resource style/xxx not found,或 resource 0x7f010023 is not defined。这种情况常见于经过资源混淆的 APK,打包方用资源混淆工具把资源 ID 重新编排过,解码出来的 public.xml 与 apktool.yml 里的映射信息不一致。
解决:不要手工去 public.xml 里硬补 ID,容易越补越坏。先把 apktool 升到新版,新版回编会尽量保留映射关系;其次确认 apktool.yml 里的 resource-overrides 没有被误删,有些教程让人删掉这个字段解决报错,但对混淆过的项目只会雪上加霜;最后,识别到自己改不动时,果断放弃重打包这个 APK,换一个不做资源混淆的版本比硬刚更划算。做逆向要记住,部分 APK 就是设计成不让你改的。
5.3 解码成功但 smali 是空的:加固壳
现象:解码一切正常,但 smali/ 目录里只有一两个类,打开一看是加固壳的入口,完全找不到业务代码。原因很明确:APK 的 classes.dex 被加固厂商替换成了一层壳,真正的 dex 被加密存放在 assets 或者其他地方,运行时才在内存里解密加载。
解决:先用特征识别壳类型,常见的有腾讯、360、爱加密等;再用对应的脱壳工具对运行中的应用做内存 dump,拿到真正的 dex 之后,再对 dex 单独做反编译。脱壳本身是一个独立领域,工具链又多又杂,这里不展开。想表达的是:apktool 本身没有脱壳能力,碰到加固包,别在 apktool 层面死磕,先解决壳的问题再谈修改。
5.4 Windows 路径里的中文和空格:文件不存在玄学
现象:在“D:\下载\测试 APK\”这类目录里执行 apktool d,工具报 java.io.FileNotFoundException 或干脆提示输入文件不存在,但文件明明就在那里。原因和 APK 无关,属于 Java 在 Windows 上处理非 ASCII 路径的兼容性问题。
解决:把 APK 和解码输出目录都放到纯英文、无空格的路径下,比如 D:\apktool_work\,再执行命令。空格路径在部分版本里也会触发问题,虽然比中文好些,但保险起见还是避免。这类玄学问题浪费过很多人时间,排查时如果命令和参数都正确,先检查路径字符再找别的原因。
5.5 重打包后装不上:INSTALL_FAILED 的原因与排查顺序
现象:adb install 提示 INSTALL_PARSE_FAILED_NO_CERTIFICATES、INSTALL_FAILED_VERSION_DOWNGRADE 或 INSTALL_FAILED_UPDATE_INCOMPATIBLE。三个报错代表三个原因:头一个是没签名或签名不完整,签名步骤漏了;第二个是手机里已安装的同名应用版本号高于当前 APK,改版本号但改低了;第三个是签名不一致,手机里原有应用用的是另一个证书签名,覆盖安装时系统不允许更换签名。
解决:第一个重新执行签名流程;第二个把 apk 的 versionCode 加高,注意同时改 AndroidManifest.xml 和 apktool.yml 两处;第三个只能先卸载旧应用再装,没有别的办法。安装类问题先看报错原文,再决定走哪条路,不要无脑卸载重装。
这一章的五条,前三条属于源码层面的兼容性问题,后两条属于环境和安装问题。遇到问题时按先升级工具、再检查路径、最后看签名的顺序排查,大部分坑都能在十分钟内定位。
6. 把 apktool 编进批量脚本:一次处理几十个 APK 的验证心得
6.1 用 for 循环串起解码、回编和日志
单包操作很简单,但实际工作中经常一次拿到几十个 APK 要统一处理。逐条敲命令效率太低,我会写一个批处理脚本,一边跑一边记日志,出问题能直接定位到具体 APK。
#!/bin/bash for apk in *.apk; do name="${apk%.apk}" apktool d "$apk" -o "$name" -f --no-assets >> decode.log 2>&1 if [ $? -eq 0 ]; then apktool b "$name" -o "${name}_rebuild.apk" >> build.log 2>&1 fi done解码日志和解码失败的应用都输出到日志文件里,回编同理。如果哪个包中途失败,直接把对应行从日志里抓出来单独处理,不会影响其他包的进度。
这里有个细节:每个 APK 都用它自己的名字建输出目录,避免批处理时目录互相覆盖;加了 -f 是让重复运行脚本时不报目录已存在;--no-assets则是我检查过这批 APK 不依赖 asset 改动才加的提速项。如果你处理的是 asset 相关的改动,去掉这个参数。
6.2 用 aapt dump badging 验证改完的结果
改完几十个包,总不能一个个 adb install 去试。我会用 aapt 的 dump badging 子命令快速验证关键信息:
aapt dump badging signed.apk | grep -E "package:|application-label:|versionCode:"输出会显示包名、应用名、版本号,和期望值一对比就知道改没改对。这个方法尤其适合验证改包名的场景:改完 AndroidManifest.xml 的 package 后,badging 输出能立刻反映出来;如果没变,多半是只改了清单没改 smali 里调用包名的地方,需要进一步排查。
签名验证也可以用 apksigner verify 批量跑:
for apk in *_rebuild.apk; do apksigner verify "$apk" > /dev/null 2>&1 && echo "$apk OK" || echo "$apk FAIL"; done这些年我养成的一个习惯是:任何改包操作,不管多小,都要求自己把解码、回编、签名、badging 验证四步走完整。早期我图省事,改完图标直接装,结果装了十台手机才意识到签名没做,白白浪费了半天。后来把所有步骤固化到一个脚本里,输入 APK,输出验证报告,几次下来就再没出过这种低级问题。希望这套流程对你也有帮助。
本文还有配套的精品资源,点击获取