如前所述,服务器管理员可以创建具有任意数量自定义的服务器,包括自定义地图和声音。每当玩家加入具有此类自定义设置的服务器时,需要传输自定义设置背后的文件。服务器管理员可以为服务器播放列表中的每个地图创建需要下载的文件列表。
在连接阶段,服务器向客户端发送 HTTP 服务器的 URL,从这里下载必要的文件。对于每个自定义文件,都会创建一个 cURL 请求。为每个请求设置的两个选项引起了我们的兴趣:CURLOPT_HEADERFUNCTION
和CURLOPT_WRITEFUNCTION
. 前者允许注册为 HTTP 响应中的每个 HTTP 标头调用的回调。后者允许注册每当接收到正文数据时触发的回调。
以下屏幕截图显示了如何设置这些选项:
我们有兴趣了解 Valve 开发人员如何处理传入的 HTTP 标头以及对我们命名为 的函数进行逆向工程CurlHeaderCallback()
。
事实证明,它CurlHeaderCallback()
只是解析了Content-Length
HTTP 标头并Content-Length
相应地在堆上分配了一个未初始化的缓冲区,因为它应该对应于应该下载的文件的大小。
然后CurlWriteCallback()
将简单地将接收到的数据写入该缓冲区。
最后,一旦 HTTP 请求完成且不再接收数据,缓冲区将写入磁盘。
我们立即注意到 HTTP 标头解析中的一个缺陷Content-Length
:如下面的屏幕截图所示,进行了区分大小写的比较。
区分大小写的Content-Length
标题搜索。
这种比较是有缺陷的,因为 HTTP 标头也可以是小写的。这仅适用于 Linux 客户端,因为它们使用 cURL 然后进行比较。在 Windows 上,客户端只假设 Windows API 返回的值是正确的。这会产生相同的错误,因为我们可以只发送一个Content-Length
带有小的响应主体的任意标头。
我们使用 Python 脚本设置了一个 HTTP 服务器,并使用了一些 HTTP 标头值。最后,我们想出了一个触发错误的 HTTP 响应:
HTTP/1.1 200 OK Content-Type: text/plain Content-Length: 1337 content-length: 0 Connection: closed
当客户端收到这样一个文件下载的 HTTP 响应时,它会识别第一个Content-Length
标头并分配一个大小为 的缓冲区1337
。但是,接下来是content-length
具有大小的第二个标头0
。尽管 CS:GO 代码Content-Length
由于其区分大小写的搜索而错过了第二个标头,并且仍然需要1337
正文数据字节,但 cURL 使用最后一个标头并立即完成请求。
在 Windows 上,即使响应格式错误,API 也只会返回第一个标头值。CS:GO 代码然后将分配的缓冲区以及缓冲区中包含的所有未初始化的内存内容(包括指针)写入磁盘。
尽管 CS:GO 似乎使用 Windows API 来处理 Windows 上的 HTTP 下载,但完全相同的 HTTP 响应起作用并允许我们在玩家的机器上创建包含未初始化内存内容的任意大小的文件。
然后服务器可以通过CNETMsg_File
消息请求这些文件。当客户端收到此消息时,他们会将请求的文件上传到服务器。它的定义如下:
message CNETMsg_File { optional int32 transfer_id = 1; optional string file_name = 2; optional bool is_replay_demo_file = 3; optional bool deny = 4; }
文件上传后,攻击者控制的服务器可以搜索文件的内容以查找指向engine.dll
或堆指针以破坏 ASLR。我们在附录部分详细描述了这一步Breaking ASLR
。
为了进一步实现游戏的定制,服务器和客户端交换ConVar
s,它们本质上是配置选项。
每个 ConVar 都由一个全局对象管理,存储在engine.dll
. 以下代码片段显示了此类对象的简化定义,用于解释为什么 ConVars 成为帮助利用 OOB 访问的强大小工具:
struct ConVar { char *convar_name; int data_len; void *convar_data; int color_value; };
社区服务器可以ConVar
在比赛期间更新其值并通过发送CNETMsg_SetConVar
消息通知客户端:
message CMsg_CVars { message CVar { optional string name = 1; optional string value = 2; optional uint32 dictionary_name = 3; } repeated .CMsg_CVars.CVar cvars = 1; } message CNETMsg_SetConVar { optional .CMsg_CVars convars = 1; }
这些消息由一个简单的键/值结构组成。当将消息定义与struct ConVar
定义进行比较时,假设value
ConVar 消息的完全攻击者可控制的字段被复制到客户端的堆中并且指向它的指针存储在对象的convar_value
字段中是正确的ConVar
。
正如我们之前讨论的,OOB 访问CSVCMsg_SplitScreen
发生在指向对象的指针数组中。下面是OOB访问发生的代码的反编译作为提醒:
由于数组和所有ConVars
都位于 的.data
部分engine.dll
,我们可以可靠地设置player_slot
参数,使得ptr_to_object
指向ConVar
我们之前设置的值。这可以说明如下:
【51CTO.com快译】 数据可视化工具不断发展,提供更强大的功能,同时改善可访问...
Docker生成新镜像版本的两种方式 There are two ways Docker can generate new m...
建站 什么 虚拟主机 够用?这要看搭建的是什么类型的网站。比如个人博客类型的网...
本文整理自直播《Hologres 数据导入/导出实践-王华峰(继儒)》 视频链接: https:/...
摘要 元旦期间 订单业务线 告知 推送系统 无法正常收发消息,作为推送系统维护者...
在Python语言中有如下3种方法: 成员方法 类方法(classmethod) 静态方法(staticm...
信息化2.0时代提出开展智慧教育创新发展行动。2019年2月,中共中央、国务院印发...
前提条件 请您在购买前确保已完成注册和充值。详细操作请参见 如何注册公有云管...
从 10.0.0 版开始,异步迭代器就出现在 Node 中了,在本文中,我们将讨论异步迭...
2021年3月24日,主题为《数据的世界,世界的数据》的星环科技2021春季新品发布会...