Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
ds3-nrssr-rce — CVE-2022-24125 和 CVE-2022-24126 的文档和概念验证代码。 | Kitploit
工具/GitHubGitHub/tremwil/ds3-nrssr-rce
漏洞利用框架漏洞分析漏洞利用逆向工程Shellcode渗透测试学习与教育红队Shellcode 生成Payload 开发二进制利用
GitHub
169854年前Kitploit 审核通过
tremwil/ds3-nrssr-rce

ds3-nrssr-rce

CVE-2022-24125 和 CVE-2022-24126 的文档和概念验证代码。

查看仓库

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

更新:黑暗之魂3 1.15.1

黑暗之魂3 于 2022/08/25 发布了新游戏更新 1.15.1,同时恢复了在线服务。该更新修复了 CVE-2022-24125 和 CVE-2022-24126,以及游戏中 P2P 网络通信中存在的各种其他潜在安全漏洞(越界读/写)。此外,所有已知的能够损坏其他玩家存档的漏洞利用均已被修复。许多在在线多人游戏中经常遇到的常见低级作弊(例如“诅咒匕首”)也已被修补。

ds3-nrssr-rce

本仓库包含影响 FROM SOFTWARE 游戏的最新 RCE 漏洞利用概念验证代码和文档,即 CVE-2022-24126。虽然理论上其他游戏也存在此漏洞,但本研究的重点对象是黑暗之魂3。目前概念验证代码仅针对黑暗之魂3,但已确认该漏洞存在于以下游戏中:

  • 黑暗之魂1 PTDE(致谢:LukeYui)
  • 黑暗之魂 重制版(致谢:metal-crow)
  • 黑暗之魂2(含原罪学者)(致谢:LukeYui)
  • 黑暗之魂3(直至 1.15.0)(致谢:tremwil)

易受攻击的代码同样存在于只狼中(致谢:LukeYui),尽管目前没有触发它的方法。恶魔之魂中是否存在该漏洞尚未确认,但可能性极大。虽然封闭网络测试受此影响,但艾尔登法环的正式版不受影响。 事实上,黑暗之魂3 中存在的大量网络崩溃、越界读/写以及允许玩家修改对等方游戏数据的漏洞利用,在艾尔登法环中均已被修补。感谢 LukeYui 汇编了这份清单,也感谢 FROM SOFTWARE 迅速采取行动!我很高兴地说,就黑客所能造成的破坏程度而言,艾尔登法环无疑是 FROM SOFTWARE 最安全的作品。

澄清误解

与普遍看法相反,这不是一个点对点网络漏洞利用。它与匹配服务器有关,因此严重得多,因为由于另一个匹配服务器漏洞(CVE-2022-24125),你无需参与任何多人游戏活动便会受到攻击。

在黑暗之魂3中,滥用此漏洞的恶意攻击者能够在数秒内可靠地在每位在线玩家的机器上执行多达 1.3MiB 的 shellcode。

1

鉴于在服务器关闭前的几个月里,该游戏平均同时在线玩家数约为 20,000 人,这显然是一个需要立即修复的问题,尤其是考虑到它有可能出现在艾尔登法环中。由于在我提交初始报告(附概念验证视频和详细的漏洞利用文档,本 README 的大部分内容即基于此)超过 40 天后,FROM SOFTWARE 仍未采取行动,我决定以无害的方式公开演示该漏洞存在的证据,希望通过引起关注促使开发人员解决它,而这一做法奏效了。

目录

  • 漏洞利用摘要 (CVE-2022-24126)
  • 传播途径 (CVE-2022-24125)
  • 适用于所有游戏的通用利用策略
    • Bug #1:条目列表解析器中缺少边界检查
    • Bug #2:NRSessionSearchResult 解析器中的缓冲区溢出
    • 为 ROP 链铺平道路
  • 黑暗之魂3 概念验证代码
    • 运行 PoC 代码
    • 攻击向量
    • 虚函数调用重定向链
    • 补充信息

漏洞利用摘要 (CVE-2022-24126)

在解析 NRSessionSearchResult 匹配数据时,由于对栈缓冲区及数据大小字段的边界检查不当,攻击者可借此执行任意代码。栈溢出允许攻击者覆盖流读取器内部使用的 DLMemoryInputStream 对象的低两个字节 vftable_ptr,从而将执行重定向到精心挑选的相邻代码。巧妙利用 DLMemoryInputStream 对象的结构及数据大小字段,即可实现任意代码重定向,此时 RCX 指向我们数据包的地址。由此,通过一系列具有不同偏移量的虚函数调用进行代码重定向(这些调用现在将跳转到我们写入数据包缓冲区的任意地址),即可实现任意代码执行。

