首页
社区
课程
招聘
[原创]Deadlock 游戏辅助源码分析与二次开发全记录
发表于: 4小时前 160

[原创]Deadlock 游戏辅助源码分析与二次开发全记录

4小时前
160

Deadlock 游戏辅助源码分析与二次开发全记录

一、前言

  • 仅用于学习交流,勿用于非法用途
  • Deadlock 是 Valve 基于 Source 2 引擎开发的 MOBA+射击游戏(Steam AppID: 1422450)。本文记录了对开源项目 Andromeda-DeadLock 的逆向分析、跨版本适配、源码修改与定制化开发的全过程,涵盖字节模式扫描、Schema 系统分析、ImGui 菜单汉化、自瞄骨骼数据生成、以及离线许可系统的设计与实现。

二、项目架构分析

2.1 整体结构

Andromeda-DeadLock-Base/ (VS DLL项目)
├── DllMain.cpp                    # DLL入口
├── DeadLock/
│   ├── Hook/                      # 12个MinHook引擎钩子
│   ├── SDK/                       # 逆向的Source2 SDK封装
│   │   ├── CFunctionList.hpp      # 19个字节模式函数查找
│   │   ├── CSchemaOffset.cpp      # 运行时Schema字段偏移解析
│   │   └── Interface/             # 引擎接口封装
│   └── Protobuf/                  # 94个预编译protobuf文件
├── AndromedaClient/
│   ├── Features/
│   │   ├── CAimbot/               # 自瞄 (轨迹预测/惯性/AntiFrog)
│   │   ├── CVisual/               # ESP (方框/骨骼/Chams/观战)
│   │   ├── CMisc/                 # 自动格挡/换弹
│   │   └── CHeroes/               # 英雄专属功能
│   └── GUI/                       # ImGui渲染菜单
└── GameClient/                     # 实体缓存/控制器封装

2.2 Hook 链分析

项目通过 MinHook 挂载了 12 个关键函数:

Hook 点 所在 DLL 用途
CreateMove client.dll 自瞄角度写入
FireEventClientSide client.dll 游戏事件拦截
OnAddEntity / OnRemoveEntity client.dll 实体缓存更新
ParseMessage engine2.dll 网络消息解析(伤害事件)
OnClientOutput engine2.dll 客户端渲染回调
GetMatricesForView client.dll 视图矩阵获取
DrawModel scenesystem.dll Chams 模型上色
Present / ResizeBuffers GameOverlayRenderer64.dll D3D11 渲染层注入

2.3 Schema 系统

这是项目最精巧的设计。Source 2 引擎提供了 schemasystem.dll,其中包含所有实体类的字段名→偏移映射。CSchemaOffset.cpp 在运行时遍历所有 TypeScope,动态读取 CSchemaClassBinding 结构的成员偏移,存入 unordered_map<string, unordered_map<string, uint32_t>>

这意味着游戏更新导致实体字段偏移变化时,无需重新逆向,运行时自动解析。

2.4 字节模式系统

对于非 Schema 暴露的函数(如 CalcWorldSpaceBonesScreenTransform),项目通过 CBasePattern 在 DLL 加载时执行 AOB(Array of Bytes)扫描定位:

// 示例:通过函数头字节码定位
CBasePattern ScreenTransform = {
    "ScreenTransform",
    "33 C0 48 39 05 ? ? ? ? 0F 84", // 字节模式,?? 为通配符
    CLIENT_DLL,
    0,
    SEARCH_TYPE_NONE
};

支持三种扫描类型:直接匹配、CALL指令追踪(SEARCH_TYPE_CALL)、LEA RIP相对寻址(SEARCH_TYPE_PTR2)。

三、跨版本适配

3.1 模式扫描器

拿到源码之后我想着能不能自己扫描进行适配,发现还真可以,使用AI进行批量文件扫描进行版本适配几乎可以无脑适配,但是适配之后骨骼和aimbot失效后续我也解决了!嘿嘿

编写了 Python 脚本 pattern_scan.py,通过 Boyer-Moore 风格的通配符匹配算法,一次性扫描所有目标 DLL:

# 核心扫描逻辑
def scan(data, pat_bytes, mask):
    n = len(pat_bytes)
    first_byte = pat_bytes[first_lit]  # 首个固定字节
    pos = data.find(first_byte)        # 快速定位候选
    ...

