简介:这份资源面向需要在 macOS 环境下进行 APK 逆向与资源提取的开发者与安全学习者,核心是 apktool 工具及其配套使用说明,帮助解决在 Mac 上获取、反编译 APK 资源时工具缺失、命令不熟、环境配置繁琐等问题。压缩包共 3 个文件,包含 1 个 jar 主程序、1 个 sh 启动脚本和 1 个 md 说明文档,整体约 10.25MB,体积轻便,下载后即可按说明快速调用。其中 jar 为反编译核心,sh 脚本简化命令行调用,md 文档则交代了具体用法与注意事项,适合刚接触 APK 逆向、需要快速上手工具链的读者。目前已有 2829 人学习下载,说明该工具组合在同类需求中具备一定参考价值。借助这份资源,读者可以省去自行搜索与配置的环节,直接获得可运行的 apktool 环境,并依据说明完成 APK 资源提取与反编译操作,遇到问题还可留言交流,便于在实践中积累逆向分析经验。
1. 反编译不是黑魔法:apktool 到底能帮你拿到什么
很多人第一次接触 apk 逆向,目标其实很朴素——把某个 apk 里的图片、布局、字符串或者 AndroidManifest 捞出来看看。但真上手就会发现,直接解压 apk 得到的是一堆二进制 XML 和 resources.arsc,用文本编辑器打开全是乱码。apktool 解决的正是这个痛点:它把 apk 里的二进制资源还原成可读的 XML、把 dex 反汇编成 smali、把资源索引重建出来,让你能像看一个正常 Android 工程一样去读它。它不负责把 dex 还原成 Java 源码,那是 jadx 或 dex2jar 的活,但资源层面的还原,apktool 目前仍是最稳的选择。这篇文章面向两类人:想提取 apk 资源做二次开发或汉化的开发者,以及需要审计第三方 apk 资源合规性的安全从业者。下面从安装、解码、回编到踩坑,一步步拆开讲。
2. 环境搭建与基础解码:从 apk 到可读工程目录
2.1 安装方式选型:包管理器还是手动部署
apktool 本质是一个 Java 程序,核心是apktool.jar,外面套一层启动脚本。安装方式主要有三种,选哪种取决于你的使用场景。
第一种是包管理器安装。macOS 上brew install apktool,Ubuntu/Debian 上sudo apt install apktool,Windows 上choco install apktool。这种方式最省事,但版本往往滞后,遇到新版本 Android 资源格式可能报错。
第二种是手动下载 jar 包。从官方仓库拿到apktool.jar和对应平台的 wrapper 脚本(apktool.bat或apktool),放到同一目录,加执行权限即可。这种方式版本可控,推荐做逆向的用这种。
第三种是直接用java -jar apktool.jar调用,跳过 wrapper。适合在 CI 或脚本里集成。
我一般用第二种,因为 apktool 对版本敏感——用旧版本解新 apk,经常在 resources.arsc 解析阶段就崩了。手动部署能保证我随时切到最新版。
# 手动部署 apktool(Linux/macOS) # 1. 下载最新 apktool.jar 和 wrapper 脚本到同一目录 mkdir -p ~/tools/apktool && cd ~/tools/apktool # 假设已下载 apktool_2.x.x.jar 和 apktool 脚本 mv apktool_2.x.x.jar apktool.jar chmod +x apktool apktool.jar # 2. 验证安装 ./apktool --version # 输出类似:2.9.3 # 3. 可选:加入 PATH,方便全局调用 echo 'export PATH=$PATH:~/tools/apktool' >> ~/.bashrc source ~/.bashrc这里的关键参数是--version,它同时验证了 Java 环境和 jar 包完整性。如果报Unable to access jarfile,说明路径不对;如果报UnsupportedClassVersionError,说明你的 Java 版本低于 apktool 要求(新版一般要 Java 8 以上,部分要 Java 11)。
2.2 解码命令与输出目录结构
装好之后,最核心的命令就是d(decode)。一个典型的解码命令长这样:
# 基础解码:把 target.apk 解到 out 目录 apktool d target.apk -o out # 带常用参数的解码 apktool d target.apk -o out \ --force-manifest \ # 强制解码 AndroidManifest,即使有异常 --no-src \ # 不解码 dex,只要资源时用,速度快很多 --only-main-classes \ # 只反编译主 dex,忽略 multidex 的其余部分 -p framework.apk # 指定 framework 资源,处理系统 apk 时用解码完成后,out目录的结构大致是:
| 目录/文件 | 内容 | 说明 |
|---|---|---|
AndroidManifest.xml | 解码后的清单文件 | 可读的 XML,能看到权限、组件、包名 |
res/ | 资源目录 | 布局、图片、字符串、颜色等,XML 已还原 |
smali/ | 反汇编的 dex | 每个类一个 .smali 文件,按包名分目录 |
smali_classes2/ | multidex 的其余 dex | 有多个 dex 时出现 |
assets/ | 原始 assets | 一般不被解码,原样保留 |
lib/ | native 库 | .so 文件,原样保留 |
apktool.yml | 解码元信息 | 记录版本、是否压缩等,回编时要用 |
original/ | 原始文件备份 | 含未解码的 AndroidManifest 和 META-INF |
unknown/ | 未识别文件 | 通常是 apktool 不认识的资源 |
apktool.yml这个文件很关键,它记录了 apktool 版本、是否压缩资源、dex 数量等信息。回编时 apktool 会读它,所以不要手动删。original/目录里存的是原始二进制文件,回编签名时有时需要从这里取 META-INF。
--no-src这个参数值得单独说。如果你只想拿资源(图片、布局、字符串),加上它能让解码速度从几十秒降到几秒,因为跳过了最耗时的 dex 反汇编。反过来,如果你要改 smali 代码,就不能加。
2.3 资源提取的实操路径
解码完,资源就在res/下。但实际工作中,提取资源往往有更细的需求。
比如只想拿所有图片:
# 提取 res 下所有图片到独立目录 find out/res -type f \( -name "*.png" -o -name "*.jpg" -o -name "*.webp" -o -name "*.9.png" \) \ -exec cp --parents {} ./extracted_images/ \;这里--parents会保留原始目录结构,避免不同 drawable 目录下的同名文件互相覆盖。.9.png是 Android 的九宫格图,提取时要单独注意,它的边缘有 1px 的标记线,直接用普通图片查看器打开会看到黑边,那是正常的。
再比如提取字符串资源。字符串在res/values/strings.xml里,但可能有多个语言版本,分别在res/values-zh/、res/values-en/等目录。要合并查看:
# 列出所有 strings.xml 及其语言 find out/res -name "strings.xml" | sort # 提取默认语言的所有字符串键值对 grep -oP '(?<=name=")[^"]+' out/res/values/strings.xml | head -20如果你要的是「某个按钮上显示的文字」,更快的路径是先在res/layout/里找到对应布局,看它引用的@string/xxx,再去 strings.xml 里查。这个链路比全局搜索字符串高效得多。
3. 回编与签名:改完资源怎么装回手机
3.1 回编命令与常见报错
改完资源后,用b(build)命令回编:
# 基础回编 apktool b out -o modified.apk # 带参数的回编 apktool b out -o modified.apk \ --use-aapt2 \ # 使用 aapt2 而非 aapt,处理新格式资源 -c \ # 保留原始文件时间戳,便于对比 --no-crunch # 跳过 PNG 压缩,加快速度但 apk 会变大回编阶段最容易翻车。最常见的报错是资源引用找不到,比如error: resource style/Theme.AppCompat not found。这通常是因为 apktool 解码时没能正确解析某些资源引用,或者你手动改了res/values/下的文件但没同步更新引用。
另一个高频报错是brut.androlib.AndrolibException: could not exec aapt。这多半是 aapt 二进制和你的系统不兼容,或者 apktool 版本和 aapt 版本不匹配。解决办法是换用--use-aapt2,或者升级 apktool 到最新版。
还有一种情况是回编成功但安装失败,报INSTALL_PARSE_FAILED_NO_CERTIFICATES。这不是回编的问题,是签名的问题,下一节讲。
3.2 签名:为什么回编的 apk 装不上
Android 要求所有安装的 apk 必须有签名。apktool 回编出来的 apk 默认没有签名(或者只有调试签名),所以直接装会失败。你需要用apksigner或jarsigner重新签名。
# 1. 生成签名密钥(如果还没有) keytool -genkeypair -v \ -keystore mykey.keystore \ -alias mykey \ -keyalg RSA \ -keysize 2048 \ -validity 10000 \ -storepass 123456 \ -keypass 123456 \ -dname "CN=Test, OU=Dev, O=Demo, L=City, S=State, C=CN" # 2. 用 apksigner 签名(推荐,支持 v2/v3 签名方案) apksigner sign \ --ks mykey.keystore \ --ks-key-alias mykey \ --ks-pass pass:123456 \ --key-pass pass:123456 \ --out signed.apk \ modified.apk # 3. 验证签名 apksigner verify --verbose signed.apkkeytool的参数里,-validity 10000是有效期天数,-keysize 2048是 RSA 密钥长度。-dname里的字段可以随便填,但格式要对。apksigner的--ks-pass和--key-pass分别对应 keystore 密码和 key 密码,如果生成时设成一样,这里也写一样。
注意:如果你要覆盖安装原 apk,签名必须和原 apk 一致,否则会报
INSTALL_FAILED_UPDATE_INCOMPATIBLE。要么卸载原 apk 再装,要么用原 apk 的签名密钥(通常拿不到)。
3.3 回编后的验证流程
签名完不要急着装,先做几项检查:
第一,用aapt dump badging signed.apk看包名、版本号、权限是否正常。如果包名变成null或乱码,说明 AndroidManifest 解码有问题。
第二,用unzip -l signed.apk看文件列表,确认res/、classes.dex、AndroidManifest.xml都在。如果classes.dex丢了,说明回编时 smali 编译失败但没报错。
第三,装到测试机上跑一遍。重点看启动页、权限申请、资源加载是否正常。有些资源在回编后 ID 会变,导致运行时找不到资源而崩溃,这种问题只有实机跑才能发现。
4. 避坑指南:apktool 实操中的五个高频翻车点
4.1 解码报错 “Could not decode file”
现象:执行apktool d时中途报错,提示某个资源文件无法解码,解码中断。
原因:通常是 apktool 版本低于 apk 的目标 SDK 版本,或者 apk 用了 apktool 不支持的资源格式(比如某些厂商自定义的资源类型)。
解决:先升级 apktool 到最新版。如果还不行,加--no-src跳过 dex 解码,看是否资源阶段就失败。如果确认是某个资源文件的问题,可以加-s跳过资源解码,只解 dex,反过来验证。实在不行,用--force-manifest强制继续,但解出来的资源可能不完整。
4.2 回编报错 “resource not found”
现象:apktool b时报某个资源 ID 找不到,比如error: resource drawable/xxx not found。
原因:解码时 apktool 把资源 ID 写进了res/values/public.xml,如果你手动删了或改了某个资源文件,但 public.xml 没同步,回编时就会找不到。
解决:不要手动删res/values/public.xml。如果确实要删资源,先删文件,再从 public.xml 里删对应条目。或者干脆重新解码一份干净的,把你的修改重新应用上去。
4.3 回编成功但安装后闪退
现象:apk 能装,但一打开就闪退,logcat 里报Resources$NotFoundException。
原因:回编时资源 ID 重新分配了,但 smali 代码里硬编码了旧的资源 ID(比如const v0, 0x7f080001),导致运行时找不到。
解决:这种情况在改 smali 代码时尤其常见。要么不改 smali,只改资源;要么改完 smali 后,用--use-aapt2回编,aapt2 对资源 ID 的处理更稳定。如果还是不行,用 jadx 反编译看 Java 层有没有硬编码 ID,有的话在 smali 里同步改。
4.4 签名后仍报 “No signature found”
现象:明明用 apksigner 签了名,装的时候还是报没有签名。
原因:apksigner 的签名方案和 Android 版本不匹配。Android 7.0 以下只认 v1 签名(jarsigner),Android 7.0 以上认 v2,Android 9.0 以上认 v3。如果你只签了 v2,在旧设备上就认不到。
解决:用 apksigner 时加--v1-signing-enabled true --v2-signing-enabled true --v3-signing-enabled true,三种方案都签。或者直接用 jarsigner 签 v1,兼容性最好但安全性低。
4.5 解码后 smali 目录为空
现象:apktool d跑完,smali/目录是空的,或者只有几个文件。
原因:apk 用了加固或 dex 加密,dex 文件被壳保护了,apktool 解不出来。或者 apk 是 split apk,dex 在别的 split 里。
解决:加固的 apk 需要先脱壳,这不是 apktool 能解决的。split apk 需要把所有 split 合并,或者用--only-main-classes只解主 dex。如果是 multidex,检查smali_classes2/等目录是否存在。
5. 进阶技巧:用脚本批量处理与资源对比
5.1 批量解码与资源指纹提取
实际工作中,经常需要对比两个版本的 apk 资源差异,或者批量提取一批 apk 的图标和字符串。手动一个个跑 apktool 效率太低,我一般写个脚本。
#!/bin/bash # batch_decode.sh - 批量解码 apk 并提取资源指纹 # 用法:./batch_decode.sh /path/to/apk_dir /path/to/output_dir APK_DIR="$1" OUT_DIR="$2" mkdir -p "$OUT_DIR" for apk in "$APK_DIR"/*.apk; do name=$(basename "$apk" .apk) echo "处理: $name" # 解码,只解资源不解 dex,加快速度 apktool d "$apk" -o "$OUT_DIR/$name" --no-src --force-manifest 2>/dev/null if [ $? -ne 0 ]; then echo " 解码失败,跳过" continue fi # 提取包名和版本 pkg=$(grep -oP 'package="\K[^"]+' "$OUT_DIR/$name/AndroidManifest.xml" | head -1) ver=$(grep -oP 'android:versionName="\K[^"]+' "$OUT_DIR/$name/AndroidManifest.xml" | head -1) # 统计资源数量 res_count=$(find "$OUT_DIR/$name/res" -type f 2>/dev/null | wc -l) img_count=$(find "$OUT_DIR/$name/res" -type f \( -name "*.png" -o -name "*.webp" \) 2>/dev/null | wc -l) # 输出指纹到汇总文件 echo "$name|$pkg|$ver|$res_count|$img_count" >> "$OUT_DIR/fingerprint.txt" done echo "完成,指纹文件: $OUT_DIR/fingerprint.txt"这个脚本的核心逻辑是:对每个 apk 解码后,从 AndroidManifest 里抓包名和版本号,统计资源文件数和图片数,汇总成一行。--no-src是关键,它让每个 apk 的解码时间从几十秒降到几秒,批量处理时差距明显。2>/dev/null屏蔽了 apktool 的进度输出,让脚本输出更干净。
跑完之后,fingerprint.txt里每行是一个 apk 的指纹,格式是文件名|包名|版本|资源总数|图片数。用这个文件可以快速筛选出资源量大的 apk,或者对比两个版本的资源增减。
5.2 资源差异对比的实操方法
拿到两个版本的解码目录后,对比资源差异最直接的方式是diff -r,但 apktool 解码后的 XML 里有很多无关的格式差异(比如属性顺序、空白字符),直接 diff 会报大量噪音。我一般先做一层归一化。
# 对比两个版本的 res 目录,忽略空白和属性顺序 # 1. 先对 XML 做归一化:用 xmllint 格式化 find out_v1/res -name "*.xml" -exec sh -c 'xmllint --format "$1" > "$1.norm" 2>/dev/null' _ {} \; find out_v2/res -name "*.xml" -exec sh -c 'xmllint --format "$1" > "$1.norm" 2>/dev/null' _ {} \; # 2. 对比归一化后的文件 diff -rq out_v1/res out_v2/res --exclude="*.norm" | grep -v "\.norm" # 3. 只看新增或删除的图片 diff <(cd out_v1/res && find . -name "*.png" | sort) \ <(cd out_v2/res && find . -name "*.png" | sort)xmllint --format会把 XML 重新缩进,消除空白差异。diff -rq只报告哪些文件不同,不输出具体内容,适合快速定位。最后一步用进程替换对比两个目录的图片列表,<(...)是 bash 的进程替换语法,把命令输出当成文件用。
注意:如果 apk 用了资源混淆(比如 AndResGuard),资源路径会被改成
r/a.png这种短名,对比时路径完全对不上,需要先反混淆。反混淆需要 mapping 文件,通常拿不到,这种情况只能对比文件内容哈希。
5.3 一个我常犯的错误
早期我用 apktool 时,习惯解码后直接在out目录里改资源,改完就回编。后来发现一个问题:如果同一个 apk 要改多次,每次都在上一次的基础上改,apktool.yml里的元信息会累积偏差,回编出来的 apk 偶尔会出现资源错乱。从那以后我每次改之前都先复制一份干净的out目录,在副本上改,改完回编,原目录留着做对比。这个习惯帮我省了很多次重新解码的时间。希望帮到你。
本文还有配套的精品资源,点击获取