☰
Android逆向三件套搭建:apktool/dex2jar/jd-gui避坑
2026/10/31 1:11:10 网站建设 项目流程

简介:「Android逆向助手」是一套面向Android开发与安全学习者的逆向分析工具合集,整合apktool、dex2jar、jd-gui、autosign等常用组件,可用于APK反编译、资源提取、Java字节码查看、重签名及安装测试,服务于安全审计、功能分析、性能排查与授权机制研究等场景,适合具备一定Android基础的中级开发者。压缩包共69个文件、约15.21MB,涵盖18个jar核心工具库、17个bat与14个sh脚本、8个exe可执行程序、6个txt说明文档,以及签名证书、配置文件和so库等辅助文件,Windows与Linux环境下均有对应入口。已有145人学习,包内工具链较完整,便于读者搭建从反编译、代码查看到重签名的分析流程,并借助说明文档快速定位组件用途,减少零散收集与版本匹配成本。

1. 装 android 逆向助手之前,先认清三件套各管哪一段

很多人搜 android 逆向助手,心里期待的是一个双击安装、拖进 APK 就出源码的绿色软件。真把环境搭起来会发现,这套能力是三件工具串成的一条链:apktool 负责拆包和还原资源、dex2jar 负责把 dex 转成 jar、jd-gui 负责把 jar 读成带包名的 Java 代码。链上任何一环版本对不上,整条就断在中间——最常见的翻车现场是 dex2jar 在 JDK 17 下静默产出一个几百字节的空 jar,jd-gui 打开一片空白,人还以为样本加壳了,白白折腾一下午。

这套环境适合三类人:排查自己团队开发的应用在特定机型上的兼容问题、做移动安全课程实验、审计集成进项目的第三方 SDK 到底干了什么。前提只有一个,分析对象必须是你有权处置的 APK。下面按我实际搭的顺序写,从 JDK 选型一路写到跑通验证。

2. 环境准备:JDK 版本、目录规划和 apktool 落地

装这套东西最容易忽略的不是工具本身,是 Java 运行时。三件套里有两件是 Java 程序,还有一个是 shell 脚本包着 jar 跑,JDK 版本选错,后面所有步骤都会以一种很难看懂的方式失败。

2.1 JDK 到底选 8、11 还是 17

先给结论:主力用 JDK 11,另备一份 JDK 8。apktool 2.9.x 官方要求 Java 8 及以上,实测 8、11、17 都能正常拆包和回编译,它是三者里最不挑的。jd-gui 1.6.6 有自带运行时的打包版,也有纯 jar 版,纯 jar 版在 JDK 8 和 11 上表现一致。真正挑食的是 dex2jar,2.4 版本内部依赖的老版本字节码库对 JDK 17 的模块系统不友好,在 17 上跑d2j-dex2jar有时直接抛NoClassDefFoundError,有时更坏——不报错,但输出的 jar 里只剩一个空的META-INF。

我一般会同时装两个 JDK,用环境变量切换,而不是把系统默认 JDK 改来改去,那样迟早会影响别的项目。检查当前版本:

# 先看默认 JDK,三件套里所有 Java 相关的报错都从这个输出开始排查 java -version 2>&1 javac -version 2>&1 # Linux / macOS 下如果有多个 JDK,看看装了哪些 /usr/libexec/java_home -V 2>/dev/null || ls /usr/lib/jvm 2>/dev/null

java -version输出里的第一行就是版本号,1.8.0_xxx是 JDK 8,11.0.x是 JDK 11,17.0.x是 JDK 17。第二段命令在 macOS 上列出所有已安装的 JDK 路径,Linux 上直接列/usr/lib/jvm目录。Windows 下对应的是C:\Program Files\Java\和C:\Program Files\Eclipse Adoptium\这类目录,看一眼就知道了。

切换靠JAVA_HOME,不靠改 PATH 里的顺序:

# 临时切到 JDK 8 跑 dex2jar,只影响当前终端会话 export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 export PATH="$JAVA_HOME/bin:$PATH" java -version # 切回 JDK 11 跑 apktool export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 export PATH="$JAVA_HOME/bin:$PATH"

这种写法叫会话级切换,关掉终端就失效,好处是不会污染系统。参数上唯一要注意的是 PATH 必须把$JAVA_HOME/bin放在最前面,否则系统里残留的/usr/bin/java软链会抢先命中,java -version显示的还是老版本,人会以为自己没切成功。

