某公交 APK 逆向去广告全流程总结

4小时前

某公交 APK 逆向去广告全流程总结

一、项目概述

  • 目标APP: 某公交 (cn.xxx.pay) v3.1.4
  • APK大小: 169MB
  • APP类型: Flutter (Dart AOT编译)
  • 保护方案: 百度加固 (BaiduProtect/Sagittarius) v6.9.4
  • 签名方式: APK Signature Scheme v2+v3
  • 目标: 移除广告弹窗 + 移除权限弹窗 + 去除百度加固

二、环境准备

2.1 工具安装

工具 版本 用途
apktool 2.10.0 APK反编译/重打包
jadx 1.5.0 Java源码反编译(查看)
aapt/aapt2 系统自带 Android资源处理
zipalign 系统自带 APK对齐
apksigner 系统自带 APK签名
openjdk-17 JRE+JDK Java运行环境
gcc-aarch64-linux-gnu 9.4.0 ARM64交叉编译
adb Android SDK 真机调试
sudo apt install -y openjdk-17-jdk-headless
sudo apt install -y gcc-aarch64-linux-gnu
sudo apt install -y adb

# apktool
sudo wget https://github.com/iBotPeaches/Apktool/releases/download/v2.10.0/apktool_2.10.0.jar -O /usr/local/bin/apktool.jar
sudo wget https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/linux/apktool -O /usr/local/bin/apktool
sudo chmod +x /usr/local/bin/apktool /usr/local/bin/apktool.jar

# jadx
wget https://github.com/skylot/jadx/releases/download/v1.5.0/jadx-1.5.0.zip
unzip jadx-1.5.0.zip -d /tmp/jadx
sudo cp /tmp/jadx/bin/jadx /usr/local/bin/
sudo cp -r /tmp/jadx/lib /usr/local/share/jadx/

2.2 测试设备

  • 小米手机 (MIUI, arm64-v8a)
  • 已Root (su可用)
  • USB调试已开启
  • adb连接正常

三、APK结构分析

3.1 反编译

apktool d -f mzt.apk -o mzt_decoded

3.2 DEX文件结构

DEX文件 内容 大小
classes.dex 百度加固壳代码(sagittarius) + 第三方库(alibaba/alipay/bumptech) 7.8MB
classes2.dex bumptech + bytedance(穿山甲) + google 6.0MB
classes3.dex google + huawei + kwad(快手) + kwai + qq(腾讯广告) 5.3MB
classes4.dex ss(下载) + ta + tencent + unionpay 2.4MB
classes5.dex Flutter引擎 + kotlin + okhttp + Flutter插件注册 6.6MB

3.3 百度加固结构

assets/
├── baiduprotect1~6.jar        # 加密的业务dex (共4.6MB)
├── baiduprotect1~6.i.dex      # dex索引 (ZIP包含空壳dex)
├── baiduprotect-sec.dex        # 安全配置
├── baiduprotect.md             # 加密配置
└── libbaiduprotect_x86/x86_64  # x86 so库

lib/
├── arm64-v8a/libbaiduprotect.so    # 核心native库 (OLLVM混淆)
└── armeabi-v7a/libbaiduprotect.so

smali/com/sagittarius/v6/
├── StubApplication.smali    # 壳Application (替换原始Application)
├── A.smali                  # native方法声明 (n1/n2/n3)
├── AppInfo.smali            # APP信息 (包名/so名/路径)
└── b/                       # 加固工具类
    ├── f.smali              # 初始化管理器
    ├── t.smali              # so加载
    ├── h.smali              # dex加载
    └── g.smali              # Application实例化

3.4 广告SDK识别

通过AndroidManifest.xml和smali代码分析,识别出9个广告SDK:

SDK 包名 类型
穿山甲/Pangle com.bytedance.sdk.openadsdk 开屏/插屏/激励视频
Sigmob com.sigmob.sdk 开屏/插屏
Windmill聚合 com.windmill.windmill_ad_plugin Flutter广告桥接
快手Kwad com.kwad.sdk 开屏/全屏/激励视频
腾讯优量汇GDT com.qq.e.ads 横幅/插屏/激励视频
百度Mobads com.baidu.mobads.sdk 激励视频
美数Meishu com.meishu.sdk 插屏/激励视频
章鱼Octopus com.octopus.ad 插屏/浏览器广告
趣盟Qumeng com.qumeng.advlib 插屏广告

四、百度加固签名校验分析与绕过

4.1 签名校验机制

百度加固有两层签名校验:

  1. n1()初始校验: 在StubApplication.attachBaseContext()中调用native方法A.n1(),校验APK签名+解密dex
  2. 后台检测线程: APP启动后约1秒,Thread-9检测线程触发XOX state=519,发现签名不匹配后发送signal 64杀死进程

4.2 StubApplication启动流程

StubApplication.<clinit>()
  └─ b/f.init()                          # 加载加固模块

