news 2026/10/7 8:31:17

如何伪造 AppTicket 解锁 Steam 游戏:OpenSteamTool 利用 SteamDRMP off-by-four 漏洞原理深度剖析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何伪造 AppTicket 解锁 Steam 游戏:OpenSteamTool 利用 SteamDRMP off-by-four 漏洞原理深度剖析

如何伪造 AppTicket 解锁 Steam 游戏:OpenSteamTool 利用 SteamDRMP off-by-four 漏洞原理深度剖析

【免费下载链接】OpenSteamToolOpen Source Steam Unlocker项目地址: https://gitcode.com/gh_mirrors/op/OpenSteamTool

想知道 OpenSteamTool 如何让没买过的游戏也能正常启动吗?这个开源 Steam 解锁工具的核心武器,是一个被称为SteamDRMP off-by-four的本地票据解析漏洞——它允许 OpenSteamTool 在不注入游戏进程的情况下伪造 AppTicket,绕过 DRM 授权。本文从零讲起:AppTicket 是什么、漏洞为什么叫"off-by-four"、伪造票据的每一字节如何拼接,以及 Denuvo 与 SteamStub 两条路径各自需要什么。读完你会对 Steam 客户端 DRM 体系有一个相当完整的认识。

1. AppTicket 是什么?为什么它是 DRM 的命门

Steam 的 DRM 校验逻辑大致是:

  1. 游戏(或其 DRM 模块,如 SteamDRMP、Denuvo)向 Steam 客户端发起 IPC 请求GetAppOwnershipTicket;
  2. 客户端返回一张AppTicket——一张由 Valve 服务器私钥签名的"所有权凭证";
  3. DRM 层用公开的 Valve 公钥验证签名,再从中解析出AppId和SteamID;
  4. 三者都对得上 → 授权通过,游戏运行。

从票据结构上就能看出关键信息的位置,见 AppTicket.h:

偏移 8 → SteamID 偏移 16 → AppId 票据末尾 128 字节 → 签名(kAppTicketSignatureSize = 128)

所以只要你能让 DRM 层读到一个 AppId 是目标游戏的票据,且签名校验通过,就等价于"拥有"了该游戏。难点在于:签名由 Valve 私钥生成,无法凭空捏造。

OpenSteamTool 的破局思路是:不造新票据,只把一张真实票据"偷梁换柱"。

2. 漏洞原理:为什么"off-by-four"能让伪造签名通过?

漏洞位于 SteamDRMP 解析票据的流程中。正常流程应该是:

① 读取整张票据 → ② 用 Valve 公钥验证签名(签名只覆盖票据前 N 字节)→ ③ 签名合法后,再按偏移提取 AppId、SteamID → ④ 校验 AppId 是否等于"请求方 AppId"。

而 SteamDRMP 的实际实现是:

  1. 读取票据的签名前正文,用公钥验签;
  2. 验签通过,认为票据可信;
  3. 直接按固定偏移读取正文里的 AppId,与游戏自身的 AppId 比对;
  4. 比对成功 → 授权通过。

漏洞在于第 3 步:它信任的"票据里的 AppId",和它校验的"请求 AppId",其实可以被中间人篡改——只要签名校验时读的范围与提取 AppId 时读的范围不一致,且这个不一致恰好是4 字节(一个 AppId 的大小)。这就是 off-by-four:

  • 验签时,验证器读到"签名起点"为止的字节;
  • 提取 AppId 时,却从另一个偏移读,且多读了 4 字节——这 4 字节原本在签名覆盖范围之外,但被当作 AppId 来用。

换言之,签名只保护到 A 处,但 DRM 却从 A+4 处读 AppId。这 4 字节的"信任盲区",正好塞进任意一个我们想伪造的 AppId。

3. OpenSteamTool 的伪造流程:三步拼接法

理解了原理,看代码就一目了然。核心实现在 ForgeLocalAppOwnershipTicket:

第 1 步:取一张"可信源票据"