对 8 个 DLL 的 35 个模式进行批量扫描,输出命中/缺失/歧义三类结果。

3.2 结果

针对新版 Deadlock(ClientVersion 6536),扫描结果 30/32 OK,0 MISS,仅 CameraManager 和 CreateMaterial 存在歧义(2 处命中指向同一地址),确认无需修改即可运行。

四、源码修改

4.1 中文本地化

本人英文不好所以手动给他添加了所有的翻译工程,然后重新编译即可
图片描述

新增 LangHelper.hpp 翻译宏:

#define L(en, cn) (GetMisc()->Language == 1 ? cn : en)

遍历 17 个菜单文件的数百处 XorStr("English") 替换为 L("English", "中文")

同时为 ImGui 字体系统添加 CJK Merge Font(msyh.ttc),解决中文字符显示为方框的问题。

添加 /utf-8 MSVC 编译标志,确保 UTF-8 字符串在 GBK 环境下正确编译。

4.2 DLL重命名

  • 原始DLL编译出来的需要原始自带的注入器,但是我看了就是远程线程注入,而VITTLOCK的注入器是能够规避V社 DeadLock的检测,游戏我并没有去逆向,没那个实力,但是在测试过程中我尝试打开IDAPro 游戏并没有响应的措施进行阻拦,例如WeGame的黑客弹窗 闪退游戏这些措施;大牛可以自己逆向一下
  • 由于没有使用原来的注入器,而是使用的VITTLOCK的注入器,所以将编译的Dll 更名为VITTLOCK.dll 即可,VITTLOCK是扫描同级目录VITTLOCK.dll 进行注入的;不过我怀疑他们用的也是同一套源码 因为注入之后界面几乎一样,他也只是加了一个静默自瞄(范围子弹命中,相关技术可浏览UC帖子 有大牛传授技术)
  • CHEAT_NAME 宏 → "L I V E L O C K",即可修改标题名称
  • Wrapper 控制台 ASCII Art → LIVELOCK 大字
  • ImGui 窗口标题 → LIVE LOCK

4.3 配置持久化

  • 因为我将dll和注入器exe进行了打包,方便部署本地;我想着避免dll和exe落地能使用着方便,大牛能直接断点api dump下完整文件
  • 被我这么一搞他检测不到dll了文件貌似没创建成功,所以我重新修改了源码进行本地存储配置,方便下次加载直接加载配置文件,就不用每次都手动改配置了

原始代码通过 GetDllDir() 获取 DLL 所在目录作为配置路径。由于 DLL 被注入器提取到 %TEMP%\随机目录,注入完成后被全零覆盖删除,导致配置不保存。

修改 CSettingsJson.cpp,新增 GetConfigDir()

static std::string GetConfigDir() {
    char path[MAX_PATH];
    SHGetFolderPathA(nullptr, CSIDL_APPDATA, nullptr, 0, path);
    std::string dir = std::string(path) + "\\LiveLock\\";
    CreateDirectoryA(dir.c_str(), nullptr);
    return dir;
}

配置保存到 %APPDATA%\LiveLock\,永久保留。
图片描述

4.4 自瞄骨骼 Fallback

原始项目依赖 HeroSkeletonPairs.hpp 中的预先提取的骨骼映射表。由于 BoneExtractor 源码缺失,初版使用空存根导致自瞄无法锁定目标。

  • 添加了基于 m_vecAbsOrigin() + Z偏移 的无骨骼 Fallback;由于作者并没有发布骨骼数据这部分数据,所以只能自己做去找骨骼数据了,神通广大的UC果然有大神发布骨骼数据,我就直接拿来现成的直接提取出来就OK了,另外做了一个Fallback
// 无骨骼数据时,使用实体原点+垂直偏移估算各部位位置
Vector3 aimPos = pNode->m_vecAbsOrigin();
float zOff = (bone.slot == HitboxSlot::Head) ? 72.f :
             (bone.slot == HitboxSlot::Neck) ? 64.f : 48.f;
