首页
社区
课程
招聘
[原创]GameGuard NP CS_Auth3协议
发表于: 2026-8-10 00:09 1245

[原创]GameGuard NP CS_Auth3协议

2026-8-10 00:09
1245

虽然GameGuard 或者说棒子那边的反外挂已经基本上距离ACE/BE水平还差5个EAC?
但是做为技术分析还是有点用?
毕竟换汤不换药

CSAuth3 是 GameMon(客户端反作弊)和对端之间的心跳。问答循环:对端发挑战包,客户端回心跳包,断了就判掉线。

头部明文(KDF解密后):

两端头相同(同 K=0x91284712, counter=0)。客户端首包载荷 8 字节全 0,空载荷不 scramble。

首包 K=0x91284712,密钥流 = 12 47 28 91 24 8E 50 24 36 D5 78 B3 48 1C A1 44
首包 seed=0(包计数器清零后),后续包 seed=counter。

76B 首包: crc1(pkt[0:74]) = 0x518B = 存储值 8B 51

key 派生:

76B 首包: crc2(解密头+descramble后载荷, 0x96E8) = 0x5B67 = 存储值。注意 CRC2 算的是 descramble 之后的载荷,所以构造包时必须先 scramble 再算 CRC2。

载荷用 Blowfish 变换

scramble 用解密方向(P 逆序),逐 8 字节块就地变换:

收包 descramble 与发包 scramble 用同一个 bf_dec(解密方向),因为 Blowfish 解密是加密的逆;发送方把明文 TLV 跑一遍 bf_dec 得到线上密文,接收方再跑一遍 bf_dec 还原——所以"scramble"和"descramble"是同一函数,靠 Blowfish 的加解密对称性工作。

密钥写死、常量是公开的 Blowfish π,P/S 编排结果确定,所以 scramble 能离线复现,不依赖运行时状态。

发包后对端返回的错误码(抓包/自己黑盒测试不一定对,我自己的理解):

msg_count(头 word2)= 期望消息数。

会话状态结构(双端各自维护一份):

首包(counter=0):

后续包(counter≠0):

拿首包(counter=0)跑一遍识别:

载荷需含至少消息类型1(同步包大小),否则后续 CRC2 key 源对不上。

CSAuth3 心跳是一套自包含的对称协议,全部密码学材料都是硬编码常量:

唯一随会话变化的是 counter(驱动 KDF 和 CRC2 key)与 prev_size(由消息1同步)。其余全是固定常量。因此只要拿到首包挑战、复现密钥编排,就能纯脱机心跳
State 1456 会根据版本协议 老版本应该有0x61x系列,auth2 应该是16byte
但是gameguard应该是已经摆烂了,这个分析是基于24年 但是一直更新只是驱动稳定性问题

客户端有个callback 里面根据参数 然后收到挑战包 然后客户端自己走自己发送发过去
那么你注入进去hook到这个callback handler 就知道发什么了,转发一下也可以

含义
0xBAE 首包标志(正常)
0xBB8 CRC2 不匹配(映射模式基值)
0xCE5 seq > 5
0xCE8 CRC2 不匹配(严格)
0xCE9 CRC1 不匹配
0xCEA magic ≠ 0x1E
0xCEC size < 16
0xCEE size > 0x1000
0xCEF size < 16(body)
0xD4A body 未 8 对齐
0xD52 消息数不符
0xD67 消息类型2 失败
0xD7A 消息类型11 内容不符
0xD913 消息类型1 哈希失败
≥0xBB8 置错误标志 → 后续 Check 假通过、Get 停发
type 含义 关键操作
1 同步 hash = value² × msg.word2 × 0x622B60B;同步下个包大小(CRC2 key源)
2 认证 设 ID/IP;查表校验
3 日志
10 配置
11 查表 索引 ≤ 50
12 列表 ≤10 项 × 10 字节
13 扩展 按模式分支
20-24 状态 写状态字段,含 magic 0x9128/0x8757/0x4813
size magic seq msg_count flags protocol crc1 crc1_ok
对端 76B 76 0x001E 1 3 0x0002 0 0x518B 算出来一致
客户端 28B 28 0x001E 1 3 0x0002 0 0x0BEB 算出来一致
环节 算法 密钥来源
头加密 XOR 流密码, 密钥流 {K,2K,3K,4K} K = KDF(counter), counter=0 时 K=0x91284712
载荷 scramble Blowfish (解密方向) 固定密钥 0x9F5A8603/0x7E72A330 + 标准 π 常量
CRC1 CRC-16, poly 0x0FAE, xor-then-shift 无密钥
CRC2 CRC-16, shift-then-xor, keyed counter=0 时 0x96E8; 否则 prev_size 派生
客户端                          对端
  GameMon                        CSAuth3 实例
    │  ◄──── 挑战包 ──────────────│  Get
    │  ────── 心跳包 ───────────►│  Check
    └──────── 循环 ──────────────┘
