简介:面向在 macOS 环境下进行 APK 逆向、资源提取与反编译学习的开发者、移动端测试人员及安全爱好者,这套工具包围绕 apktool 的本地使用展开,重点解决工具获取、环境配置和命令调用入口不清晰的问题,适合 APK 结构查看、素材提取与逆向入门练习等场景。压缩包共 3 个文件,包含 1 个 jar 主程序、1 个 sh 启动脚本和 1 份 md 使用说明,整体约 10.25MB,体积轻量,解压后可依照文档核对依赖、执行方式与常见注意事项,并通过脚本简化主程序的启动流程。已有 2832 人学习,说明其在同类工具资料中具有一定参考价值;读者可借此快速搭建本地 APK 分析工具链,减少手动配置与反复试错,把精力放在熟悉反编译命令、资源目录结构和后续样本分析上,对初次接触 apktool 的读者较为友好。
1. 从一次资源找不到的返工说起:apktool 到底拆出了什么
在模拟项目X里换启动图时,我一开始只把安装包当 zip 解开,图片是拿到了,但布局里的引用全变成一串数字 ID,strings.xml也只剩二进制块。后来用 apktool 做 APK 逆向,才把resources.arsc还原成可读目录,获取 APK 资源这件事才算真正落地。它适合三类人:查 Manifest 权限和组件、替换图标文案布局、定位 smali 里某个字符串但不想先啃整套 Java 逻辑。反直觉的是,apktool 不是“把安装包变回源码”的按钮,它更擅长把资源拆成可编辑文件、把 dex 反汇编成 smali;真想读 Java 语义,得配合别的反编译工具。下面按“拆开、改、装回去、排错”走一遍。
2. 拆包模型:resources.arsc、dex 与 AndroidManifest 各归哪
2.1 为什么先解码资源,而不是直接解压安装包
安装包本质上是 zip,但资源不是以源码形态存放的。res/layout下的 XML 被编译过,res/values下的字符串被压进resources.arsc,直接解压只能拿到二进制块和编译后的引用表。你改一张图片可以,改一个字符串就会遇到“文件能打开、引用对不上”的问题。apktool 做的事情可以拆成两条线:一条线解析resources.arsc,把资源表还原成res/目录和public.xml映射;另一条线用 baksmali 把classes.dex反汇编成smali/目录。AndroidManifest.xml也会从二进制 XML 还原成可读文本,apktool.yml记录构建时需要的 sdk 信息、版本信息和压缩规则。
选型上,如果只查包名、版本号、权限,aapt dump badging更快;如果要读 Java 层逻辑,jadx 这类工具更直观;但要做到“改资源再装回去”,apktool 的闭环更完整,因为它把资源编译、dex 反汇编和重打包串在了一条命令链上。代价是 smali 不是 Java,改逻辑要按寄存器和方法签名来,不能直接照抄 Java 代码。我的习惯是:先全量解码一份基线,不改任何文件,拿它当对照;要动资源时再复制一份工作目录,避免把基线污染。
2.2 工作目录与 Java 环境的最小准备
apktool 的可执行包一般是一个 jar,运行时依赖 Java。不要把它丢在桌面上直接双击,命令行环境里固定目录更好排错。我一般建三个目录:tools/放工具 jar,work/input/放原始安装包,work/out/放解码产物,日志单独进logs/。原始包只读,所有改动都在输出目录里做,这样构建失败时能快速回到起点。资源包里带的说明文档可以先扫一遍参数表,但参数背后的边界还是得自己跑一次才知道。
# 检查 Java 与 apktool 可执行包是否就位 java -version java -jar tools/apktool.jar --version # 建立工作目录,原始包只读存放 mkdir -p work/input work/out logs cp demo.apk work/input/input.apk # 只读解码:先不修改,保留一份基线 java -jar tools/apktool.jar d -f -o work/out/decoded_baseline work/input/input.apk这段命令先确认 Java 能启动 jar,再把原始包复制到固定位置。-f表示输出目录已存在时强制覆盖,-o指定输出目录,d是 decode 子命令。基线目录不要直接改,后面所有对比都以它为准。如果--version都跑不起来,先看 Java 是否在 PATH 里,别急着怀疑安装包本身。
2.3 一次只读解码:命令、参数与产物对照
只读解码的目标不是“马上改”,而是确认这个包能不能被 apktool 正常拆开。执行后先看终端日志:资源表加载、Manifest 解码、dex 反汇编分别有没有 warning。出现Could not decode arsc file这类信息,说明资源表格式或包结构有特殊情况,继续改资源大概率会翻车。日志里如果只是个别 unknown 文件没处理,通常不影响资源替换,但要在构建前确认这些文件是否被引用。
解码完成后,目录里通常会出现res/、smali/、smali_classes2/、assets/、lib/、original/、AndroidManifest.xml和apktool.yml。res/是可编辑资源,smali/是反汇编代码,original/保留原始 Manifest 和签名信息,apktool.yml是构建时的黑匣子,记录版本号和压缩配置。不要删apktool.yml,也不要直接改original/里的文件,构建时 apktool 会参考这些内容。
2.4 产物目录怎么读:res、smali、original 的分工
res/values/strings.xml是字符串入口,res/layout/是布局,res/drawable/是图片和形状资源。改文案优先动strings.xml,不要在布局里硬编码中文,否则多语言目录会漏。public.xml记录资源 ID 映射,构建时资源引用靠它对齐;删资源文件但布局还引用,构建就会报 resource not found。smali/按包名分目录,类名和方法名保留,但寄存器变量名会变成v0、v1,读起来需要耐心。
original/META-INF里是原始签名文件,重打包后不能直接复用,必须重新签名。original/AndroidManifest.xml是原始二进制 Manifest 的备份,排查“解码后的 Manifest 和安装包信息不一致”时有用。apktool.yml里的doNotCompress记录哪些后缀不压缩,改错了可能导致安装后资源读不到。我的做法是:只读解码后先跑一次find看目录规模,再决定是全量改还是只动res/。
提示:只处理自有或获得授权的安装包,别把工具用在未授权的包上。资源替换前先备份原始包和基线目录。
3. 反编译命令与参数:把 APK 资源提取成可读文件的四种跑法
3.1 全量解码与只解资源:-s、-r 的取舍
全量解码会同时处理资源和 dex,产物最完整,适合第一次拆包和需要改 smali 的场景。只解资源用-s,它跳过 dex 反汇编,输出目录里没有smali/,构建时也不碰代码。只替换图片、改文案、调颜色时,我优先用-s,因为速度快、目录小、误改代码的概率低。只反编译 dex 用-r,它不处理资源,适合读 smali 逻辑或对比两个包的代码差异,但构建回安装包时资源还是原始二进制,不能直接改布局。
这里的关键不是记参数,而是先明确目标:你要改的是资源还是代码。改资源却用了-r,输出目录里没有可编辑的res/,只能对着二进制资源表干瞪眼;改 smali 却用了-s,输出目录里没有smali/,构建时也不会重新编译代码。两种模式不要混着用,工作目录分开命名,日志分开存。
3.2 输出目录、覆盖与框架路径:-o、-f、-p 怎么配
-o指定输出目录,建议每次带项目名和模式名,例如full、res_only、smali_only。-f强制覆盖已有目录,适合脚本重复跑,但手工操作时容易把刚改好的文件冲掉。我的习惯是:第一次解码加-f,后续改动前先复制目录,不在原目录上反复覆盖。-p用来指定框架文件存放路径,遇到系统资源引用找不到时,先安装框架再解码,别直接删报错资源。
# 全量解码:资源 + 全部 dex java -jar tools/apktool.jar d -f -o work/out/full work/input/input.apk # 只解资源:保留 dex 不动,适合只替换图片和文案 java -jar tools/apktool.jar d -s -f -o work/out/res_only work/input/input.apk # 只反编译 dex:不处理资源,适合读 smali 逻辑 java -jar tools/apktool.jar d -r -f -o work/out/smali_only work/input/input.apk # 只反编译主 dex:多 dex 包减少输出体积 java -jar tools/apktool.jar d --only-main-classes -f -o work/out/main_dex work/input/input.apk这四条命令覆盖了常见解码模式。-s和-r是互斥思路,不要同时加;--only-main-classes只反编译主 dex,适合快速看主逻辑,但多 dex 包里非主 dex 的类会被跳过,改代码时不要用这个模式。-f会覆盖输出目录,跑之前确认目录里没有未备份的改动。
3.3 只反编译主 dex:--only-main-classes 的边界
多 dex 安装包里,classes.dex之外还有classes2.dex、classes3.dex。--only-main-classes只处理主 dex,输出目录小,搜索快,适合定位入口类、Application 类和主 Activity。但它不是“完整反编译”,如果目标字符串在非主 dex 里,搜不到不代表不存在。改 smali 时我一般先全量解码,再用grep -r搜类名,确认目标类在哪个 dex 目录,最后才决定要不要缩小范围。
另一个边界是构建。只反编译主 dex 的目录拿去重打包,未反编译的 dex 不会自动补回来,构建出的包可能缺类。正确做法是:读代码可以用--only-main-classes,要回打包就用全量解码的工作目录。两者不要混用同一个输出目录,否则后面排查“为什么改了没生效”会多花一倍时间。
3.4 解码日志怎么看:从 warning 判断能不能继续
解码日志里,I:是信息,W:是警告,E:是错误。资源表加载失败、Manifest 解析失败、dex 反汇编失败属于硬错误,先停下;个别文件 unknown、重复资源、压缩方式不一致属于警告,可以继续但要记录。我的做法是把日志重定向到文件,解码后先tail -n 50看结尾,再grep -i "error\|warning"过滤关键行。日志里出现资源 ID 冲突时,不要急着改public.xml,先确认是不是删了资源又没清引用。
注意:解码日志不要只看最后一行。构建失败的原因经常藏在中间的资源编译警告里,尤其是被覆盖的
strings.xml和缺失的 drawable。
4. 重打包与签名:资源替换后的闭环怎么跑通
4.1 改 res 与 strings.xml:先小改再构建
第一次改资源不要批量替换。先改一个字符串或一张图,构建、签名、安装验证一遍,确认链路通了再扩大范围。字符串要放在res/values/strings.xml里,保留name属性不变,只改标签之间的文本。中文里的&、<、>必须转义,或者用 CDATA 包起来,否则资源编译会报 not well-formed。图片替换要保持文件名和后缀一致,改完先看res/drawable下的引用有没有同名冲突。
<!-- decoded/res/values/strings.xml --> <resources> <string name="demo_title">演示标题</string> <string name="demo_tip">保存前检查 & 和 < 是否已转义</string> </resources>这段 XML 只动文本,不动资源名。name是资源 ID 的锚点,改了它,布局里的@string/demo_title就会找不到。&和<是 XML 实体,构建时会被还原成&和<。保存编码用 UTF-8,不要用带 BOM 的格式,否则个别构建环境会在文件头报错。
4.2 apktool b 的构建参数与 aapt2 选择
构建命令是apktool b 目录 -o 输出.apk。资源编译报错时,先看错误行指向哪个文件,再决定是修资源还是换编译链。较新的 apktool 支持--use-aapt2,对部分新资源格式更友好,但它不是万能开关:aapt2 对资源命名和 XML 合法性更严格,原来能编译的旧资源可能暴露出隐藏问题。我的顺序是先用默认编译链跑一次,失败再换 aapt2,两次日志都保留,方便对比。
构建成功后得到的是未签名包,不能直接安装。构建阶段只负责把res/、smali/、AndroidManifest.xml和apktool.yml重新组装成 zip,签名是下一步。构建日志里出现brut.androlib.AndrolibException时,先看它上面的资源行号,不要直接搜异常类名,那只是外壳。
4.3 对齐与签名:zipalign、keytool、apksigner 的顺序
顺序很重要:构建、对齐、签名、验证。zipalign必须在签名前做,签名后再对齐会破坏签名块。签名用apksigner,不要用老式jarsigner代替,后者对现代签名方案支持不完整。演示用签名文件可以用keytool生成,但不要把它当成发布签名;正式场景用团队规定的签名材料,密码不要写进脚本明文。
# 构建未签名包 java -jar tools/apktool.jar b work/out/decoded -o work/out/unsigned.apk # 如果资源编译报错,改用 aapt2 再试一次 java -jar tools/apktool.jar b work/out/decoded --use-aapt2 -o work/out/unsigned_aapt2.apk # 4 字节对齐,必须在签名前做 zipalign -f 4 work/out/unsigned.apk work/out/aligned.apk # 生成演示用签名文件,交互式输入密码 keytool -genkeypair -v -keystore work/out/demo.keystore -alias demo -keyalg RSA -keysize 2048 -validity 10000 # 签名并验证 apksigner sign --ks work/out/demo.keystore --ks-key-alias demo --out work/out/signed.apk work/out/aligned.apk apksigner verify --verbose work/out/signed.apkzipalign -f 4的4是对齐字节数,-f覆盖输出文件。keytool的-validity 10000是有效天数,-keyalg RSA -keysize 2048是密钥参数。apksigner sign的--ks指定密钥库,--ks-key-alias指定别名,--out是签名后的输出。apksigner verify --verbose会列出签名方案和校验结果,看到Verified才继续安装。
4.4 安装验证:包名、版本号与覆盖安装
安装前先确认包名和版本号没变。aapt dump badging signed.apk可以看package: name=和versionCode=,这两个值和原包不一致时,覆盖安装会失败。签名不同也不能直接覆盖,系统会报INSTALL_FAILED_UPDATE_INCOMPATIBLE,需要先卸载旧版。卸载会丢应用数据,测试机上先备份,别在存有业务数据的设备上直接试。
# 查看包名、版本号和启动 Activity aapt dump badging work/out/signed.apk | head -n 5 # 安装到测试设备,-r 表示覆盖安装 adb install -r work/out/signed.apk # 安装失败时看设备日志里的包管理错误 adb logcat -d | grep -i "PackageManager\|INSTALL_"aapt dump badging只读不装,适合签名后先核对身份信息。adb install -r的-r是替换安装,前提是签名一致、版本号不降低。adb logcat过滤PackageManager能看到具体失败码,比只看“安装失败”四个字有用。测试机上先卸旧版再装新版,能排除签名冲突,但别忘了数据备份。
5. 避坑与排查:五类翻车现场的现象、原因与解决
5.1 构建报资源找不到:先看引用,不要先改 public.xml
现象是构建中断,日志里出现resource ... not found或No resource identifier found。常见原因是删了res/drawable下的图片,但布局或values里还在引用;也可能是批量替换时改了资源名,忘了同步public.xml和布局。解决顺序是先在解码目录里全局搜资源名,确认引用位置,再决定恢复文件还是改引用;不要一上来手改public.xml,那是资源 ID 映射表,改错会引发更多找不到。
我的习惯是每次只改一个资源族:先改字符串,构建通过;再改图片,构建通过;最后动布局。构建失败时看日志里第一个错误行,不要被后面的连锁错误带偏。资源文件删了但引用还在,最省事的做法是恢复文件再重新改,不要用空文件占位,空 drawable 可能在运行时报 inflate 错误。
5.2 安装报签名不一致:重打包后必须重签
现象是adb install返回INSTALL_FAILED_UPDATE_INCOMPATIBLE或INSTALL_PARSE_FAILED_NO_CERTIFICATES。原因是 apktool 构建出的包没有签名,或者用了和原包不同的签名文件。重打包一定会改变签名,除非你持有原签名材料,否则不能覆盖安装。解决方法是先对齐再重签,签名不同就卸载旧版再装,但卸载前备份数据。
另一个隐蔽原因是签名后又被zipalign处理了一次,签名块被破坏,验证时能看到签名方案异常。记住顺序:构建、对齐、签名、验证,签名后不要再对齐。正式发布不要用演示签名,团队有固定签名材料时按团队流程走,别把密码写进命令历史。
5.3 反编译卡在 resources.arsc:资源表格式与 split APK
现象是解码刚开始就报Could not decode arsc file,或者资源目录只出来一半。原因可能是安装包用了较新的资源表格式,也可能是 split APK:基础包和配置包分开,单独拆一个base.apk资源不全。先确认手里是不是完整安装包,再看 apktool 是否需要换较新的可执行包。不要拿一个 split 配置包当完整包改,构建出来的东西装不上。
如果是 split APK,先把基础包和对应的配置包放在一起看,确认目标资源在哪个包;只改基础包时,配置包里的同名资源可能覆盖你的改动。解决思路是先在测试环境合并安装再验证,或者只改基础包里没有被 split 覆盖的资源。遇到加固或资源表异常时,先判断是否值得继续,别在不可读的包上反复试参数。
5.4 smali 改了没生效:多 dex 与类加载路径
现象是改了smali里的字符串或方法,重打包安装后行为没变。原因通常是改错了 dex:多 dex 包里目标类在smali_classes2,你改的是smali下的同名类;或者用了--only-main-classes解码,非主 dex 根本没输出。另一个原因是方法重载,你改的是同名但参数不同的方法,实际调用走的是另一个签名。
解决方法是全量解码后全局搜目标字符串,确认类所在的目录和方法签名,再改对应文件。改完构建前用grep -r再搜一遍,确认没有第二个同名资源或同名类。运行验证时看日志里的类加载路径,不要只凭界面表现判断。smali 不是 Java,改寄存器和方法签名要谨慎,改错不报编译错但运行会崩。
5.5 中文与特殊字符导致编译失败
现象是构建时报not well-formed或资源编译错误,指向strings.xml的某一行。原因是中文文案里带了&、<、>或未闭合的引号,XML 解析器不认。解决方法是把&写成&,<写成<,>写成>,或者用 CDATA 包住整段文本。保存文件用 UTF-8,不要用系统默认编码另存。
批量替换文案时尤其容易踩这个坑:从表格里复制过来的文本可能带不可见字符,构建日志只报行号不报字符。我的做法是改完先用 xmllint 或构建命令跑一遍,再签名安装;不要等装到设备上才发现资源编译不过。中文文案尽量放strings.xml,不要在布局里硬编码,后面查引用会轻松很多。
6. 进阶:批量反编译与改动验证的一个小流程
当手里有多个模拟包要对比资源时,手工一条条解码很容易漏。我一般写一个小循环,把每个包解到独立目录,日志和 Manifest 哈希分开存。这样后面用diff -ru对比改动前后,只关注res/和smali/的差异,不会被日志和时间戳干扰。脚本里加set -euo pipefail,任何一步失败就停下,避免半成品目录混进结果。
#!/usr/bin/env bash set -euo pipefail TOOL="java -jar tools/apktool.jar" mkdir -p work/out/batch logs for apk in work/input/*.apk; do name=$(basename "$apk" .apk) $TOOL d -f -o "work/out/batch/$name" "$apk" > "logs/$name.decode.log" 2>&1 sha256sum "work/out/batch/$name/AndroidManifest.xml" >> logs/manifest.sha256 doneset -euo pipefail让脚本在命令失败、未定义变量和管道错误时退出;basename取文件名去掉后缀;>把标准输出和错误都重定向到日志,后面排查直接看对应文件。sha256sum记录 Manifest 哈希,改完再算一次就能知道清单有没有被动过。批量解码不要共用输出目录,每个包独立目录,避免资源互相覆盖。
验证阶段我固定看四项:包名版本、签名、安装结果、目标资源差异。包名版本用aapt dump badging,签名用apksigner verify --verbose,安装用adb install -r,资源差异用diff -ru对比基线和工作目录。四项里任何一项不符合预期,都回到解码目录查,不要直接改签名或换安装包。
| 验证项 | 命令 | 期望 |
|---|---|---|
| 包名与版本 | aapt dump badging signed.apk | 与原包一致,版本不降低 |
| 签名 | apksigner verify --verbose signed.apk | 输出 Verified |
| 安装 | adb install -r signed.apk | 返回 Success |
| 资源差异 | diff -ru baseline decoded | 只出现目标资源改动 |
从那以后我每次重打包都先把只读解码、构建、验签三件事写成脚本跑一遍,再动第二个文件;资源改多了反而容易忘记基线长什么样。希望帮到你。
本文还有配套的精品资源,点击获取