起因
家里电视上那个视频 App 的开屏和贴片广告,我忍了很久,终于动手想把它去掉。
先把边界说清楚:自己设备上自用,不去碰它的会员体系和付费内容。我想处理的只是广告。
结果这半天,广告一个没去掉,倒是把”客户端安全”这件事看明白了一大截。
一、先看清广告是从哪来的
拆开包看了一圈,第一个发现就跟我预想的不一样:它的广告不是第三方广告 SDK 发的。
我原本以为会是穿山甲、优量汇、百度那几家联盟广告,结果一个都没有 —— 开屏广告的接口是它自家 API 的一部分。
这个发现当场就废掉了我原本最省事的一条路:DNS 拦截。既然广告和正片走同一个域名、同一套鉴权,”封域名”就等于把整个 App 封了,根本没有可下手的地方。
二、改包本身,顺得让我有点意外
定位广告代码花了不到半小时:它把广告单独放在一个包里,开屏有专门的拉取类,广告视频走一个专门的播放页,逻辑非常直白。
改了几处字节码、把版本号抬高(顺手让应用商店的自动更新永远觉得”本地已经是最新的”)、再用我自己的密钥重新签名。
这一步顺到我心里发凉:“知道该怎么改”这件事,在今天已经基本不是门槛了。
三、装上去,闪退
装上、打开,十来秒之后它自己退了。
注意这里:没有崩溃栈。日志里没有 FATAL EXCEPTION,没有 SIGSEGV,什么都没有。就好像它什么都没发生,只是”我决定不干了”。
(后来在日志里翻到它自己打的一行启动上报,写着”新启动失败”。但它报完就走,不留现场。)
四、那一步对照实验
到这儿我面前有两个假设:是我的改动把它弄坏了,还是它压根不认我这个包?
做对照实验是最快的分辨方法:官方原包一个字都不改,只用我自己的密钥重新签一次名,装上去。
结果:一样退。
一次到位 —— 不是我的改动有问题,是它在自检签名。
五、为什么”改包”必然被自检抓到
这里有个概念值得单独写下来:APK 的签名覆盖整个包。除了 META-INF 里那几个签名文件本身,包里每个字节都要进摘要。也就是说,只要改动一丁点内容,就必须重新签名;而重新签名必然改变签名指纹。
所以只要它做了”比对签名指纹”这件事,任何改包都会被命中。这不是技术水平的差距,是数学上的必然。
顺带一个有意思的细节:它退出的方式是 exited cleanly (0) —— 退出码是 0,不是崩溃。这说明它的判断发生在 Java/Kotlin 层(原生层崩溃不会给你一个干净的 0)。这属于”读现象”,跟”怎么绕过”是两回事,我也没有继续往下走。
六、真正的门不在客户端
想通第五点之后,第六点几乎是自动浮现的:客户端这道门就算过了,后面还有一道我过不去的。
它的会员权益、清晰度、播放地址,都是服务端按账号权益签发的。我哪怕把客户端改得再干净,服务端不发那个流,还是没得看。广告也一样 —— 广告接口是它整条 API 的一部分,跟正片共享同一套鉴权和同一个域名。
所以我最后的选择很朴素:算了一下账,买了它的电视档会员。不是因为绕不过,而是因为绕的成本(时间,加上它每更新一版就得重来一遍)远高于那点钱。
顺便记一个消费陷阱:这档电视会员跟我手机上那份会员不是一回事,要单独买。这种事我一律记在”大厂商的定价策略”那一栏。
七、它给我当了一次反面教材
它自检失败时的处理方式,是我今天最大的收获之一:静默退出。
- 用户侧看到的是”莫名其妙闪退”;
- 我这边为了定位它多花了十几分钟 —— 没有栈、没有日志、退出码还是 0。
如果这个自检是我写的,我会这么设计:
- 判定只上报,核心逻辑不直接退出 —— 该降级降级,该限制限制;
- 失败必须能解释:错误码、期望值、实际值、时间,都记下来;
- 只对”包被改才变”的东西报警:签名指纹、dex 校验和这类。千万别拿设备型号、系统版本当特征 —— 否则第一个被误伤的就是天天刷机改 ROM 的我自己;
- 判定尽量搬到服务端,客户端只当一个”举报者”。
八、如果我要给自己分发的 App 加门,会按这个顺序
| 顺序 | 做什么 | 成本 | 收益 |
|---|---|---|---|
| 1 | R8 混淆 + 资源压缩,顺手清干净调试面、日志、测试入口 | 半小时 | ★★★★ |
| 2 | 清掉客户端硬编码的密钥,把”判定”(权限 / 额度 / 风控)搬服务端 | 看架构 | ★★★★★ |
| 3 | 轻量完整性自检(签名指纹 + 关键资源校验),失败 = 降级 + 上报 | 半天 | ★★★ |
| 4 | 请求签名(时间戳 + nonce)+ 服务端风控 | 1–2 天 | ★★★★ |
| 5 | 关键逻辑进 native / 上商业加固 | 数天起 + 花钱 | ★★ |
一句话原则:客户端永远能被改,目标不是”不可破”,而是”破的成本大于收益”。
还有一条我自己的体会,今天最有价值的一句:
别为了一个不存在的敌人,加一道会咬自己的门。
我手上那些自己写、自己签名、只给自己设备用的 App,加这套东西收益约等于 0,成本却是真的 —— CI 变复杂,每次重装调试都可能误伤自己。这事值得等”真要分发给别人”的那天再做。
九、AI 时代,门槛到底还在不在
今天最大的认知在这里。
AI 确实把”知识门槛”抹平了。 传统上要摸清一个 App 的广告架构、更新机制、自检行为,熟手也得几天;今天这些我一个多小时就理清了。这部分门槛,对 AI 来说基本是 0。
但最后卡住我的两件事,跟知识一点关系都没有:
- 持有物。签名私钥不在客户端里。我就是把它的算法全读懂了,也伪造不出它的签名 —— AI 再强,也变不出别人手里的那把钥匙。
- 服务端。判定和内容都在服务端。客户端改到天上去也没用。
还有一件我觉得以后会越来越值钱的能力:验证。
AI 一秒能给你一版改法,也能一秒给你一版错的。今天真正管事的全是”验证”动作:做对照实验、反查产物里的指令数、冷启动盯着它 25 秒看它退不退。能判断”到底成没成”的人,才是不可替代的。
十、结论
- 客户端逻辑在 AI 时代的边际保护价值已经接近 0(谁都能读懂)→ 把判定往服务端挪;
- 客户端的价值,从”守住秘密”变成”提高单次改造成本 + 留下可观测的失败痕迹“;
- 有些东西该付费就付费。不是因为它防得住所有人,而是因为对”只是想省点事”的人来说,付费永远是最便宜的那条路。
合规声明
- 本文只记方法和认知,不点名具体 App,也不提供任何可以直接使用的绕过步骤;
- 记录目的是给自己要分发的 App 做防护,以及搞清楚客户端安全的边界在哪;
- 没有传播任何破解产物。
附:用到的工具
- apktool —— 拆包 / 重打包
- jadx / dexdump —— 读代码(一个看 Java,一个看字节码)
- apksigner —— 签名,以及查看签名指纹
- logcat —— 看它到底是怎么失败的(这次最有用的一步)
评论
0 条评论