简介:这套 APK 反编译工具合集面向 Android 开发、逆向学习与安全审计人员,围绕应用解包、重新签名、字节码转换和源码查看等常见需求,集成 apktool、auto-sign、dex2jar 与 jd-gui 等常用组件,适合希望理解应用结构、分析代码逻辑或开展二次开发的学习者。压缩包共 58 个文件,约 10.58MB,以 17 个 bat、14 个 sh 脚本和 11 个 jar 程序为主,另有 9 个 txt 说明及少量 exe、配置与签名文件,兼顾 Windows 和类 Unix 环境,目录按工具分块便于查找。已有 13978 人浏览学习,说明其在 Android 逆向入门与工具链实践中具有一定参考价值。读者可从中获得一套相互配套的逆向工具组合,用于观察资源组织方式、对照反编译结果理解程序逻辑,并配合说明文件掌握各组件之间的协作关系,为代码学习、漏洞排查与合规安全审计提供可复用的工具基础。
1. 先把 APK 反编译工具链装齐:apktool、dex2jar、jd-gui 和 auto-sign 不是四个独立软件
很多人第一次接触 APK 反编译工具,是遇到一个装不上、打不开、资源显示不对的包,于是搜索“APK 反编译怎么安装”,结果一连下了 apktool、dex2jar、jd-gui、auto-sign 四个东西,每个都能启动,但要么卡在解包,要么卡在smali看不懂,要么改完之后adb install直接拒绝。问题通常不在单个工具,而在这条链路上:apktool 负责资源和smali,dex2jar 和 jd-gui 负责把dex变回能读的 Java,auto-sign 负责让改动后的包重新获得安装资格。四个环节里的 Java 版本、路径、签名顺序任意一环错位,前面做的事情就都白费。这篇记录写的是我反复安装后整理出的顺序,适合刚接触这条链路、希望一次把环境配好的开发者。
2. JDK 与工作目录:apktool、dex2jar、jd-gui 都依赖 Java,版本不能随意统一
2.1 三个工具对 Java 版本的要求并不一致
这条链路上所有工具都是 Java 程序,但各自编译时使用的目标字节码版本不同,所以“装一个最新 JDK 就全都能跑”这个假设经常不成立。apktool 2.x 的早期版本对 Java 8 兼容性最好;较新的 apktool 要求 Java 11 及以上。dex2jar 在 2.0 时代固定 Java 8,2.1 之后才逐步抬高门槛。jd-gui 的老版本启动很快,新版本对 Java 版本更敏感,版本不对会直接抛出UnsupportedClassVersionError,连窗口都不弹。
我一般的做法是:主力用 JDK 17,同时保留一个 JDK 8 作为备用,遇到老工具起不来就切过去。切换只改JAVA_HOME,不改系统的默认java,这样不会影响机器上其他项目。
| 工具 | 建议 JDK | 版本不匹配时的典型表现 |
|---|---|---|
| apktool 2.x | JDK 8 / 11 / 17 | 启动即报UnsupportedClassVersionError |
| dex2jar | JDK 8 / 11 | 转换到一半抛NoSuchMethodError |
| jd-gui | JDK 8 / 11 | 双击无窗口,命令行能看到主版本号报错 |
| auto-sign(signapk) | JDK 8 | 签名过程静默失败,产物体积为 0 |
提示:不要用系统包管理器装 JDK 后就不管了,
update-alternatives会把默认java改掉,第二天开终端发现 apktool 起不来,排查半天才发现版本变了。
2.2 装 JDK 与环境变量:一次配好三个终端都能用
Linux 和 macOS 上我习惯把 JDK 解压到固定目录,再写进 shell 配置文件。下面是 Linux 的做法,/opt/jdk-17换成你实际解压的位置即可。
# 确认解压后的 JDK 可用,先看它自己的版本 /opt/jdk-17/bin/java -version # 期望输出类似:openjdk version "17.0.x" ... # 写入当前用户的 shell 配置 cat >> ~/.bashrc <<'EOF' export JAVA_HOME=/opt/jdk-17 export PATH="$JAVA_HOME/bin:$PATH" # APK 链路专用目录,后面所有工具都放这里 export APKLAB="$HOME/apk-lab" export PATH="$APKLAB/bin:$PATH" EOF source ~/.bashrc java -version echo $JAVA_HOME这段配置做了两件事:把JAVA_HOME固定在 JDK 17 上,避免被其他软件的安装脚本改掉;把$APKLAB/bin放进PATH,后面自己写的 wrapper 脚本、dex2jar 的启动脚本都能直接按名字调用。JAVA_HOME必须指向 JDK 根目录,而不是bin目录,这是最常见的写错点之一。
Windows 上不用改~/.bashrc,在“系统属性 → 环境变量”里新建JAVA_HOME,然后在Path里加一条%JAVA_HOME%\bin。如果你用 PowerShell,可以用下面这两行确认是否生效:
# 新开一个 PowerShell 窗口再执行,老窗口读不到新变量 $env:JAVA_HOME java -version需要注意:Windows 上改了环境变量,已经打开的终端和 IDE 不会自动刷新,必须重开。很多人反馈“配了但还是找不到 java”,八成就是旧终端在跑。
2.3 目录结构:把工具、输入、产物分开
工具乱放是后面一系列问题的来源:apktool 的框架文件默认写到家目录,dex2jar 的输出跟着dex位置走,签名产物和未签名产物混在一个文件夹里,最后自己都分不清哪个是能装的。我一般先建一个固定的工作区:
mkdir -p ~/apk-lab/{bin,frameworks,input,work,output,keys} tree -L 1 ~/apk-lab结构说明如下:bin放 apktool.jar、dex2jar 解压目录、auto-sign 目录和自写的 wrapper;frameworks是 apktool 的框架缓存目录,用-p参数指过来,避免污染~/.local/share/apktool;input放待处理的 apk;work放反编译出来的中间产物;output只放可交付的 jar 和已签名 apk;keys放自己生成的 keystore。
这个划分看起来有点多余,但等你同时处理三四个包时,就能体会到“产物只有一个出口”有多省事。尤其是在避坑阶段,你需要快速判断失败发生在解包、回编译还是签名,目录清晰能省掉大量猜的时间。
3. apktool 安装与配置:反编译资源、回编译、auto-sign 签名的完整链路
3.1 拿到 apktool.jar 和 wrapper,不要直接双击
apktool 的发行包通常是一个apktool.jar加一个启动脚本。jar 是本体,脚本只是帮你把java -jar这层壳包起来。我一般两个都放~/apk-lab/bin:
# 把下载到的 apktool.jar 放进工作区 mv ~/Downloads/apktool.jar ~/apk-lab/bin/ chmod +x ~/apk-lab/bin/apktool.jar # 自写 wrapper,固定堆内存和框架目录,避免每次敲长命令 cat > ~/apk-lab/bin/apktool <<'EOF' #!/usr/bin/env bash # 优先用 JAVA_HOME,没配再退回 PATH 里的 java JAVA_BIN="${JAVA_HOME:+$JAVA_HOME/bin/java}" JAVA_BIN="${JAVA_BIN:-java}" BASE_DIR="$(cd "$(dirname "$0")" && pwd)" # -Xmx2g 是给大包留的余量,dex 多、资源多的包 512m 很容易 OOM exec "$JAVA_BIN" -Xmx2g \ -jar "$BASE_DIR/apktool.jar" \ -p "$HOME/apk-lab/frameworks" \ "$@" EOF chmod +x ~/apk-lab/bin/apktool apktool --versionwrapper 里做了三个决定:用JAVA_HOME指向的 java,保证版本可控;固定-Xmx2g,避免处理体积大的包时中途OutOfMemoryError;固定-p框架目录,让框架文件集中在一个地方,出问题时删掉整个目录重来就行。
Windows 上把上面这段换成apktool.bat,内容大意是java -Xmx2g -jar "%~dp0apktool.jar" -p "%USERPROFILE%\apk-lab\frameworks" %*。%~dp0表示脚本所在目录,是 Windows 批处理里最稳的相对路径写法。
注意:不要用系统里已有的旧版 apktool 覆盖新版本,先
apktool --version看清楚在跑哪个,再决定要不要替换。
3.2 反编译命令与参数取舍:-f、-s、-p 怎么选
apktool 的反编译命令是d,常用参数不多,但每个都影响后面能不能顺利回编译。
# 完整反编译:资源 + smali,输出到 work 目录 apktool d -f input/app-release.apk -o work/app # 只要资源,不碰 dex:改布局、改字符串时最省时间 apktool d -f -s input/app-release.apk -o work/app-res # 处理系统包或厂商 ROM 时先装框架,再反编译 apktool if input/framework-res.apk apktool d -f input/SystemUI.apk -o work/systemui参数解释:-f表示目标目录存在时强制覆盖,不加这个参数,重复解同一个包会提示目录已存在然后退出;-s表示跳过dex反编译,产物里classes.dex原样保留,改资源场景下能省一大半时间;-p在 wrapper 里已经固定,所以命令里不用再写;apktool if是安装框架文件,只有涉及系统资源引用时才需要。
这里有个容易误解的点:-s不是“不反编译”,而是“不反编译代码”。如果你只是想把界面上的英文改成中文、改个图标、调整一下AndroidManifest.xml里的东西,用-s足够了,回编译也更快。反过来,如果你要看smali逻辑,必须走完整反编译,产物里的smali/、smali_classes2/就是多 dex 拆出来的结果。
反编译完成后先别急着改,花一分钟看一眼产物结构:
ls work/app # 期望看到:AndroidManifest.xml apktool.yml res/ smali/ assets/ ...apktool.yml是回编译时的“配方”,记录了minSdkVersion、targetSdkVersion、是否debuggable等信息。后期回编译报版本相关的错,很多时候要回来看这个文件。
3.3 回编译、zipalign 与 auto-sign 签名:顺序错了就白干
回编译用b,产物默认是未签名包,直接安装会被系统拒绝。完整顺序是:回编译 → 对齐 → 签名 → 安装。这里顺序错一步,签名就废了。
# 1. 回编译,输出未签名包 apktool b work/app -o output/app-unsigned.apk # 2. 对齐,必须在签名之前做 zipalign -f -v 4 output/app-unsigned.apk output/app-aligned.apk # 3. 用 auto-sign 里的 signapk 签名 cd ~/apk-lab/bin/auto-sign java -jar signapk.jar testkey.x509.pem testkey.pk8 \ ../../output/app-aligned.apk ../../output/app-signed.apkzipalign的4是 4 字节对齐,Android 从很早的版本就要求这个对齐,未对齐的包在部分设备上会安装失败或运行时报错。-f表示覆盖输出文件。signapk.jar的用法是“公钥证书 + 私钥 + 输入 + 输出”,testkey.x509.pem和testkey.pk8是 auto-sign 包自带的测试密钥对,适合本机验证流程是否跑通。
如果你嫌每次cd进去麻烦,可以在~/apk-lab/bin写一个apksign脚本,把路径固定下来:
#!/usr/bin/env bash # 用法:apksign 输入.apk 输出.apk set -e SIGN_DIR="$HOME/apk-lab/bin/auto-sign" java -jar "$SIGN_DIR/signapk.jar" \ "$SIGN_DIR/testkey.x509.pem" \ "$SIGN_DIR/testkey.pk8" \ "$1" "$2" echo "signed -> $2"set -e是关键,签名失败时脚本会立刻退出,而不是继续往下走,产出一个 0 字节的“成功”文件。这一点在批量处理时特别重要,我见过太多人因为脚本没有set -e,最后拿到一堆空包还以为签名成功了。
想确认签名到底有没有生效,可以用 JDK 自带的工具看证书信息:
keytool -printcert -jarfile output/app-signed.apk能打印出证书指纹和有效期,说明 v1 签名写入成功。如果报Not a signed jar file,说明签名环节根本没跑通,别急着往下装。
4. dex2jar 与 jd-gui:把 classes.dex 还原成能读的 Java 代码
4.1 dex2jar 解压即用,但脚本要加执行权限
dex2jar 是绿色包,解压就能用,不需要安装。把解压后的目录整体放到~/apk-lab/bin,然后给脚本加执行权限:
unzip dex2jar-*.zip -d ~/apk-lab/bin/ cd ~/apk-lab/bin/dex2jar-* chmod +x d2j-*.sh ./d2j-dex2jar.sh --helpLinux 和 macOS 上必须给.sh加执行权限,不然会报Permission denied;Windows 上直接跑d2j-dex2jar.bat即可,不需要chmod。
真正转换前,先把 apk 里的dex取出来。一个包可能有多份dex,classes.dex、classes2.dex依次排下去,只转第一份会漏掉大量代码:
# 只解出 dex,避免把整个 apk 铺开 unzip -o input/app-release.apk 'classes*.dex' -d work/dex ls work/dex-o表示覆盖已有文件,'classes*.dex'用引号包起来是为了防止 shell 自己展开通配符。这一步做完,work/dex下应该能看到一到多个 dex 文件。
4.2 批量转换的循环写法与失败隔离
单个转换的命令很短:
sh d2j-dex2jar.sh work/dex/classes.dex -o output/app-classes.jar-o指定输出 jar 路径。不加-o会默认在 dex 同目录生成classes-dex2jar.jar,产物散落,不方便管理。实际工作中更常用的是循环处理多 dex:
for f in work/dex/*.dex; do name="$(basename "${f%.dex}")" # 单个 dex 失败不影响后续,失败信息单独记下来 if ! sh d2j-dex2jar.sh "$f" -o "output/${name}.jar" 2>"output/${name}.err"; then echo "转换失败: $f,详见 output/${name}.err" fi done${f%.dex}是去掉后缀的写法,basename取文件名,两者拼起来得到不带路径的 base name。2>把错误输出重定向到单独的.err文件,转换成功时这个文件是空的,失败时里面通常会有Detail: ...的堆栈。用if !而不是||是为了让失败信息更清楚,一眼能看出是哪个 dex 出的问题。
提示:dex2jar 对已经被加固或深度混淆的包基本无能为力,转换过程会抛大量空指针或
ArrayIndexOutOfBoundsException,这属于正常现象,不是环境装错了。
4.3 jd-gui 打开 jar 与交叉验证
jd-gui 是图形工具,启动方式取决于你的系统。Linux 上一般是一个带jd-gui启动脚本的目录,macOS 上是.app,Windows 上是.exe。启动后直接把output/*.jar拖进去,左侧就能看到包结构树。
判断转换是否成功,看三个地方:左侧有没有类;右侧打开某个类时方法体是不是完整;有没有大量// This method has been decompiled incorrectly之类的注释。如果类名全是a、b、c,方法体只有一两行跳转,那是混淆的结果,和工具无关。
交叉验证是个好习惯:拿 apktool 反编译出来的smali和 jd-gui 里的 Java 对照看。同一个方法,smali里能看到真实的调用顺序和寄存器操作,jd-gui 里能看到接近源码的结构。两边对不上时,以smali为准,因为 jd-gui 的反编译结果只是推测。
# 从 smali 里快速定位某个字符串的引用位置 grep -rn "your_keyword" work/app/smali*/ | head -20-r递归,-n打印行号,smali*/会同时匹配smali、smali_classes2等多个目录。定位到具体方法后,再去 jd-gui 里搜同名类,比在两个工具里来回瞎翻快得多。
5. 避坑排查:五条踩过的记录
5.1 安装时报 INSTALL_FAILED_INVALID_APK
现象:adb install直接返回Failure [INSTALL_FAILED_INVALID_APK],包名和版本号都正常。
原因:回编译产物根本没有签名,或者zipalign放在了签名之后。zipalign会重写文件偏移,v1 签名是基于文件内容计算的,先签名再对齐等于把签名破坏掉了。
解决:固定顺序为b→zipalign→signapk,中间不要插别的操作。签完之后用keytool -printcert -jarfile确认证书存在,再执行安装。如果设备上已经装了同包名的原版应用,还要先卸载,否则会变成INSTALL_FAILED_UPDATE_INCOMPATIBLE,那是签名不一致导致的,不是包坏了。
5.2 启动就抛 UnsupportedClassVersionError
现象:java -jar apktool.jar或d2j-dex2jar.sh一执行就报has been compiled by a more recent version of the Java Runtime,后面跟着两个主版本号。
原因:工具的字节码版本比当前java新。比如用 JDK 8 去跑要求 Java 11 的 apktool,或者JAVA_HOME指向了 A 版本而PATH里的java是 B 版本,两边不一致。
解决:先which java和echo $JAVA_HOME对一遍,确认指向同一个 JDK。确认之后再决定是升级 JDK 还是换旧版工具。我的习惯是给这条链路单独留一个 JDK 8,遇到老工具就用JAVA_HOME=/opt/jdk-8 apktool ...临时覆盖,不改全局配置。
5.3 回编译报资源解码失败或资源 ID 错乱
现象:apktool b中途抛AndrolibException: Could not decode arsc file,或者回编译成功但装上去界面一片空白、图标错位。
原因:一是处理的是系统包或厂商定制包,资源引用了framework-res.apk里的内容,而本地没有安装对应框架;二是目标包是 split APK,资源分散在多个base.apk、config.xx.apk里,单独拿一个出来必然缺资源。
解决:系统包先apktool if framework-res.apk再反编译;split 包要把同一目录下的所有 apk 都取到,以base.apk为主体操作。另外 apktool 版本太旧也会解不动新版资源格式,遇到Could not decode arsc先升级 apktool 再排查其他原因,这条能省掉一半时间。
5.4 dex2jar 转换后 jd-gui 里全是空类
现象:jar 能打开,左侧树也有类名,点进去方法体是空的,或者只有一个return。
原因:包做了加固,真实代码在运行时才解密加载,磁盘上的 dex 里只有壳代码;或者用了强混淆,方法体被拆成了无意义的跳转。
解决:这类包不适合用 dex2jar 静态分析,换思路,从smali层面看运行时行为,或者直接放弃。装环境阶段遇到这种情况不要怀疑自己的配置,先拿一个自己编译的、没有加固的测试包验证链路,能跑通就说明工具没问题。
5.5 路径里有空格或中文导致脚本静默失败
现象:d2j-dex2jar.sh或signapk.jar执行完没有报错,但输出文件不存在,或者大小为 0。
原因:路径里有空格、中文、括号,脚本内部拼接参数时没有加引号,参数被 shell 拆成了多段。
解决:工作区路径只用英文、数字、连字符,~/apk-lab这种就很好。如果实在要放在带空格的目录,所有变量引用都加双引号,"$f"、"$1"一个都别省。Windows 上尤其注意C:\Users\张三\Desktop这种路径,signapk对这种路径经常处理不好,换个盘符根目录下的英文路径最稳。
6. 装完怎么验证:用一个自己编译的小包跑通整条链路
环境装完最忌讳的事,是直接拿一个来路不明的包去试,失败了你根本分不清是环境问题还是包的问题。我的验证方法是自己编译一个最小应用,走完整条链路,每一环都能单独确认。
先在 Android Studio 或命令行里编译一个 debug 包,或者用任意一个自己写的、没有加固的小 apk。然后按顺序跑:
# 1. 反编译,确认资源和 smali 都能出来 apktool d -f input/demo.apk -o work/demo test -d work/demo/res && echo "资源目录 OK" test -d work/demo/smali && echo "smali 目录 OK" # 2. 改一处字符串,验证回编译有效 grep -rn "Hello" work/demo/res/values/strings.xml # 3. 回编译 + 对齐 + 签名 apktool b work/demo -o output/demo-unsigned.apk zipalign -f -v 4 output/demo-unsigned.apk output/demo-aligned.apk apksign output/demo-aligned.apk output/demo-signed.apk # 4. 校验签名并安装 keytool -printcert -jarfile output/demo-signed.apk | head -5 adb install -r output/demo-signed.apk每一步都有明确的信号:第 1 步看目录是否存在,第 2 步看能不能搜到目标字符串,第 3 步看产物文件大小是否合理,第 4 步看证书能不能打印、安装是否返回Success。只要有一环失败,就停在那里排查,不要继续往下走。
验证表可以照着填,出问题时它比日志更快定位:
| 环节 | 通过标准 | 失败时先看什么 |
|---|---|---|
| apktool d | 生成 res/ 与 smali/ | Java 版本、apk 是否 split |
| apktool b | 输出 apk 体积接近原包 | 是否有未解析的资源引用 |
| zipalign | 输出Verification succesful | 是否在签名之后才对齐 |
| signapk | 输出 apk 非 0 字节 | 密钥路径、输出路径权限 |
| adb install | 返回Success | 设备上是否有同包名旧版本 |
最后一环还有个容易被忽略的验证点:装上去之后打开应用,确认界面正常、目标字符串确实变了。只看adb install返回成功是不够的,资源错位、AndroidManifest.xml改坏、smali改动导致启动崩溃,这些都是“装得上但跑不起来”的情况。我自己就遇到过一次,改完资源回编译、签名、安装全部通过,一打开就白屏,最后发现是apktool.yml里的minSdkVersion被改低导致的兼容问题。
从那以后,我每次搭完这套环境,都强制走一遍“自己编译的小包 → 改一个字符串 → 回编译 → 对齐 → 签名 → 安装 → 打开确认”的完整流程,全部通过之后才去碰真实的目标包。这套动作花不了十分钟,但能把 Java 版本、路径、签名顺序这三类最容易翻车的问题一次性暴露出来。希望帮到你。
本文还有配套的精品资源,点击获取