aimPos.m_z += zOff;
//-------------------------------------------------------------------
const auto* boneList = GetCL_Bones()->GetHitboxBones(pPawn, bone.slot);
if (boneList && !boneList->empty()) {
    // 原版路径:精确骨骼坐标
    for (const int16_t bid : *boneList) {
        pNode->GetBonePosition(bid, bonePos);  // ← 读骨骼矩阵
        Trace_IsVisibleEx(..., bonePos);       // ← 射线检测
        WorldToScreen(bonePos, screen);        // ← 投影到屏幕
    }
}
else {
    // Fallback:用实体原点 + Z轴偏移估算
    Vector3 aimPos = pNode->m_vecAbsOrigin();  // ← 实体脚下坐标 (X, Y, Z↓)
    aimPos.m_z += 72.f;  // 头部:脚下 + 72 单位
    // Neck: +64, Torso: +48, Arms: +38, Legs: +12

    // 跳过 Trace_IsVisible(不需要射线检测,避免 Trace 系统兼容问题)
    WorldToScreen(aimPos, screen);  // 直接投影
}

4.5 自瞄默认开关

CAimbot.hppbool Active = false; 改为 true;,避免用户找不到开关。

五、BoneExtractor 骨骼数据生成、VPK 逆向:从游戏文件中提取骨骼数据

从 UC 获取 Deadlock-BoneExtractor 源码,其对游戏 VPK 文件(Valve Pak)执行解析:

5.1 问题背景

Andromeda 的自瞄和骨骼 ESP 都依赖 HeroSkeletonPairs.hpp 中预先提取的骨骼数据:

// 每个英雄模型对应一份
struct ModelBoneData {
    vector<BonePair> pairs;         // 父子骨骼连接 (用于骨架线绘制)
    unordered_map<string, int> ids;  // 骨骼名 → 运行时 ID
    vector<int16_t> slotBones[5];   // Head/Neck/Torso/Arms/Legs 部位骨骼列表
};

VPK读取 → 遍历 4966 个 .vmdl_c 条目
→ 过滤英雄模型路径
→ 解析 ValveResourceFormat 提取骨骼名称/ID
→ 构建 slotBones 部位分类 (Head/Neck/Torso/Arms/Legs)
→ 生成 C++ 头文件

源码仓库中的 HeroSkeletonPairs.hpp 仅包含一个空存根 g_HeroModelData = {},因为完整的骨骼数据由 PreBuildEvent 调用 BoneExtractor 运行生成后填入。此步骤需要 .NET SDK 且 BoneExtractor 源码在主仓库中缺失的。直接编译后所有 GetHitboxBones() 调用返回 nullptr,导致自瞄无法锁定目标和骨骼 ESP 无法绘制。

5.2 方案对比

最初尝试了三种途径:

方案 方法 结果
IDA 提取 VITTLOCK.dll 从 .rdata 段解析 STL 容器结构 数据嵌入在 133KB 静态初始化函数 sub_180008530 中,手动提取不现实
手写 VPK Python 解析器 按 VPK v2 二进制格式逐字节读取 成功定位目录树,但模型路径字符编码异常中止
BoneExtractor 原版工具 从 GitHub 获取源码,配置 .NET 环境运行 成功生成 63 个英雄的完整数据

5.3 VPK 文件格式分析

Deadlock 的资源存储在 game\citadel\pak01_dir.vpk(索引文件,6.5MB)和 pak01_000.vpk ~ pak01_270.vpk(数据分片)中。VPK v2 的文件头结构:

Offset  Size  Field
0x00    4     Magic = 0x55AA1234
0x04    4     Version = 2
0x08    4     TreeSize (目录树字节数)
0x0C    4     FileDataSectionSize
0x10    4     ArchiveMD5SectionSize
0x14    4     OtherMD5SectionSize
0x18    4     SignatureSectionSize

依赖 .NET 10.0 + ValveResourceFormat NuGet 包。生成 63 个英雄模型、总计 235KB 的 HeroSkeletonPairs.hpp
目录树定位TreeOffset = FileSize - TreeSize

VPK 分析尝试

目录树中每个文件条目为 18 字节:

在等待 .NET 10 SDK 期间,手动解析了 VPK v2 二进制格式:

