从Steam说起(一):如何劫持Steam客户端流量

English Ver. and the PoC code is available on GitHub: Y2Nk4/node-steam-client-traffic-tracer

效果图:

Screnshot

0x01 项目的实现原理

how-this-work

如上图所示,通常来说,Steam客户端会通过3种方式连接Steam的内容管理服务器(CM Server),TCP、UDP、WebSocket(over SSL) 中的一种。

简单来说,所有东西都是安全的,Steam、RSA、AES都是正常的。本项目中的数据解密是基于中间人攻击、本地信任的自签证书进行的。另一个比较出名的、也是基于该原理的的项目是 Fiddler

再次声明

本项目是基于本地信任的自签证书进行的,如果你的电脑没有信任恶意的证书,则所有流量都是安全的。(除了这种特殊情况

在项目开始之初,我考虑的是去解密Steam的TCP连接,但最后发现这基本上是不可能的。Steam通讯中所使用的TCP协议中并非传输裸的TCP数据,相反地,在Steam的TCP协议中的数据包是使用一个会话秘钥通过 AES-256-CBC 进行加密的,而且 Valve 在2016年还改进了加密机制,加入了HMAC_SHA1来生产IV以防范填充提示攻击,详情可看这篇文章:Breaking Steam Client Cryptography

同时上文提到数据包是使用一个会话秘钥加密的,那这个会话秘钥是如何生成的?我们是否能劫持到这个秘钥?

答案是技术上可行,但比较难实现。要回答这个问题,我们首先需要知道Steam TCP连接的建立流程。

当Steam启动(一个新的会话建立),Steam客户端会首先和Steam CM服务器建立一个未加密的TCP连接,握手成功后本地会生成一个会话秘钥保存在本地,然后使用内置的RSA公钥对会话秘钥进行加密,然后向Steam CM服务器发送建立加密请求,Steam CM服务器收到请求后会使用私钥对请求内容进行解密,以获得这个会话秘钥并存储在服务器端。这个过程与HTTPs连接的建立过程基本一致,但使用的是私有的SSL秘钥对,而非证书系统。

但为什么上文说是技术上可行的呢?我认为可行的方案是使用注入的方式去Hook Steam客户端的生成秘钥/请求建立加密的方法来获取这个会话秘钥,但我对 c#/c++/c 了解甚少,所以我没有选择使用这个方法。但如果你对这个有兴趣,可以自行尝试去Hook,如果成功了我可以在数据解析上提供帮助。

至于UDP协议,我觉得加密方法基本与TCP的加密一致,所以也没有深究。

所以我决定研究Steam的WebSocket协议。在项目开始的时候,我以为只有两种协议(TCP和UDP),但是我在使用WireShark抓包的时候,发现Steam的流量有时候识别成TCP,有的时候则是TCP over TLSv1.2,我看了下TLS建立的过程,居然与HTTPs的建立过程异常地相似,所以我意识到这可能是基于WebSocket。而且DoctorMcKay的steam-user项目则进一步佐证了我的猜想。

WebSocket连接的建立流程是先建立一个HTTP连接,然后设置 Connection 头为 Upgrade 来指示这是一个升级请求,且 Upgrade 头设置为 websocket 来指明连接要升级为 WebSocket。而 WebSocket over SSL 则是类似地先建立 HTTPs 连接再升级为 WebSocket over SSL。

正因如此,我们可以通过自签证书来劫持HTTPs/WSS流量。这种方法比解密Steam的TCP协议流量简单得多。

同时,Fiddler可以捕捉 HTTP、HTTPs、WebSocket、WebSocket over SSL流量,所以你可以手动通过 Fiddler 来实现捕捉Steam流量。但绝大部分的Steam流量都是使用ProtoBuf进行编码,所以直接读取这些二进制数据是非常困难的。所以我写了这个项目来捕捉并解析这些ProtoBuf数据。

为了实现这个攻击,我们需要先让客户端信任我们的中间人服务,这就是为什么我们需要信任我们自签的证书。通过 Fiddler 抓包,我们可以发现 Steam CM服务器的域名均为 *.cm.steampowered.com,所以我们可以直接使用OpenSSL给 *.cm.steampowered.com 签发一个泛域名证书并信任即可。

相关内容此处不再过多叙述,具体文档可以看这里

除了中间人服务器,我们还需要一个HTTP隧道代理来把Steam客户端的流量引导到我们的中间人服务器上。

设置好HTTP隧道代理后,Steam连接会通过代理与我们的中间人服务并验证服务器提供的证书并交换会话秘钥。若正确设置自签证书后,中间人服务和Steam客户端的HTTPs连接会被成功建立,进而Steam客户端会发送一个Upgrade请求,请求升级为WebSocket over SSL协议。

当WebSocket连接建立成功后,我们的中间人服务器也需要和Steam CM服务器建立WebSocket连接。两边连接都建立成功后,我们的中间人服务仅需要把接收到的数据发送给另一方即可,同时,我们也获得了Steam客户端和CM服务器之间通讯的未加密数据。至此,我们只需要写一个Steam流量的解析器就可以获得数据包中的有用数据了。

一句话总结整个过程

这是一次教科书般的HTTPs中间人劫持攻击,只不过是使用在了WebSock上。

0x02 运行DEMO

项目基于 Node.JS 所以你需要安装 Node

安装依赖:

npm install

启动服务器:

node app.js

脚本运行后,它会建立3个服务器,分别是

  • HTTP隧道代理服务器(端口:10068)
  • 中间人服务器(端口:10070)
  • 用于可视化数据的HTTP服务器(端口:8050)

然后在浏览器中打开 http://localhost:8050 并启动你的Steam客户端,然后你就可以在浏览器上看到Steam客户端和CM服务器发送的数据。

无法捕捉数据?

在我的测试中,Steam客户端会随机选择 TCP、UDP、WebSocket来连接CM服务器。且据我所知,没有一个专门的启动选项来强制Steam使用WebSocket协议。

但我测试中发现,先试用 -tcp 启动选项启动Steam客户端,然后再退出并删掉 -tcp 选项可以加大其使用WebSocket协议的几率。

0x03 配置网络

如上文所述,这个项目的原理与 Fiddler 相似(你完全可以使用 Fiddler 完成上面的所有操作),本项目使用中间人劫持的方式来截取Steam客户端的流量。

  1. 前往 Internet 选项 面板
    • 你可以运行 inetcpl.cpl 来前往 Internet 选项 面板
    • 然后前往 连接 面板,点击 局域网设置,输入 HTTP隧道代理服务器的地址和端口(127.0.0.1:10068)
  2. 使用 winhttp 把流量导向HTTP隧道代理
    • 需要使用管理员权限打开
    • 运行 netsh winhttp set proxy 127.0.0.1:10068 “<-loopback>”
    • 若需要取消代理,则运行 netsh winhttp reset proxy

然后打开 Steam客户端 即可在网页上看到 Steam 的流量包数据。

Screnshot

0x04 如何避免?

本项目对Steam客户端流量的劫持是基于信任了自签的证书。正常情况下只要你的设备没有被安装恶意证书,则流量是安全的。

但我在写这篇文章的时候,突然想到了一种应用场景,即在公共设备上(例如图书馆、咖啡馆、网吧等)可能被安装了恶意的证书和MITM服务,那么这时候你的Steam账号则有几率会被盗取(包括登陆信息等敏感信息)。

那么该如何避免呢?

添加 -tcp 启动选项来强制Steam客户端使用 TCP 协议来连接CM服务器。详情可以看这篇Valve的文章:How to force Steam use TCP connection

如上文第一部分所述,TCP使用的是Steam内置的RSA公钥来加密并与Steam CM服务器交换会话秘钥,一定程度上增加了破解的难度

0x05 相关文献

0 条评论

昵称

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

与博主谈论人生经验?