// src/Utils/Tickets/AppTicket.cpp std::vector<uint8_t> source = Hooks_Decryption::GetCacheAppOwnershipTicket(kLocalAppTicketSourceAppId);

这里kLocalAppTicketSourceAppId = 7。AppId 7 是一张 Steam 客户端本地必然存在的缓存票据(通过 hookConfigStore::GetBinary从apptickets\7读取,见 GetCacheAppOwnershipTicket)。它签名真实、结构完整,是完美的"空白画布"。

第 2 步:精确截断——切掉原签名,只保留签名覆盖的正文

const size_t signedSize = source.size() - kAppTicketSignatureSize; ticket.insert(ticket.end(), source.begin(), source.begin() + signedSize);

第 3 步:在签名与正文之间"塞"4 字节目标 AppId

ticket.insert(ticket.end(), appIdBytes, appIdBytes + sizeof(AppId_t)); // ← 关键 4 字节 ticket.insert(ticket.end(), source.begin() + signedSize, source.end());

最终伪造出的票据布局:

┌──────────────────────────────────┬────────┬──────────────────┐ │ 源票据正文(签名真实覆盖) │ 目标 │ 源票据签名 │ │ SteamID / 原 AppId=7 / ... │ AppId │ 128 字节 │ └──────────────────────────────────┴────────┴──────────────────┘ ↑ off-by-four:DRM 从签名后 4 字节处读 AppId

再看响应侧如何"骗"过解析器——GetAppOwnershipTicket 中精心计算的四个指针:

ticket.totalSize = ticket.data.size() - sizeof(AppId_t); // 故意少报 4 ticket.appIdOffset = ticket.totalSize - kAppTicketSignatureSize; // 指向插入的 AppId ticket.signatureOffset= ticket.appIdOffset + sizeof(AppId_t); // 跳过 4 字节才是签名 ticket.signatureSize = kAppTicketSignatureSize;

注意totalSize故意少报 4 字节、signatureOffset故意后移 4 字节——这两个"错位"正是漏洞的利用点:DRM 验签时按totalSize读范围(不包含我们插入的 4 字节),但提取 AppId 时却按appIdOffset读(正好读到我们插入的目标 AppId)。签名合法 + AppId 匹配,授权通过。

最后,这张票据通过 hook 的 IPC 后处理函数 HandlerPost_IClientUser_GetAppOwnershipTicketExtendedData 注入回 Steam 客户端的响应缓冲,游戏完全无感。

4. 三种票据来源与 Denuvo 授权窗口

并非所有游戏都吃"伪造"这一套。OpenSteamTool 在 AppTicketSource 中定义了三级策略:

模式适用场景说明
CredentialStoreOnlyDenuvo 游戏(授权窗口内)只读显式票据/缓存票据,不伪造
ForgeOnlySteamStub 游戏无显式票据时直接伪造
CredentialStoreThenForge通用回退先读缓存,读不到再伪造

为什么 Denuvo 不能伪造?因为 Denuvo 的授权是一次"窗口期"行为:游戏进程通过命名管道与 Steam 客户端握手,DenuvoAuth 状态机在检测到 Denuvo 特征后,选定一个"授权管道",只在该管道、且授权未结束时(IsAuthorizedPipe)才放行 SteamID 与票据欺骗。一旦握手结束(第 2 次握手后),窗口关闭。

而 SteamStub 游戏走的是本地 ConfigStore 票据路径,全程离线可伪造,这也是为什么 README 说"SteamStub-only games do not require configuring AppTicket"。

Denuvo 用户怎么办?用项目自带的 extract_tickets 工具:在一台真实拥有该游戏的机器上运行,它会从运行中的 Steam 客户端 dump 出appticket.bin/eticket.bin并输出 hex 字符串,然后写入 Lua 配置:

setAppTicket(1361510, "14000000...") -- 写入 AppTicket setETicket(1361510, "...") -- 写入 ETicket

