夜雨聆风学习资料网

ARTICLE · 1221995

我给一个安卓APP做了台外科手术——亲手切掉所有广告

我给一个安卓APP做了台外科手术——亲手切掉所有广告

我给一个安卓APP做了台"外科手术"——亲手切掉所有广告

你有没有遇到过一款APP:功能很香,但广告多到想砸手机?

我遇到了。一款摄像头APP,每次打开先塞一个15秒开屏广告,刷个相册弹个横幅,点进去再看个插屏。功能倒挺好,但广告密度堪比春运车站。

我就想:能不能把广告切掉,只留功能?

于是我做了一次"外科手术"——反编译、改smali、重签名、在鸿蒙4.2上实测。

全程没写一行Java。

〇、先给Agent装两只"眼睛"

在做手术之前,我给自己的AI Agent装了两只"眼睛"——两个开源逆向工程工具,让Agent能像人类逆向工程师一样看代码、理解结构、找到正确的工具链。

1. Reverse-Skill —— 逆向技能路由包

GitHub: zhaoxuya520/reverse-skill · ⭐ 41K+

这不是一个工具,而是一个路由系统。44条路由规则(R0–R44),175个回归测试用例,45个技能模块。当Agent遇到一个APK、一个ELF、一段JS加密、一个CTF题目,它不猜——它查路由表,找到正确的playbook。

# 安装curl -sL -o reverse-skill.tar.gz \  https://codeload.github.com/zhaoxuya520/reverse-skill/tar.gz/refs/heads/maintar xzf reverse-skill.tar.gz && mv reverse-skill-main reverse-skill# 生成工具索引(检测本机可用的jadx/apktool/Frida/IDA等)cd reverse-skill && bash skills/scripts/refresh-tool-index.sh

路由测试:

$ bash skills/scripts/master-route.sh --hint "APK reverse engineering smali patch"PRIMARY -> skills/apk-reverse/SKILL.mdLabel: APK reverse | confidence: high

Agent说"我要反编译这个APK",路由引擎立刻指向apk-reverse模块,而不是让Agent在jadx和apktool之间瞎猜。

2. REA —— Reverse Engineer Anything

GitHub: morluto/rea · ⭐ 278K+ · npm: rea-agents

REA是一个MCP服务器,让Agent直接调用逆向分析能力。它能:

  • • 检查APK/ELF/PE/Mach-O/SQLite/WASM/PCAP等任何格式
  • • 返回结构化Evidence(带sha256摘要、置信度、限制条件)
  • • 静态分析JavaScript/Electron应用(无需运行)
  • • 分析.NET程序集(纯静态,无依赖)
  • • 对接Hopper/Ghidra/IDA做深度native分析
# 安装(npm local)npm install rea-agents# 注册Agent skillnpx rea-agents setup --yes# 测试:检查APK结构npx rea-agents inspect-artifact app.apk --json

测试输出(节选):

{  "format": "apk",  "node_count": 3669,  "occurrence_count": 3827,  "edge_count": 3826,  "coverage": { "scope": "container-entries", "status": "complete" }}

一个APK,3669个文件节点,3827个文件出现,3826条包含关系边。这就是Agent"看到"的完整结构图。

为什么两个都要?

Reverse-Skill 是"大脑"——它决定做什么(哪个模块、哪个playbook、哪个工具链)。

REA 是"眼睛"——它决定看到什么(APK内部结构、文件清单、格式识别、Evidence)。

大脑+眼睛=Agent能独立完成逆向任务,不需要你手把手指挥。


一、广告凭什么住进你的手机

先说清楚一件事:APK不是黑盒。

安卓的.apk文件,本质上就是一个ZIP压缩包。里面装着:

  • • classes.dex —— Java字节码(Dalvik格式)
  • • res/ —— 资源文件(图片、布局、字符串)
  • • assets/ —— 原生资源
  • • lib/ —— 本地.so动态库
  • • AndroidManifest.xml —— 应用清单(二进制XML)

用apktool一拆,字节码变成smali(一种接近汇编的中间表示),资源还原成可读的XML和图片。

smali长这样:

