简介:这份资源是面向Android开发者与逆向爱好者的APK修改工具合集,聚焦APK解包、反编译、资源替换与重签名等核心环节,适合希望个性化定制应用或研究应用内部结构的初中级用户。压缩包约28.58MB,上游未提供具体文件清单,内容围绕Apktool、dex2jar与JD-GUI、Jeb、Apk Editor、SignApk等常用工具展开,覆盖资源编辑、代码反编译重打包、签名验证与权限修改等典型场景。资源同时提示版权、安全、兼容性与隐私四类风险,并给出从了解APK组成到实践项目的学习路径,便于读者按图索骥建立完整认知。已有1318人学习关注,可作为入门APK修改与逆向分析的参考起点。
1. 拆开一个 APK 到底在改什么:从“装不上”到“改得动”的那一步
一个 APK 装到手机上闪退,或者渠道包里塞了个写死的推广 ID 想换掉,又或者某个老应用的接口域名已经下线、需要指向自建服务——这些场景里,改包几乎是绕不开的动作。apk修改工具要解决的正是这件事:把编译好的安装包拆开、定位到要动的那一层、改完再合回去并重新签名,让系统愿意装、让逻辑真的生效。它适合两类人:一类是想快速验证某个改动是否可行的应用侧开发者,另一类是需要在既有包体上做定制、做兼容适配的工程人员。不适合完全没接触过 Android 构建流程的人直接上手,因为改包失败的原因八成不在工具本身,而在签名、对齐、资源索引这些底层环节。下面按“拆—改—合—签—验”这条主线,把每一步的真实参数和翻车点摊开讲。
2. 拆包与回编译:apktool 的目录结构与资源索引重建
2.1 为什么不是直接解压 zip
很多人第一反应是把.apk当 zip 解压,拿到classes.dex和res/就以为完事。这条路只在“替换一张图片、改一个纯文本配置”时勉强能用,一旦涉及AndroidManifest.xml或res/values/strings.xml,立刻卡住——因为安装包里的这两类文件是二进制格式(AXML 与编译后的资源表),直接改字节几乎必错。apktool 的价值在于它把二进制清单反编译成可读 XML,把resources.arsc还原成res/values/下的明文,改完再重新编译回二进制并重建资源索引。这是“改得动”的前提。
常见做法是先确认 JDK 版本,apktool 对高版本 JDK 的兼容性时好时坏,我一般固定用 JDK 17 跑,避免反编译阶段报Unsupported class file major version。
# 反编译:-f 覆盖已有输出目录,-o 指定输出路径 apktool d -f target.apk -o target_src # 只反编译资源、跳过 dex(只想改清单或资源时更快) apktool d -f -s target.apk -o target_res_only # 回编译:输出到指定 apk apktool b target_src -o target_patched.apk-s表示跳过classes.dex的反编译,产物里 dex 保持原样,适合只动资源或清单的场景,能省掉大量时间并规避 smali 反编译失败。-f是覆盖开关,不加的话第二次跑会因目录已存在而中断。回编译阶段如果报resource not found,九成是res/values/public.xml里的资源 ID 与新增资源冲突,需要手动核对新增条目的type和name是否与已有项重名。
2.2 目录里哪些能动、哪些别碰
反编译产物里,smali/是 dalvik 字节码的反汇编,smali_classes2/等多 dex 应用会有多个目录;res/是资源;AndroidManifest.xml是明文清单;apktool.yml记录编译参数,回编译时读取。能安全改的是清单里的权限、application属性、activity的exported,以及res/values/下的字符串和布尔值。不建议手改resources.arsc对应的二进制,也不建议在没有 smali 基础时动smali/里的方法体——寄存器编号和.locals声明对不上,回编译能过但运行必崩。
一个高频需求是改minSdkVersion让老包装到新系统上,或者改targetSdkVersion绕过某些行为变更。改清单里的uses-sdk后回编译,如果系统仍拒绝安装,要看是不是apktool.yml里缓存的sdkInfo没同步,必要时删掉该字段让它重新推断。
提示:反编译前先备份原始 apk,回编译产物不要直接覆盖原文件,签名阶段还要用到原始包做对比。
3. 改 dex 与 smali:定位方法、改寄存器、绕开校验的实操边界
3.1 先定位再动手
改逻辑的第一步不是打开 smali 就改,而是先确定目标方法在哪个 dex、哪个类。用jadx或apktool反编译后全局搜字符串常量、日志 TAG、接口路径,比盲搜类名高效得多。定位到方法后,看它的.method声明、.locals寄存器数量和返回类型,这三项决定了你能怎么改。
# 用 apktool 只反编译 dex,得到 smali 目录 apktool d -f -r target.apk -o target_dex_only # 在 smali 里按字符串定位(示例:找写死的接口域名) grep -rn "api.example.internal" target_dex_only/smali*/-r表示跳过资源反编译,只出 smali,速度快。grep命中后看所在文件的包路径,再回到方法体里找调用点。常见改法是替换一个const-string常量,或者把一个返回布尔的方法改成恒返回true。
# 原方法:校验失败返回 false .method public static check()Z .locals 1 const/4 v0, 0x0 return v0 .endmethod # 改后:恒返回 true,注意 .locals 不变,寄存器编号沿用 .method public static check()Z .locals 1 const/4 v0, 0x1 return v0 .endmethod这里的关键是.locals声明的方法内寄存器总数不能随意改,v0是局部寄存器,const/4只能装 4 位立即数(-8 到 7),改成0x1合法。如果目标方法返回的是对象或需要更多寄存器,就得用const/16或const-string,并确认.locals够用,否则回编译报register index out of range。
3.2 校验与完整性检查的现实边界
不少应用在启动时做签名校验或 dex 完整性校验,改完 smali 直接装会闪退。常见做法是定位校验方法后让它恒返回通过,但这类改动属于对抗性修改,边界很明确:只应在自己拥有合法授权的包体上做兼容性验证,不要用于绕过他人产品的授权机制。工程上更稳妥的路径是改资源或配置层能解决的问题就不动 dex,因为 dex 改动面越大,回归风险越高。
如果确实要改,改完先用apktool b回编译,再用zipalign对齐,最后签名,三步缺一不可。跳过对齐在部分系统上会报INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,这个报错信息很模糊,容易误判成签名问题。
4. 签名与对齐:v1/v2/v3 方案选择与安装失败的排查顺序
4.1 三种签名方案怎么选
Android 7.0 起引入 v2 签名,12 起对 v3 有要求,老设备只认 v1。改包后重新签名,如果只签 v1,新系统可能拒绝;只签 v2,极老设备装不上。稳妥做法是 v1+v2 同时签,用apksigner而不是老旧的jarsigner,后者不产生 v2 签名块。
# 1. 对齐(必须在签名之前) zipalign -f -p 4 target_patched.apk target_aligned.apk # 2. 生成调试用密钥(仅本地验证,不要用于发布) keytool -genkeypair -v -keystore debug.keystore -alias dbg \ -keyalg RSA -keysize 2048 -validity 10000 -storepass android -keypass android # 3. 签名,同时启用 v1 与 v2 apksigner sign --ks debug.keystore --ks-pass pass:android \ --v1-signing-enabled true --v2-signing-enabled true \ --out target_signed.apk target_aligned.apk # 4. 校验签名结果 apksigner verify --verbose target_signed.apkzipalign的-p参数按 4 字节页对齐,-f覆盖输出。apksigner verify会打印 v1/v2/v3 各自的校验结果,看到Verified using v2 scheme: true才算过。注意顺序不能反:先签名后对齐会破坏签名块,verify直接报DOES NOT VERIFY。
4.2 安装失败的排查顺序
装不上时按这个顺序查,比乱试快得多:
| 现象 | 优先怀疑 | 处理 |
|---|---|---|
INSTALL_FAILED_INVALID_APK | 未对齐或签名块损坏 | 重跑 zipalign 再签 |
INSTALL_FAILED_UPDATE_INCOMPATIBLE | 与已装版本签名不一致 | 卸载旧包或统一签名 |
INSTALL_PARSE_FAILED_NO_CERTIFICATES | 完全没签名 | 补 apksigner |
| 装上了但启动闪退 | dex 改动破坏寄存器或校验 | 回退 smali 改动逐项验证 |
签名不一致这条尤其常见:手机上装着官方包,你改完用自己的密钥签,系统认为不是同一来源,直接拒装。要么先卸载,要么用原包签名密钥——后者通常拿不到,所以本地验证阶段卸载重装是常态。
注意:
keytool生成的调试密钥只用于本地验证,任何对外分发的包都不应使用固定口令的调试密钥。
5. 避坑与常见问题:改包路上最容易翻车的五件事
现象一:回编译报brut.androlib.AndrolibException: could not exec aapt。原因多半是 apktool 版本与 aapt/aapt2 不匹配,或者系统里装了多个构建工具版本互相干扰。解决是固定 apktool 版本,并在回编译时显式指定--use-aapt2,让资源编译走 aapt2 路径,减少旧 aapt 的兼容问题。
现象二:改完清单里的package名,装上了但应用内跳转全挂。原因是包名被硬编码在 smali 的BuildConfig或第三方 SDK 初始化里,只改清单不改代码,运行时拿到的包名与预期不符。解决是全局搜原包名字符串,确认没有残留引用,或者干脆不改包名、只改版本号和清单属性。
现象三:资源改完回编译成功,运行时报Resources$NotFoundException。原因是新增资源没有在res/values/public.xml里登记,或者登记的类型与引用处不一致。解决是新增资源统一走public.xml声明,类型(string/drawable/bool)与引用处严格对齐,改完用aapt2 dump resources核对资源表。
现象四:签名校验通过,安装也成功,但一联网就提示“应用被篡改”。这是应用内的完整性校验在起作用,常见于有风控逻辑的包。解决思路不是硬绕,而是评估这个改动是否必要——如果只是本地调试,可以在测试环境关掉校验分支;如果是对自有包做定制,应在构建阶段就改源码而不是改成品包。
现象五:多 dex 应用只改了smali/下的类,运行报ClassNotFoundException。原因是目标类实际在smali_classes2/或更高编号目录里,改错了位置。解决是先确认类所在 dex,用grep -rln "类名" target_dex_only/smali*定位,改对应目录,回编译时 apktool 会按原多 dex 结构打包。
6. 批量改包与自动化验证:把重复劳动压成一条流水线
单个包改完能装不算完,真正耗时间的是同一套改动要套到几十个渠道包上,或者每次改完都要手动装一遍验证。我的做法是把“反编译—替换—回编译—对齐—签名—校验”串成一个脚本,参数化输入输出,跑完直接出可安装产物。
#!/usr/bin/env bash set -euo pipefail IN_APK="$1" OUT_APK="$2" WORK="build_$(basename "$IN_APK" .apk)" apktool d -f "$IN_APK" -o "$WORK" # 在这里插入你的改动,例如替换配置或 smali 常量 # sed -i 's/old_value/new_value/g' "$WORK/res/values/strings.xml" apktool b "$WORK" -o "${WORK}/unsigned.apk" zipalign -f -p 4 "${WORK}/unsigned.apk" "${WORK}/aligned.apk" apksigner sign --ks debug.keystore --ks-pass pass:android \ --v1-signing-enabled true --v2-signing-enabled true \ --out "$OUT_APK" "${WORK}/aligned.apk" apksigner verify --verbose "$OUT_APK"set -euo pipefail让任何一步失败立即中断,避免带着半成品继续往下签。WORK目录按输入包名生成,批量跑时互不干扰。改动段留成注释位,实际使用时按需求替换成sed或cp覆盖。跑完看apksigner verify的输出,v1/v2 都为 true 才算这条流水线可信。
验证环节我一般再加一步:用aapt2 dump badging读产物里的包名、版本号、minSdkVersion,和预期值逐项比对,比装到手机上肉眼看快得多。
aapt2 dump badging "$OUT_APK" | grep -E "package:|sdkVersion|targetSdkVersion"这一步能抓出“改了清单但 apktool.yml 缓存没更新”这类隐蔽问题——产物里的sdkVersion和你改的值对不上,说明回编译读的是旧缓存。从那以后我每次批量出包前都强制跑一遍 badging 比对,宁可多花十秒,也不愿把一批装不上的包发出去。希望这套流程帮到你,少走几次“签了名却装不上”的弯路。
本文还有配套的精品资源,点击获取