Windows 下我更推荐写两个批处理文件放在工具目录里,需要哪个点哪个:

rem jdk8.bat —— 只改当前命令行窗口的环境变量 @echo off set JAVA_HOME=C:\Java\jdk1.8.0_381 set PATH=%JAVA_HOME%\bin;%PATH% java -version

set在批处理里是会话变量,不会写进系统环境变量,双击运行的窗口关掉就恢复。这一点比在「系统属性 → 高级 → 环境变量」里反复改要安全得多,那些改动是全局的,改错了影响的是整台机器上的其他 Java 项目。

提示:不要用 JDK 21 及以上去跑 dex2jar 2.4。我试过一次,脚本能启动、进度条也走完,输出文件大小正常,但 jd-gui 打开后每个类都只有一行注释,排查了两个小时才怀疑到 JDK 头上。凡是「工具跑完了但结果是空的」这种玄学现象,第一件事就是换 JDK 8 重跑。

2.2 目录规划:把三件套和产物分开

工具装得散是后面所有混乱的源头。我在用户目录下建一个固定根目录,工具、工作区、签名文件三块严格分开,这样清理产物的时候不会误删工具,迁移机器的时候打包一个目录就走。

# 建目录骨架 mkdir -p ~/re/android-re/{bin,tools,work,keys} cd ~/re/android-re # 最终结构长这样 # ~/re/android-re/ # ├── bin/ 自己写的包装脚本,加入 PATH # ├── tools/ apktool / dex2jar / jd-gui 的原始文件 # ├── work/ 每次分析的产物,按 APK 名字建子目录 # └── keys/ 自己的调试签名,回编译时用

bin单独放包装脚本是为了把所有工具统一成「一条命令」的调用方式,不用每次cd到工具目录再执行。work按样本名分子目录,是因为 apktool 拆包会生成几十个文件夹,混在一起根本分不清哪个是哪个版本的产物。

把bin加进 PATH,写进 shell 配置文件里:

# 追加到 ~/.bashrc 或 ~/.zshrc export ANDROID_RE_HOME="$HOME/re/android-re" export PATH="$ANDROID_RE_HOME/bin:$PATH"

ANDROID_RE_HOME这个变量后面所有脚本都会引用,好处是换机器时只改这一行,脚本里的相对路径不用动。

2.3 apktool 的安装与两个配套文件

apktool 的发布物是一个 jar 加一个启动脚本,官方不提供安装程序。下载apktool_2.9.x.jar之后重命名为apktool.jar,放进tools/apktool/目录。重命名不是强迫症,是为了让启动脚本里写死的文件名固定下来,将来升级版本只换 jar 不改脚本。

Linux / macOS 的包装脚本:

#!/usr/bin/env bash # 保存为 ~/re/android-re/bin/apktool,并 chmod +x set -euo pipefail APKTOOL_HOME="${ANDROID_RE_HOME:-$HOME/re/android-re}/tools/apktool" JAR="$APKTOOL_HOME/apktool.jar" if [ ! -f "$JAR" ]; then echo "找不到 $JAR,检查 apktool.jar 是否放对位置" >&2 exit 1 fi # -Xmx1g 是给大型 APK 留的堆,默认堆拆大包容易 OOM exec java -Xmx1g -jar "$JAR" "$@"

set -euo pipefail让脚本遇到错误立即退出,避免 apktool 失败后脚本继续往下跑。-Xmx1g把最大堆设成 1GB,拆带大量资源的包时默认堆会抛OutOfMemoryError,这个参数是最省事的预防。"$@"把用户输入的所有参数原样透传给 apktool,所以apktool d xxx.apk和直接java -jar的效果完全一样。

Windows 的对应版本:

rem 保存为 apktool.bat,和 apktool.jar 放同一目录 @echo off java -Xmx1g -jar "%~dp0apktool.jar" %*

%~dp0是批处理所在目录的绝对路径,带上反斜杠结尾。用它拼 jar 路径,脚本放哪都能跑,不用写死盘符。

装完立刻验证,别等到拆包时才发现脚本有问题:

chmod +x ~/re/android-re/bin/apktool apktool --version # 期望输出类似 2.9.3

--version能打出版本号,说明 java 能找到、jar 路径正确、脚本权限到位,三件事一次验完。

apktool 还有两个配套文件要提一句。一个是framework-res.apk,反编译系统应用或者带厂商定制的包时需要用它提供资源表,命令是apktool if framework-res.apk,装一次就缓存在用户目录下。另一个是 smali/baksmali,apktool 内部已经内置了对应版本,不需要单独下载;但如果你的目的是改代码而不是读代码,那走的是apktool d出来的smali/目录这条路,跟 dex2jar 那条路是并行的两套方案,别混着用。

