☰
APK反编译与重打包签名全流程:apktool、dex2jar、jd-gui实战指南
2026/10/13 2:47:58 网站建设 项目流程

简介:面向安卓开发者和逆向工程学习者,这套资源围绕安装包反编译需求,整合出一站式工具链。其中APKTool负责将资源文件解码为可读的XML格式,并支持修改后重新打包;自动签名工具用于快速为修改后的安装包生成有效数字签名;Dex2Jar能把安卓虚拟机字节码转换成Java类库,再配合界面化的JD-GUI,直观浏览还原出的Java源码。整套流程覆盖解包、修改资源、重签名、字节码转换到源码查看的主要环节,既适合学习他人实现技巧,也适用于安全审计、恶意行为分析和自查应用漏洞。资源共58个文件,以批处理脚本、Shell脚本、Jar工具、文本说明为主,并包含少量可执行程序和密钥材料,Windows与Linux环境均可直接使用,整体体积仅10.58MB,便携易用。目前已有13971人学习下载,工具按功能分模块存放,便于按需调用;无论是刚接触逆向的新手,还是需要快速处理安装包的开发者,都能从中找到对应工具。

1. 拿到一个 APK,别指望一键还原

做移动端开发或安全测试的人,迟早会遇到这样的场景:手里有一个 APK,想看某个功能的实现思路,或者要分析某个 SDK 的调用逻辑,又或者要改图标、改包名、汉化某个海外工具。这时候第一反应就是搜 APK 反编译工具,下载所谓的一键反编译神器。我最初也是这么干的,下载了个一键工具,点完按钮等着出 Java 源码,结果工具跑完只吐出一堆 smali 文件和没头没尾的资源文件。APK 反编译工具能解包,但不代表能直接给你一份可以重新编译的工程级源码。apktool、dex2jar、jd-gui 这三件套加 auto-sign 签名脚本,是目前从业者最常用的一套组合,能解决从资源提取、DEX 字节码转换、Java 代码阅读到重打包签名的全过程。这篇笔记会把这套链路的每一步走一遍,尤其是重打包签名这一步,网上教程大多一笔带过,实际上坑最深。

2. 工具链选型:四个小工具各管一段,别混着用

2.1 apktool:负责资源和 smali,不负责 Java 代码

apktool 在整个链条里的角色是拆解和重组,它把 APK 当作一个 zip 包加一层解析逻辑来处理。执行apktool d之后得到的是资源文件、AndroidManifest.xml 的明文形式、以及 classes.dex 反汇编出来的 smali 字节码。很多人在这里有个误解,觉得看到 smali 就完事了,这玩意儿是寄存器级的中间表示,人眼直接读非常痛苦。它更像是给后续 dex2jar 和 jd-gui 打前站。

实际使用中,apktool 的价值在于两点:一是资源可控,res 目录下的 XML 会被还原成可读的格式,修改后再用apktool b打包回去;二是适配性,新版 apktool 对 Androguard 和 AAPT2 的兼容做得比较稳,老版本根本解不开某些厂商加固后的资源。注意,apktool 的解包不处理签名,也不处理代码还原,它的边界很清楚。

2.2 dex2jar:把 DEX 字节码转成 JAR,但别期待还原变量名

dex2jar 做的事情是把 classes.dex 里的 Dalvik 字节码转换成 JVM 的 class 字节码,输出成一个 JAR 包。关于这个工具有个常见的玄学:转出来的 JAR 在 jd-gui 里可能看不到源码,只能看到方法签名和异常表,这时候会有人怀疑工具坏了。实际上不是工具坏了,是 dex2jar 内部依赖的混淆映射信息为空,方法体反编译失败时会静默跳过,这正是后面要提到的坑之一。

工具链选型的时候要注意版本匹配,dex2jar 2.x 版本在 2020 年之后就基本不再更新,对于新的 DEX 格式,尤其是加了某些厂商自定义 opcode 的包,转换出来不完整是非常正常的。这种时候别硬用一个工具走到底,可以跳到第 6 章看更务实的替代方案。但作为标准链路,dex2jar 目前仍然是转 JAR 最快的方案。