.method public static initNativeAD()V    .registers 3    const-string v0, "10002"    sput-object v0, Lcom/naxclow/common/util/AdpAdInitUtil;->AD_ID:Ljava/lang/String;    # ... 初始化广告SDK    return-void.end method

看明白了吗?广告SDK的初始化代码,就这么明晃晃地躺在那里。

二、找到广告住在哪里

我的工具链:

工具
用途
Reverse-Skill
Agent逆向技能路由(44条规则,45个模块)
REA
Agent逆向分析(MCP,结构化Evidence)
apktool 3.0.3
反编译/重打包APK
Zulu JDK 17
跑apktool
Gradle 8.7
从源码编译apktool
androguard
Python分析DEX
apksigner
重签名APK(v1/v2/v3)

反编译之后,我全局搜索了广告SDK的包名。这款APP(com.naxclow.v720)里住着:

  • • CJMobile —— 穿山甲广告联盟(主力)
  • • AnyThink —— 聚合广告SDK
  • • Baidu —— 百度广告
  • • 几个混淆后的包名(com.byazt等)

但关键不是SDK本身——SDK是"子弹",真正扣扳机的是APP自己的代码。

我在com.naxclow包里找到了三个"扳机":

类
方法
作用
AdpAdInitUtilinitNativeAD()
初始化穿山甲SDK
AdpAdSplashUtilinitNativeAD()
加载开屏广告
AdpAdInfoUtilinitNativeExpressAD()
加载原生插屏广告

只要这三个方法变成空操作,广告SDK永远不会启动。

三、动刀:smali补丁

外科手术的核心原则:切得精准,缝合得干净。

我对每个目标方法做了同一个操作——把方法体清空,只留一个return-void:

# 改之前.method public static initNativeAD()V    .registers 3    const-string v0, "10002"    sput-object v0, ...->AD_ID:Ljava/lang/String;    new-instance v0, Lcom/cjmobile/CJMobileAd;    # ... 一堆初始化代码    return-void.end method# 改之后.method public static initNativeAD()V    .registers 1    return-void.end method

用Python脚本批量替换,三个文件,三刀下去:

for method in ["initNativeAD", "initNativeExpressAD"]:    pattern = rf'(\.method.*{method}.*?\n)(.*?)(?=\n\.end method)'    new_body = "    .registers 1\n    return-void\n"    content = re.sub(pattern,        lambda m: m.group(1) + new_body, content, flags=re.DOTALL)

⚠️ 注意:不是所有方法都返回void。你要检查返回类型。initNativeAD()和initNativeExpressAD()都返回void,所以return-void是对的。如果方法返回对象,你得用const/4 v0, 0x0 + return-object v0。

刀口干净,没有碎骨头。

四、缝合:重打包与签名战争

现在到了最坑的一步。apktool b重建了APK,但签名已经失效了(你改了DEX文件)。需要重新签名。

第一轮:jarsigner(仅v1)

jarsigner -keystore my.keystore -storepass android \  -sigalg SHA256withRSA -digestalg SHA-256 \  unsigned.apk myalias

结果:安装失败——"无效安装包"。

为什么?原APK里有一个v2/v3签名块,藏在ZIP数据和中央目录之间。apktool重建时,把这个旧签名块当成"未知文件"保留了下来。现在APK里有了两个互相冲突的签名——旧的v2块 + 新的v1 JAR签名。Android的包解析器直接拒绝。

第二轮:apksigner(v2+v3,不带v1)

下载Android build-tools 34,用apksigner:

apksigner sign \  --ks harmony.keystore --ks-pass pass:android \  --v1-signing-enabled false \  --v2-signing-enabled true \  --v3-signing-enabled true \  --out final.apk clean_unsigned.apk

这次产出了一个干净的APK,只有v2+v3签名块,META-INF里没有冲突的v1文件。

VerifiesVerified using v1 scheme (JAR signing): falseVerified using v2 scheme (APK Signature Scheme v2): trueVerified using v3 scheme (APK Signature Scheme v3): true

第三轮:鸿蒙4.2兼容性

目标设备跑的是鸿蒙4.2(华为自研系统,大致相当于Android 12级别)。鸿蒙的包校验比标准安卓更严格。v2+v3签名方案是最稳的选择——现代安卓应用都用这个,鸿蒙也认。

五、实测:鸿蒙4.2真机