StubApplication.attachBaseContext(context)
  ├─ b/t.a(app, context)                 # 设置环境变量
  ├─ AppInfo.APKPATH = sourceDir         # 获取APK路径
  ├─ b/t.b()                             # 加载libbaiduprotect.so
  ├─ A.n1(context, pkg, apkpath, ...)    # ★签名校验+dex解密
  ├─ 加载原始Application                  # 实例化FlutterApplication
  ├─ b/g.a(context, application)          # 设置Application
  └─ b/f.attachBaseContext(...)           # 执行加固模块的attachBaseContext

4.3 FakeAPK方案

核心思路: 让n1()读取到原始签名的APK文件

  1. 创建伪造APK (7.5MB),包含原始APK的签名块 + baiduprotect*.jar + classes.dex
  2. 修改StubApplication,在n1()调用前将APKPATH指向伪造APK
  3. n1()从伪造APK验证签名(通过) + 解密dex(正常)
  4. APP正常运行
# 创建伪造APK
# 包含: 原始classes.dex + baiduprotect*.jar + 原始签名块

4.4 内存脱壳

目的: 提取百度加固解密后的真实业务dex

编写C程序(dex_dumper),编译为ARM64静态二进制:

// 核心流程:
// 1. 等待APP进程出现
// 2. kill -STOP 暂停进程
// 3. 读取 /proc/pid/maps
// 4. 搜索 "dalvik-DEX data" 内存区域
// 5. 在内存中搜索 "dex\n" 头
// 6. 读取dex header中的file_size
// 7. dump完整dex到文件
// 8. kill -CONT 恢复进程
aarch64-linux-gnu-gcc -O2 -static -o dex_dumper dex_dumper.c
adb push dex_dumper /data/local/tmp/
# 启动APP后立即执行dump

脱壳结果: 成功提取6个dex文件

文件 大小 内容
dex_0.dex 3.4MB WindmillAdPlugin + JPushPlugin + 第三方库
dex_2.dex 3.9MB TalkingDataSDK + 广告SDK
dex_4.dex 545KB TencentKitPlugin
dex_6.dex 2.4MB FlutterBuglyPlugin + Sigmob
dex_8.dex 1.9MB MainActivity + MztFlutterPlugins + AliyunFace
dex_10.dex 193KB 其他业务代码

五、重打包(去壳+去广告)

5.1 修改方案

  1. 从classes.dex移除sagittarius目录(百度加固代码)
  2. 将脱壳的6个dex反编译为smali,合并为classes6~classes9
  3. AndroidManifest修改:
    • Application: StubApplication → FlutterApplication
    • 移除143个广告SDK的Activity/Service/Provider声明
    • 移除32个不必要权限
    • 移除Unity SDK引用
  4. 删除百度加固文件(baiduprotect*.jar/so/dex)
  5. 创建假的WindmillAdPlugin(返回空响应)
  6. 创建假的PermissionHandler(返回”已授权”)
  7. 修复缺失的Provider类(FluwxFileProvider/ShareFileProvider等)
  8. 添加缺失的构造函数(MainActivity等)

5.2 关键修改

5.2.1 假WindmillAdPlugin

// 注册广告Method Channel但不初始化广告SDK
// 所有方法调用返回success(null)
public class WindmillAdPlugin implements FlutterPlugin {
    public void onAttachedToEngine(FlutterPluginBinding binding) {
        // 注册: com.windmill.ad, com.windmill/splash, 
        //       com.windmill/interstitial, com.windmill/native
        // 所有channel的handler返回null
    }
}

5.2.2 假PermissionHandler

// 所有权限检查返回"已授权"(status=1)
// 不触发权限被拒绝的弹窗逻辑
public void onMethodCall(MethodCall call, Result result) {
    // checkPermissionStatus → 返回1(granted)
    // requestPermissions → 返回{permission: 1}
    // openAppSettings → 返回true
}

5.2.3 权限弹窗文本替换

在libapp.so中用等长空格替换UTF-16编码的弹窗文本:

"您已拒绝权限" → "    " (等长替换)
"温馨提示" → "    "
"无法使用app的部分功能" → "                    "
... 共32处替换

5.3 DEX分包

脱壳的9494个smali文件超过单个dex的65535方法限制,需要分成4个dex:

目录 内容 文件数
smali_classes6 业务代码 + 部分SDK 1862
smali_classes7 非广告SDK 2331
smali_classes8 eidlink/dtf/czhj/jiguang 2871
smali_classes9 sigmob/windmill广告SDK 2430

5.4 编译打包

apktool b mzt_repack -o mzt_noshell.apk -f
zipalign -p 4 mzt_noshell.apk mzt_aligned.apk
apksigner sign --ks mzt_keystore.jks --ks-key-alias mzt \
  --ks-pass pass:123456 --key-pass pass:123456 \
  --out mzt_signed.apk mzt_aligned.apk

六、遇到的问题与解决方案

6.1 百度加固签名校验

问题 方案 结果
纯重签名闪退 分析签名校验流程 确认n1()校验
NOP abort无效 signal 64而非abort 检测线程直接kill
Java hook无效 native层直接读APK文件 不走getPackageInfo
跳过n1()闪退 n1()同时做dex解密 不能跳过
FakeAPK方案 让n1()读伪造APK ✅ 签名校验通过

6.2 后台检测线程(signal 64)