2.3 jd-gui:阅读 JAR 源码的可视化终端

jd-gui 是一个图形化工具,它读取 dex2jar 产出的 JAR 包,在内存里反编译 class 文件并展示成接近原始的 Java 代码。它的强项是快速浏览和搜索,弱点是反编译的还原度在复杂控制流上会丢逻辑,比如 switch 语句嵌套深了可能变成一堆 if-else,循环边界也会偶尔错位。

选型建议:jd-gui 适合定位代码入口和关键调用的第一轮阅读,不要拿它当作最终代码审查工具。它显示的代码是供人脑理解逻辑的,不是可以拿回去编译的。如果 jd-gui 打开 JAR 报 Internal Error,通常是 JAR 内 class 版本过高或者带上了 Java 11+ 的字节码特性,3.x 版本的 jd-gui 对老项目的兼容明显比 1.x 强,优先用最新版。

2.4 auto-sign:重打包的最后一道工序,最容易翻车

auto-sign 是一个签名脚本集合,本质是把重打包后的 APK 用测试密钥签名,让 Android 系统能正常安装。这个脚本对应的是一套固定的测试 keystore,说白了就是一把大家共用的钥匙。它解决的是开发环境里快速安装验证的问题,生产环境谁把 auto-sign 的密钥拿去上架,那等于把自己的应用放进一个谁都能打开的箱子。

工具链选型这里有个重要边界:apktool 自带的重打包命令不签名,dex2jar 不反编译资源,jd-gui 不负责回写,auto-sign 只管签名不管对齐。把边界理清了,后面每一步的输入输出才顺。

3. 把 APK 拆成素材和代码:四项命令的完整链路

3.1 环境准备和版本核对

动手之前先把 Java 环境确认好,整套工具链都跑在 JVM 上。JDK 8 到 JDK 11 之间是安全区,我一般在 JDK 11 上跑 apktool 和 dex2jar,JDK 8 跑 auto-sign,原因是 auto-sign 脚本里用的 keytool 和 jarsigner 在更高版本 JDK 里默认算法变了,默认 SHA-256 签名在 Android 4.2 以下的兼容性有问题,虽然现在这层兼容已经不太有人关注,但不希望在这个位置消耗时间。

# 验证 Java 版本,建议 JDK 8 或 11 java -version # 确认 apktool 版本 java -jar apktool.jar --version

apktool 的版本会影响解包行为,2.6.0 以上的版本对 Android 10 及以后的应用资源 ID 处理更完整。版本写进这一步的原因是,很多网上的教程还在用 2.4.0,拿到新应用时会碰到资源 ID 溢出导致的重打包失败,这不是操作问题,是工具版本问题。

3.2 解包命令:apktool d 的参数与输出

# 基础解包,-o 指定输出目录 java -jar apktool.jar d target.apk -o app_source # 带 only-main-cls 模式,只反编译主 dex,适合快速定位 java -jar apktool.jar d target.apk -o app_source --only-main-cls # 禁止反编译资源,只要 smali 代码,速度更快 java -jar apktool.jar d target.apk -o app_source -r

常用参数里,--only-main-cls在只关心入口类时非常好用,能控制解包后的体积,节省后续 dex2jar 的转换时间。-r参数的意思是 keep resources as-is,资源文件保持原始二进制,不解码 XML,这样解包速度会快不少,代价是 res 目录下的 XML 全是二进制乱码,如果目标是改造 UI 就别加这个参数。解包完成后检查输出目录,重点确认AndroidManifest.xml是可读文本,smali 目录下有真实的非空文件,这一步没问题再往下走。

3.3 dex 转 jar:dex2jar 的输出与常见失败点

# 转换主 dex 文件 ./d2j-dex2jar.sh target.apk -o output_classes.jar # 对 apktool 解包后的 classes dex 目录批量转换 ./d2j-dex2jar.sh app_source/classes*.dex -o output_classes.jar

