通过 Winsock Hook 监听英雄联盟客户端RTMP通信
英雄联盟和 RTMP 的关系
很多人看到这个标题可能会一脸懵逼,RTMP 不是用来搞流媒体传输的吗?赫赫,这你就不懂辣。
从 2016 年拳头发布的 LCU 客户端更新技术细节 中可以看到,在 2008 年 Adobe AIR
的易用性与表达能力远超当时的 HTML,得到了拳头的青睐。而 RTMP
也是 Adobe 主导开发的可靠传输协议,与前者共同处于 Adobe Flash 框架之下,理所当然的被拳头用于客户端数据传输。
时至 2023 年末,拳头和腾讯已经尽可能将 RTMP 协议上的服务迁移到 HTTP,但是仍然有部分通讯由 RTMP 实现(如登录、Teambuilder)。
前人项目经验
远在 LCU 客户端更新之前的 2015 年,在 GitHub 上就已经有了一个基于 C#的 RTMP 监听实现,名为 frostycpu/FinalesFunkeln
原理
它的主要原理大概如下:
程序会在
127.0.0.1:2099
启动一个 RTMP 服务器用于转发消息,并同时将消息显示在程序 GUI在游戏客户端启动时,计算出
WSAConnect
函数的地址这个是可行的,因为
ws2_32.dll
的基地址在不同进程空间的地址不变,只需要提前获取函数的偏移即可计算出内存地址。因为函数的偏移基本也是固定的,所以可以说
WSAConnect
在所有 32 位进程中的地址是一样的。将已经设计好的字节码直接写入
WSAConnect
的地址,从而加入自己的逻辑。这里有一个冷门的知识点,32 位的 WindowsAPI 函数汇编第一句都是
mov edi,edi
是有意为之的无意义指令。而 FinalesFunkeln 正是利用了这一点,实现了简易的热补丁。
详见 Why do Windows functions all begin with a pointless MOV EDI, EDI instruction? - The Old New Thing
在写入的逻辑中会检查
WSAConnect
的第二个参数,也就是那个sockaddr*
指针,如果连接的远程服务器是基于AF_INET
的,并且端口是 2099(RTMP 服务默认端口),则将其服务器地址更改为127.0.0.1
,这样我们就成功把消息劫持到了本地的 RTMP 服务
已不可用
由于年久失修,这个项目已经不可用状态,原因主要有:
- SSL 验证机制的变化
- 游戏客户端从 32 位升级为 64 位,硬编码的的 32 位 Hook 汇编代码失效
那么对应的思路也是有的:
- 关闭客户端与 RTMP 服务器之间的 SSL 验证
- 使用 C++重新实现关键函数的 Hook
缝缝补补
原来的 FinalesFunkeln 项目中,本地服务器的搭建和对客户端的 Hook 是在同一个项目中的,为了方便,这里我会把原项目中的 Hook 部分独立使用 C++重新编写。
原项目的修补
这部分是我一个朋友做的,所以暂时没有办法放出源码,我大概讲一下做什么
删除原有的 AIR 客户端验证逻辑,已经彻底没用了
将 RTMP 服务器地址硬编码,原先的逻辑不适用
腾讯运营的区服中,RTMP 服务器地址一般是
区名-cloud-feapp.lol.qq.com
,如艾欧尼亚的是hn1-cloud-feapp.lol.qq.com
删除证书验证逻辑,因为我们打算直接关闭 SSL 验证
相应的,需要在
system.yaml
中的server
项下添加如下内容,以关闭 SSL 校验1
2
3
4
5server:
# ....
lcds:
ssl: false
# ....
Winsock Hook
我的逆向工程水平连入门都称不上,Intel 指令集手册让我翻冒烟了我都没做到用 64 位汇编重新实现 Hook 功能,所以我决定从 C++层面实现。这里使用的是我从 GitHub 上找到的一个极简的Hook 库 ,非常好用👍。
我将使用传统派的思路,编写一个 Payload DLL,随后使用远程线程注入。
Payload
这里我引入了上面提到的 Hook 库,代码很简单,甚至没检查协议,只需要检查端口是不是 2099 就行了(实测在本端口只有这一处调用)
1 |
|
Injector
需要注意的是,国服的LeagueClient.exe
尽管是带 ACE 的,但是在最初启动的前两秒左右是未被保护的,我们可以趁这个时候将 Payload 载入到目标进程里。
这里从 KooroshRZ/Windows-DLL-Injector 借用一下提权的逻辑,使用AdjustTokenPrivileges
将进程提升到SeDebugPrivilege
级别,以防权限不够。
远程线程注入方法是十分简单的,大概可以分为如下几步
- 将 DLL 地址写入目标进程
- 获取
LoadLibraryA
的地址(W 也行,看自己需要) - 调用
CreateRemoteThread
在目标进程执行LoadLibraryA
从而加载我们准备好的 Payload
唯一需要注意的点就是,这个 Payload 的路径是相对目标进程的,而非相对注入器,所以我直接在下面写了绝对路径,省事一些。
主体代码也比较简单,不到 40 行
main.cc
1 |
|
运行实测
基于如上的改动,我们基于如下顺序操作启动游戏
首先启动 FinalesFunkeln 和 注入器
修改客户端的
system.yaml
以关闭 SSL 校验运行游戏
WeGame 校验不通过的话,直接用 TCLS 登录,或者其他你想得到的办法
效果图如下:
通过 Winsock Hook 监听英雄联盟客户端RTMP通信
https://bakaft.github.io/2023/12/28/通过-Winsock-Hook-监听英雄联盟客户端RTMP通信/