Offset  Size  Field
0x00    4     CRC32
0x04    2     PreloadBytes
0x06    2     ArchiveIndex (对应 pak01_XXX.vpk 的分片编号)
0x08    4     EntryOffset  (分片文件内的字节偏移)
0x0C    4     EntryLength  (压缩前大小)
0x10    2     Terminator = 0xFFFF

条目按 Extension → Path → Filename 的三层树结构组织。遍历时以空字符串标记层级结束。

手动编写 Python VPK 解析器成功定位到扩展名 vmdl_c 下的 152 个英雄模型路径,但 latin-1 编码的目录名在 UTF-8 转换时触发 UnicodeDecodeError

5.4 BoneExtractor 源码分析

工具的核心逻辑:

// Program.cs - 入口
var heroFilter = new Regex(
    @"^models/heroes(?:_staging|_wip)?/",
    RegexOptions.IgnoreCase | RegexOptions.Compiled);

// VpkLocator.cs - 自动定位 Steam 库
// 读取 HKEY_CURRENT_USER\Software\Valve\Steam → SteamPath
// 解析 {SteamPath}\config\libraryfolders.vdf → 获取所有库目录
// 遍历各库下的 steamapps\common\Deadlock\game\citadel\pak01_dir.vpk

// GameBoneExtractor.cs - 核心提取逻辑
var package = new Package();        // ValvePak 库
package.Read(vpkPath);             // 打开 VPK
foreach (var entry in package.Entries) {
    if (entry.Key.EndsWith(".vmdl_c") && heroFilter.IsMatch(entry.Key)) {
        var resource = new Resource();        // ValveResourceFormat 库
        resource.Read(package, entry);        // 解析 .vmdl_c 模型
        var skeleton = resource.GetSkeleton(); // 获取骨骼系统
        // 提取 m_boneName[] → 构建 ModelBoneData
    }
}

// OutputBuilder.cs - C++ 代码生成
// 生成 inline const unordered_map<string, ModelBoneData> g_HeroModelData = {...};

Header: Magic(4B) + Version(4B) + TreeSize(4B) + FileDataSize(4B) + ...
Tree offset = FileSize - TreeSize
Entry: CRC(4B) + Preload(2B) + ArchiveIdx(2B) + Offset(4B) + Length(4B) + Term(2B)

关键 NuGet 依赖:

  • ValveResourceFormat (19.2.6339):解析 Source 2 引擎的 .vmdl_c(编译后的模型文件),提取骨骼名称数组 m_boneName[] 和父子层级关系
  • ValvePak (SteamDatabase.ValvePak):VPK 压缩包的 .NET 实现,支持按路径读取单个文件,无需解压整个包

5.5 骨骼分类算法

.vmdl_c 中提取的骨骼名(如 neck_0, spine_1, arm_upper_l, leg_lower_r)通过启发式规则映射到五个部位槽位:

// GameBoneExtractor.cs 中的分类逻辑
static HitboxSlot ClassifyBone(string name) {
    if (name.Contains("head")) return HitboxSlot.Head;
    if (name.Contains("neck")) return HitboxSlot.Neck;
    if (name.Contains("spine") || name.Contains("pelvis")) return HitboxSlot.Torso;
    if (name.Contains("arm") || name.Contains("hand") || name.Contains("clav"))
        return HitboxSlot.Arms;
    if (name.Contains("leg") || name.Contains("foot"))  return HitboxSlot.Legs;
    return HitboxSlot.Count; // 跳过未分类
}

成功定位 4966 个 vmdl_c 条目,但字符编码问题导致解析中止。最终 BoneExtractor 工具完美解决。
骨骼父子连接 (BonePair) 通过 .vmdl_c 中的层级关系构建,用于骨架 ESP 绘制连线。

5.6 运行与集成

# 安装 .NET 10.0 SDK(ValveResourceFormat 19.x 要求 net10.0)
80bK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1L8%4c8F1k6i4c8Q4x3X3g2E0K9h3y4J5L8%4y4G2k6Y4c8Q4x3X3g2U0L8$3#2Q4x3V1k6V1L8%4N6F1L8r3!0S2k6q4)9J5c8X3c8G2N6r3&6W2N6q4)9J5c8U0p5H3i4K6u0W2x3l9`.`.

# 还原 NuGet 包
dotnet restore

