
简介这是一套面向 Android 开发与安全研究者的 APK 反编译工具集合适合已具备基础 Android 开发经验、希望学习他人代码实现、进行恶意样本分析或二次开发的中高级技术人员使用。资源整合了 apktool、auto-sign、dex2jar 与 jd-gui 四个常用组件覆盖从 APK 解包、资源与清单文件还原、DEX 转 JAR 到图形化查看 Java 源码的完整链路。压缩包为 rar 格式内含 58 个文件约 10.58MB以 17 个 bat 与 14 个 sh 脚本承载各工具在 Windows 与 Linux 下的命令行入口11 个 jar 构成 apktool、dex2jar、signapk 等核心程序另有 3 个 exe 图形界面程序、9 个 txt 说明文档以及签名所需的 pem、pk8 密钥文件开箱即可搭建本地反编译环境。目前已有 13978 人学习下载工具链组合成熟、脚本齐备便于读者按需查阅官方说明并快速上手实践。1. 一个只剩 APK 的交接包把我逼回了这套工具链上周接手一个交接项目仓库里只剩一个 40MB 的 release 包源码、构建脚本、签名文件全丢了。要查的东西很具体某个第三方 SDK 的版本号、一张图片资源被谁引用、清单里多出来的权限是谁塞的。这种时候 APK 反编译工具就是唯一入口——apktool 把资源和 smali 拆开dex2jar 配 jd-gui 把 dex 还原成能读的 Javaauto-sign 负责改完之后重新签名装回设备。这套组合不算新但在「只想快速看清一个包」的场景里依然最省事。下面把我跑通的流程、参数和翻车点摊开写适合审计自有或已获授权应用、排查资源与 SDK 依赖的开发测试同学。2. apktool 拆包与回编译目录结构、开关参数与 smali 的可读边界apktool 干的事是把一个二进制 APK 拆成「人能看、也能再打回去」的工程目录。它内部做了两件独立的工作用自带的解析器把AndroidManifest.xml、resources.arsc和各类 XML 还原成可读文本用 baksmali 把classes.dex反汇编成 smali 汇编。这两件事可以分开做也正因为能分开后面排查问题时才能只拆一半、少踩很多坑。先把定位说清楚apktool 不产出 Java 源码它产出的是 smali。smali 是可读、可改、可回编译的 Dalvik 汇编寄存器、类型描述符、方法签名都是显式的。想读业务逻辑的 Java 形态得走第 3 章的 dex2jar 路线想改资源、改清单、改一个常量的返回值apktool 这条链更直接。2.1 环境准备与三种调用方式apktool 本身是 Java 程序跑之前先确认 JDK 在位。版本上没必要追新JDK 8 到 17 都能覆盖绝大多数包反而是 JDK 太新偶尔会遇到老版本 apktool 的反射告警。# 先看 Java 是否可用输出里要有 version 行 java -version # 方式一官方 wrapper 脚本Linux / macOS脚本和 jar 放同一目录 chmod x apktool apktool.jar ./apktool --version # 方式二直接调 jar适合在 CI 或多版本共存的机器上用 java -jar apktool_version.jar --version # 方式三Windows 下用 apktool.bat本质也是包了一层 java -jar apktool.bat --version三种方式效果一致区别只在路径管理。我一般在项目里固定放一份 jar用完整路径调用避免不同 shell 会话里apktool指向不同版本——这类「昨天还能拆今天报错」的玄学十有八九是 PATH 里有第二份旧 jar。框架资源framework-res是另一个容易被忽略的前置。系统应用或者带特殊主题的包反编译时会引用厂商私有资源apktool 找不到就会在回编译阶段报资源 ID 不存在。常规做法是先装一份对应系统版本的框架# 从设备或系统镜像里取 framework-res.apk 后安装到本地框架目录 apktool if framework-res.apk # 多版本共存时打标签拆包时用 -p 指定 apktool if framework-res.apk -t android-33 apktool d target.apk -p android-33if是 install-framework 的缩写-t给这份框架起别名-p在拆包时按别名取用。第三方应用绝大多数不需要这一步只有碰到系统级包或定制 ROM 里的应用才用得上别一上来就装一堆框架把自己绕进去。2.2 拆包一条命令背后的四类产物最常用的拆包命令就一行但每个开关都对应一个明确的取舍。# 标准拆包资源 smali 全拆-f 覆盖已存在的输出目录 apktool d app-release.apk -o out_full -f # 只拆资源跳过 dex 反汇编速度快很多适合只查清单和图片 apktool d app-release.apk -o out_res -s -f # 只拆 dex跳过资源解析适合只想看 smali 逻辑 apktool d app-release.apk -o out_dex -r -f # 大包只反编译主 dex减少等待时间 apktool d app-release.apk -o out_main --only-main-classes -f-o指定输出目录不给就按 APK 文件名建目录-f强制覆盖不加的话目录已存在会直接退出-s是 skip sources 的缩写只保留classes.dex原文件不做反汇编-r是 skip resources资源相关目录不解析--only-main-classes只处理主 dex分包里的classes2.dex、classes3.dex原样保留适合先摸清主逻辑再决定要不要全量拆。拆完的目录长这样每类产物对应不同的排查目标产物路径内容典型用途AndroidManifest.xml反编译后的明文清单查权限、组件导出状态、debuggable标记res/解码后的图片与 XML 资源查图标、布局、字符串替换后回编译smali/、smali_classes2/各 dex 对应的 smali 目录定位常量、改返回值、追调用链apktool.yml版本、压缩方式、资源 ID 偏移等元信息回编译失败的第一个排查点original/原始清单与签名信息副本对照反编译结果是否失真lib/、assets/原生库与资产文件通常原样保留查 so 版本、看内置配置original/这个目录值得单独说一句。apktool 会把原始AndroidManifest.xml和META-INF下的签名文件备份在这里方便你比对反编译结果有没有丢东西。很多人拆完直接删掉等回编译报错又找不到对照物只能重新拆一遍白等十分钟。2.3 回编译与 smali 的可读边界改完东西要打回去命令同样简单但参数选择直接影响产物能不能装。# 标准回编译输出未签名的 APK apktool b out_full -o repacked.apk # 资源解析报错时换上 aapt2 解析器重试 apktool b out_full -o repacked.apk --use-aapt2 # 保留原始清单和签名块只替换资源 apktool b out_full -o repacked.apk -c-o是输出文件名--use-aapt2切换资源编译器-c是 copy original 的缩写会沿用original/里的清单和签名块。第三种用法在「只换图片资源」时特别省事能绕开一堆资源 ID 重排导致的引用错乱。回编译出来的 APK 是未签名的直接adb install会报INSTALL_PARSE_FAILED_NO_CERTIFICATES这是正常的签名流程见第 4 章。现在说得现实一点smali 的可读性是有上限的能不能读懂取决于这个包做了多少保护。第一层是代码混淆。类名方法名变成a、b、a$a这种交叉引用还在但语义没了。这类包读 smali 的体验是「结构清晰、含义不明」适合定位具体常量和方法调用不适合通读业务。真要理解逻辑还是得靠 Java 层面的反编译再配合调用链推断。第二层是字符串加密。常见做法是把关键字符串塞进一个解密方法运行时才还原。smali 里能看到的是const-string v0, x9f2...加一次静态方法调用。这时候光看 smali 没用得找到解密函数要么按它的算法在本地跑一遍要么在设备上挂调试器看返回值。这一步是分水岭很多人到这儿就卡住了。第三层是资源混淆。资源路径被重写成res/a/b.png这种短名resources.arsc里的 ID 也重排过。表现是 apktool 能拆开但你不知道哪张图对应哪个界面。常规做法是按文件大小和图片尺寸筛或者回编译后装上跑一遍用布局检查工具反查。提示只对自有应用或已获得书面授权的应用做反编译。第三方商业包的保护措施往往对应明确的授权条款动手前先确认边界别把技术问题变成合规问题。3. dex2jar 加 jd-gui从 classes.dex 到能读的 Java卡点在哪apktool 给的是 smalidex2jar 加 jd-gui 给的是 Java 形态的近似源码。这两条路不冲突实际排查里经常是先用 jd-gui 快速定位到某个类再回 smali 里改。理解 dex2jar 干了什么比记住命令更重要它把 Dalvik 字节码翻译成 JVM 字节码产出一个标准 jarjd-gui 再用自己的反编译器把 class 还原成 Java。翻译和还原是两次有损转换所以结果一定是「接近」而不是「等于」原源码。3.1 先从 APK 里精确取出 dexdex2jar 的输入是 dex 文件不是 APK。多 dex 的包要逐个处理别指望它自己遍历。# 只解出 dex 文件其余条目不解速度快 unzip -o app-release.apk classes*.dex -d dex_out # 确认拿到了几个 dex分包应用通常有 2 到 5 个 ls -lh dex_out # Windows 下没有 unzip 时用 PowerShell 的 Expand-Archive 解全包再挑 # Expand-Archive -Path app-release.apk -DestinationPath apk_outclasses*.dex加引号是为了让 shell 把通配符交给 unzip 处理不加引号在部分环境下会被当前目录的同名文件干扰。-d指定输出目录-o覆盖已有文件。这一步看着琐碎但它决定了后面转换的输入是否干净——直接把整个 APK 丢给 dex2jar 是不行的。3.2 dex2jar 转换与参数说明拿到 dex 之后逐个转 jar。Linux / macOS 用.shWindows 用.bat参数一致。# 单个 dex 转 jar-f 强制覆盖已有输出-o 指定 jar 路径 ./d2j-dex2jar.sh dex_out/classes.dex -f -o jars/classes.jar # 多 dex 逐个转输出名带序号避免互相覆盖 for f in dex_out/classes*.dex; do base$(basename $f .dex) ./d2j-dex2jar.sh $f -f -o jars/${base}.jar done # 转换过程报异常但想继续用 --skip-exceptions 跳过无法处理的指令 ./d2j-dex2jar.sh dex_out/classes.dex -f --skip-exceptions -o jars/classes.jar-f强制覆盖输出文件不加的话目标存在会直接失败-o指定输出 jar 路径不指定就按输入名生成在同目录--skip-exceptions遇到无法翻译的指令时跳过而不是中断代价是产物里对应方法体可能缺失。转换过程中刷出来的WARN大多是正常的只要最后有Done且 jar 文件大小合理就可以往下走。多 dex 分开转是对的但要知道后果classes.jar和classes2.jar是两个独立的 jarjd-gui 打开其中一个时引用另一个 jar 里的类会显示成未解析状态。这是 dex2jar 路线的固有短板不是操作失误。想省事的话可以在 jd-gui 里把多个 jar 一起加进 classpath或者直接换用能一次性加载整个 APK 的反编译工具做补充阅读。3.3 jd-gui 打开与阅读技巧jd-gui 是个 GUI 程序双击 jar 就能开但大包直接双击大概率卡死。原因是默认堆内存太小加载几千个类就撑爆了。# 给 jd-gui 更大堆内存2G 起步超大包给到 4G java -Xmx2g -jar jd-gui.jar # 打开指定 jar省去手动选文件 java -Xmx2g -jar jd-gui.jar jars/classes.jar-Xmx控制最大堆32 位 JVM 用不了太大确认是 64 位再加。打开之后有几个操作能明显提效CtrlF全局搜索类名和方法名比在目录树里翻快得多CtrlShiftT按类名跳转前提是类名还没被混淆成单字母右键菜单里的「查找用法」能追引用但对跨 jar 的引用基本失效。阅读上有几个判断要提前建立。一是 Kotlin 代码反编译后结构会很怪协程、扩展函数、内联类还原出来经常是一堆带$的合成类这不是工具坏了是字节码层面的信息损失。二是 lambda 表达式会被还原成lambda$xxx$0形式的合成方法读的时候要跟调用点对上。三是泛型信息在字节码里是擦除的反编译出来的List往往不带类型参数得靠上下文推。还有一个现实问题dex2jar 对新版 dex 格式的支持是滞后的。遇到Unsupported dex version这类报错先别怀疑包有问题大概率是工具版本跟不上 Android 的 dex 版本迭代。解法有两个换新版的 dex2jar或者改用直接解析 APK 的反编译工具。我的习惯是两条腿走路——dex2jar 加 jd-gui 用来快速浏览和搜索遇到读不动的地方换另一个工具交叉验证两边结论一致才敢下判断。4. auto-sign 与现代签名链从测试证书到 apksigner 的完整顺序改完的 APK 不签名装不上这是硬门槛。auto-sign 是一批老脚本的统称核心逻辑是用内置的测试证书对 APK 做 v1 签名一条命令搞定。它在「自己改着玩、装模拟器验证」的场景里确实方便但放到今天有两个明显局限用的是公开测试证书任何设备都不会信任它为正式发布身份只做 v1 签名对 targetSdk 30 以上的包不够用。所以真正要落地还是得走 keystore 加 apksigner 这条现代链路。auto-sign 可以留着当快速验证的快捷方式别拿它当发布流程。4.1 auto-sign 到底做了什么这类脚本通常包含一个sign.bat/sign.sh、一个签名工具的 jar以及一对测试密钥文件。执行时它做的动作是把 APK 里的META-INF清掉用测试私钥对每个条目算摘要写进CERT.SF和CERT.RSA完成 v1 签名。# 典型用法传入待签名 APK输出到指定文件 ./sign.sh repacked.apk signed_test.apk # Windows 下 sign.bat repacked.apk signed_test.apk用起来是省事但要知道自己在用什么。测试证书的私钥是公开的意味着任何人都能伪造出「同一个签名」的包所以它只适合本机调试。另外它不处理 zipalign签完的包在部分设备上会因为未对齐而影响加载效率甚至在某些校验严格的场景下装不上。4.2 自己生成 keystore 的完整命令正式一点的做法是自己生成密钥库一次生成长期复用。下面的命令生成一个 2048 位 RSA 密钥、有效期 10000 天的 keystore。# 生成 keystore-dname 里的信息按自己项目填CN 建议用项目代号 keytool -genkeypair -v \ -keystore demo-release.jks \ -keyalg RSA -keysize 2048 -validity 10000 \ -alias demo \ -storepass store_password -keypass key_password \ -dname CNdemo, OUdev, Ointernal, Lcity, STstate, CCN # 查看 keystore 里的条目确认 alias 和有效期 keytool -list -v -keystore demo-release.jks -storepass store_password-keyalg和-keysize决定密钥强度RSA 2048 是当前通用选择-validity是有效天数Google Play 要求至少到 2033 年自己算好-alias是条目别名签名时要对上-storepass和-keypass在 PKCS12 格式下通常要一致不一致会告警。-dname里的字段不用太较真但不能留空。提示这个 keystore 一旦用于发布就不能丢也不能换。换签名意味着已安装用户无法覆盖升级只能卸载重装。生成后至少留两份异地备份密码单独存。4.3 对齐与签名顺序以及 v1/v2/v3 的选择这一段是血泪经验最集中的地方顺序错了包能装上但校验不过或者干脆装不上。正确顺序是先 zipalign 对齐再 apksigner 签名。原因是签名会往 APK 里写入签名块并改变文件偏移如果先签名后对齐对齐操作会破坏已有签名。反过来先对齐再签名apksigner 会自己处理对齐后的偏移v2 及以上签名不受影响。# 第一步4 字节对齐-p 让 so 文件按页对齐-c 只检查不写文件 zipalign -p -f 4 repacked.apk aligned.apk # 验证对齐结果输出 Verification succesful 才算过 zipalign -c -v 4 aligned.apk # 第二步用 apksigner 签名默认会同时产出 v1 和 v2 apksigner sign \ --ks demo-release.jks --ks-key-alias demo \ --ks-pass pass:store_password --key-pass pass:key_password \ --out signed.apk aligned.apk # 第三步验证签名并打印证书指纹 apksigner verify --print-certs signed.apkzipalign的-p针对未压缩的 so 做页对齐-f覆盖输出4是对齐字节数不能改。apksigner sign的--ks指定 keystore--ks-key-alias对应生成时的别名--ks-pass和--key-pass传密码--out是输出文件。最后一个位置参数是输入 APK位置别搞反。签名方案的覆盖关系值得记一下方案引入版本覆盖范围备注v1很早Android 7.0 以下必须基于 jar 签名逐条目摘要v2Android 7.07.0 到 9.0 优先使用整包签名校验更快防篡改更强v3Android 9.09.0 及以上优先使用支持密钥轮换v4Android 11与增量安装配合依赖单独的签名文件常规打包用不到实际打包时不用手动选apksigner 会根据--min-sdk-version和--max-sdk-version自动决定生成哪几套。targetSdk 30 以上的包v2 是硬性要求只做 v1 会被系统拒绝安装。这也是 auto-sign 在今天不够用的直接原因。5. 避坑与排查五类翻车现场的现象、原因和解法这一章按「现象、原因、解决」写都是我在真实排查里反复遇到的。顺序按流程走从拆包到反编译再到签名安装碰到哪条对哪条。5.1 拆包与回编译阶段现象一回编译报No resource identifier found for attribute或某个资源 ID 找不到。原因通常是两类。一类是包里引用了厂商私有资源本地没装对应框架另一类是拆包时用了-r跳过资源解析但 smali 里还引用了资源 ID回编译时对不上。前者的判断方法是看报错里提到的属性名如果是android:开头之外的自定义命名空间基本就是缺框架。后者更常见很多人只想看 smali 就加了-r改完直接b必然失败。解决办法缺框架就apktool if装上对应版本并用-p指定用-r拆的包不要直接回编译重新完整拆一遍再改。另外检查apktool.yml里的sdkInfo和versionInfo这两个字段被手动改错也会导致资源编译异常。现象二回编译成功装上去闪退日志里是Resources$NotFoundException。原因是资源 ID 在回编译时被重排了而 smali 里硬编码的资源 ID 还是旧的。apktool 默认会尽量保持 ID 稳定但如果原包做过资源混淆或者拆包和回编译用的 apktool 版本不一致映射就会错位。解决办法拆包和回编译必须用同一个 apktool 版本这是硬性要求。如果原包资源被混淆过尽量走-c保留原始资源的路径只替换目标文件。排查时可以对照res/values/public.xml如果有里的 ID 和 smali 里的const值。5.2 反编译与签名阶段现象三dex2jar 报Unsupported dex version转换直接中断。原因是 dex2jar 的版本落后于 dex 格式版本。新版 Android 工具链产出的 dex 会带更高的版本号老版本工具解析不了文件头。解决办法换用更新的 dex2jar 版本如果换版本还不行改用能直接解析 APK 的反编译工具做交叉验证。这类问题不要在原地死磕工具链迭代跟不上是常态换路子比修工具快。现象四签名验证通过但安装报INSTALL_FAILED_UPDATE_INCOMPATIBLE。原因是设备上已装的版本用了不同签名的证书。反编译重打包必然换签名系统把这当成另一个应用拒绝覆盖。解决办法先卸载旧版本再装注意卸载会清数据。如果要保留数据做对比用adb install -r之前先确认签名一致或者干脆装在另一台设备/另一个用户空间里。这个坑在测试机上尤其容易撞因为测试机上往往已经装着正式包。现象五apksigner verify报DOES NOT VERIFY或者提示 v2 签名缺失。原因基本是两个对齐和签名的顺序搞反了先签名后 zipalign 破坏了签名块或者用了jarsigner只做了 v1而包的 targetSdk 要求 v2。前者在「照着老教程走」的场景里特别常见后者是把老流程直接搬到新包上。解决办法严格按 align 再 sign 的顺序重做一遍需要 v2 就用 apksigner不要用 jarsigner。验证时加--print-certs看证书指纹和自己的 keystore 对得上才算真签成功。如果verify通过但装不上再看--min-sdk-version是不是设得比设备版本高。6. 差分对比与签名验证改完之后怎么证明自己没改坏改包最怕的不是改不动是改完之后说不清自己改了什么。尤其是排查「这个包和上一版差在哪」这类问题时靠肉眼比对目录树完全不现实。我的做法是固定一套差分加验证的流程每次改完强制跑一遍五分钟换一个确定性。6.1 用目录差分定位真实改动思路很简单把两个版本的 APK 分别拆到独立目录用 diff 递归比对过滤掉 apktool 自己生成的元信息噪声。# 两个版本分别拆包输出目录分开 apktool d app-v1.apk -o out_v1 -f apktool d app-v2.apk -o out_v2 -f # 递归差分只输出有差异的文件名-q 抑制相同内容 diff -rq out_v1 out_v2 | grep -v apktool.yml # 只看 smali 目录的差异快速定位代码改动范围 diff -rq out_v1/smali out_v2/smali | head -50 # 对单个文件看具体改动 diff -u out_v1/AndroidManifest.xml out_v2/AndroidManifest.xml-r递归-q只报文件名不报内容两个参数配合能把噪声压到最低。过滤apktool.yml是因为它记录了拆包时间等信息每次都不一样留着会淹没真正的差异。head -50是防止输出太长刷屏确认范围后再去掉限制看全量。差分结果里最值得关注的是三类清单文件的权限和组件变化、res/下的新增资源、smali/里新增或被修改的类。前两类直接对应功能变化第三类往往能定位到具体 SDK 的引入。如果差异里出现大量 smali 文件同时变动但内容只是行号偏移说明是编译期优化导致的不用管。6.2 四条命令确认包没被改坏改完、签完装之前先跑一遍验证。下面四条覆盖了版本信息、对齐、签名和证书身份缺一条都可能留下隐患。# 1. 看包的版本号、包名和 minSdk确认改的不是这一版 aapt dump badging signed.apk | head -5 # 2. 检查 4 字节对齐输出 Verification succesful 才算过 zipalign -c -v 4 signed.apk | tail -3 # 3. 验证签名方案和完整性 apksigner verify -v signed.apk # 4. 打印证书指纹和自己的 keystore 比对 apksigner verify --print-certs signed.apkaapt dump badging的输出里package: name是包名versionCode和versionName是版本第一行就能看完。zipalign -c是只检查模式-v打详细结果末尾必须是成功提示。apksigner verify -v会列出 v1、v2、v3 各自的验证结果哪一项false都说明有问题。--print-certs输出 SHA-256 摘要和keytool -list -v里的指纹逐位对上才算签的是自己的证书。这四步里最容易跳过的是第二步。很多人签完直接verify通过就装了结果在低端设备上加载慢或者偶发资源读取失败回头查半天才发现是没对齐而不是代码问题。验证项命令通过标准失败后果版本信息aapt dump badging包名版本符合预期改错包白忙一场对齐zipalign -c -v 4输出 succesful加载变慢偶发读取失败签名完整性apksigner verify -vv1/v2 均 true安装被拒或运行校验失败证书身份--print-certs指纹与 keystore 一致无法覆盖升级用户数据丢失差分和验证这两件事单独看都不复杂难的是养成固定动作。我现在的习惯是任何一次改包先拆两份做差分改完按上面四条命令过一遍全部通过才往设备上装。这套流程救过我两次——一次是差分发现包里多了一个不在预期内的权限追下去是某个依赖版本被动升级带进来的另一次是verify报 v2 缺失避免了把只做了 v1 的包发出去。从那以后我每次改包都强制走一遍差分加四条验证不再靠「装上去能跑就行」这种运气判断。希望帮到你。本文还有配套的精品资源点击获取