使用 Shadowrocket 去除应用内置广告

众所周知,在App Store内,有着一款功能非常强大的代理软件,名叫Shadowrocket

(虽然这是一个付费的软件,但是这是我认为在iOS最值得购买的软件之一)

当然如果你想要白嫖,这里我准备了几个共享这款软件的网站,你可以点击前往(需要记住的是,里面的任何让你付费的东西,都不要去点击,谨防上当受骗!)

*提示:共享的Apple ID千万不要在这【设置】里面登录,也不要给这不属于你的ID充钱(我最推荐的方法还是自己注册一个外区的Apple ID)

上图是一个登录共享 ID 使用的教程,自己可以跟着做(已经更新iOS26系统的,需要在【设置】的App Store软件的选项中,退出和登录Apple ID)

可曾想过,它不仅仅可以是一个代理软件,它也可以是一个广告过滤软件?

我们平时把 Shadowrocket 当成一个"科学上网"的工具,导入机场订阅、点一下连接,仅此而已。但它的本体其实是一台运行在你 iPhone / iPad 上的本地流量处理引擎(这款软件能做的事情和玩法,超乎你的想象)

所有出入设备的网络请求都要先经过它,这意味着它不仅能决定"这条流量走代理还是直连",还能决定"这条流量要不要被改写、要不要被拦截、响应内容要不要被清洗"

去广告,用的就是后面这套能力

这篇文章会分两条线

  1. 操作部分:在一台干净的iOS设备上,一步步复现整个过程(这里我演示的设备是搭载了iPadOS 18.5,并且仅仅导入了机场订阅和基础的 VPN,其余未配置的干净环境)
  2. 原理部分:为什么一个代理软件能去掉 YouTube 和 Spotify 的内置广告,它到底对你的流量做了什么

首先,我先来带领你从零开始搭建这个过程

 

 

一、先想清楚:广告是怎么进来的

你有没有想过,在我们打开的软件中广告到底藏在哪?

很多人以为去广告就是"把广告服务器的域名封掉"。对于早期那种"广告单独从 ads.xxx.com 拉取"的场景,这确实有效(但 YouTube 和 Spotify 这样的商业公司早就不这么干了,他们的技术也在升级)

YouTube 为例,它的视频信息、推荐列表、播放器配置,全都通过一个核心接口返回:

https://youtubei.googleapis.com/youtubei/v1/player
https://youtubei.googleapis.com/youtubei/v1/browse
https://youtubei.googleapis.com/youtubei/v1/next

广告数据和正片数据,走的是同一个域名、同一个接口、同一个响应。 广告以 adPlacementsplayerAdsadSlots 这样的字段,混在返回给 App 的那一大坨 JSON 里

这就直接堵死了"封域名"这条路:你一封 youtubei.googleapis.com,广告是没了,但视频也打不开了——因为它们是同一个东西

所以真正的思路必须换成:放行这个域名,但在流量经过 Shadowrocket 时把它拆开,从响应里精准地删掉广告字段,再把清理干净的内容交给 App

而"拆开"这个动作,就是整篇文章的技术核心——因为这些流量是 HTTPS 加密

二、原理:为什么需要"解密",MITM 是怎么做到的

2.1 HTTPS 把门锁上了

现代 App 的流量几乎全走 HTTPS,也就是 HTTP over TLS。TLS 的作用是加密:数据从 App 出来时就被加密成密文,一直到服务器才解开

中间任何节点——包括你的路由器、你的运营商、以及此刻的 Shadowrocket——看到的都只是密文

这就是矛盾所在:Shadowrocket 想删掉响应里的广告字段,但它看到的是一团加密后的乱码,根本读不到里面的 adPlacements

要动内容,必须先解密,而解密,就要用到 MITM

2.2 MITM:站在通信双方中间

MITM 是 Man-In-The-Middle(中间人) 的缩写,名字听起来像黑客攻击,其本质手法也确实和攻击一样

在通信双方中间插入一个能读写流量的节点,区别只在于:真正的攻击是攻击者背着你做的;而这里是你自己你自己的设备上主动授权 Shadowrocket 去做(手法相同,性质相反)

要理解它怎么绕过 TLS,得先明白 TLS 是靠什么防住中间人的——数字证书

TLS 握手时,服务器要证明"我真的是 youtube.com",它出示一张数字证书,这张证书由一个受信任的 CA(Certificate Authority,证书颁发机构)**签名担保。你的设备里内置了一份"受信任 CA 名单",只有名单上的 CA 签发的证书才被认可。正常情况下,任何中间人都伪造不出一张能通过验证的 youtube.com 证书,因为它没有可信 CA 的签名——TLS 的防线就在这里

Shadowrocket 的破法是:让它自己变成你设备认可的一个 CA