3. dex2jar 与 jd-gui 配置:把 dex 变成能读的 Java 源码

apktool 拆完,你拿到的是资源和 smali 汇编,可读性很差。想把逻辑读成 Java,得靠 dex2jar 转格式、jd-gui 看结果,这两步的配置问题比 apktool 多。

3.1 dex2jar 解压即用,但要先给脚本加执行权限

dex2jar 是绿色包,下载dex-tools-2.4.zip解压到tools/dex2jar/就算装完,没有安装程序也没有环境变量。它的入口是一批d2j-*.sh和d2j-*.bat脚本,压缩包解开后默认没有执行权限,直接跑会报Permission denied:

cd ~/re/android-re/tools/dex2jar chmod +x *.sh # Linux / macOS 必须做这一步 ls d2j-*.sh # 确认 d2j-dex2jar.sh 在里面 # 用一个空 dex 试跑,验证脚本本身能起来 ./d2j-dex2jar.sh --help

chmod +x *.sh把目录下所有 shell 脚本加上可执行位。--help能打出用法说明,说明脚本里的 java 调用链是通的,这一步比直接拿真实 APK 试要快得多,出问题也容易定位。

最常用的命令是直接从 APK 转:

# -f 覆盖同名输出;-o 指定输出文件名;--force 忽略内部错误继续 ./d2j-dex2jar.sh -f -o ~/re/android-re/work/demo/demo.jar ~/re/android-re/work/demo/demo.apk

参数逐个说清楚:-f是强制覆盖已存在的输出文件,不加这个参数第二次跑会直接报错退出。-o指定输出路径,不指定的话 jar 会落在当前工作目录,多跑几次就分不清哪份是最新的。--force在遇到个别无法解析的指令时继续转换而不是中断,反编译第三方加固过的包时几乎是必加项。

多 dex 的包要注意,classes.dex、classes2.dex、classes3.dex是分开的,dex2jar 一次只处理一个:

# 先解压出所有 dex cd ~/re/android-re/work/demo unzip -o demo.apk "classes*.dex" -d dex/ # 逐个转换,输出名带序号 for f in dex/classes*.dex; do n=$(basename "$f" .dex) ~/re/android-re/tools/dex2jar/d2j-dex2jar.sh -f -o "jar/${n}.jar" "$f" done

unzip -o的-o是覆盖,-d指定解压目录,引号里的classes*.dex是通配,只解 dex 不碰资源。循环里basename取的是不带后缀的文件名,输出到jar/目录下,这样classes.jar和classes2.jar不会互相覆盖。很多人在这一步只转了主 dex,然后在 jd-gui 里找不到某个类,误以为代码被抽走了,其实类就在第二个 dex 里。

3.2 jd-gui 的启动方式与 JDK 绑定

jd-gui 有两种发布形态:带运行时的jd-gui-osx/jd-gui.exe打包版,和纯jd-gui-1.6.6.jar。打包版开箱即用但绑死了自带的 JRE,遇到需要调堆大小的情况没法改;jar 版灵活,前提是你得指定 java。

# jar 版启动,-Xmx 调大一点,大 jar 反编译很吃内存 java -Xmx2g -jar ~/re/android-re/tools/jd-gui/jd-gui-1.6.6.jar

-Xmx2g给到 2GB 堆。jd-gui 打开几十 MB 的 jar 时,默认堆会卡在加载界面或者直接抛OutOfMemoryError: Java heap space,这不是文件损坏,纯粹是内存不够。启动后从菜单打开前面生成的 jar,左侧树按包名展开,双击类名就能看源码。

jd-gui 有两个已知的显示问题值得提前知道。一是 Java 8 之后的 lambda 表达式和内部类,反编译结果经常显示成// 反编译失败或者synthetic方法,这是 jd-gui 的解析能力限制,不是你的操作失误。二是大型混淆包打开后类名全是a、b、c,这属于正常现象,需要配合 apktool 拆出来的资源文件里的字符串去对。

遇到 jd-gui 打不开的类,兜底方案是用 JDK 自带的javap看字节码:

# 从 jar 里看某个类的结构,不需要反编译成源码 javap -p -c -classpath ~/re/android-re/work/demo/demo.jar com.example.demo.MainActivity