第一次尝试(jarsigner v1):

安装失败——"无效安装包 / 与操作系统不兼容"。

第二次尝试(jarsigner v1,旧签名块还在):

还是同样的错误。

第三次尝试(apksigner v2+v3,干净重建):

APP启动了。没有开屏广告,没有横幅,没有插屏。只有干净的摄像头首页。

版本3.3.2,首页/相册/活动/更多四个标签页全部正常。广告SDK因为初始化代码被切掉,从未启动过。

六、踩坑总结

1. APK签名方案不是"选一个"就完事

安卓有四种签名方案:

方案
存储位置
适用系统
v1 (JAR)
META-INF/目录
Android < 7.0
v2 (APK签名块)
ZIP数据与中央目录之间
Android ≥ 7.0
v3
与v2同一个块
Android ≥ 9.0
v4
APK外部的独立文件
仅ADB安装

关键坑:如果你用apktool重建APK,而原APK有v2/v3签名,旧签名块可能被当成"未知文件"保留。混合旧v2 + 新v1 = 无效安装包。解决办法:干净重建,只用apksigner签名。

2. smali比Java更适合做手术

你不需要理解整个APP的架构。你只需要找到广告SDK初始化的入口。在smali里,这些入口通常是:

  • • 工具类里的init*方法
  • • Activity的onCreate/onResume调用
  • • Application.onCreate钩子

把这些入口变成空操作,SDK就永远不会启动。

3. 鸿蒙4.2很挑剔

它不是纯安卓。华为的包校验器更严格:

  • • 拒绝混合签名方案
  • • 拒绝ZIP中央目录大小不匹配的APK
  • • 优先接受v2/v3,v1-only可能不通过

apksigner开启v2+v3、关闭v1是最稳的组合。

4. "手术"比"核弹"更安全

你可以把广告SDK的所有类从DEX里删掉。但那样风险很大:

  • • ClassNotFoundError崩溃(APP代码还在引用它们)
  • • 方法解析失败
  • • 资源指向已删除的类

把入口变成空操作更安全:SDK类还在(满足类加载器),但永远不会被初始化。零崩溃。

七、工具链速查

# 0. 安装Agent逆向工具链(可选但推荐)# 0a. Reverse-Skill(技能路由)curl -sL -o rs.tar.gz https://codeload.github.com/zhaoxuya520/reverse-skill/tar.gz/refs/heads/maintar xzf rs.tar.gz && mv reverse-skill-main reverse-skillcd reverse-skill && bash skills/scripts/refresh-tool-index.sh# 0b. REA(逆向分析MCP)npm install rea-agentsnpx rea-agents setup --yes# 1. 安装JDK(如果没有)curl -O https://cdn.azul.com/zulu/bin/zulu17.68.203-ca-crac-jdk17.0.20.1-linux_x64.tar.gztar xzf zulu17*.tar.gz# 2. 从源码编译apktoolcurl -O https://codeload.github.com/iBotPeaches/Apktool/tar.gz/refs/tags/v3.0.3tar xzf Apktool-3.0.3.tar.gz && cd Apktool-3.0.3# 需要Gradle 8.7(从repo.huaweicloud.com下载)# 3. 反编译java -cp "apktool-cli/lib/*" brut.apktool.Main d -f -o apk_dec app.apk# 4. 修改smali(Python正则替换)# 5. 重打包java -cp "apktool-cli/lib/*" brut.apktool.Main b -o rebuilt.apk apk_dec# 6. 签名(Android build-tools 34)apksigner sign --ks key.jks --ks-pass pass:xxx \  --v1-signing-enabled false --v2-signing-enabled true --v3-signing-enabled true \  --out final.apk rebuilt.apk# 7. 验证签名apksigner verify --verbose final.apk

写在最后

广告是一种商业模式。但当它占用的是你的手机、你的时间、你的体验时——你应该有选择权。

我没有黑掉广告服务器,没有拦截网络请求。我只是告诉APP:"别把广告打开。"

然后它就真的不弹了。在鸿蒙4.2设备上。不需要root,不需要Xposed,不需要adblock。

只有smali和一把手术刀。

有时候,最好的广告拦截器,是一个永远不加载广告的代码库。

相关学习资料