马甲包APK逆向修改
通用方法论
从识别到修改,从原生到跨平台 -- 一份覆盖主流框架的完整逆向工程实践指南
什么是马甲包
马甲包(Vest APP)是指基于同一套源码,通过修改包名、更换图标、调整皮肤主题、混淆代码等方式重新打包发布到应用市场的APP变体。其本质是对同一产品的反复套壳上架,以绕过平台审核和竞品检测。
常见应用场景
影视聚合、短视频解析、直播聚合等。此类马甲包数量最多,通常内置大量广告SDK和VIP付费解锁逻辑。
聊天、交友、社区类应用。常通过换皮实现多账号矩阵运营,核心通信逻辑完全一致。
清理、加速、壁纸、日历等。功能简单但广告密集,马甲包迭代速度极快。
共性特征
无论外表如何变化,马甲包在技术层面具有高度可识别的共性:
- 随机包名:每次上架使用不同的包名,如
com.xyz.video2024、com.app.stream.hd - 相同代码框架:核心类路径一致,典型如
com.androidx.lv.base.*,可跨包名识别 - 相同的广告SDK:穿山甲、优量汇、百度联盟等SDK的包名和调用方式完全相同
- 相同的VIP判断逻辑:无论UI如何变化,后端的VIP验证、免费次数限制等业务逻辑完全一致
逆向修改标准流程(SOP)
以下是经过大量实践验证的7步标准操作流程。每一步都有明确的输入、操作和验证标准,确保修改过程可重复、可追溯。
flowchart TD
S1["Step 1: 快速分析
apktool d + grep关键词
~5分钟"]
S2["Step 2: Tab隐藏
setVisibility GONE
不改数组大小"]
S3["Step 3: VIP解锁
6个核心方法
return true/1/-1"]
S4["Step 4: 广告去除
开屏+弹窗+公告
return-void"]
S5["Step 5: 金币视频
金币对话框屏蔽
return-void"]
S6["Step 6: 打包签名
apktool b + zipalign
+ apksigner"]
S7["Step 7: 验证安装
v1+v2+v3签名验证
真机测试"]
S1 --> S2 --> S3 --> S4 --> S5 --> S6 --> S7
style S1 fill:#0f3460,stroke:#e94560,stroke-width:2px,color:#eee
style S2 fill:#0f3460,stroke:#e94560,stroke-width:2px,color:#eee
style S3 fill:#0f3460,stroke:#e94560,stroke-width:2px,color:#eee
style S4 fill:#0f3460,stroke:#e94560,stroke-width:2px,color:#eee
style S5 fill:#0f3460,stroke:#e94560,stroke-width:2px,color:#eee
style S6 fill:#0f3460,stroke:#e94560,stroke-width:2px,color:#eee
style S7 fill:#0f3460,stroke:#e94560,stroke-width:2px,color:#eee
快速分析 ~5分钟
使用 apktool d 反编译APK,然后通过 grep 搜索关键类名和方法名,确定框架类型和核心修改点。
# 反编译 apktool d target.apk -o output/ # 快速识别框架 ls output/lib/arm64-v8a/ # 看SO库 ls output/smali*/ # 看DEX结构 find output/ -name "*.smali" | wc -l # smali文件数量 # 搜索关键类 grep -rl "UserInfo" output/smali*/ grep -rl "SplashActivity" output/smali*/
Tab隐藏
隐藏底部导航栏中不需要的Tab页(如"我的"页中的充值入口)。关键原则:只改可见性,不改数组大小。
// 定位到 Tab 隐藏代码 // 修改前: invoke-virtual {v0}, Landroid/view/View;->setVisibility(I)V const/16 v1, 0x0 # VISIBLE // 修改后: invoke-virtual {v0}, Landroid/view/View;->setVisibility(I)V const/16 v1, 0x8 # GONE
Fragment[] 数组从5个改成4个,后续所有 getItemAt(index) 调用都会产生索引越界崩溃。
VIP解锁
找到VIP相关的6个核心判断方法,将其返回值修改为"已VIP"状态。这是整个逆向修改的核心步骤。
# 需要修改的6个核心方法: isVIP() → return true isVip() → return true vipType() → return 1 getFreeWatches() → return -1 # 无限观看 isCanWatch() → return true getExpiredVip() → return "2099-12-31" # smali 修改示例: # 修改前: .method public isVIP()Z iget-boolean v0, p0, Lcom/xxx/UserInfo;->mIsVip:Z return v0 .end method # 修改后: .method public isVIP()Z const/4 v0, 0x1 return v0 .end method
广告去除
移除开屏广告、弹窗广告和福利公告弹窗。将广告展示方法的函数体清空为 return-void。
# 开屏广告 - SplashActivity .method public adShowMine()V .registers 2 return-void .end method # 福利弹窗 .method public showWelfareDialog()V .registers 2 return-void .end method # 活动弹窗 .method public showActivityDialog()V .registers 2 return-void .end method
金币视频屏蔽
屏蔽"看视频领金币"等激励广告对话框,避免用户误触。
# 金币对话框 .method public createGoldDialog()V .registers 2 return-void .end method .method public showGoldDialog()V .registers 2 return-void .end method
打包签名
将修改后的smali重新打包为APK,并完成对齐和签名。
# Step 6a: 重新打包 apktool b output/ -o modified.apk # Step 6b: ZIP对齐 zipalign -v -p 4 modified.apk aligned.apk # Step 6c: 签名(v1+v2+v3) apksigner sign \ --ks keystore.jks \ --ks-key-alias alias \ --ks-pass pass:password \ --key-pass pass:password \ --v1-signing-enabled true \ --v2-signing-enabled true \ --v3-signing-enabled true \ aligned.apk
验证安装
签名完成后,验证APK签名信息并在真机上安装测试。
# 验证签名 apksigner verify --print-certs -v final.apk # 真机安装测试 adb install final.apk # 确认v1+v2+v3签名方案均存在 apksigner verify --verbose final.apk
举一反三 -- 适配不同马甲包
马甲包虽然外表千变万化,但底层框架通常只有几种。通过识别框架类型,可以快速确定对应的修改策略。以下决策树帮助你在5分钟内完成框架识别。
flowchart TD
START(["拿到一个APK"]) --> Q1{"lib/下有哪些SO文件?"}
Q1 -->|"libflutter.so + libapp.so"| FLUTTER["Flutter/Dart框架"]
Q1 -->|"libpython3.xx.so"| PYTHON["Python/Kivy框架"]
Q1 -->|"libojdcosca.so
assets/apps/__UNI__/"| UNIAPP["uni-app/DCloud框架"]
Q1 -->|"无特殊SO
3个DEX
smali 18000+"| NATIVE["原生Android框架"]
FLUTTER --> F1["reflutter重打包
+ frida hook"]
PYTHON --> P1["反编译pyc为py
+ buildozer重打包"]
UNIAPP --> U1["JS层逆向
需要解密JS资源"]
NATIVE --> N1["标准SOP直接适用
成功率99%"]
style START fill:#e94560,stroke:#e94560,color:#fff
style FLUTTER fill:#fdcb6e,stroke:#fdcb6e,color:#1a1a2e
style PYTHON fill:#6c5ce7,stroke:#6c5ce7,color:#fff
style UNIAPP fill:#e94560,stroke:#e94560,color:#fff
style NATIVE fill:#00b894,stroke:#00b894,color:#1a1a2e
style F1 fill:#16213e,stroke:#fdcb6e,color:#eee
style P1 fill:#16213e,stroke:#6c5ce7,color:#eee
style U1 fill:#16213e,stroke:#e94560,color:#eee
style N1 fill:#16213e,stroke:#00b894,color:#eee
- 识别特征
-
- 3个DEX文件(classes.dex, classes2.dex, classes3.dex)
- smali文件数量 18,000+
- 存在
UserInfo.smali、VideoBean.smali、CdnBean.smali等核心类 - 包名路径中包含
com.androidx.lv.base
- 修改方案
- 完全适用上述7步SOP,所有修改均在smali层完成,无需处理JS或Dart代码。
- 关键类清单
-
UserInfo|VideoBean|CdnBean|SplashActivity|MainActivity|FastDialogUtils - 实战经验
- 这类马甲包最容易修改,代码结构清晰,所有业务逻辑都在smali中可直接定位。成功率接近100%。
- 识别特征
-
- 仅有1个超大DEX文件
- smali文件极少(通常仅2-5个)
- assets目录下存在
apps/__UNI__XXXXXX/目录结构 - 存在
libojdcosca.so加固文件
- 修改方案
- 需要进入JS层进行逆向。核心业务逻辑在加密的JS文件中,smali层几乎没有可修改内容。
- 核心难点
- JS资源被
libojdcosca.so加密,需要先脱壳解密JS,再定位业务逻辑进行修改。 - 实战经验
- 不建议在smali层面修改此类框架。如果可能,寻找同一产品的原生Android版本进行修改。
- 识别特征
-
- lib目录下存在
libflutter.so - 存在
libapp.so(Dart编译产物,AOT Snapshot) - assets目录下存在
flutter_assets/
- lib目录下存在
- 修改方案
-
- 使用
reflutter提取Dart Snapshot - 分析并修改Snapshot中的关键逻辑
- 使用frida进行动态Hook辅助
- 重新打包
libapp.so
- 使用
- 核心难点
- Dart代码编译为Native机器码,无法直接反编译为可读源码。需要依赖
reflutter进行Snapshot级别的修改。 - 实战经验
- Flutter马甲包的修改难度明显高于原生。reflutter是目前最成熟的工具,但版本兼容性需要注意。
- 识别特征
-
- lib目录下存在
libpython3.xx.so(如libpython3.8.so) - 存在
libpybundle.so - assets或私有目录中包含
private.tar,其中有main.pyc
- lib目录下存在
- 修改方案
-
- 解压
private.tar获取main.pyc - 使用
uncompyle6/decompyle3反编译pyc为py源码 - 修改Python源码中的VIP判断、广告逻辑等
- 重新编译pyc并使用
buildozer打包
- 解压
- 核心难点
- C扩展依赖(如numpy、opencv等)难以在重打包时补齐。如果
private.tar缺少_applibs目录,说明打包不完整,几乎无法修复。 - 实战经验
- 除非有完整的Python环境和所有依赖,否则不建议尝试。成功率极低,建议放弃或寻找其他版本。
修改难度对比
核心修改模式速查表
以下表格总结了马甲包逆向修改中最常见的10个修改目标,包括搜索关键词、修改方式和适用框架。建议打印此表作为实操时的快速参考。
| 修改目标 | 搜索关键词 | 修改方式 | 适用框架 |
|---|---|---|---|
| VIP身份 | isVIP, isVip, vipType |
return true / 1 |
原生Android |
| 观看次数 | getFreeWatches, freeWatch |
return -1(无限) |
原生Android |
| 视频权限 | isCanWatch, canPlay |
return true |
原生Android |
| CDN线路 | isVip(CdnBean内) |
return true |
原生Android |
| 开屏广告 | SplashActivity, adShowMine |
goto失败分支 或 return | 原生Android |
| 弹窗广告 | showWelfareDialog, showActivityDialog |
return-void | 原生Android |
| 金币弹窗 | createGoldDialog, showGoldDialog |
return-void | 原生Android |
| Tab隐藏 | llSwitch, tabLayout.getChildAt |
setVisibility(GONE) |
原生Android |
| VIP到期时间 | getExpiredVip, vipExpireTime |
return "2099-12-31" |
原生Android |
| 广告点击 | AdClickService, onAdClick |
删除 或 return-void | 原生Android |
避坑指南
以下8条规则来自大量实战踩坑经验总结。违反任何一条都可能导致APK无法运行或功能异常。
隐藏Tab时只改 setVisibility(GONE),绝对不能删除Fragment数组元素,否则 getItemAt(index) 索引越界直接崩溃。
该方法同时初始化网络模块和Context。一旦修改,整个APP的网络请求全部失效,所有功能瘫痪。
API域名是APP运行的基础。修改域名后APP无法连接后端服务器,所有数据加载失败。
使用 goto 跳转到其他分支时,必须确保目标分支使用的寄存器已经正确初始化,否则空指针闪退。
清空方法体改为 return-void 时,保留原有的 @Bindable 等注解。删除注解会导致DataBinding框架崩溃。
权限申请、CDN线路选择、初始化逻辑是启动链路的核心,任何改动都可能导致APP卡在启动页或闪退。
确认文件大小合理(与原包差异不超过30%)、DEX数量不变、SO文件完整未删除。任何遗漏都会导致功能异常。
仅使用v2签名在某些低版本Android设备上无法安装。三重签名方案确保最大兼容性。
工具清单
以下是马甲包逆向修改过程中使用的全部工具及其用途。按功能分类列出,建议搭建完整的工具链环境。