传播途径 (CVE-2022-24125)

传播途径是使这一特定 RCE 尤为严重的原因(它本身已经是 RCE)。该漏洞利用通过包含 NRSessionSearchResult 信息的匹配推送请求进行传播。这意味着攻击者可以针对任何加入其在线会话的玩家。具体到黑暗之魂3:

  • 召唤(PushRequestSummonSign)
  • 暗灵入侵者(PushRequestAllowBreakInTarget)
  • 通过誓约加入的玩家(PushRequestVisit)
  • 竞技场战斗者(PushRequestAcceptQuickMatch)

这已经很糟糕了,但真正的潜力由 RequestSendMessageToPlayers 请求解锁:

root@kitploit:~
message RequestSendMessageToPlayers { 
    repeated uint32 player_ids = 1; 
    required bytes push_message = 2;
}

主机使用此请求直接向入侵者发送 PushRequestAllowBreakInTarget 推送消息,以便他们获得出生坐标并加入其 P2P 会话。仅此而已。这是游戏使用此请求的唯一方式。

然而它允许任何客户端向成千上万的特定玩家发送任意推送消息。

我无法强调这究竟有多么不安全。任何玩家基本上都可以冒充匹配服务器。通过使用此请求经由 PushRequestVisit 发送漏洞利用,只要攻击者知道对方的玩家 ID,就可以远程攻击任何在线玩家。攻击者还可以通过发送多个请求(每个请求包含大量可能的玩家 ID 切片)来非常迅速地攻击整个在线玩家群体。

适用于所有游戏的通用利用策略

虽然该 RCE 无法完全移植到每款游戏,但赋予攻击者任意代码重定向能力的漏洞利用核心思想是相同的。如果能够实现这一点,就很可能找到特定于游戏的虚函数调用链或 ROP 链。这个“第一步”利用了以下漏洞:

Bug #1:条目列表解析器中缺少边界检查

包含会话加入信息的匹配推送请求将该信息存储为一种自定义二进制格式,该格式由一系列长度分隔的数据条目组成的链构成。每个条目的格式如下:

root@kitploit:~
struct Entry
{
    uint32_t type_or_id; // 不确定,但可能是一个类型(定长 = 2,变长 = 1?)
    uint32_t size;
    uint8_t data[size];
}

负责复制这些条目数据的游戏函数盲目信任 size 字段,从而造成越界读取。恶意客户端可通过将 size 字段设置为 0x7FFFFFFF 之类的值来利用此漏洞,导致内存分配失败并使受害者游戏崩溃。随后,这个 size 还会被传递给 DLMemoryInputSteam 的构造函数,而这是该漏洞利用的关键组成部分。

Bug #2:NRSessionSearchResult 解析器中的缓冲区溢出

上述数据结构中的条目之一是序列化后的 NRSessionSearchResult 对象。该数据的解析器首先解析一个属性列表。这些属性可以是 4 字节整数、8 字节整数或以空字符结尾的宽字符串。该属性列表之后是主机 Steam 人名为以空字符结尾的宽字符串,以及一些对漏洞利用不重要的附加数据。该函数和属性列表解析器都使用固定大小的栈缓冲区来读取字符串,并且在这两种情况下均未对缓冲区执行边界检查。以下是负责复制主机名的游戏代码(使用 Ghidra 反编译器生成后经清理):

root@kitploit:~
size_t idx = 0;
wchar_t wchr = 0;
do {
  // read_wchar() 函数位于 DLEndianStreamReader 的 vftable 索引 17
  wchr = stream_reader->read_wchar();
  player_name_buff[idx] = wchr;
  idx++;
} while (wchr != 0);

这会导致缓冲区溢出漏洞利用,使攻击者能够破坏栈。

为虚函数调用 / ROP 链铺平道路

为了实现任意代码重定向,我们利用上述缺陷以及由调用解析器的函数在栈上实例化的 DLMemoryInputStream 对象的内存布局,该对象被流读取器在内部使用:

root@kitploit:~
struct DLMemoryInputStream {
    uintptr_t* vftable_ptr; // 偏移量 0
    size_t data_size;       // 偏移量 4(32 位)/ 8(64 位)
    uint8_t* data_buffer;   // 偏移量 8(32 位)/ 16(64 位)
    // 缓冲区之后的条目对漏洞利用不重要
}

