• 栖白

    前几天在某音符软件刷到了一个视频,是用Openspeedy的变速功能实现网盘下载加速。

    在简单了解了一下这个加速软件后,我很好奇为何这个游戏变速器能实际影响网盘的下载速度。是否是百度网盘在用户端限制了用户一段时间内能获取的字节数?而时间加速骗过了这个计时器,让实际传输的数据变多了。

    逆向这块我基础很弱,过程中大量使用GPT来协助整理,于是写了这个帖子记录一下这次的成果,希望能有所帮助。

    以下均为个人观点,欢迎指正!

    本次主要分析的是 Windows 客户端 kernel.dll 3.0.20.234。

    先说一下我目前理解的限速框架```
    下载策略 //大概是拿到一些下载行为用到的控制参数
    ↓
    sl = 120 //实际下载策略里发现的字段,猜测可能是Speed Limit?
    ↓
    客户端换算
    120 × 1024
    ↓
    122880 B/s
    ↓
    多个限速来源进行仲裁 //目前观察到4种,而限速逻辑存在一定的优先级区分
    ↓
    得到最终生效的限速值
    ↓
    Token Bucket(令牌桶) //类似一个额度池,定期补充,每次下载数据都会消耗
    ↓
    按经过的时间补充下载额度
    ↓
    下载数据消耗额度
    ↓
    没有额度就等待
    ```先说说这个下载策略,点击下载按钮以后,客户端不是随便找个服务器就开始传文件,而是需要拿到相应的信息,比如:

    -去哪个地址下载

    -与此次下载相关的控制参数

    -速度相关参数

    客户端拿到这些信息之后,建立下载任务,才真正开始传输数据。

    而sl这个字段,就是其中控制速度的参数(暂且这么认为)

    而进一步下追之后,这个值会进入限速的相关逻辑,被转化成

    120 × 1024 = 122880 B/s也就是:120 KiB/s

    于是这个值进入了客户端的限速系统。

    注意,这个数值不是当前实际下载速度,而更类似一个基准,下载速度在其周围浮动。

    而我们为什么要说下载策略,而不是直接说这个sl参数呢?

    是因为我们发现客户端不只是听sl这一个值,还有其他来源。

    例如(此处列举2种):locatedownload → 返回 sl 等下载相关参数

    CMS配置 → 也可以提供整体速度限制相关参数

    这些来源之间,通过一定的仲裁策略/优先级进行协调

    可以理解成:```
    locatedownload : //此处抽象简单化了
    “我这里限 120 KiB/s”

    CMS :
    “整体下载这里也有一个限制”
    ```客户端比较这些来源决定最后哪个限制值生效

    那么最后的实际限速值是怎么拿到的呢?

    先解释一个关键点:

    locatedownload、CMS、P2P SDK 等限速来源,其参数起作用,并不是按类似“谁最后到就听谁的”这种策略,客户端内部有一套set_sl的仲裁逻辑,会同时记录:

    -限速值

    -这个值是谁设置的

    -当前有没有更高优先级的来源已经生效

    我们用百度原始kernel.dll跑了一遍,可以抽象成这样的过程:```
    初始 / reset
    ↓
    CDN = 524288 B/s
    TOTAL = 524288 B/s

    locatedownload 到来
    sl = 120
    ↓
    120 × 1024 = 122880 B/s
    ↓
    CDN = 122880 B/s ← locatedownload
    TOTAL = 122880 B/s ← locatedownload

    随后 CMS 配置到来
    ↓
    CDN = 122880 B/s ← locatedownload 保持
    TOTAL = 122880 B/s ← CMS 接管
    ``也就是说,同一个 122880 B/s,实际上可以有不同来源,在当时的一次真实长任务下载中, CDN 这一层的 120 KiB/s 来自 locatedownload,而 TOTAL(整体下载)这一层最终由 CMS` 接管。

    所以最后的实际限速值受多个来源影响。

    另外一个支撑这套理论的证据是,我们在客户端里找到的CMS原始配置其实是

    total_limit_speed = 81920 B/s也就是大约80 KiB/s

    但最终的total却是122880 B/s

    所以说,客户端内部其实做了一个兼容判断```
    CMS 原始候选 = 81920

    当前 locatedownload CDN = 122880

    locatedownload 当前有效
    ↓
    CMS 候选比当前 CDN 更低
    ↓
    把 CMS TOTAL 候选提高到 122880
    ``后来在历史日志中,还看到了一次当前 CDN = 204800 B/s`

    CMS 原始值仍然为 81920 B/s

    按照同一套逻辑,最后得出的total就是204800 B/s

    所以说这里可以得到一个结论:

    120 KiB/s 不是写死在这套仲裁逻辑里的。相同的 CMS 原始输入,在当前下载状态不同的时候,可以计算出 120 KiB/s,也可以计算出 200 KiB/s。

    而有了这个120 KiB/s的限速值,就一定是它把下载速度卡住了吗?

    暂时还不能

    因为这里只是证明,客户端最终配置出了一个 122880 B/s 的限制。

    但是客户端内部还有其他限制:

    -CDN 限制 //内容分发网络的速度上限

    -TOTAL 整体限制 //整个下载系统总体最多能放多少数据

    -task 级限制 //针对某一个具体下载任务的速度限制

    -NetGrid 等对象里的限制 //某个下载任务背后实际负责组织网络连接,数据传输的一层执行对象

    一个限速值即使存在,它可能并未被真正使用```
    整个客户端
    │
    ├─ TOTAL
    │ 控制总体下载速度
    │
    ├─ CDN
    │ 控制 CDN 下载路径
    │
    └─ 某个具体任务
    │
    ├─ task 级限制
    │
    └─ NetGrid / peer 执行层限制
    ```其实类似不同的闸门,取了其中最紧的一个。最紧的一层决定瓶颈。

    所以后面我没有继续只盯着配置值,而是启动真实下载任务,观察哪个限速器的“额度”真的在被不断消耗。也就是前面的Token Bucket(令牌桶)

    对于令牌桶,我们主要关注三个东西```
    rate
    每秒应该补多少下载额度

    token
    现在还剩多少下载额度

    timestamp
    上一次补额度时的时间
    ``可以理解为``
    时间过去
    ↓
    按照 rate 补 token
    ↓
    下载数据
    ↓
    消耗 token
    ↓
    token 不够
    ↓
    等待继续补
    ``如果某一个限速器确实在卡我们的下载速度,那么他对应的令牌桶,应该出于一种不断被消耗,不断被补充,而消耗的速度大于被补充的速度,导致token`长期接近耗尽的一种状态。

    反之,如果一个限速器下载速度写的很小,但是他的token与timestamp实际并没有变化的话,至少这次下载没有从这里取额度。

    在一次真实下载任务中,客户端显示的速度长期处于122764 \~ 126223 B/s范围。

    与此同时,我们看到TOTAL:rate = 122880 B/s

    更重要的是它的token,在连续采样之后,我们得到```
    480 / 482 次
    token < 16 KiB
    ```也就是在下载过程的绝大多数时间,它剩下的下载额度都很少,是一个十分典型的卡住吞吐的状态。

    与此同时,我们也观察了其他限速器,此中的一个task 级限制,约为 500 MiB/s

    额度一直很充足,显然不是它卡住了我们的下载速度。

    还有一个task / NetGrid CDN = 16 KiB/s

    那么为何下载速度不是16 KiB/s呢?

    因为他的```
    token 没变
    timestamp 没变
    ```说明这次下载,并未消耗他的额度。

    真正表现了瓶颈特征的,是122880 B/s

    为了进一步证明,total限速器不是一个待在内存里的无关对象,我们还获取了暂停下载任务时的数据。当任务运行时```
    TOTAL token 在变化
    TOTAL timestamp 在变化
    文件进度在增长
    ``暂停时``
    TOTAL rate 仍然 = 122880 B/s

    但是:

    token 冻结
    timestamp 冻结
    文件进度停止
    网络读取也停止
    ``甚至捕获到,冻结时token = 18 bytes`。

    所以说明,total限速器的活动和真实下载生命周期是同步的。

    接下来就进入了Openspeedy的作用层了,也就是令牌桶是按什么时间补充额度的

    核心就一个公式

    新增 token ≈ rate × elapsed_time

    比如```
    rate = 122880 B/s

    程序认为过去了 1 秒
    ↓
    大约补 122880 bytes

    程序认为过去了 0.5 秒
    ↓
    大约补 61440 bytes
    ``而这个elapsed_time` 是从哪里来的呢!

    我们继续追百度原始 kernel.dll 的令牌桶补充逻辑,最后追到了 Windows 的 FILETIME 时间接口。也就是说,这个限速器不是单纯“每隔固定 1 秒发一次额度”,而是会读取时间,算出“距离上次过去了多久”,再据此补 token。```
    上一次时间
    timestamp_old
    ↓
    读取当前时间
    timestamp_now
    ↓
    elapsed = now - old
    ↓
    新增 token ≈ rate × elapsed
    ↓
    更新 timestamp
    ```Openspeedy影响的,就是限速器对于时间的感知。

    到这里,也只是在理论上说明了此套机制可行,我们还做了更进一步的实验。

    首先,我们加载百度原来的签名 kernel.dll,让他自己的限速器工作,保持rate不变,只改变了它感知时间的速度,结果:```
    0.25x → 29.79 KiB/s
    0.50x → 59.56 KiB/s
    1.00x → 119.13 KiB/s
    2.00x → 239.25 KiB/s
    5.00x → 598.13 KiB/s
    ```5个点做线性拟合,基本就是一条直线。

    这里的“改变时间倍率”并不是在实验代码里直接修改 elapsed_time。我们在自己的测试程序中加载了 OpenSpeedy 官方的 speedpatch64.dll,通过它提供的倍率控制让整个进程看到的 FILETIME 时间变快或变慢。

    与此同时,我们另外用一条不受本次 FILETIME 虚拟化影响的时间NtQuerySystemTime作为现实时间对照,因此可以确认百度限速器实际感知到的是 0.5x、2x、5x,而不是手工把计算结果乘了一个系数。

    不过这还不够,完全可以质疑是在实验程序里调用限速函数,我们需要证明,这个数字能被传入实际的数据传输与文件写入。

    之前的测试相当于拆一个水龙头阀门,而接下来,我们要把这个阀门装上真正的水管,看看水流是否真的经过这个阀门了。

    我们接入了真实数据传输```
    本地 TCP sender
    ↓
    百度原始 TOTAL 限速器
    ↓
    recv() 接收数据
    ↓
    写入文件
    ↓
    FlushFileBuffers()
    ```只有令牌桶允许,才能接受之后的数据

    最终结果仍然是```
    无时间修改 → 119.14 KiB/s

    0.5x → 59.57 KiB/s
    1x → 119.13 KiB/s
    2x → 239.20 KiB/s
    5x → 599.53 KiB/s
    ```到这里,时间倍率影响的已经不只是“令牌桶函数算出来的数字”,而是实际经过 socket 接收并写进文件的数据吞吐。

    现在已经能证明:只要改变这个进程对时间的感知,百度原始限速器控制下的下载速度也会变化。

    但前面的实验还有一个区别:speedpatch64.dll 是我们的测试程序自己加载进去的,而平时使用 OpenSpeedy 时,并不是目标程序主动加载它,而是 OpenSpeedy 从外部把时间补丁注入目标进程。

    所以最后我们又补了一层验证:直接使用OpenSpeedy官方的Bridge流程,对我们自己的测试程序进行注入。 并没有修改真实运行中的百度网盘进程。```
    OpenSpeedy 官方 Bridge
    ↓
    将 speedpatch64.dll 加载进测试程序
    ↓
    改变测试程序看到的时间
    ↓
    百度原始 kernel.dll
    ↓
    TOTAL 令牌桶
    ↓
    TCP recv()
    ↓
    写入文件
    ``得到的结果:``
    0.5x → 59.57 KiB/s
    1x → 119.15 KiB/s
    2x → 239.23 KiB/s
    5x → 599.65 KiB/s
    ``也就是说,从OpenSpeedy`的实际外部注入链,到百度原始限速代码,再到真正的文件接收和写入,是能完整跑通的。

    目前的证据支持:

    sl=120会进入客户端限速逻辑,并换算成122880 B/s

    真实普通长文件下载中,真正表现出瓶颈特征的是TOTAL = 122880 B/s

    TOTAL 令牌桶按经过时间补充额度,改变进程感知的时间后,百度原始限速代码控制下的实际数据吞吐会近似按倍率变化

    这里要声明一点,我们没有对真实在线的 baidunetdiskhost.exe 注入 OpenSpeedy ,所以不能真实完整证明,百度下载一定按某种倍率提升。真实百度进程部分始终只做了只读观察。

    另外,这套结论主要对应我们观察到的普通长文件路径。客户端还存在小文件特殊路径,例如 fsl=0 时会跳过一个特定的全局 token consume,因此不能拿 120 KiB/s 这一条规则解释所有下载场景。

    写完有点头昏脑胀的,如果有些地方描述不清或者有逻辑错误,欢迎各位佬指正,也请包涵。 ^-^

    主楼
  • 烧冻鸡翅

    始皇严选,严肃学习ing

    #1
  • MartinMa

    所以有办法突破限速吗,从百度盘通过插件获取api下载链接,然后使用第三方下载软件,有没有办法做到?

    #2
  • hanhanye

    你可以把文章喂给AI,组织一遍再自己看 ^-^

    #3
  • marki

    图片

    长文总结一下,太长有点头晕… 另外哈基米3.8flash怎么还在ab

    #4
  • 沧浪同学

    这个不是一两年前就修复了吗

    还是说今年新的科研成果

    #5
  • miku_tc

    所以按照这个逻辑在本地就可以直接解除百度网盘的限速 ^-^

    #6
  • yamika

    最近也在各个视频平台又刷到了,可以用Openspeedy直接加速下载速度,感觉原理还是一样的

    #7
  • Luwei

    我记得通过加速解除限速已经被修复了,所以综合来说目前通过加速解除限速只是解决限速的其中一环 要突破限速还要解决其它的限速源

    #8
  • 栖白

    SorrySorry,以后写完先放几天,到时候再自己重新看一遍能不能易于理解

    #9
  • 拉香蕉的奥德彪

    ^-^上一个这么干的人不知道现在出来没有

    #10
  • MrQ

    加速的方法早就有了,后续他们加了更多限制,可能老版本还能用这个方法。

    另外度盘已经算良心了,充会员就给速度,迅雷现在白金VIP 网盘取回都限速 4m/s

    #11
  • 网好卡卡卡卡卡卡罗特

    10年前我就想问一个问题了 就百度云这吃相 为啥刷不到骂他的帖子啥的呢?

    (虽然我也妥协了都冲年费会员充到svip7了 ^-^)

    #12
  • xlxzhc

    图片

    图片

    #13
  • vfx

    以前变速齿轮也可以加速下载,但肯定是BUG被修复了啊.知道原理没啥用,破解不了的.唯有充值才能变强.

    #14
  • Caphhh

    折腾这个收益《《 成本,限制远不止这一处,上游一改全报废

    研究消耗的 token 还不如拿来冲年费会员/买临时账号

    #15
  • buste

    还是蓝绿破解器好使吧 用了几年了没出过问题 官方还给我送了几个礼品鼓励我继续用 ^-^

    #16
  • microsoft2333

    我寻思着 百度网盘他们不做服务器端的限速吗

    #17
  • 一梦浮生

    临时用baidu的时候 用过几次这个软件,效果还是可以的.

    #18
  • kt8tqbhvk

    会员好像不限速的,折腾这些没有意义的内容,浪费的时间远远比买个会员更贵。

    #19

发表回复

登录后回复