============================================================
           马甲包(APK Clones) 逆向工程经验笔记
           生成时间: 2026-07-09
           适用对象: 同架构换皮APP、视频/社区类马甲包
============================================================

一、马甲包特征识别
--------------------------------------------------
1. 包名被随机化: 如 u0sy4.ziw19.ijky3y.rzguo.xxx
   - 但内部Java包结构仍保持原始: com/grass/mh/...
   - 资源ID、布局ID不变
   - 判断方法: 反编译后看smali目录结构

2. 签名信息被替换:
   - 原始开发者签名被替换成分发平台签名
   - 但代码逻辑完全相同

3. 常见加固方式:
   - 大多不加固或仅做简单混淆
   - 核心逻辑在smali层可直接修改


二、快速分析流程（5分钟定位）
--------------------------------------------------
1. 反编译: apktool d -o out xxx.apk
2. 定位MainActivity:
   grep -r "MainActivity" smali/ | grep "class public"
3. 定位底部导航栏:
   grep -n "llSwitch\|tab\|bottom" MainActivity.smali
4. 定位Fragment:
   grep -n "Fragment" MainActivity.smali | head -20
5. 定位付费/VIP逻辑:
   grep -rn "isVip\|isVIP\|canWatch\|getFreeWatches\|createGoldDialog\|createVipDialog" smali/


三、Tab隐藏最佳实践（不闪退）
--------------------------------------------------
【错误做法】
- 减少tab数组大小 → setCurrentItem硬编码越界崩溃
- 直接访问private字段 → NoSuchFieldError/ClassCastException
- 修改布局XML删减tab → 数组越界

【正确做法】
1. 底部导航栏隐藏:
   - 在initView()中通过binding获取目标View
   - 调用setVisibility(8) [GONE]
   - 注意: 检查后续代码是否又把visibility改回来！

2. Fragment内子Tab隐藏:
   - 保持tabTexts和fragments数组大小不变
   - 通过public API: tabLayout.getChildAt(0)获取mTabsContainer
   - mTabsContainer.getChildAt(index)获取具体tab
   - 设置tab.setVisibility(8)
   - 设置默认选中: viewPager.setCurrentItem(1)

3. 关键原则:
   - 只增不减数组大小
   - 只改可见性，不改结构
   - 优先用public API，不用反射/直接访问private


四、VIP/付费解锁通用模板
--------------------------------------------------
【强制VIP身份】
# UserInfo.smali
.method public isVIP()Z
    const/4 v0, 0x1
    return v0
.end method

.method public getVipType()I
    const/4 v0, 0x1
    return v0
.end method

.method public getCinemaVipType()I
    const/4 v0, 0x1
    return v0
.end method

【无限观看】
# UserInfo.smali
.method public getFreeWatches()I
    const/4 v0, -0x1
    return v0
.end method

【视频可播放】
# VideoBean.smali
.method public isCanWatch()Z
    const/4 v0, 0x1
    return v0
.end method

【CDN VIP】
# CdnBean.smali
.method public isVip()Z
    const/4 v0, 0x1
    return v0
.end method


五、广告去除通用方案
--------------------------------------------------
1. 广告数据回调屏蔽:
   - 找到广告请求回调(如SplashActivity$X)
   - onLvSuccess → 直接return-void

2. 广告展示入口跳过:
   - 找到显示广告的方法(如adShowMine)
   - 方法开头直接调用跳转方法(startMainUI)
   - 然后return-void

3. 对话框全局屏蔽（核选项）:
   # FastDialogUtils.smali
   .method public createGoldDialog(Landroid/content/Context;)V
       return-void
   .end method
   
   .method public createCommonVipDialog(...)V
       return-void
   .end method

4. 福利/活动弹窗屏蔽:
   # MainActivity.smali
   .method public showWelfareDialog()V
       return-void
   .end method
   
   .method public showActivityDialog()V
       return-void
   .end method
   
   .method public onAdRetryEvent(...)V
       return-void
   .end method


六、金币视频解锁方案
--------------------------------------------------
1. 找到金币对话框触发点:
   grep -rn "createGoldDialog" smali/

2. 方案A: 全局屏蔽对话框（最简单）
   - 直接修改FastDialogUtils.createGoldDialog → return-void

3. 方案B: 修改业务逻辑（更彻底）
   - 找到触发金币对话框的HTTP回调
   - 当code=1019(金币不足)时，走购买成功分支
   - 即复制code=200的处理逻辑替换code=1019的处理

4. 方案C: 修改播放检查
   - VideoBean.isCanWatch() → return true
   - VideoPlayActivity.playVideo() 中跳过金币检查


七、常见闪退原因及规避
--------------------------------------------------
1. Native SO注入(AutoSignPatch):
   - 修改libglide-webp.so的ELF头 → Native Crash
   - 规避: 不做Native层修改，纯Java/Smali修改

2. 数组越界:
   - 减少tab/fragment数组后，setCurrentItem未同步
   - 规避: 数组大小不变，只改可见性

3. private字段访问:
   - Smali直接iget-object private字段 → 运行时异常
   - 规避: 用public API(getChildAt/findViewById)

4. apktool资源损坏:
   - 修改res/values/public.xml导致ID错位
   - 规避: 不动资源文件，只改smali


八、签名方案选择
--------------------------------------------------
- v1: JAR签名（兼容性好）
- v2: APK Signature Scheme v2（Android 7.0+）
- v3: APK Signature Scheme v3（Android 9.0+，支持密钥轮换）

推荐: v1+v2+v3全签
命令: apksigner sign --ks keystore.jks --out signed.apk aligned.apk


九、修改检查清单（每次修改后核对）
--------------------------------------------------
□ 没有修改Native SO文件
□ 没有减少任何数组大小
□ 没有直接访问private字段
□ 所有setCurrentItem值在有效范围内
□ 签名通过(v1+v2+v3)
□ zipalign对齐完成
□ 文件大小合理(不应异常增大)


十、快速复盘模板
--------------------------------------------------
APP: xxx
包名: xxx
版本: xxx
修改项:
  1. Tab隐藏: xxx
  2. VIP解锁: xxx
  3. 广告去除: xxx
  4. 金币视频: xxx
测试结果:
  - 是否闪退: xxx
  - 功能正常: xxx
  - 问题遗留: xxx
============================================================
