APK反编译工具链搭建:apktool、dex2jar、jd-gui与签名避坑

发布时间:2026/10/30 20:51:15
APK反编译工具链搭建:apktool、dex2jar、jd-gui与签名避坑 简介面向Android逆向学习与安全审计场景的APK反编译工具合集适合具备一定安卓基础、希望理解应用结构或开展二次开发的开发者使用。工具覆盖解包与资源还原、字节码转换、源码查看及重签名测试等环节能够帮助读者分析清单文件、布局与字符串资源并进一步定位混淆后的代码逻辑理解界面设计与功能实现之间的对应关系。压缩包共58个文件约10.58MB以批处理与命令行脚本、Java工具包、可执行图形程序及说明文档为主脚本便于按流程调用工具包与图形程序承担核心反编译和查看功能文档则提供必要的操作提示。目前已有13978人学习具有较高的参考热度。对于需要系统练习APK逆向、排查自身应用风险或进行安全审计的读者这套合集可减少逐个搜集与配置工具的时间较快搭建从解包、转换到源码阅读的本地环境并保留重打包签名的验证环节便于对照分析资源改动与代码行为也可用于学习他人的实现思路与结构组织。1. APK 反编译工具装错版本的代价这条链路上到底有几个组件很多人拿到一个 APK第一反应是装个 jd-gui 拖进去结果窗口一片空白。APK 反编译工具从来不是单个软件它是四条腿一起走的链路apktool 负责拆资源和清单dex2jar 把 classes.dex 转成 jarjd-gui 负责看 Java 源码auto-sign 或 apksigner 负责回编译之后再签回去。任何一条腿版本对不上后面全白干。我见过最常见的翻车是把 JDK 17 当成万能钥匙结果 dex2jar 在反射那一步抛一堆告警jar 出来是残缺的jd-gui 打开只看到空壳类。这套环境适合三类人做逆向分析的、做老项目排障的、以及要给第三方 APK 打补丁的。下面按我实际装机的顺序从 JDK 落位一路写到签名验证。2. 环境与目录规划JDK 版本和工具落位先定死2.1 JDK 版本怎么选三条工具链的交集这四个工具里有三个是 Java 写的所以 JDK 是地基。麻烦在于它们对 Java 版本的口味并不一致。apktool 2.9 这条线官方要求 Java 8 以上实际上在 11 和 17 上都跑得很稳dex2jar 2.x 在 Java 8 上最安分换到 17 之后部分混淆过的 dex 会在转 jar 时打出一串WARNING: Illegal reflective access虽然多数情况还能出结果但遇到加固样本就容易半途而废jd-gui 自带反编译器对 JDK 版本不敏感但它吃内存堆给小了打开大 jar 直接假死。所以我的选择是主用 JDK 17同时在机器上留一套 JDK 8 备用遇到 dex2jar 翻车就临时切过去。不追最新版本JDK 21 上我实测过 apktool 的 wrapper 脚本没问题但有几个老版本 dex2jar 的脚本会读取JAVA_HOME拼路径路径里带-21-字样时行为不一致没必要给自己找事。Linux 下装两套运行时命令很直接# Debian/Ubuntu 系同时装 17 和 8后面按需切换 sudo apt update sudo apt install -y openjdk-17-jdk openjdk-8-jdk # 确认安装后的真实目录名JAVA_HOME 必须和这里一字不差 ls -1 /usr/lib/jvm/这段命令的关键在第二条。/usr/lib/jvm/下的目录名在不同发行版、不同小版本上会变有的是java-17-openjdk-amd64有的是java-1.17.0-openjdk直接抄别人的JAVA_HOME是新手最常犯的错。先ls看清再写变量。环境变量落到 shell 配置文件里# 写入 ~/.bashrc注意路径替换成你 ls 出来的那个 export JAVA_HOME/usr/lib/jvm/java-17-openjdk-amd64 export PATH$JAVA_HOME/bin:$PATH # 备一套 8用 jdk8 这个函数临时切不动全局 jdk8() { export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 export PATH$JAVA_HOME/bin:$PATH java -version }PATH把$JAVA_HOME/bin放在最前面是为了让java、keytool、jarsigner这几个命令始终跟着当前JAVA_HOME走。jdk8写成 shell 函数而不是脚本是因为函数在当前 shell 里执行export才会生效写成独立脚本的话子进程改完环境变量就退出了父 shell 一点感觉都没有。这是很多人切版本切了个寂寞的原因。macOS 上路径不用猜系统自带查询工具# macOS 用 java_home 查询输出直接就是可用的 JAVA_HOME /usr/libexec/java_home -V export JAVA_HOME$(/usr/libexec/java_home -v 17)-V列出机器上所有已安装的 JDK-v 17按大版本筛选。Windows 则走系统属性里的环境变量面板新建JAVA_HOME指向 JDK 根目录再在Path里加一条%JAVA_HOME%\bin注意别把bin写两遍。装完必须验证别信“应该好了”java -version javac -version echo $JAVA_HOME三条都要有输出且java -version里的版本号要和JAVA_HOME路径里的版本对得上。对不上说明 PATH 里有更靠前的 java用which -a java找出来清掉。2.2 工具链目录结构与 PATH 约定我踩过的最没必要的坑是把 apktool.jar 直接丢进/usr/local/bin。后果是半年后想升版本已经不记得当初放的是哪个文件也不敢删怕删错。后来统一改成家目录下一个独立工具区所有东西自己管自己# 一次性建好四个工具目录加一个工作区工作区单独放方便整体清空 mkdir -p ~/apk-tools/{apktool,dex2jar,jd-gui,sign,work}目录职责我固定成这样不做例外目录放什么是否进 PATH~/apk-tools/apktoolapktool.jar与 wrapper 脚本是只加脚本~/apk-tools/dex2jar解压后的 dex-tools 全套是加解压目录~/apk-tools/jd-guijd-gui 的 jar 包否用别名调用~/apk-tools/signkeystore 与签名脚本是~/apk-tools/work反编译产物、临时 dex、测试 APK否PATH 只追加脚本目录不把 jar 所在目录整个塞进去# 追加到 ~/.bashrc 末尾只暴露可执行入口不暴露一堆 jar export PATH$HOME/apk-tools/apktool:$HOME/apk-tools/dex2jar:$HOME/apk-tools/sign:$PATH为什么不把~/apk-tools/jd-gui也加进去因为那个目录里除了 jar 没别的加了也没命令可用反而在补全时多出一堆噪音。jd-gui 我用别名解决# 别名里固定堆大小避免每次手敲 -Xmx alias jdguijava -Xmx2g -jar $HOME/apk-tools/jd-gui/jd-gui.jar这种划分还有一个隐性好处升级版本时只动一个目录work 区里的历史产物不受影响哪天整条链路要迁到另一台机器把~/apk-tools打包拷过去改一下JAVA_HOME就能用。工具链的可迁移性靠的是目录约定不是记忆。3. 逐个装四个组件按依赖顺序落地3.1 apktooljar 和 wrapper 两件套缺一不可apktool 的发布形态是一个 jar 加一个启动脚本。jar 是本体脚本是壳。很多人只下 jar然后每次敲java -jar apktool.jar d xxx.apk能用但参数一多就容易漏-jar之外的东西而且在不同目录下要写绝对路径。先把 jar 放进目录并统一命名# 下载后重命名成固定名字后续脚本和别名都引用这个名字 mv ~/Downloads/apktool_2.9*.jar ~/apk-tools/apktool/apktool.jar ls -lh ~/apk-tools/apktool/重命名这一步是有意为之。版本号写在文件名里看着清楚但脚本里就得跟着改固定成apktool.jar升级时只覆盖文件脚本一行不动。版本信息记在旁边的文本里或者靠apktool --version查。然后是 wrapper 脚本Linux/macOS 版#!/usr/bin/env bash # apktool 启动壳固定 jar 路径把所有参数原样透传给 apktool exec java -Xmx1g -jar $HOME/apk-tools/apktool/apktool.jar $$加引号是必须的否则参数里带空格的路径会被拆成多段。exec让 java 进程替换掉当前 shell 进程不多留一层壳这样 CtrlC 的信号能直接打到 java 上不会出现“脚本退了 java 还在跑”的僵尸情况。-Xmx1g是给反编译大 APK 留的余量处理几百 MB 的资源包时默认堆会 OOM。保存成~/apk-tools/apktool/apktool给执行权限chmod x ~/apk-tools/apktool/apktool apktool --versionWindows 下没有 shebang 这一套用 batecho off REM %~dp0 表示脚本自身所在目录保证在任意路径下双击都能找到 jar java -Xmx1g -jar %~dp0apktool.jar %*%~dp0是 Windows 批处理里定位自身目录的标准写法末尾自带反斜杠所以后面直接拼文件名不要再加一个。%*对应 bash 里的$但 Windows 上对空格的转义本来就弱路径里尽量别有空格。3.2 dex2jar解压、授权、验证入口dex2jar 发布的是压缩包解压即用没有安装步骤但有两个必做的动作确认解压后的目录层级以及给脚本补执行权限。# 解压到工具目录注意压缩包内层通常还套一层 dex-tools-2.4 unzip ~/Downloads/dex-tools-2.4.zip -d ~/apk-tools/dex2jar # 看一眼真实层级别想当然 ls ~/apk-tools/dex2jar/如果ls出来只有一个dex-tools-2.4目录说明多套了一层需要把里面的内容提上来或者把 PATH 指到内层。我一般直接提上来保持路径短# 内层目录内容上提一级删掉空壳目录路径稳定下来 mv ~/apk-tools/dex2jar/dex-tools-2.4/* ~/apk-tools/dex2jar/ rmdir ~/apk-tools/dex2jar/dex-tools-2.4然后是权限。从压缩包解出来的.sh通常没有执行位# 给所有 sh 脚本补执行权限然后跑一次 help 验证入口通不通 chmod x ~/apk-tools/dex2jar/*.sh ~/apk-tools/dex2jar/d2j-dex2jar.sh --help--help能打印出用法说明 Java 环境和脚本本身都没问题。如果这一步报Permission denied是权限没给上报JAVA_HOME is not set或者No such file是第 2 章的环境变量没生效回去source ~/.bashrc。别在这一步就开始怀疑工具九成是环境。Windows 下对应的是.bat不需要 chmod但要注意解压工具可能给文件加了“来自网络”的锁定标记右键属性里解除锁定否则双击闪退。3.3 jd-gui免安装运行与内存参数jd-gui 有两个发行版一个是纯 jar需要本机有 Java一个是打包了运行时的可执行文件。我选纯 jar理由是版本可控跟着JAVA_HOME走不会出现“系统里有两套 JREjd-gui 用了不认识的那个”这种事。# 放到工具目录并统一命名方便别名引用 mv ~/Downloads/jd-gui-1.6*.jar ~/apk-tools/jd-gui/jd-gui.jar java -Xmx2g -jar ~/apk-tools/jd-gui/jd-gui.jar-Xmx2g不是随便写的。jd-gui 打开一个几十 MB 的 jar 时会把所有类一次性读进内存建索引堆给 512m 的话界面会卡在加载条上不动看起来像死机其实是 GC 在疯狂挣扎。2g 是处理常规应用 jar 的舒适区遇到特别大的包再往上加。启动后不要急着拖文件先在菜单里确认反编译引擎的选项。jd-gui 内置的反编译器对某些 Java 8 之后的语法支持一般遇到 lambda 或者 switch 表达式会显示成乱码或者反编译失败这是引擎能力边界不是装错了。真要抠细节后面再上更专业的反编译工具jd-gui 的定位是快速浏览、找关键词、定位类。3.4 auto-sign密钥库和签名脚本auto-sign 是一套老脚本核心逻辑是调jarsigner给 APK 签名。它跑起来需要两样东西一个 keystore和一个能定位到 keystore 的脚本配置。先造 keystore用 JDK 自带的 keytool# 生成一个 100 年有效期的调试密钥库口令统一成 android 方便脚本引用 keytool -genkeypair \ -keystore ~/apk-tools/sign/debug.keystore \ -alias debugkey \ -keyalg RSA \ -keysize 2048 \ -validity 36500 \ -storepass android \ -keypass android \ -dname CNdebug, OUdev, Odev, Ldev, STdev, CCN逐个参数说-alias是密钥别名签名时要对上-keyalg RSA -keysize 2048是当前通用组合别用 DSA-validity 36500约一百年避免调试包过期-storepass和-keypass设成一样是因为部分老脚本只读一个口令-dname随便填但格式必须是CN...这种键值对串少一个字段 keytool 会交互式追问脚本化的时候就卡住了。keystore 生成完验证一下能读出来# 列出密钥库内容确认别名和算法都对 keytool -list -v -keystore ~/apk-tools/sign/debug.keystore -storepass android然后处理 auto-sign 脚本本身。这类脚本通常把 keystore 路径、别名、口令写死在文件头部需要改成自己的# 在脚本头部定位这几个变量改成实际路径和口令 # KEYSTORE$HOME/apk-tools/sign/debug.keystore # ALIASdebugkey # STOREPASSandroid这里要说明一个取舍。auto-sign 走的是jarsigner产出的是 v1 签名JAR 签名。Android 7.0 之后系统要求 v2 签名只签 v1 的包在新系统上会报INSTALL_PARSE_FAILED_NO_CERTIFICATES。所以 auto-sign 现在只适合两个场景快速验证回编译产物是否结构完整以及给 Android 7 以下的老设备准备包。真要装到新手机上末尾还得用 build-tools 里的 apksigner 补一道 v2。这个顺序不能反v2 签完再跑 jarsignerv2 签名块会被破坏。如果只是想跑通链路装到这里就够了。四个组件各就各位剩下的都是配置。4. 配置细节反编译链路里那几个必须改的参数4.1 apktool 的 framework 目录与 apktool.ymlapktool 拆 APK 时资源文件res 目录下的 XML里的引用是数字 ID要还原成人类可读的名字靠的是 framework 资源表。系统级的 framework 没装拆出来的布局文件里全是2131427328这种鬼东西。# 首次处理某类 ROM 的 APK 前先把它对应的 framework-res.apk 装进本地仓库 apktool if framework-res.apkif是 install-framework 的缩写执行后会在~/.local/share/apktool/framework/下生成一个以包 ID 命名的 apk 文件。多个 framework 可以共存apktool 按 APK 里声明的 ID 自动匹配。拆解时如果报Could not decode arsc file八成是缺 framework 或者 apktool 版本比目标 APK 老先补 framework再考虑升级。拆出来之后根目录会有个apktool.yml里面几个字段值得改# apktool.yml 关键字段回编译时直接影响产物 sdkInfo: minSdkVersion: 21 # 低于目标机型的实际值会导致装上闪退 targetSdkVersion: 30 # 影响系统对权限、后台行为的判定 versionInfo: versionCode: 1 # 和原包不一致会导致覆盖安装失败 versionName: 1.0minSdkVersion改小了没意义改大了会拒绝安装在低版本设备上versionCode是最容易被忽略的一条回编译产物如果版本号和已安装的一致甚至更低adb install会直接报INSTALL_FAILED_VERSION_DOWNGRADE用-r -d强装又可能踩到别的坑。要么改大版本号要么先卸载原包。回编译命令本身也有讲究# -c 保留原始清单里的调试信息-r 跳过资源重新编译 # 只改了 smali 时用 -r速度提升非常明显 apktool b ~/apk-tools/work/smoke -o ~/apk-tools/work/rebuilt.apk -c-c让 apktool 沿用原始 AndroidManifest 里的某些标记避免重编后清单被改写-r是纯 smali 修改场景的加速开关跳过 aapt 那一段。但如果是改了布局或者 strings就不能加-r否则改动不生效。4.2 dex2jar 的内存设置与编码dex2jar 的入口脚本d2j-dex2jar.sh本身很薄真正干活的参数在d2j_invoke.sh里。默认堆往往只给了 512m处理大 dex 会直接 OOM。# 把默认堆提到 2g同时强制 UTF-8避免中文常量变成问号 sed -i s/-Xmx[0-9]*m/-Xmx2g/ ~/apk-tools/dex2jar/d2j_invoke.sh grep -n Xmx ~/apk-tools/dex2jar/d2j_invoke.sh用sed改而不是手改是因为这个脚本可能有多处引用改完用grep确认看不到-Xmx2g就说明匹配模式没对上需要打开文件看实际写法。编码问题更隐蔽Java 默认用平台编码读 class 文件里的字符串常量终端是 GBK 而 dex 里是 UTF-8 时转出来的 jar 里中文全是问号。加一条启动参数# 在同一个脚本的启动参数里补上编码声明 # 找 java 调用那一行加上 -Dfile.encodingUTF-8编码错乱不会报错只会让 jd-gui 里看到一堆???很多人以为是反编译失败其实是转码阶段就烂了。这个坑我吃过一次排查了两个小时。4.3 签名对齐zipalign 与 apksigner 的先后顺序回编译产物要装进设备签名是最后一道。这里有两个工具顺序不能错先对齐再签名。# 第一步对齐4 字节边界是 Android 的硬要求-p 让 so 文件也按页对齐 zipalign -v -p 4 ~/apk-tools/work/rebuilt.apk ~/apk-tools/work/aligned.apk # 第二步签名v1 和 v2 都打开兼顾新老设备 apksigner sign \ --ks ~/apk-tools/sign/debug.keystore \ --ks-pass pass:android \ --key-pass pass:android \ --ks-key-alias debugkey \ --v1-signing-enabled true \ --v2-signing-enabled true \ --out ~/apk-tools/work/signed.apk \ ~/apk-tools/work/aligned.apk为什么顺序不能反v2 签名是对整个 APK 文件做的摘要一旦签完再动文件内容对齐就是在动文件结构摘要立刻失效装上去就是INSTALL_PARSE_FAILED_NO_CERTIFICATES。这个错误的迷惑性在于apksigner verify会告诉你签名有效但安装就是失败因为它验的是签名块不是文件完整性。验证签名要单独跑一条# 确认 v1 和 v2 两个方案都通过 apksigner verify --verbose --print-certs ~/apk-tools/work/signed.apk输出里Verified using v1 scheme: true和Verified using v2 scheme: true两行是判断依据缺一个都说明参数没生效。有些 build-tools 版本对--v2-signing-enabled的默认值和文档不一致显式写出来最稳。5. 避坑排查五条血泪记录这一章的五条是我在不同机器、不同样本上真实撞过的。每条按现象、原因、解决三段说照着对号入座就行。5.1 安装阶段的命令找不到与权限被拒第一条apktool: command not found。现象是刚配完 wrapper 脚本敲apktool提示找不到命令但ls ~/apk-tools/apktool/明明有文件。 原因有两个可能脚本没给执行权限或者 PATH 追加后没重新加载。bash 只在启动时读一次.bashrcexport写在文件里但当前 shell 不知道。 解决是先chmod x ~/apk-tools/apktool/apktool再source ~/.bashrc然后which apktool确认解析到的就是你那个脚本。如果which输出的是/usr/bin/apktool之类的路径说明系统里装过旧版且优先级更高把 PATH 追加顺序调到最前面。第二条d2j-dex2jar.sh: Permission denied。现象是脚本存在、Java 也正常一执行就拒绝。 原因是 zip 压缩包不保存 Unix 执行位解压出来的 sh 全是 644。 解决就是补权限chmod x ~/apk-tools/dex2jar/*.sh。顺带一提用unzip解压和用图形界面解压器解压在权限上没区别都得补这一步。5.2 反编译阶段的解析失败与乱码第三条brut.androlib.AndrolibException: Could not decode arsc file。现象是apktool d跑到解析资源表就中断堆栈里明确提到 arsc。 原因通常是两个目标 APK 用了比当前 apktool 更新的资源格式或者缺 framework 资源。 解决顺序是先apktool if framework-res.apk补框架还不行就升 apktool 版本。反过来如果 apktool 太新而去拆很老的包也可能报AndrolibException: Could not decode arsc这时候退回 2.4 那条线反而能过。版本不是越新越好是越匹配越好。第四条拆出来的 strings.xml 中文全是乱码。现象是文件能打开但中文变成测试这种。 原因是文件本身编码没问题是终端或者编辑器的显示编码不是 UTF-8或者 dex2jar 那一步没加编码参数导致 jar 里字符串已损坏。 解决分两步先确认终端echo $LANG输出里带UTF-8如果是 dex2jar 转出来的 jar 乱码回到 4.2 节补-Dfile.encodingUTF-8重新转一次。已经转坏的 jar 救不回来必须重跑。5.3 回编译与签名阶段的装不上第五条回编译成功安装报INSTALL_PARSE_FAILED_NO_CERTIFICATES。现象是apktool b输出 BUILD SUCCESSFULadb install直接被系统拒绝。 原因只有两类压根没签名或者只签了 v1 而设备是 Android 7 以上。 解决是用 apksigner 补 v2并且严格保证“对齐 → 签名”的顺序。如果之前用 auto-sign 签过必须从对齐那一步重来不能在已签名的包上叠加因为 v2 签名块会互相覆盖。 还有一种变体签名都通过了装上也成功但一启动就闪退。那多半不是签名问题是资源 ID 错位或者原包有完整性校验需要回到 apktool 的-c参数和 smali 层面查不要继续在签名上打转。提示这五条里有四条最终都指向环境或顺序而不是工具本身有 bug。排查时先看which、java -version、echo $PATH这三条输出能省掉一半时间。6. 装完怎么验证拿一个 APK 走完整条链路装完不等于能用。我的验证方式很土找一个体积不大的 APK把四个组件串起来跑一遍每一步都要看到预期输出中间任何一步没有输出就是没通。# 冒烟测试四步串行任何一步失败就停下来查不要往下走 cd ~/apk-tools/work # 1. 拆包能生成 smali 和 res 两个目录算过 apktool d -f -o ./smoke ./smoke.apk # 2. 单独取出 classes.dexdex2jar 只认这个文件 unzip -o ./smoke.apk classes*.dex -d ./ # 3. 转 jar看到 Detail: ... - ./classes.jar 算过 ~/apk-tools/dex2jar/d2j-dex2jar.sh -f -o ./classes.jar ./classes.dex # 4. 打开 jar能看到类树和反编译出的方法体算过 java -Xmx2g -jar ~/apk-tools/jd-gui/jd-gui.jar ./classes.jar每一步的成功标志和失败时的第一排查点我整理成一张表贴在工位上步骤成功标志失败第一眼看哪里apktool d生成smali/、res/、apktool.ymlapktool --version与 framework 是否装unzip取 dex目录下出现classes.dexAPK 是否用了多 dex改classes*.dexd2j-dex2jar.sh输出Detail:行且无异常堆栈JAVA_HOME、-Xmx、-Dfile.encodingjd-gui 打开左侧有包结构右侧有方法体jar 大小是否为 0堆是否给够再补一条端到端的验证把“拆了能装回去”这件事跑通# 回编译 对齐 签名三步连做最后 verify 两个 scheme 都是 true apktool b ./smoke -o ./rebuilt.apk -c zipalign -f -p 4 ./rebuilt.apk ./aligned.apk apksigner sign --ks ~/apk-tools/sign/debug.keystore --ks-pass pass:android \ --key-pass pass:android --ks-key-alias debugkey \ --v1-signing-enabled true --v2-signing-enabled true \ --out ./signed.apk ./aligned.apk apksigner verify --verbose ./signed.apk这里有个我自己的习惯验证用的 APK 永远不换固定放在work目录下名字就叫smoke.apk。因为换个样本就换了一堆变量出了问题是工具坏了还是样本特殊根本分不清。同一个样本反复跑行为才可比。还有个小技巧值得说把上面这段验证写成~/apk-tools/verify.sh每次升级任何组件之后先跑一遍而不是直接拿真实目标样本去试。升级引入的回归问题用冒烟脚本能在三十秒内暴露用真实样本可能排查半天还以为是样本加固了。这个习惯是从一次 apktool 小版本升级后资源解析行为变化开始的——那次我直接拿业务样本试拆出来的布局少了一半控件查了整整一个下午最后发现旧版本反而正常。从那以后我每次动工具链不管是升 JDK、换 apktool 版本还是重装 dex2jar都强制走一遍这个冒烟脚本四步全绿才碰真实样本。环境这东西能复现才叫装好了装好了不验证等于没装。希望帮到你。本文还有配套的精品资源点击获取