简介面向 Android 逆向入门者与移动安全学习者的 APK 反编译工具包针对 macOS 环境下解包 APK、提取资源与 smali 代码的实际需求整理而成。压缩包共 3 个文件、约 10.25MB以 jar 主程序、sh 启动脚本与 md 说明文档为主jar 承担反编译与回编译的核心功能sh 脚本省去在终端反复输入命令的麻烦md 文档则交代了环境准备、常用参数与执行流程三者配合可直接跑通解包、查看资源、重新打包的完整链路。已有 2832 人学习下载说明其在入门场景中具备一定通用性。读者拿到后可按说明文档逐步操作理解 apktool 的目录产出结构包括资源文件、清单文件与 smali 目录并借助脚本快速完成批量解包与回编译遇到报错也能从文档中找到常见问题的排查方向适合作为 APK 逆向获取资源的常备工具集。1. apktool 不是“反编译即源码”先看清它能拿到什么apktool 工具以及使用说明这类资源真正有价值的地方不是把 APK 丢进去看个热闹而是让 apk逆向 里的获取apk资源、布局核对和回编译验证变成可重复的流程。很多人第一次搜“获取apk资源”以为 apktool 一解包就能拿到图片、字符串和布局结果发现 res 目录里全是短文件名values 里的中文变成乱码回编译又报资源找不到。这个工具包解决的是从 APK 到可读资源目录、再到可回编译工程的链路问题适合做兼容性分析、自有应用排查、界面还原和教学演示的从业者。它不替代 jadx 这类 Java 反编译工具也不负责替你判断授权边界它擅长的是资源表解码、Manifest 还原、smali 反汇编和重打包。先接受“apktool 给的是可编辑工程不是原始工程”这个前提后面的命令和排错才不会跑偏。2. 环境与工具链apktool、Java、aapt2、签名工具的版本配平2.1 安装与目录约定apktool 本身是一个 jar 包加启动脚本但它对 Java 版本、aapt/aapt2、Android SDK 构建工具比较敏感。常见翻车不是命令写错而是 Java 版本太新或太旧导致 apktool 在解析 resources.arsc 时直接抛异常。我一般先把工具链拆成三个目录tools/apktool放 apktool.jar 和包装脚本tools/build-tools放 aapt2、apksigner、zipalignwork/放待处理的 APK 和输出目录。这样做的原因是 apktool 回编译时会调用 aapt/aapt2签名时又要找 apksigner如果全靠 PATH 里碰运气换一台机器就容易出现“同一份工程昨天能编、今天不能编”的玄学问题。先做一次环境自检不要急着解码。下面这段命令只检查版本和可执行文件位置不修改任何文件# 检查 Java 是否可用apktool 启动脚本依赖 java java -version # 检查 apktool 是否在 PATH 中输出应为 apktool 的版本信息 apktool -version # 检查 Android 构建工具是否可用后面签名和对齐要用 aapt2 version apksigner version zipalign -h | head -n 5逻辑说明java -version看的是运行时版本不是编译器版本apktool 对 Java 版本有下限要求太老的 Java 会在启动阶段就报UnsupportedClassVersionError。apktool -version能正常输出说明 jar 包和启动脚本的路径关系是对的。aapt2 version和apksigner version用来确认构建工具目录已经进入 PATH如果这里报command not found后面回编译和签名会接连失败。参数说明zipalign -h | head -n 5只是确认命令存在不是完整帮助head在这里避免帮助信息刷屏。实际使用时aapt2、apksigner、zipalign建议来自同一套 build-tools不要一个来自旧目录、一个来自新目录。版本不要求完全一致但跨度太大时资源编译和签名校验容易对不上。工具包里的说明文档如果写了推荐目录就按它的目录走没有写时按“同一套构建工具放一起”的原则整理。注意只处理自己拥有或获得授权的 APK别把工具用在绕过授权、去除付费功能或分发他人应用上。资源提取和回编译的边界先在心里划清楚。2.2 第一次解码apktool d 的参数与输出结构第一次解码不要一上来就改东西先用一个干净输出目录跑通。下面这条命令把 APK 解码到work/app_out-f表示目标目录存在时强制覆盖避免旧文件混进来# 进入工作目录APK 放在当前目录下 cd work # 解码 APK-f 覆盖旧输出-o 指定输出目录 apktool d app.apk -o app_out -f # 查看输出目录的一级结构 find app_out -maxdepth 1 -type d -o -maxdepth 1 -type f | sort逻辑说明apktool d的d是 decode默认会同时反汇编 dex 和解码资源。输出目录里通常会出现res/、smali/、smali_classes2/、assets/、lib/、original/、AndroidManifest.xml和apktool.yml。res/是解码后的资源目录smali/是反汇编后的字节码目录original/里通常保留原始签名信息和部分未解码内容。find只是看结构不参与构建。参数说明-o后面跟输出目录目录不存在时会创建-f会删除已有输出目录再重建适合反复试验但如果你已经在输出目录里改了文件-f会把改动清掉。只想提取资源、不想反汇编 dex 时常见做法是加-s或--no-src只想看 smali、不解码资源时加-r或--no-res。这两个参数能显著缩短大 APK 的处理时间也减少资源解码失败对整体流程的干扰。输出结构里最容易被误解的是res/values/strings.xml和res/values/public.xml。前者是字符串资源后者记录资源 ID 与名称的对应关系。很多“资源文件名被混淆”的问题最后都要回到public.xml和资源表里查而不是在res/drawable里硬猜。apktool.yml记录版本、SDK 信息、是否压缩等元数据回编译时 apktool 会读它手改这个文件要谨慎尤其是doNotCompress和sdkInfo相关字段。2.3 框架文件与多设备资源差异apktool 解码和回编译时依赖 framework 资源。不同 Android 版本的 framework 资源不一样尤其是系统属性、主题和样式。遇到No resource identifier found for attribute ... in package android这类错误先不要怀疑自己的 XML 写错了优先检查 framework 是否匹配。常见做法是把目标设备或目标系统版本的framework-res.apk安装到 apktool 的框架目录# 安装框架文件apktool 会把它放到自己的框架目录 apktool if framework-res.apk # 如果使用自定义框架目录显式指定便于多版本隔离 apktool if framework-res.apk --frame-path ./framework # 解码时指定同一个框架目录保证编解码使用同一套 framework apktool d app.apk -o app_out -f --frame-path ./framework逻辑说明apktool if的if是 install-framework不是条件判断。它把 framework 资源注册到 apktool 的框架缓存里。--frame-path指定缓存目录适合同时维护多个系统版本的框架资源。解码和回编译都带同一个--frame-path可以避免“解码时用 A 框架、回编译时用 B 框架”的错位。参数说明framework-res.apk要从目标系统或对应 SDK 里取不要随便拿一个不相关的包。--frame-path后面跟目录目录不存在时 apktool 通常会创建。多设备场景下我一般按系统版本建子目录比如framework/api33、framework/api30解码时显式指定。这样做的代价是命令变长但排错时能快速定位“是不是框架换错了”。提示框架文件不是越多越好。缓存里堆了多个版本又不显式指定时apktool 可能选到不匹配的那个。每次处理新系统版本先确认--frame-path指向哪里。3. 获取 APK 资源从 resources.arsc 到可读目录的完整链路3.1 资源解码失败的三种典型原因“获取apk资源”最常见的失败不是命令不会写而是解码阶段就断了。第一种原因是 APK 使用了较新的资源表格式或压缩方式旧版 apktool 解析resources.arsc时直接报Could not decode arsc file。这类问题优先换工具包内的 apktool 版本或者用aapt2 dump resources先看资源表是否能读。不要在手改二进制资源表上耗时间成本高且容易把包改坏。第二种原因是资源目录名或文件名不符合 aapt 的规则。APK 里可能存在大小写混用、特殊字符、超长文件名解码后落在res/下回编译时 aapt2 会报invalid resource directory name或file name must end with .xml。这类问题要在解码后先跑一次目录检查把明显非法的名字列出来再决定是重命名还是放弃回编译、只做资源提取。第三种原因是 9-patch 图片。*.9.png不是普通 PNG它四周的黑线决定拉伸区域。用普通图片编辑器把黑线裁掉、缩放或重新保存回编译时就会报9-patch file ... malformed。我一般对 9-patch 只做“原样复制”或“用支持 9-patch 的工具改”不在不确定的情况下重采样。下面这段命令用来快速定位资源解码问题和目录合法性不直接改文件# 只解码资源不反汇编 dex减少干扰 apktool d app.apk -o app_res -f -s # 列出 res 下所有目录名检查是否有空格、大写或特殊字符 find app_res/res -mindepth 1 -maxdepth 1 -type d -printf %f\n | sort # 找出所有 9-patch 图片后续修改时重点保护 find app_res/res -name *.9.png | sort逻辑说明-s让 apktool 跳过 dex 反汇编资源解码失败时错误信息更集中。find的第一个命令只看res/下一级目录名方便发现Drawable、layout这类不符合小写规范的目录。第二个find把所有 9-patch 图片列出来修改资源前先知道哪些文件不能随便动。参数说明-printf %f\n只输出文件名不输出完整路径便于肉眼扫。-name *.9.png的引号不能省避免 shell 展开。实际处理时如果目录名非法但资源还要用可以先把非法目录整体复制到独立资源池再在可回编译工程里按合法名称重建不要直接在原目录上批量重命名后立刻回编译先备份再试。3.2 只提取资源的最小命令组合如果目标只是获取图片、布局、字符串和 assets不需要改 smali最省事的组合是apktool d -s加一次目录整理。-s保留 dex 原样不生成 smali处理速度快很多。解码后把res/、assets/、AndroidManifest.xml和apktool.yml复制到资源池即可。需要批量处理多个 APK 时用脚本比手敲命令稳下面这段 Python 只做“批量解码 资源目录检查”不涉及任何网络请求from pathlib import Path import subprocess # 待处理 APK 目录和输出目录按实际路径修改 APK_DIR Path(work/apks) OUT_DIR Path(work/decoded) APKTOOL apktool OUT_DIR.mkdir(parentsTrue, exist_okTrue) for apk in sorted(APK_DIR.glob(*.apk)): out OUT_DIR / apk.stem # -s 不反汇编 dex-f 覆盖旧输出-o 指定输出目录 cmd [APKTOOL, d, -s, -f, str(apk), -o, str(out)] print(RUN:, .join(cmd)) subprocess.run(cmd, checkTrue) res_dir out / res assets_dir out / assets print(RES:, res_dir if res_dir.exists() else missing) print(ASSETS:, assets_dir if assets_dir.exists() else missing)逻辑说明脚本遍历work/apks下的 APK按文件名创建输出子目录。subprocess.run(..., checkTrue)在 apktool 返回非零时立刻抛异常避免坏结果继续往下走。输出只打印资源目录和 assets 目录是否存在方便先判断解码是否完整再决定是否进入人工筛选。参数说明APK_DIR和OUT_DIR按你的工作目录改APKTOOL如果不在 PATH写成绝对路径或启动脚本路径。-s是跳过源码反汇编适合资源提取如果你后面要改 smali把这个参数去掉。-f会覆盖同名输出目录批量跑之前确认输出目录里没有需要保留的手工修改。脚本没有处理文件名冲突同名 APK 放在不同子目录时输出目录会重名需要自己加前缀。注意批量解码前先确认磁盘空间。res/里可能有大量图片和多语言资源几十个 APK 一起解空间消耗比想象中大。3.3 资源 ID 与多语言目录的核对方法解码后的资源目录不一定保留原始文件名。APK 构建时资源文件名可能被压缩成短名resources.arsc里保存的是资源 ID、类型和值原始文件名不一定完整保留。遇到res/drawable/a.png、res/layout/b.xml这种名字不要靠猜。先看res/values/public.xml它通常记录资源 ID 和名称的对应关系再用aapt2 dump resources看资源表交叉核对类型和 ID。多语言资源在res/values-zh-rCN/、res/values-en/这类目录下。核对时先确认默认values/strings.xml里有对应 key再看限定目录里是否有覆盖。只改values-zh-rCN/strings.xml而默认资源里没有这个 key回编译可能不报错但运行时会回退到默认值看起来像“修改没生效”。下面这张表是我核对资源时常用的几个位置位置用途核对重点res/values/public.xml资源 ID 与名称映射ID 是否连续、是否有被删条目res/values/strings.xml默认字符串key 是否存在于默认资源res/values-zh-rCN/strings.xml中文限定资源是否只覆盖部分 keyres/layout/*.xml布局文件引用的资源 ID 是否存在res/drawable*/*.9.png9-patch 图片黑线是否完整assets/原始资源文件是否被代码按路径直接读取核对资源 ID 时常见做法是先在布局里搜string/、drawable/、layout/引用再回到public.xml确认名称存在。如果布局里引用了某个资源但public.xml里没有对应条目回编译会报No resource found。这时不要随手删布局里的引用先确认是资源被混淆、被裁剪还是 apktool 解码不完整。资源表本身有问题时优先换工具版本或换解码参数不要靠手写public.xml硬补。4. 修改与回编译smali、Manifest 和资源替换的边界4.1 修改前的边界与备份apktool 能回编译不代表所有修改都适合做。我的习惯是先划三条线只改自己拥有或获得授权的 APK只改文案、布局、调试开关、资源替换这类可验证内容不碰授权校验、付费逻辑、签名校验绕过和他人应用分发。原因很实际前一类修改出问题还能靠 logcat 和资源对比回退后一类修改一旦越界技术问题会变成合规问题而且运行时崩溃的排查成本极高。备份不是复制一份 APK 就完事。回编译工程里至少保留三样东西原始 APK、解码后的完整输出目录、apktool.yml。原始 APK 用来对比和重新解码完整输出目录用来回退手工修改apktool.yml用来确认回编译参数没有漂移。我一般把输出目录打成压缩包命名带日期改坏了直接解压覆盖不靠记忆回滚。提示修改前先跑一次“空回编译”。也就是解码后不改任何文件直接apktool b再签名安装。空回编译能通过说明工具链和框架基本匹配空回编译都失败先修环境不要急着改业务文件。4.2 smali 与资源替换的最小改动smali 是可读的字节码汇编改字符串常量比改逻辑安全。比如把某个提示文案从旧值改成新值找到对应的const-string指令只改字符串内容不动寄存器数量和分支结构。资源替换更简单把res/drawable下的图片按原文件名、原格式替换9-patch 保持黑线完整XML 布局只改文本和间距不随意删id。下面是一个最小改动示例只改字符串资源不碰 smali 逻辑# 解码资源并保留 smali便于同时检查资源和代码引用 apktool d app.apk -o app_mod -f # 查看目标字符串 key确认默认资源里存在 grep -n app_name app_mod/res/values/strings.xml # 修改后回编译输出到新的 APK不覆盖原始 APK apktool b app_mod -o app_mod_unsigned.apk逻辑说明第一条命令生成可编辑工程第二条命令只用来定位 keygrep -n输出行号方便编辑时跳转第三条命令回编译输出未签名 APK。这里没有直接改 smali因为字符串资源改动的验证路径最短改strings.xml、回编译、签名、安装、看界面是否生效。这个链路跑通后再考虑 smali 里的小范围改动。参数说明apktool b的b是 build后面跟工程目录-o指定输出 APK。回编译时 apktool 会读apktool.yml和res/values/public.xml所以不要手删public.xml里的条目。如果工程里同时存在smali/和smali_classes2/apktool 会按多 dex 处理不要手动合并目录。改 smali 时寄存器数量变化要同步改.locals否则回编译可能通过、运行时却崩溃。4.3 回编译、对齐、签名一条龙回编译出来的 APK 是未签名的直接安装通常提示INSTALL_PARSE_FAILED_NO_CERTIFICATES。签名顺序不能反先zipalign对齐再apksigner签名。先签名再对齐会破坏签名块安装时校验失败。下面是一套可复现的命令keystore 只用于本地测试不要把测试密钥用到对外分发的包上# 生成一个本地测试 keystore仅用于调试安装 keytool -genkeypair -v \ -keystore demo.keystore \ -alias demo \ -keyalg RSA \ -keysize 2048 \ -validity 10000 \ -storepass 123456 \ -keypass 123456 \ -dname CNDemo, OUTest, OLocal, LLocal, STLocal, CCN # 先对齐再签名-p 表示按页对齐4 是常见对齐字节数 zipalign -f -p 4 app_mod_unsigned.apk app_mod_aligned.apk # 使用 apksigner 签名输出最终可安装包 apksigner sign \ --ks demo.keystore \ --ks-key-alias demo \ --ks-pass pass:123456 \ --key-pass pass:123456 \ --out app_mod_signed.apk \ app_mod_aligned.apk # 校验签名和对齐结果 apksigner verify --verbose app_mod_signed.apk zipalign -c -v 4 app_mod_signed.apk逻辑说明keytool生成密钥对和 keystorezipalign调整 APK 内未压缩文件的偏移减少运行时内存映射开销apksigner写入签名块。apksigner verify --verbose会输出 v1/v2/v3 签名方案是否启用zipalign -c -v 4只做校验不修改文件。参数说明-storepass和-keypass只是本地示例实际使用换成自己的密码管理方式-dname里的字段是本地测试信息不要填真实个人或机构信息。--ks-key-alias必须和生成时一致。zipalign -p 4的4是常见对齐值-p让共享库按页对齐不是所有场景都必须加-p但回编译包做对齐校验时建议保留。apksigner的--out不要和输入文件同名避免覆盖后无法回退。5. 避坑与排查回编译报错、资源错乱、安装失败的现场记录5.1 现象资源文件名变成短名找不到原始图片现象很直观apktool d之后res/drawable里出现a.png、b.pngres/layout里出现c.xml和源码里的命名完全对不上。新手第一反应是 apktool 坏了或者以为需要某个“还原文件名”的开关。实际原因通常不是解码失败而是 APK 构建阶段对资源文件名做了压缩或混淆resources.arsc里保留的是资源 ID、类型和值原始文件名不一定完整保留。apktool 能还原资源结构和大部分值但不可能凭空恢复构建时被丢掉的命名信息。解决思路是换查询入口不要跟文件名较劲。先在布局 XML 里搜drawable/、layout/引用拿到资源名称再在res/values/public.xml里按名称找资源 ID最后用 ID 去aapt2 dump resources的输出里核对类型和值。需要批量定位时用grep -R在res/下搜资源名比在图片目录里逐个人眼比对快。还要注意短文件名不影响回编译只要public.xml和资源目录结构完整apktool 能按 ID 重新打包。真正会影响回编译的是手删public.xml条目、改目录名、替换 9-patch 时破坏黑线这些才是要优先排查的点。5.2 现象回编译提示资源 ID 找不到或属性不存在现象是apktool b输出No resource identifier found for attribute xxx in package android或者No resource found that matches the given name。原因通常有三类framework 不匹配、资源被误删、资源目录名非法。framework 不匹配最常见尤其是解码时用了系统 A 的框架回编译时 apktool 缓存里却是系统 B 的框架。资源被误删也很常见比如为了减小体积删了某个values条目但布局里还在引用。目录名非法则多出现在手工复制资源时把drawable-v4这类旧限定符或大小写混用的目录带进来。解决顺序是先确认--frame-path在解码和回编译时一致再检查res/values/public.xml是否完整最后跑目录名检查。framework 问题用apktool if重新安装目标框架或者显式指定框架目录。资源缺失问题不要靠手写public.xml硬补先找回原始资源或从原始 APK 重新解码。目录名问题按 aapt2 的规则改成合法小写目录改完先做一次空回编译验证。还有一点容易忽略apktool 新旧版本对--use-aapt2的默认行为不同工具包说明里如果指定了 aapt2就按说明走不要一会加参数一会不加。5.3 现象签名后安装提示证书无效或解析失败现象是adb install返回INSTALL_PARSE_FAILED_NO_CERTIFICATES、INSTALL_FAILED_INVALID_APK或者安装到一半提示“解析包时出现问题”。原因常见于签名顺序错误先apksigner签名再zipalign对齐过程会破坏签名块或者只用了 v1 签名而目标系统要求 v2/v3还有 keystore alias 写错、密码错、输出文件被覆盖。另一个原因是回编译生成的 APK 本身结构不完整签名工具只是把问题暴露出来。解决时先跑apksigner verify --verbose看签名方案再跑zipalign -c -v 4看对齐。顺序必须是zipalign在前、apksigner在后。targetSdk 较高时只靠jarsigner做 v1 签名已经不够常见做法是直接用apksigner。如果签名校验通过但安装仍失败看adb install的完整错误码INSTALL_FAILED_UPDATE_INCOMPATIBLE通常是设备上已有同包名但签名不同的应用卸载旧版或换测试设备。不要为了装上而关闭系统校验那会把问题带到运行时。5.4 现象改了 smali 或资源后启动崩溃现象是安装成功启动就崩logcat 里出现ClassNotFoundException、Resources$NotFoundException、VerifyError或NoSuchMethodError。原因可能是 smali 寄存器数量不对、分支跳转标签被破坏、资源 ID 变化后代码仍引用旧 ID或者删了资源但代码按路径读取。资源替换导致崩溃时先看崩溃堆栈里的资源 ID再回public.xml对照smali 改动导致崩溃时先回退到空回编译版本确认是改动引入还是环境问题。解决方法是把改动拆到最小。先只改strings.xml跑通安装和启动再改布局最后才动 smali。smali 改动只改字符串常量时确认const-string后面的寄存器没有被其他指令占用改方法逻辑时同步检查.locals和try/catch块。崩溃日志用adb logcat过滤包名和AndroidRuntime不要只看最后一行。改坏了就解压备份覆盖不要在一个已经崩溃的工程上继续叠改动。6. 进阶用法把 apktool 做成资源快照与回编译验证的固定流程做到后面单次解码和回编译不是难点难点是“这次改完和上次到底差在哪”。我一般把 apktool 当成资源快照工具每次解码后对res/和AndroidManifest.xml做一次目录快照改完再解一次用diff看差异。这样能快速发现误删资源、布局被意外覆盖、多语言条目只改了一半的问题。下面这段脚本只做目录对比不修改 APK#!/usr/bin/env bash set -euo pipefail # 用法./diff_res.sh old_dir new_dir OLD_DIR$1 NEW_DIR$2 # 只比对资源目录和 Manifest输出到独立文件避免刷屏 diff -ruN $OLD_DIR/res $NEW_DIR/res res.diff || true diff -ruN $OLD_DIR/AndroidManifest.xml $NEW_DIR/AndroidManifest.xml manifest.diff || true # 统计差异行数先看规模再决定是否逐行看 wc -l res.diff manifest.diff逻辑说明diff -ruN递归比较目录-N把新增文件按空文件处理方便看新增资源|| true是因为diff发现差异时返回非零但脚本不希望因此中断。输出拆成res.diff和manifest.diff避免终端被大量内容淹没。wc -l先给差异规模几十行可以逐行看几千行就先按目录过滤。参数说明OLD_DIR和NEW_DIR都是 apktool 解码后的工程目录。res.diff里以开头的是新增-开头的是删除是变更位置。实际使用时我还会加一张固定检查表检查项命令或位置目的资源 ID 是否连续res/values/public.xml发现误删条目9-patch 是否完整find res -name *.9.png防止黑线被破坏多语言是否只改一半diff -r res/values*发现漏改限定目录签名方案apksigner verify --verbose确认 v2/v3 是否启用对齐结果zipalign -c -v 4确认签名前对齐这套流程的价值在于可回退。资源 diff 能告诉你“改了哪些”签名校验能告诉你“包能不能装”空回编译能告诉你“环境有没有漂移”。从那以后我每次回编译前都会先跑一遍资源快照和签名校验不再凭感觉判断“应该能装”。工具包和说明文档我放在项目资源里需要的时候直接取用希望帮到你。本文还有配套的精品资源点击获取