这个命令有个细节值得注意:第一个写法直接传 APK 文件,dex2jar 内部会自己解压并找出所有 dex 文件;第二个写法传的是 dex 文件通配符,适合已经用 apktool 解过包的情况。参数-o指定输出文件路径,不指定时会把名字写在当前目录下。多数情况下的失败原因只有两类:一类是 dex 文件本身被加固过,转换出来只有几个空壳 class;另一类是内存不足,大项目后直接 OutOfMemoryError。出现 OOM 时加一个 JVM 参数再执行:

# 扩大堆内存到 2GB ./d2j-dex2jar.sh -Xmx2g target.apk -o output_classes.jar

dex2jar 脚本本身是 shell 封装,可以把 JVM 参数直接写在脚本入口的命令后面,但注意脚本内部是靠d2j_invoke转调 java 的,直接把-Xmx2g写在脚本后面不一定有效,稳妥的做法是设置环境变量JAVA_OPTS再跑脚本。

3.4 jd-gui 打开 jar 与第一个定位动作

jd-gui 运行起来后把 output_classes.jar 拖进去,左侧会按包名展开目录树。不要一进来就从上往下读,正确的第一个动作是拿应用名拼音或包名关键词搜一下。按下快捷键或点击 File 下的 Search,输入包名主后缀,定位到 Application 子类或主 Activity。这一步能在秒级确认反编译是否成功,如果点开类文件能看到完整的方法体代码,链路就通了一半。

常见问题:看到的代码是throw new RuntimeException("Method not implemented")这种占位内容,这代表 dex2jar 遇到混淆代码后无法还原方法体,属于正常现象。处理的思路是跳过这里,去全局搜索关键业务字符串,比如 API 地址、AppKey、特征常量,再从引用这些常量的方法读起。

4. 重打包与签名实操:apktool b 和 auto-sign 的配合细节

4.1 修改资源后重建 APK 包

第 3 章解包出来的目录就是重打包的输入。改资源文件时要保持目录结构不变,尤其注意 res/values 下的 XML 如果被改动了格式,比如把<string>节点的引号弄丢,打包会直接失败。

# 重打包为未签名 APK java -jar apktool.jar b app_source -o rebuilt.apk

重打包的输入目录是解包时用-o指定的那个目录。-o指定输出文件路径,如果不指定,apktool 会在输入目录的 dist 子目录里生成 APK。这一步产出的 APK 是未签名的,安装到机器上会提示解析包错误,需要接签名流程。这里有个参数很重要:b命令默认把 resources.arsc 重新构建,如果解包时没用-r,一般都能正常完成;若是解包时加了-r保留了二进制资源,重打包时不加任何参数也能过,但改过的资源文件若以明文形式放进二进制目录,打包后必崩。

4.2 auto-sign 签名的执行与 JDK 版本注意

auto-sign 脚本的核心是把 rebuilt.apk 用内置的测试密钥完成签名。它内部调用 keytool 和 jarsigner,参数固定指向一套自带的 keystore。JDK 8 的 keytool 默认用 SHA1withRSA,Android 5.0 及以上的系统都认这套。JDK 9 之后 jarsigner 默认签名算法切到了 SHA-256,也兼容,但脚本中如果用了-sigalg参数指定了旧算法,高版本 JDK 会直接抛错。

# 常规执行签名(Windows 是 .bat,Linux/macOS 是 .sh) ./sign.sh rebuilt.apk # 签名完成后生成 rebuilt.apk.signed.apk,直接安装 adb install rebuilt.apk.signed.apk

注意sign.sh执行时会先对 APK 做一次 zipalign,这个工具在 auto-sign 包里自带了一个,但它拿到 APK 之后会先删除原始 META-INF 目录下的签名文件,再写入新的签名文件。如果你的 APK 在之前步骤中已经用 debug keystore 签过一次,auto-sign 脚本也能覆盖,但覆盖后签名指纹会变成 auto-sign 的固定指纹。每次取回签名后的 APK 都要确认输出文件后缀是.signed.apk,脚本跑完没有任何提示不代表成功,要用下面命令验证:

# 查看签名是否生效 apksigner verify --verbose rebuilt.apk.signed.apk

apksigner 来自 Android SDK 的 build-tools,优先用这个工具验证而不是 jarsigner,apksigner 会显示签名方案 v1、v2 的启用情况。

4.3 v1 与 v2 签名机制:新版系统的关键门槛

auto-sign 脚本产出的签名默认只有 v1 方案,Android 7.0 及以上系统默认接收 v2 方案,安装老式 v1 签名的 APK 时会多一层兼容提示,大多数设备依然能装,但存在一个硬性翻车点:如果把 auto-sign 签出来的包拿到 Android 11 及以上并开启“增强型包验证”的机器上装,会被射频拦截,提示“应用未安装”。

处理方式是在 auto-sign 签名后用 apksigner 追加 v2 签名:

# 追加 v2 签名,保持 v1 不变 apksigner sign --ks auto-sign的密钥路径 --ks-pass pass:密码 \ --v1-signing-enabled true --v2-signing-enabled true \ rebuilt.apk.signed.apk

这样一个包同时带 v1 和 v2,新旧设备通吃。我在本地一直保留着一份 build-tools 里的 apksigner,因为 auto-sign 里没有这个程序,完整工具链补齐后,重打包的路才算走通。

4.4 重打包后安装崩溃的第一排查顺序

重打包后频繁出现两类问题,这里直接给排查顺序:一是安装包解析错误,优先确认签名是否执行成功,再确认 zipalign 是否对齐,如果 apktool b 时使用了--use-aapt2参数,对齐工作 apktool 自己做,再跑 auto-sign 脚本里的 zipalign 反而可能二次对齐引发偏移;二是装上了但一打开就闪退,这类问题大概率出在资源 ID 或签名校验逻辑上,资源 ID 的排查要去比对 res/values/public.xml 里的 ID 映射表,签名校验逻辑则要找 smali 里类似checkSignature、getPackageInfo、signature字符串的调用点,找到后把验证分支改成无条件跳过或返回真值再重打包。

5. 反编译避坑手册:来自拆包一线的五条记录

5.1 jd-gui 打开 jar 提示 Internal Error

现象:拖入 jar 文件后,大部分类显示正常,少数类双击就弹 Internal Error,看不到任何代码。

原因:dex2jar 转换出来的 class 文件里含有新版 Java 字节码指令,jd-gui 的 1.x 版本解析不了。常见触发点是项目里使用了 Lambda 表达式或者 Switch 字符串拼接生成的方法,老版反编译器无法还原这些语法树的节点。

解决:先确认 dex2jar 和 jd-gui 都用各自的 2.x / 3.x 最新版本。如果问题依旧,方法体里逻辑可能已经被混淆到了极深层次,不再适合用 jd-gui,直接跳过。我一般会改用命令行方式单独查看,或者退而求其次看 smali 逻辑。

5.2 dex2jar 转换完只有一个体积很小的 jar

现象:目标 APK 有 30 MB,转完 jar 只有几十 KB,查里面只有几个类,其他全丢了。

原因:APK 使用了加固壳,真实的 dex 在运行时才被解密释放。静态拆解时 classes.dex 只是一个壳入口,真正的代码在 assets 目录下的加密文件或 so 库里。dex2jar 只会老老实实转 classes.dex,转出来的自然是体积很大的壳代码。

解决:先识别壳种类,常见的加固厂商在包名和 so 名字上都有明显特征。脱壳通常需要借助运行设备来 dump 内存中的 dex,不在静态工具链的讨论范围内。如果目标是阅读业务代码并且找得到对应版本的应用市场渠道包,优先找未加固的渠道版本,解包体验会顺畅得多。

5.3 apktool 解包后 res 目录下 XML 大量乱码