客户端 GameMon                        对端 CSAuth3
     │   counter=0, prev_size 初始         │
     │                                     │
     │  ◄──── 挑战包 (头16+载荷56+CRC2+CRC1) │
     │                                     │
     │  ① 解密头: KDF XOR, K=derive(counter)│
     │  ② 校验 magic / seq / CRC1          │
     │  ③ descramble 载荷 (Blowfish 解密方向)│
     │  ④ 解析 TLV 消息, 更新 state          │
     │  ⑤ 取消息1的 prev_size (下轮 CRC2 key)│
     │  ⑥ 构造心跳包载荷 (8 对齐)            │
     │  ⑦ scramble 载荷 (空载荷跳过)         │
     │  ⑧ 算 CRC2 / CRC1                    │
     │  ⑨ 加密头: KDF XOR, K=derive(counter)│
     │                                     │
     │  ──── 心跳包 (头16+载荷+CRC2+CRC1) ──► │
     │                                     │
     │           counter++                 │
     │                                     │
     └──── 循环, counter 递增驱动 KDF 与 CRC2 key
客户端包:
┌────────┬──────────────┬───────┬───────┐
│ 头 16B │ 载荷 (8对齐)  │ CRC2  │ CRC1  │
│ KDF加密│ scramble+TLV  │ 2B    │ 2B    │
└────────┴──────────────┴───────┴───────┘
                        ↑size-4 ↑size-2

对端包: 同结构 (头16 + 载荷56 + CRC2 + CRC1)
struct CsAuth3Header {
    uint16_t magic;      // [0-1]  0x001E
    uint16_t seq;        // [2-3]  序号, ≤5
    uint16_t msg_count;  // [4-5]  期望消息数
    uint16_t flags;      // [6-7]  bit0=错误码映射 bit2=首包
    uint8_t  protocol;   // [8]    协议号 (首包=0)
    uint8_t  pad[7];     // [9-15]
};
【对端发来】 76B
0C 47 29 91 27 8E 52 22 36 D5 78 B3 48 1C A1 44   <- 头(密文)
C5 83 18 C9 C8 F8 43 40 06 70 09 20 B8 79 4C A9   <- 载荷(scramble后)
28 4D 4D 3B FA CB E7 9D 10 96 F2 AC E1 23 08 CB
CB E4 91 8F 92 37 C6 C1 9B 78 E2 B9 47 F9 98 F9
39 A0 BB 9F 36 4C CE 6F                          <- 载荷尾
67 5B                                            <- CRC2
8B 51                                            <- CRC1

解密头 (XOR K=0x91284712):
  1E 00 01 00 03 00 02 00 00 00 00 00 00 00 00 00
  magic=0x001E seq=1 msg_count=3 flags=0x0002(首包) protocol=0
CRC1 算出来 0x518B,包里存 8B 51,对得上
descramble 载荷 (Blowfish) → 明文 TLV
CRC2 算出来 0x5B67,覆盖「解密头 + descramble 后载荷」

【客户端回发】 28B
0C 47 29 91 27 8E 52 22 36 D5 78 B3 48 1C A1 44   <- 头(同K, 同counter)
00 00 00 00 00 00 00 00                          <- 载荷(8字节全0, 空载荷不scramble)
00 00                                            <- CRC2 = 0
EB 0B                                            <- CRC1

解密头: 同对端包头
CRC1 算出来 0x0BEB,包里存 EB 0B,对得上。
CRC2 = 0x0000(首包空载荷,下面讲)。
uint32_t derive_K(uint32_t seed) {
    uint32_t K = (seed == 0) ? 0x91284712 : seed;
    if (K >= 100) K %= 100;
    if (K % 100 != 0) {
        for (int i = 0; i < K % 100; i++) {
            K = K * 0x27D2 - ((uint64_t)K * 0x76C4E92B >> 16) * 0x55D49D5A;
            if (K == 0) K = 0x7FFFFFFF;
        }
    }
    return K;
}

void encrypt_header(uint8_t plain[16], uint32_t K, uint8_t out[16]) {
    uint32_t key[4] = { K, 2*K, 3*K, 4*K };
    for (int i = 0; i < 16; i++)
        out[i] = plain[i] ^ ((uint8_t*)key)[i];
}

传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!

最后于 2026-8-10 07:00 被MaMy编辑 ,原因:
收藏
免费 5
打赏
分享
最新回复 (2)
雪    币: 3839
活跃值: (4273)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2026-8-13 10:58
0
雪    币: 56
活跃值: (551)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3

非常好的文章,我在搞一个非常老的游戏,用的np,这篇文章我丢给ds,他说:

(游戏名)虽是 UE2 老游戏,但它的 GameGuard 一直跟着 nProtect 更新到 2024 年(驱动模块 npggNT 甚至更新到 2024.10.11,比 GameMon 还新)。文章作者那句"gameguard 已经摆烂了,一直更新只是驱动稳定性问题"与(游戏名)的实际情况完全吻合。

他还说

两个印证点:

 1. 网络心跳确认存在(WS2_32 + DNSAPI + IPHLPAPI),与文章"对端↔客户端心跳"吻合。

 2. 导入表里没有 CryptoAPI 加密函数(没有 CryptEncrypt/CryptDecrypt)→ 说明 Blowfish / CRC 全部是 GameMon 内置实现,印

    证文章"密钥写死、标准 Blowfish π 常量、可离线复现"的说法。

说真的我在这方面没什么经验,感谢作者的文章,我学习学习。

2026-8-14 11:23
0
游客
登录 | 注册 方可回帖
返回