注意 Denuvo 票据有30 分钟有效期,过期后会报88500005,需要刷新。

5. 票据从哪里读、写到哪里?

Windows 上,票据存储在注册表HKCU\Software\Valve\Steam\Apps\<AppId>下,由 SteamCredentialStore 统一封装。OpenSteamTool 的读写接口:

  • 读:GetAppOwnershipTicketFromCredentialStore(appId)— 仅当 appId 在addappid白名单内才返回;
  • 写:WriteAppOwnershipTicket/WriteEncryptedTicket/WriteSteamID— 把 Lua 配置里的 hex 票据落盘。

还有一个巧妙的细节在 GetSpoofSteamID:伪造票据里的 SteamID 必须与 Steam 本地期望的一致,否则授权链路会断。所以工具优先读缓存 SteamID,读不到就直接从票据偏移 8 处解析——"让伪造的票据里写着 Steam 自己相信的那个 ID"。

6. 快速上手:从零到解锁

  1. 构建:项目根目录运行build.bat(需 Windows + CMake 3.20+ + VS2022);
  2. 将dwmapi.dll、xinput1_4.dll、OpenSteamTool.dll复制到 Steam 根目录(steam.exe同级);
  3. 在<Steam>\config\lua\下创建 Lua 脚本,addappid(你的AppId)即可解锁;
  4. SteamStub 游戏到此结束;Denuvo 游戏则需补setAppTicket+setETicket。

配置样例见 opensteamtool.example.toml。

7. 技术要点回顾

  • off-by-four 本质:验签范围与 AppId 提取范围错位 4 字节,形成信任盲区;
  • 伪造 ≠ 生成:全程不伪造签名,只"移植"一张真实票据的签名;
  • AppId 7 是钥匙:本地必然存在的缓存票据,提供结构完整、签名真实的源数据;
  • 指针操纵是核心:totalSize少报 4、signatureOffset后移 4,恰好对应漏洞的两个错位点;
  • 两条 DRM 路径:SteamStub 走本地 ConfigStore(可伪造),Denuvo 走管道授权窗口(需显式票据)。

8. 声明与延伸阅读

⚠️ 本项目仅供研究与教育目的。请遵守当地法律、平台服务条款与软件许可。

相关源码入口:

  • 伪造核心:src/Utils/Tickets/AppTicket.cpp
  • IPC 注入点:src/Hook/Hooks_IPC_ISteamUser.cpp
  • ConfigStore Hook:src/Hook/Hooks_Decryption.cpp
  • Denuvo 授权状态机:src/Pipe/Features/DenuvoAuth/DenuvoAuth.cpp
  • 票据提取工具:tools/extract_tickets/extract_tickets.cpp
  • 完整文档:README.md / README_ZH.md

【免费下载链接】OpenSteamToolOpen Source Steam Unlocker项目地址: https://gitcode.com/gh_mirrors/op/OpenSteamTool

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 8:31:07

caveman:AI编码代理的极简主义实践与token优化指南

1. 从“caveman”说起&#xff1a;一个AI编码代理的极简主义实践第一次看到“caveman”这个词被用作项目名&#xff0c;我脑子里浮现的画面是原始人拿着石斧敲代码。但真正上手之后才发现&#xff0c;这个命名其实精准得可怕——它要解决的核心问题&#xff0c;就是让AI编码代理…

作者头像 李华
网站建设 2026/10/7 8:30:36

嵌入式 NPU 零拷贝实战:利用 rknn_create_mem 绕过主存搬移直通硬件

嵌入式 NPU 零拷贝实战&#xff1a;利用 rknn_create_mem 绕过主存搬移直通硬件在瑞芯微 RK3588 或 RK3568 等具备独立 NPU 的工业单板机上部署端侧轻量小模型&#xff08;SLM&#xff09;或高分辨率工业视觉时&#xff0c;很多开发者对推理性能的调优往往全部押注在“模型剪枝…

作者头像 李华