-p显示私有成员,-c反汇编出字节码指令。字节码可读性差,但胜在绝对准确,jd-gui 显示异常时用它确认真实的方法签名和调用顺序,比反复猜测靠谱。

3.3 把三步串成一条链路

手动敲三遍命令容易漏步骤,我习惯在每个样本目录下放一个脚本,输入 APK 名字,输出拆包目录、jar 和一份检查清单:

#!/usr/bin/env bash # 保存为 ~/re/android-re/bin/analyze,chmod +x 后加入 PATH set -euo pipefail APK="${1:?用法: analyze <apk路径> [输出目录名]}" NAME="${2:-$(basename "$APK" .apk)}" ROOT="${ANDROID_RE_HOME:-$HOME/re/android-re}/work/$NAME" mkdir -p "$ROOT/dex" "$ROOT/jar" echo "==> 1/3 apktool 拆包" apktool d -f -o "$ROOT/apktool" "$APK" echo "==> 2/3 提取 dex" unzip -o "$APK" "classes*.dex" -d "$ROOT/dex" >/dev/null echo "==> 3/3 dex2jar 转换" for f in "$ROOT"/dex/classes*.dex; do n=$(basename "$f" .dex) "$ANDROID_RE_HOME/tools/dex2jar/d2j-dex2jar.sh" -f -o "$ROOT/jar/$n.jar" "$f" done echo "完成,产物在 $ROOT" echo " apktool 输出: $ROOT/apktool" echo " jar 输出: $ROOT/jar"

${1:?...}是参数必填检查,不传 APK 路径时直接打印用法并退出,不会带着空变量往下跑。${2:-...}是默认值,第二个参数不传就用 APK 文件名作为输出目录名。mkdir -p保证目录存在,重复跑不会报错。脚本里没有把 jd-gui 也串进去,因为它是 GUI 程序,串进脚本只会阻塞在那里等窗口关闭,人工打开更合适。

注意:脚本里apktool d用的是-f而不是-r。-r表示不反编译资源,速度确实快,但你会同时失去AndroidManifest.xml的可读版本和所有字符串资源,后面在混淆代码里想查个关键词就完全没线索了。除非你只做纯代码分析,否则别省这一步。

4. 避坑与排查:五个真实踩过的坑

这套环境的问题有个共同特点:报错信息要么不出现,要么出现的地方跟真正的原因隔着好几层。下面五条按我遇到的频率排序,每条都写清现象、原因和处理办法。

4.1 apktool 拆包报资源解析失败

现象:apktool d跑到一半抛出W: Could not decode attr value或者直接Exception in thread "main" brut.androlib.AndrolibException: Could not decode arsc file,产物目录里只有半个res/。

原因基本是两种。一种是 apktool 版本比目标 APK 的编译工具链老,新版 aapt2 生成的资源表格式有变化,老 apktool 认不出来。另一种是这个包用了厂商定制的资源表,需要先导入framework-res.apk才能解析。

解决:先升 apktool 到最新 2.9.x 再试。仍失败的话用apktool d -r跳过资源解码,先拿到smali/和AndroidManifest.xml的二进制版本,代码分析照样能做;需要看资源的话再单独用aapt2 dump resources导出。

4.2 dex2jar 跑完,jd-gui 打开是空壳

现象:命令正常结束,进度条走完,输出的 jar 文件大小也就几百 KB,jd-gui 打开后每个类只有一行注释或者干脆什么都没有。

原因:JDK 版本太高,dex2jar 2.4 在老字节码库上转换失败但不报错。这是最坑的一种失败模式,因为它把错误伪装成了成功。

解决:把JAVA_HOME切到 JDK 8 重跑,注意是会话级切换并重新java -version确认生效。第二可能是 APK 本身被加固过,dex 只是壳,真实代码在运行时才释放。判断方法是看 dex 文件大小——加固包的classes.dex通常只有几十 KB,正常应用的 dex 动辄几 MB。

4.3 jd-gui 双击没反应,或者闪一下就退

现象:双击jd-gui.exe或运行java -jar后窗口不出现,命令行窗口一闪而过。

原因:打包版 jd-gui 自带的是 32 位 JRE,在 64 位系统上偶尔起不来;jar 版则是 java 没在 PATH 里,或者系统装的是 JRE 而不是 JDK,缺javaw。

解决:改用 jar 版,明确指定 java 绝对路径启动,把堆调大,并且在命令行里运行以便看到真实报错,而不是双击。如果报java.lang.UnsupportedClassVersionError,说明启动用的 java 版本比 jar 编译版本还低,换 JDK 8 或 11 即可。