具体分三步(这也正是你安装时被要求"装一个描述文件并信任"的原因):

  1. 安装并信任 Shadowrocket 的根证书。 你手动装上它自带的 CA 证书,并在 iPad/iPhone 的 设置 → 通用 → VPN与设备管理 / 关于本机 → 证书信任设置 里打开"完全信任"(这一步等于往你设备的可信 CA 名单里,塞进了 Shadowrocket 这个成员)
  2. 对 App 一侧:现场伪造证书, 当 App 要连 youtubei.googleapis.com 时,流量先进 Shadowrocket,Shadowrocket 冒充服务器,用自己的根证书当场签发一张 youtubei.googleapis.com 的证书发给 App(因为它的根证书已被你信任,App 验证通过,握手成功——App 以为自己连上了真正的 YouTube)
  3. 对服务器一侧:正常连接, 与此同时,Shadowrocket 冒充客户端,和真正的 YouTube 服务器建立第二条正常的 TLS 连接

于是形成两段独立的加密链路,中间那一段是明文:

App  ⇄(TLS-A: Shadowrocket伪造的证书)⇄  Shadowrocket  ⇄(TLS-B: 真实证书)⇄  YouTube服务器
                                              ▲
                                    在这里,流量是解密后的明文,
                                    可以读取、可以改写

站在中间这个点上,Shadowrocket 就能读到完整的响应 JSON,把广告字段删掉,再重新加密发回给 App。这就是"中间人"三个字的字面含义

2.3 HTTP/2 又是什么,为什么配置里要开 h2 = true

在配置文件中:

这里的h2 就是 HTTP/2,HTTP 协议的第二版(现在还有更新的 HTTP/3)。相比老的 HTTP/1.1,它主要改了传输方式:

  • 二进制分帧:数据不再是纯文本,而是以二进制帧传输
  • 多路复用:一条 TCP 连接上可以并行跑多个请求/响应,不用像 1.1 那样排队
  • 头部压缩(HPACK):压掉重复的请求头

YouTube、Spotify、Google 系服务全都跑在 HTTP/2 上。所以当 Shadowrocket 拆开 TLS 后,看到的不是可读的文本,而是 HTTP/2 的二进制帧。如果不开 h2 = true,它就没法把这些帧还原成一个个完整的请求和响应,也就无从匹配规则、无从改写内容

这个开关的意义就在这里

2.4 解密之后,三种"动手"的方式

解密只是拿到了明文,真正去广告靠的是解密后对流量做的三类操作。这里的三个文件恰好把这三类都用上了:

① URL Rewrite —— 在请求发出前拦截或改写它

YouTubeNoAd.sgmodule

[URL Rewrite]
^https?:\/\/[\w-]+\.googlevideo\.com\/initplayback.+&oad - reject-200

googlevideo.com 是 YouTube 真正拉视频流的域名,其中带 &oadoverlay ad,覆盖式广告)参数的那种 initplayback 请求,就是在请求贴片广告。这条规则用正则匹配到它,直接 reject-200——返回一个空的成功响应,让 App 以为广告加载成功了,实际什么都没拿到

Spotify 文件里也有一条 Rewrite,但目的不同:

^https:\/\/...spotify.com.../artistview/v1/artist/(.*)&platform=iphone  →  ...&platform=ipad  header

它把请求里的 platform=iphone 改写成 platform=ipad,借此拿到 iPad 版接口的返回(通常界面更干净)这说明 Rewrite 不只能拦截,也能篡改请求参数

② Map Local —— 用本地伪造的响应,顶替真实响应

Spotify 文件:

[Map Local]
^https:\/\/...spotify.com.../(?:ad-|pendragon)  data-type=text data="" status-code=200

凡是路径命中 ad-pendragon(Spotify 的广告投放系统)的请求,Shadowrocket 不去问真正的服务器,直接在本地伪造一个"内容为空、状态码 200"的响应塞回给 App。App 拿到的是一个合法但空白的广告数据,自然就没广告可放了。

Map Local 和 reject-200 的区别在于:reject 是"拒绝,什么都不给",Map Local 是"我给你,但给的是我编的假数据"。后者对那些"拿不到数据就报错、就重试"的 App 更友好

③ Script —— 拆开响应正文

这是最强、也最能体现"为什么必须解密"的一类,看 YouTubeNoAd.sgmodule

[Script]
youtube.response = type=http-response,
  pattern=^https:\/\/youtubei\.googleapis\.com\/(youtubei\/v1\/(browse|next|player|search|...))...,
  script-path=https://raw.githubusercontent.com/Maasea/sgmodule/master/Script/Youtube/youtube.response.js,
  requires-body=true, binary-body-mode=true, ...

分享配置文件链接:点我下载  密码:8vxh