现象:解包成功后,res/layout下的 XML 文件打开是类似ÿþ<开头的二进制,不是正常的标签文本。

原因:apktool 在解析时把资源当作原始二进制处理了。一种是解包命令带了-r参数,另一种是资源在打包时用了非标准压缩方式,apktool 的解码器认为它不可读。

解决:去掉-r参数重新解包。若仍乱码,检查 APK 是否使用了资源混淆插件,资源混淆会把文件路径和 XML 头打乱,加--use-aapt2参数重试。

5.4 auto-sign 执行后提示 keytool 错误

现象:运行 sign.sh,输出一串和密钥库格式有关的异常,签名失败。

原因:高版本 JDK 的 keytool 默认生成的密钥库格式是 PKCS12,auto-sign 内置脚本读取的是旧版 JKS 格式的密钥库路径,格式不匹配时报错。

解决:优先使用 JDK 8 运行 auto-sign 脚本,或者在脚本的-storetype参数后面手动指定成 PKCS12,确保和本机 keytool 默认格式一致。

5.5 重打包后运行闪退且 Logcat 看不到 Java 异常

现象:加了少量资源修改重打包,打开直接退出,Logcat 没有任何 AndroidRuntime 异常堆栈。

原因:闪退发生在 Native 层。常见于壳校验、so 文件加载失败、或资源 ID 变化导致系统 UI 在 frame 渲染阶段崩溃,Java 层根本没有机会打印异常。

解决:先检查 so 文件在解包和重打包过程中是否被损坏,用 unzip 对比原始 APK 和 rebuilt APK 中lib/目录文件名是否一致;再确认是否使用了--use-aapt2,如果原 APK 是 AAPT2 打包,重打包时也要用 AAPT2,否则资源 ID 映射表对不上。最后才去看 smali 签名校验逻辑。

6. 一条效率翻倍的进阶路径:从 Manifest 入口向调用链下游推

整套工具链跑通之后,大多数人的反编译体验还停留在到处搜字符串、碰运气看代码的阶段。这里分享一个在实际项目中反复验证过的读取路径,能让定位效率高出好几倍。

打开 apktool 解包目录下的AndroidManifest.xml,搜索android.intent.action.MAIN。这个 action 和android.intent.category.LAUNCHER组合在一起的 activity,是应用的启动入口,也是逻辑链的天然起点。在 jd-gui 里找到对应这个 activity 的类名,先读onCreate方法,之后顺藤摸瓜找findViewById和setOnClickListener的调用点,再到监听器内部看具体的业务分支。

一个有价值的技巧是:大部分商业应用会在 Application 子类里做全局初始化,包括三方 SDK 的注册、路由表的加载、进程 fork 判断。jd-gui 左侧的类名中,包名路径通常带有app或者base字样的就是入口模块。从这里开始,比从任何字符串搜索切入都要直接。

另外一个进阶验证方法:修改入口 Activity 的可见性。在 apktool 解包后的 Manifest 里给某个不导出的 Activity 加上android:exported="true",在真机上用 adb 命令直接拉起它,检查这个页面对数据输入的防御机制。重打包和签名流程走的是第 4 章的完整链路,唯一的区别是这次修改点在 Manifest 而不在 res 或 smali 里。这个操作能快速验证一个页面是否存在未授权访问风险,也是移动端测试常见用例。

有一次排查某个应用升级逻辑时,我把升级检测相关类里所有字符串都搜了一遍没找到 API 地址,后来顺着 Application 子类的执行顺序读,发现调用栈藏在 startActivity 的 target 参数里,而这个 target 值是从 so 层读取后通过 JNI 回调传上来的。从那以后,我每次拿到样本都会强制走一遍「Manifest 找入口 → Application 读初始化 → Activity 读交互 → 触发点回溯」的流程,复杂度高的样本也不再抱怨。希望这些习惯和踩坑记录能帮你在工具链上少花冤枉时间。

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

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

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

立即咨询