4.4 Windows 下路径带中文或空格,脚本全体失效

现象:apktool 报文件未找到,dex2jar 报The system cannot find the path specified,明明路径肉眼看着是对的。

原因:这些工具内部大量使用字符串拼接路径,遇到中文和空格时没做转义。C:\Users\张三\Desktop\我的工具\这种路径几乎必炸。

解决:根目录建在纯英文无空格的路径下,比如D:\re\android-re\。工作产物也放这里,桌面和「我的文档」不要用来跑这些工具。这是血泪经验,前期省的一次搬家,后面要花几倍时间去排查莫名其妙的路径错误。

4.5 apktool 回编译后装不上

现象:apktool b显示构建成功,adb install报INSTALL_PARSE_FAILED_NO_CERTIFICATES或INSTALL_FAILED_UPDATE_INCOMPATIBLE。

原因:前者是产物没签名,apktool 只负责打包不负责签名;后者是设备上已经装了同包名的应用,签名不一致导致覆盖安装被拒。

解决:用keytool生成一个自己的调试签名,apksigner签完再装:

# 第一次生成调试用的 keystore,之后复用即可 keytool -genkeypair -v -keystore ~/re/android-re/keys/debug.keystore \ -alias debug -keyalg RSA -keysize 2048 -validity 10000 \ -storepass android -keypass android -dname "CN=debug" # 对回编译产物签名 apksigner sign --ks ~/re/android-re/keys/debug.keystore \ --ks-pass pass:android --out signed.apk dist/app-release-unsigned.apk # 签名后必须验签,这一步别省 apksigner verify --verbose signed.apk

-validity 10000是有效期天数,调试签名设长一点免得过一阵又要重来。-storepass和-keypass设成一样的,省得每次输两遍。apksigner verify会逐项打印摘要算法和证书链,看到Verified using v2 scheme: true才算真的签上了。

5. 装完怎么验证跑通:拿一个自己写的 Demo 走一遍全链路

工具装完不等于环境可用。我的验证习惯是写一个最小的 Demo APK——一个 Activity 加一个工具类,类里放一个能认出来的字符串常量——然后完整走一遍拆包到看源码的流程,全部环节都对得上才算装好。用自己编译的包做验证有个额外好处:你事先知道正确答案,任何环节出偏差都能立刻发现。

验证步骤和判定标准:

环节命令通过标准
apktool 可用apktool --version打出版本号,无异常堆栈
apktool 拆包apktool d -f -o out demo.apkout/AndroidManifest.xml是明文 XML,out/res/有内容
dex2jar 转换d2j-dex2jar.sh -f -o demo.jar demo.apkjar 大小在 MB 级,不是几百 KB
jd-gui 可读打开 demo.jar能搜到那个字符串常量,类名带完整包名
回编译apktool b out -o rebuilt.apk输出文件存在,大小和原包接近
签名安装apksigner sign后adb install设备上能正常启动

第三步的判定标准值得展开讲一句。很多人在这一步栽跟头,就是因为只看命令有没有报错,不看输出文件大小。正常的 jar 应该是 MB 级,如果只有几百 KB,几乎可以断定转换失败,直接回去查 JDK 版本,别在 jd-gui 那边浪费时间。

代码层面的自检可以写成一句话,跑一次就知道链路通不通:

# 从 jar 里直接搜出预先埋好的字符串,搜到就说明 dex→jar 这一步是通的 unzip -p ~/re/android-re/work/demo/jar/demo.jar '*.class' | strings | grep -i "verify-me-2024"

unzip -p把内容输出到标准输出而不落盘,'*.class'匹配 jar 里所有类文件,strings抽出可读文本,grep找那个预先埋好的标记串。这一条比打开 GUI 点半天更客观,适合写成脚本放进 CI 里当回归检查。

回编译那一步经常会漏。很多人拆包、转 jar、看代码一路顺畅,真改了一行 smali 想装回去才发现 apktool 的入口没配好,或者签名工具根本没装。所以验证必须包含「拆—读—改—回编译—签名—安装」的完整闭环,缺任何一段都算没装好。

从那以后我每次搭这套环境,都强制先跑一遍这个 Demo 验证清单,通过了才开始碰真实样本。省下来的不是验证的十分钟,而是后面在「到底是工具没装好还是样本有问题」这个岔路口反复犹豫的几个小时。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询