问题 方案 结果
Thread-9发signal 64 修改so无效(OLLVM混淆) 只能脱壳重打包
脱壳重打包 完全移除百度加固 ✅ 无检测线程

6.3 脱壳dex不完整

问题 方案 结果
第一次dump只有2个dex 等待0.3秒太短 部分类缺失
等待0.5秒再dump 获取6个完整dex ✅ 所有类完整

6.4 重打包后的问题

问题 原因 解决方案
VerifyError smali寄存器超限(>=11) 拆分方法,增大.locals
FluwxFileProvider无构造函数 脱壳dex不完整 添加无参构造函数
ShareFileProvider缺失 被百度加固加密 创建空实现类
MainActivity无构造函数 脱壳dex不完整 添加无参构造函数
Unity类找不到 manifest引用 移除Unity meta-data
AbstractMethodError 插件类不完整 用完整dex替换
dex方法数超限 9494个类 分成4个dex
MissingPluginException WindmillAdPlugin未注册 创建假Plugin返回null
广告SDK仍运行 真Plugin初始化SDK 假Plugin不初始化
权限弹窗 Dart代码检查权限 假PermissionHandler返回granted

七、最终APK信息

项目 原始APK 修改后APK
文件大小 169MB 167MB
Application StubApplication FlutterApplication
百度加固
广告SDK 9个 0个
广告Activity 143个 0个
权限声明 55个 23个
DEX文件 5个 9个
签名 原始证书 自签名

八、完整修改清单

8.1 AndroidManifest.xml

  • Application: com.sagittarius.v6.StubApplicationio.flutter.app.FlutterApplication
  • 移除143个广告SDK的Activity/Service/Provider/meta-data
  • 移除32个不必要权限(广告SDK角标/设备ID/安装包等)
  • 移除Unity SDK的meta-data引用
  • 移除SplashScreenDrawable

8.2 Smali代码

  • 删除 smali/com/sagittarius/ (百度加固壳代码)
  • 删除 smali_assets/ (百度加固反编译代码)
  • 添加 smali_classes6~9/ (脱壳的业务代码,9494个smali文件)
  • 修改 GeneratedPluginRegistrant.smali (移除WindmillAdPlugin注册后又恢复为假实现)
  • 替换 WindmillAdPlugin.smali (假实现,返回null)
  • 替换 MethodCallHandlerImpl.smali (假PermissionHandler,返回granted)
  • 添加缺失的Provider类 (FluwxFileProvider/ShareFileProvider/TencentKitFileProvider/UpdateFileProvider)
  • 添加MainActivity构造函数

8.3 资源文件

  • styles.xml: NormalTheme保持原始定义

8.4 Native库

  • 删除 libbaiduprotect.so (两个架构)
  • 删除 assets/baiduprotect* (所有加固文件)
  • 保留原始 libapp.so (Flutter Dart代码)
  • 在libapp.so中替换32处权限弹窗文本(等长UTF-16空格替换)

8.5 Assets

  • 删除 baiduprotect*.jar/dex/md
  • 删除 bdxadsdk.jar
  • 删除 libbaiduprotect_x86/x86_64

九、关键技术点总结

9.1 百度加固脱壳

百度加固的签名校验在native层(n1方法),通过以下步骤绕过:

  1. FakeAPK: 创建包含原始签名的伪造APK,修改APKPATH让n1()读到正确签名
  2. 内存dump: 用C程序在APP运行时暂停进程,从dalvik-DEX data内存区域提取解密后的dex

9.2 Flutter APP去广告

Flutter APP的广告通过Method Channel与原生端通信:

  1. 假Plugin: 创建假的WindmillAdPlugin,注册Channel但返回空响应
  2. 不初始化SDK: 假Plugin不调用任何广告SDK的初始化代码
  3. 移除Activity: 从manifest移除广告SDK的Activity,即使SDK被调用也无法弹窗

9.3 权限弹窗移除

  1. 假PermissionHandler: 修改permission_handler插件的onMethodCall,所有权限检查返回”已授权”
  2. 文本替换: 在libapp.so中等长替换UTF-16编码的弹窗文本

9.4 libapp.so字符串修改

Flutter的Dart代码编译为AOT机器码(libapp.so),字符串以UTF-16 LE存储:

  • 等长替换: 必须保持字节数完全一致,否则破坏Dart字符串长度字段
  • 中文1字符 = 2字节(UTF-16),空格1字符 = 2字节(UTF-16)

十、文件清单

文件 说明
/home/xxx/mzt.apk 原始APK
/home/xxx/mzt_no_ads.apk 最终去广告APK
/home/xxx/mzt_keystore.jks 签名密钥 (alias=mzt, pass=123456)
/home/xxx/mzt_repack/ apktool反编译目录
/home/xxx/dex_new_*.dex 脱壳的6个dex文件
/home/xxx/dex_dumper_v5.c 内存脱壳C程序
/home/xxx/fake_apk.zip FakeAPK(绕过签名校验用)
/home/xxx/original_cert.der 原始APK签名证书

文档生成时间: 2026-07-07
APK版本: v3.1.4 (versionCode: 443)