简介:本资源是面向Android开发工程师、安全研究人员及逆向学习者的专业级反编译工具集合,聚焦APK结构解析、代码还原与动态分析等核心需求。压缩包共42个文件,包含15个可执行jar工具(如dex2jar、baksmali、jadx-core)、10个Windows批处理脚本(bat)与10个Linux Shell脚本(sh),覆盖自动化反编译流程;另有apk示例文件、签名密钥(pk8/pem)、许可证及README说明文档,总大小13.49MB,开箱即用。已有175人下载学习,适合从入门到进阶的逆向实践——既提供HelloWorld.apk实操样本,又集成dexHunter敏感信息扫描、Frida动态插桩支持及JD-GUI/jadx双引擎源码还原能力,目录按功能模块分层组织,便于快速定位工具链并开展静态分析、Smali调试或安全审计。
1. Android反编译工具包:不是“一键破解”,而是逆向工程的最小可行工作台
你手头有个 APK,想看它怎么调用摄像头、为什么启动时卡在 splash 页面、第三方 SDK 是否偷偷上传了设备信息——这时候搜“Android反编译工具包”,跳出来的不是教程,而是满屏报错:java.lang.VerifyError、smali syntax error、resources.arsc is invalid、aapt2 failed: Resource entry xxx is multiply defined……
这不是工具不行,是没人告诉你:反编译从来不是“把 APK 拖进去→点一下→得到 Java 源码”这么简单。它是一套分层拆解、逐级验证、交叉校验的工程动作流。所谓“工具包”,本质是把apktool(资源层)、dex2jar+jd-gui(字节码转 Java 层)、jadx(现代一体化替代)、signapk(重签名验证层)这四类工具按依赖链打包、参数对齐、路径收敛后的最小可运行组合。它不解决业务逻辑理解,但能让你在 5 分钟内确认:这个 App 真的没调TelephonyManager.getDeviceId(),或者它确实在onCreate()里硬编码了测试环境域名。适合 Android 开发自检合规性、安全审计人员做黑盒初筛、老项目无源码维护者定位崩溃点——不是为绕过 License,而是为守住技术判断权。
2. 四层拆解:为什么必须用工具包,而不是单个命令行工具
反编译不是线性流程,而是资源、字节码、逻辑、验证四层环环相扣。单用apktool d app.apk只能拿到res/和smali/,但smali对多数人等于天书;单用jadx-gui虽能出 Java,但常因混淆、反射、动态加载导致关键逻辑缺失或方法名错乱;而dex2jar在 Android 8.0+ 后对invoke-custom指令支持极差,生成的 JAR 常无法被jd-gui正确解析。工具包的价值,在于用预设参数和脚本串联这四层,让每一步输出成为下一步的可信输入。
2.1 资源层:用 apktool 提取 res/ 与 AndroidManifest.xml 的真实意图
apktool是唯一能无损还原resources.arsc、AndroidManifest.xml(含android:debuggable="true"、uses-permission、intent-filter)和res/目录结构的工具。它的核心不是“反编译”,而是“反编译前的标准化”。例如:
AndroidManifest.xml中android:allowBackup="true"暴露数据风险,android:exported="true"未加权限则组件可被任意 App 调用;res/values/strings.xml里硬编码的 API Key 或测试域名,比 Java 层更早暴露;res/drawable/下的图标尺寸缺失会导致aapt2重打包失败,这是后续所有操作的前置地雷。
提示:
apktool默认使用--force-manifest和--no-res以外的全量模式。但实际工作中,我强制加--use-aapt2(启用 aapt2 解析),因为旧版 aapt 对vector和adaptive-icon支持极差,会导致res/目录下出现unknown_0x7f080000这类占位符,后续aapt2 compile会直接报错。
# 推荐命令(带关键参数说明) apktool d -r -s -o ./decompiled_app --use-aapt2 app-release.apk-r:跳过资源解码(只解AndroidManifest.xml和smali),适用于纯逻辑分析场景,提速 3 倍以上;-s:跳过 smali 反编译(只解资源),适用于仅检查 manifest 或资源泄露;--use-aapt2:强制用 aapt2 解析 resources.arsc,兼容 Android 9+ 的config.限定符(如values-zh-rCN);- 输出目录
./decompiled_app必须为空,否则apktool会合并旧文件导致AndroidManifest.xml错乱。
2.2 字节码层:dex2jar 已死?不,是它必须配合 procyon 才能活
dex2jar本身只负责将classes.dex转成classes-dex2jar.jar,但它生成的 JAR 包含大量invoke-static {v0}, Landroid/text/TextUtils;->isEmpty(Ljava/lang/CharSequence;)Z这类底层调用,jd-gui无法将其映射回TextUtils.isEmpty(str)。此时需用procyon(非jd-gui)反编译该 JAR:
# 先转 dex → jar d2j-dex2jar.sh -f -o classes-dex2jar.jar app-release.apk # 再用 procyon 反编译(比 jd-gui 多识别 40%+ 的泛型和 lambda) java -jar procyon-decompiler-0.5.36.jar -o ./java_from_dex2jar classes-dex2jar.jard2j-dex2jar.sh必须用-f(force overwrite),否则遇到同名 class 会跳过;procyon的-o参数指定输出目录,它会自动创建包路径(如com/example/app/MainActivity.java),而jd-gui只能导出单文件;- 关键区别:
procyon能正确还原for (String s : list)为增强 for 循环,jd-gui常转成Iterator手动遍历,掩盖真实逻辑。
2.3 一体化层:jadx 是工具包的中枢,但必须关掉“玄学优化”
jadx是目前最接近“开箱即用”的工具,它同时解析 DEX、resources.arsc、AndroidManifest.xml,并在 GUI 中联动跳转(点击R.string.xxx自动定位到strings.xml)。但默认设置会触发三大翻车点:
- 混淆字符串自动解密:开启后,
jadx会尝试解密a.b.c.d.e("12345")这类调用,但算法匹配错误时,会把getPackageName()显示成"com.example.app",实则应为"com.example.app.debug"; - 内联函数强制展开:导致
Log.d("TAG", "msg")被替换成android.util.Log.d("TAG", "msg"),掩盖了开发者自定义的Log工具类; - 资源 ID 自动映射:将
0x7f040001强制转为R.layout.activity_main,但若resources.arsc损坏,映射结果全是错的。
注意:我在所有项目中都禁用这三项。
jadx-gui启动后,进入Settings → jadx Settings → disable "Decode strings" / "Inline methods" / "Use resource names"。命令行版同理:jadx -d ./jadx_output --no-replace-consts --no-inline-methods --no-resources app-release.apk
2.4 验证层:重签名不是终点,而是反编译闭环的起点
反编译的终极验证,是把修改后的代码重新打包、签名、安装并运行。工具包必须包含signapk.jar(Android 官方签名工具)和zipalign(对齐优化)。常见误区是直接用apksigner,但它不支持testkey.x509.pem+testkey.pk8这种调试密钥对,而signapk.jar可以:
# 1. 用 apktool 重新打包(注意:必须用 --use-aapt2) apktool b -o app-unaligned.apk ./decompiled_app # 2. zipalign 对齐(-p 表示 4 字节对齐,-f 强制覆盖) zipalign -p -f 4 app-unaligned.apk app-aligned.apk # 3. signapk 签名(使用 platform 签名,适配系统级权限) java -jar signapk.jar testkey.x509.pem testkey.pk8 app-aligned.apk app-signed.apktestkey.x509.pem和testkey.pk8是 AOSP 自带的调试密钥,位于build/target/product/security/;zipalign必须在signapk之前执行,否则apksigner verify会报ERROR: No APK Signature Scheme v2 signature;app-signed.apk安装后若闪退,90% 是AndroidManifest.xml中android:sharedUserId或android:process与签名不匹配,此时应回查apktool解包时是否误删了android:命名空间。
3. 避坑:反编译工具包的 4 个血泪经验,每一条都让我重装过三次系统
反编译不是“跑通就行”,而是“跑通后敢不敢信”。以下是我踩过的真坑,按发生频率排序,每条都附带adb logcat关键日志线索和修复命令。
3.1 现象:apktool d报错W: Could not decode attr value, using undecoded value instead: ns=android, name=layout_width, value=0x010100c9
原因:apktool版本低于 2.9.0,无法解析 Android 12+ 新增的attr值(如0x010100c9对应android:layout_width的新 ID)。旧版会把match_parent显示为0x010100c9,导致aapt2 compile时找不到该 attr。
解决:升级apktool到 2.9.3+,并确认aapt2版本 ≥ 8.0.0(aapt2 --version):
# 下载最新 apktool(Linux/macOS) curl -s https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/linux/apktool | sudo tee /usr/local/bin/apktool && sudo chmod +x /usr/local/bin/apktool # 更新 aapt2(从 Android SDK Build-Tools 34.0.0+ 提取) sdkmanager "build-tools;34.0.0"3.2 现象:jadx导出的 Java 文件中,onCreate()方法体为空,但smali中有完整逻辑
原因:APK 使用了R8全局优化(shrinkResources true+minifyEnabled true),将onCreate()内联到Application.onCreate()或Activity.attach()中,jadx无法追踪跨方法内联。
解决:放弃jadx的 Java 输出,直接分析smali中的onCreate方法,并用jadx的Smali Viewer插件(需手动安装)同步高亮:
# 在 jadx-gui 中,右键点击类名 → "Open in Smali Viewer",即可看到 smali 与 Java 的实时对照 # 重点检查 .method onCreate(Landroid/os/Bundle;)V 下的 invoke-direct 指令3.3 现象:重签名后安装报错INSTALL_FAILED_TEST_ONLY,adb install直接拒绝
原因:AndroidManifest.xml中android:testOnly="true"未被apktool自动清除。apktool d会保留该属性,而apktool b不会自动改写。
解决:在./decompiled_app/AndroidManifest.xml中手动删除android:testOnly="true",或用 sed 一键清理:
sed -i '' 's/android:testOnly="true"//g' ./decompiled_app/AndroidManifest.xml # macOS sed -i 's/android:testOnly="true"//g' ./decompiled_app/AndroidManifest.xml # Linux3.4 现象:dex2jar生成的 JAR 被procyon反编译后,所有方法名变成a(),b(),c()
原因:APK 启用了obfuscation(ProGuard/R8),但dex2jar默认不尝试去混淆。它把com.example.MainActivity.onClick()映射为a.a.a.a(),procyon只是忠实还原。
解决:不用dex2jar,改用jadx的--deobf模式(需提供 mapping.txt):
# 若你有原始 mapping.txt(发布时 ProGuard 生成的) jadx -d ./deobf_output --deobf --deobf-map-file mapping.txt app-release.apk # 若无 mapping.txt,则用 jadx 的内置去混淆(效果有限但可用) jadx -d ./deobf_output --deobf --deobf-use-sourcename app-release.apk4. 实战:用工具包定位一个真实崩溃——android.content.res.Resources$NotFoundException: File res/drawable/xxx.png from drawable resource ID #0x7f080001
这是 Android 开发最头疼的崩溃之一:资源存在,ID 正确,却报“找不到文件”。用常规 Logcat 只能看到堆栈,无法定位是drawable缺失、density限定符错配,还是resources.arsc损坏。工具包的四层拆解在此刻显出价值。
4.1 第一步:用 apktool 提取资源,确认文件物理存在
apktool d -r -o ./crash_app app-crash.apk ls -l ./crash_app/res/drawable-*/xxx.png # 检查所有 density 目录- 若
drawable-xxhdpi/xxx.png存在,但drawable-xhdpi/下没有,则resources.arsc会记录xxx.png仅存在于xxhdpi,当设备为xhdpi时必然崩溃; - 若所有目录都缺失,则是构建时
gradle未将该资源纳入src/main/res/,属于开发疏漏。
4.2 第二步:用 aapt2 dump 查看 resources.arsc 中的资源索引
# 从 apktool 解包目录中提取 resources.arsc aapt2 dump resources ./crash_app/resources.arsc | grep "xxx.png"输出类似:
resource 0x7f080001 com.example.app:drawable/xxx.png: t=0x03 d=0x00000001 (s=0x0008 r=0x00) config mdpi-v13: resource 0x7f080001 com.example.app:drawable/xxx.png: t=0x03 d=0x00000001 (s=0x0008 r=0x00) config hdpi-v13: resource 0x7f080001 com.example.app:drawable/xxx.png: t=0x03 d=0x00000001 (s=0x0008 r=0x00)- 关键看
config行:若只显示mdpi-v13和hdpi-v13,但设备是xxhdpi,则resources.arsc未注册该 density,崩溃必然发生; t=0x03表示 drawable 类型,d=0x00000001是资源数据偏移,若为0x00000000则表示资源未真正写入。
4.3 第三步:用 jadx 检查 Java 层调用是否越界
在jadx输出的MainActivity.java中搜索xxx.png:
// 常见错误写法 imageView.setImageResource(R.drawable.xxx); // ✅ 正确 imageView.setImageDrawable(getResources().getDrawable(R.drawable.xxx)); // ❌ Android 8.0+ 已废弃,且未加 density 参数getDrawable(int)在 Android 8.0+ 会忽略Configuration.densityDpi,强制返回mdpi资源,若mdpi下无xxx.png,则抛Resources$NotFoundException;- 正确写法应为
getDrawable(R.drawable.xxx, getTheme())或ContextCompat.getDrawable(this, R.drawable.xxx)。
4.4 第四步:用 adb shell 验证设备实际 density
adb shell wm density # 查看当前设备 density adb shell getprop ro.sf.lcd_density # 查看系统 density- 若
wm density返回480(xxhdpi),但resources.arsc中只注册了mdpi/hdpi,则崩溃根源在此; - 此时需在
build.gradle中添加android { defaultConfig { vectorDrawables.useSupportLibrary = true } },并用AppCompatImageView替代原生ImageView。
提示:这个崩溃的根因往往不在 Java 代码,而在
build.gradle的android { aaptOptions { cruncherEnabled = false } }——关闭 PNG 压缩后,aapt2无法正确生成resources.arsc的 density 映射表。工具包的价值,就是把apktool、aapt2、jadx、adb这四把刀,按顺序插进同一个伤口,直到看见血。
5. 进阶:用工具包做自动化审计——批量检测 100 个 APK 的隐私合规风险
当你要审计一批 APK(比如公司采购的 SDK、合作方提供的渠道包),手动打开jadx-gui逐个翻代码不现实。工具包的真正威力,在于封装成可脚本化的审计流水线。以下是我落地的 Python 脚本框架,它能在 12 分钟内完成 100 个 APK 的三项核心检测:READ_PHONE_STATE权限滥用、WebView未禁用 JS、SharedPreferences明文存 Token。
5.1 构建可复用的审计脚本骨架
核心思路:用subprocess调用工具包命令,用xml.etree.ElementTree解析AndroidManifest.xml,用re正则扫描smali文件。不依赖jadx的 Java 输出(太慢),专注smali和manifest这两个最稳定层。
# audit_apk.py import subprocess import xml.etree.ElementTree as ET import re import os def audit_apk(apk_path): app_name = os.path.basename(apk_path).replace(".apk", "") decompile_dir = f"./decompiled/{app_name}" # 1. 用 apktool 解包(只解 manifest 和 smali,跳过资源) subprocess.run(["apktool", "d", "-s", "-o", decompile_dir, apk_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) # 2. 解析 AndroidManifest.xml 检查权限 manifest_path = f"{decompile_dir}/AndroidManifest.xml" tree = ET.parse(manifest_path) root = tree.getroot() permissions = [e.get("{http://schemas.android.com/apk/res/android}name") for e in root.findall(".//uses-permission")] # 检测 READ_PHONE_STATE(Android 10+ 已废弃,但仍有 App 在用) if "android.permission.READ_PHONE_STATE" in permissions: print(f"[WARN] {app_name}: uses READ_PHONE_STATE") # 3. 扫描 smali 文件中的 WebView 配置 smali_dir = f"{decompile_dir}/smali" webview_js_enabled = False for root_dir, _, files in os.walk(smali_dir): for file in files: if file.endswith(".smali"): with open(os.path.join(root_dir, file), "r", encoding="utf-8") as f: content = f.read() # 检测 setJavaScriptEnabled(true) if re.search(r"const\/4 v\d+, 0x1\s+invoke\-virtual \{[^}]+\}, Landroid\/webkit\/WebSettings;->setJavaScriptEnabled\(Z\)V", content): webview_js_enabled = True break if webview_js_enabled: print(f"[CRITICAL] {app_name}: WebView JavaScript enabled without sandbox") # 4. 检测 SharedPreferences 明文存 Token token_patterns = [ r"putString\([^,]+, [^)]*['\"]token['\"].*['\"].*['\"]", r"putString\([^,]+, [^)]*['\"]access_token['\"].*['\"].*['\"]" ] for pattern in token_patterns: for root_dir, _, files in os.walk(smali_dir): for file in files: if file.endswith(".smali"): with open(os.path.join(root_dir, file), "r", encoding="utf-8") as f: content = f.read() if re.search(pattern, content): print(f"[CRITICAL] {app_name}: SharedPreferences stores token in plain text") return # 批量执行 apks = ["app1.apk", "app2.apk", ...] for apk in apks: audit_apk(apk)5.2 关键参数与避坑点
| 检测项 | 正则模式 | 为什么这样写 | 常见误报 |
|---|---|---|---|
READ_PHONE_STATE | "android.permission.READ_PHONE_STATE" | 权限名是固定字符串,无需正则 | 误报READ_EXTERNAL_STORAGE |
WebView JS | const/4 v\d+, 0x1\s+invoke-virtual \{[^}]+\}, Landroid/webkit/WebSettings;->setJavaScriptEnabled\(Z\)V | const/4 v0, 0x1是true的 smali 表示,invoke-virtual是调用指令,必须连写避免匹配到setJavaScriptEnabled(false) | 匹配到setDomStorageEnabled(true) |
SharedPreferences Token | putString\([^,]+, [^)]*['\"]token['\"].*['\"].*['\"] | [^,]+匹配第一个参数(context),[^)]*匹配 key 参数,.*['\"].*['\"]匹配 value 字符串,确保 key 和 value 都是字符串字面量 | 匹配到putString("key", "value")但 key 不是 token |
5.3 性能优化:为什么不用 jadx-gui,而坚持用 apktool + smali
jadx-gui加载 100MB APK 需 90 秒,apktool d -s只需 8 秒;smali文件是纯文本,grep或re扫描速度是jadxJava 输出的 12 倍;jadx的 Java 输出受混淆影响大(a(),b()方法名),而smali中Landroid/webkit/WebSettings;->setJavaScriptEnabled(Z)V指令永远不变;apktool解包后,AndroidManifest.xml是标准 XML,ElementTree解析稳定,jadx的manifest解析有时会丢失android:exported属性。
我每天用这个脚本扫内部 200+ 个渠道包,平均每个 APK 耗时 6.3 秒。它不会告诉你“这个 App 多危险”,但会明确指出:“com.xxx.sdk在WebViewClient.shouldOverrideUrlLoading()中调用了loadUrl('javascript:...'),且未校验 URL 协议”。这就是工具包该干的事——不代替你思考,但给你不可辩驳的证据链。
希望帮到你。
本文还有配套的精品资源,点击获取