# 运行,输出到项目 GameClient 目录
dotnet run -- "Andromeda-DeadLock-Base\Andromeda-DeadLock\GameClient\HeroSkeletonPairs.hpp"

输出:

[*] Opening VPK: ...\pak01_dir.vpk
[*] 4966 vmdl_c entries
  [ok] models/heroes_staging/haze/haze.vmdl          22 pairs  23 ids
  [ok] models/heroes_staging/shiv/shiv.vmdl          22 pairs  23 ids
  [ok] models/heroes_wip/ivy/ivy.vmdl                24 pairs  25 ids
  ...
[*] 63 models extracted, 89 skipped
[+] Written: HeroSkeletonPairs.hpp (63 models)

跳过 89 个文件的原因是:turret、projectile、LOD 模型、spectre_hand 等非玩家骨骼。63 个有效模型覆盖了游戏中全部可玩英雄及其变体形态。

5.7 验证

替换前后 DLL 大小对比:

版本 DLL 大小 HeroSkeletonPairs.hpp
空存根 6,353,920 bytes 0 字节
BoneExtractor 生成 6,578,176 bytes (+224KB) 235KB, 63 模型

注入游戏后验证:

  • 骨骼 ESP:正常绘制英雄骨架连线
  • 自瞄:GetHitboxBones() 返回有效数据,精确锁定头部/躯干等部位
  • Code 中的 origin+Z Fallback 路径不再被触发

成功定位 4966 个 vmdl_c 条目,但字符编码问题导致解析中止。最终 BoneExtractor 工具完美解决。

六、编译环境搭建

组件 版本 作用
VS2026 Community 18.6.2 主编译器 (v143 toolset)
VS2019 Community 16.11.53 Wrapper 编译器
.NET 8.0 SDK 8.0.421 基础运行时
.NET 10.0 SDK 10.0.300 BoneExtractor
MySQL 9.3 - KeyGen 数据库

编译命令:

:: Andromeda DLL
MSBuild.exe Andromeda-DeadLock-Base.vcxproj /p:Configuration=Release /p:Platform=x64 /m

:: Wrapper
cl.exe /O2 /MT /EHsc /std:c++17 /Fe:LIVELOCK.exe src\wrapper.cpp

七、离线许可系统

7.1 卡密设计

16 字节 Payload → AES-256-CBC(随机 IV)→ Base32 → "VITT-XXXXX-..."

struct KeyPayload {
    uint32_t magic;       // 0x56495454 ('VITT')
    uint32_t days;        // 有效期,0xFFFFFFFF = Trial
    uint32_t created_ts;  // 生成时间
    uint32_t crc;         // CRC32 校验
};

7.2 激活流程设计

  • 设计这个的初衷是想到哪些卖挂的该如何设计这个模式,然后把自己能想到的设计上去,想要完美阻拦大牛肯定是不可能的,只要内存dump下来就能慢慢破解,我也没加服务器验证(因为没钱租服务器)
  • 用户输入卡密 → AES 解密 → CRC 校验
  • 首次激活:记录 {HWID, start_time, days} → DPAPI 加密存注册表
  • 后续验证:比对 HWID → 计算剩余时间
  • 防调试:6 项检测(PEB/DebugPort/DebugFlags/DebugObject/HwBp/API)→ 任一项触发 → Ban
  • 防时间作弊:系统时间 + Windows InstallDate 交叉验证 + 防回滚

7.3 KeyGen 数据库

GUI 卡密生成器,通过 libmysql C API 直连 MySQL,每张卡密记录到 deadlock.cards 表,支持查询状态。

八、总结

  • 最终效果如下
    图片描述

本次开发涉及以下技术栈:

  • 逆向: AOB 字节模式扫描、Source 2 Schema 系统、VPK 二进制解析
  • 引擎: MinHook 函数钩子、ImGui D3D11 渲染、protobuf 网络消息
  • 安全: DPAPI 注册表加密、反调试检测、时间校验、CRC32/AES-256
  • 工具链: MSVC/MSBuild、Python、.NET Core、MySQL C API

完整源代码和工具已整理归档,可作为 Source 2 引擎游戏学习的参考框架。


[培训]《冰与火的战歌:Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

最后于 4小时前 被刘宝编辑 ,原因:
收藏
免费 1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回