由于我们控制 data_size 字段(Bug #1),可将其设置为 data_buffer 字段的栈内存地址。只要该地址是常量且不太大,此操作就会成功(黑暗之魂3 满足这些要求)。由于编译器将栈缓冲区置于栈帧顶部,攻击者随后可利用 Bug #2 覆盖 DLMemoryInputStream 的 vftable_ptr 的低两个字节。因此,当下一次由 DLEndianStreamReader 读取字符时,它将在内部调用 DLMemoryInputStream,代码执行即被重定向。这 2 个字节提供了足够的空间来跳转到 DLEndianStreamReader vftable 中的第 22 个函数,该函数调用其第一个字段所指向对象的第 6 个虚方法。在 64 位进程(即黑暗之魂3)中,将执行以下指令:

root@kitploit:~
MOV       RCX,qword ptr [RCX + 0x8]
MOV       RAX,qword ptr [RCX]
JMP       qword ptr [RAX + 0x40]

由于 RCX 是指向 DLMemoryInputStream 对象的指针,第一条指令会将 data_size 字段(攻击者已通过 Bug #1 将其设置为指向 data_buffer 字段的栈地址)写入 RCX。接下来的两条指令将把执行重定向到攻击者在数据缓冲区偏移量 0x40 处写入的内存地址。至此,任意代码重定向已经实现!从那里开始,攻击者可以设置一条代码重定向链,将他们的 payload 复制到合适的内存区域并执行,方法是选择靠近具有不同偏移量的虚函数调用的代码,因为缓冲区现在充当了虚方法表。对于黑暗之魂3 的概念验证,我找到了一个仅需 3 个 gadget 即可实现 RCE 的设置:

  • 0x18: 140e97700
  • 0x40: 1422be020
  • 0x68: 140e40f15

关于这 3 个 gadget 的更多细节,请参见此处。如果对于其他游戏,这种虚函数调用方法不可行,仍可利用任意代码重定向来搭建更传统的 ROP 漏洞利用。

黑暗之魂3 概念验证代码

运行 PoC 代码

要运行概念验证代码,你必须先有一个可供连接的服务器。虽然官方服务器因该漏洞已被关闭,但你可以使用 ds3os 搭建一个私有服务器。ds3os 旨在尽可能贴近正式服务器行为,但该项目已经部署了安全补丁以修复此漏洞。不过,你仍然可以通过自行构建该项目来搭建测试环境,只需在 BuildConfig.h 中将 SEND_MESSAGE_TO_PLAYERS_SANITY_CHECKS 和 NRSSR_SANITY_CHECKS 常量设置为 false。这将模拟不安全的正式服务器行为。请按照 ds3os 提供的说明启动游戏并连接到你的服务器。

完成上述步骤且游戏已连接至服务器后,构建 PoC 代码并启动 Injector.exe 可执行文件。它会在黑暗之魂3 进程中注入一个包含漏洞利用代码的 DLL。该 DLL 随后会使用向服务器发送 FRPG 消息的游戏函数,将漏洞利用投递到你自己的客户端。

攻击向量

对于概念验证,我决定使用通过 RequestSendMessageToPlayers 发送的 PushRequestVisit 消息。这是该漏洞利用最强有力的版本,因为目标端的游戏在收到数据后会在任何情况下(甚至在主菜单中)立即解析易受攻击的数据。

虚函数调用重定向链

偏移量 0x18: 140e97700

root@kitploit:~
LEA       RAX,[DAT_144786150]
RET

该 gadget 用于偏移量 0x68 处的 gadget。我们需要将一个略低于但非常接近 144786998 的地址放入 RAX;这是最接近的一个。

偏移量 0x40: 1422be020

root@kitploit:~
MOV       RDX,RAX
MOV       R8,qword ptr [RCX]
CALL      qword ptr [R8 + 0x68]

为了能够使用偏移量 0x68 处的 gadget,我们需要将数据缓冲区地址存储在 RDX 中,并保持 RCX 不变。该 gadget 恰好实现了这一点。

偏移量 0x68: 140e40f15

root@kitploit:~
; 从偏移量 0x40 处的 gadget 跳转到此
MOV       RBX,RDX
CMP       R9,R8

 ; 从不跳转,R9 != R8
JZ        LAB_140e40f7a
MOV       RAX,qword ptr [RCX]
MOV       R8,qword ptr [RSP + 0x50]
MOV       RDX,R9
MOV       qword ptr [RSP + 0x30],RSI

; 调用偏移量 0x18 处的 gadget (140e97700)。将 144786150 加载到 RAX
CALL      qword ptr [RAX + 0x18]
MOV       RSI,RAX
TEST      RAX,RAX

; 从不跳转,RAX 是数据缓冲区地址。
JZ        LAB_140e40f4d 
CMP       RBP,RDI
MOV       RDX,RBX
MOV       RCX,RAX
CMOVC     RDI,RBP
MOV       R8,RDI ; RDI 是接近 14F3B0 的栈地址,因此 memcpy 得以成功
CALL      memcpy

LAB_140e40f4d:
TEST      RBX,RBX
; 从不跳转,因为 RBX == RDX == 数据缓冲区地址,非零。
JZ        LAB_140e40f62 

; 由于在 144786150 处执行的 memcpy,我们现在完全控制了这个 RWE 内存区域。RCE 已经实现!
MOV       RCX,qword ptr [DAT_144786998]
MOV       RDX,RBX
MOV       RAX,qword ptr [RCX]
CALL      qword ptr [RAX + 0x68]

这个 gadget 几乎为我们完成了所有工作。它调用偏移量 0x18 处的 gadget 获取 memcpy 目标指针,将我们的数据包复制到那里,然后对 144786998 处的静态对象调用偏移量 0x68 处的虚函数,而由于 memcpy 调用,我们现在完全控制了这个对象。由于 memcpy 破坏的内存量很大,且某些区域会被其他游戏线程持续写入,因此该漏洞利用首先加载一个“设置”payload,将其复制到安全位置,挂起所有其他线程,然后在跳转到实际 payload 之前重新复制它。更多信息请参见 rce.h。

补充信息

我建议查看概念验证代码的源代码,其中有大量注释详细说明了数据包的结构。如果你确实想实时查看每一步发生的情况(你应该看看,这很酷!),你可以在调试器下运行游戏并注入概念验证 DLL,在以下感兴趣地址处设置断点:

140ca5960

大体上是漏洞利用开始的地方。该函数负责解析 PushRequestVisit 消息的大小分隔条目列表数据。它首先将列表中的每个条目提取到不同的向量中:

root@kitploit:~
0x140ca59f8:
    player_data_cpy_ptr = (std_vector *)VectorCopy2_140ca4ef0(&player_data_cpy,player_data);
    FUN_140ca5010(player_data_cpy_ptr,&spawn_data,0x1c);
    FUN_140ca5010(player_data_cpy_ptr,&unk,4);
    FUN_140ca4fa0(player_data_cpy_ptr,&nrssr_data);

函数 140ca5010 会检查条目大小,但 140ca4fa0 用于变长条目,不会对 size 字段执行健全性检查(Bug #1)。要实现上述任意代码重定向漏洞利用,我们需要将其设置为 14F3B0。这将导致约 1.3MiB 的越界读取,但内存页应该足够大,不会引发访问违规。

140ca56b0

该函数由上一个函数以 nrssr_data 作为参数调用。它在栈上创建 DLMemoryInputStream 对象,然后将其作为参数传递给 NRSSR 解析器。

141955f50: ParseNRSessionSeachResult

NRSessionSearchResult 解析器。验证 NRSSR 签名和版本号(14196a0f0),解析属性列表(14196a260)、主机名(14195603a)以及一些其他信息(参见 rce.h)

14195603a

上述函数中不安全地复制主机名的循环(Bug #2)。以下是一些有助于跟踪缓冲区溢出期间发生情况的地址:

  • 解析器栈缓冲区地址:14F128
  • DLMemoryInputStream 栈地址:14F3A0
  • 覆盖后的 DLMemoryInputStream vtable 指针:1439e8b30
  • DLInputStreamReader 使用的 DLMemoryInputStream 中虚函数的偏移量:0x18

1439e8b48

root@kitploit:~
MOV       RCX,qword ptr [RCX + 0x8]
MOV       RAX,qword ptr [RCX]
JMP       qword ptr [RAX + 0x40]

这是由被覆盖的内存流 vftable 引起第一次代码重定向后我们到达的地方。虚函数调用重定向链自此开始。

Footnotes

  1. 针对黑暗之魂3 Ver. 1.15。理论上最大的 payload 大小取决于栈布局,因此会因游戏和版本而异。 ↩

下载工具