破解DxF多开搬砖脚本验证分析从零开始,看完你也会!
本帖最后由 a3341736201 于 2026-9-14 14:36 编辑# GO语言开发的 DxF外挂验证分析从零拆解,看完你也会!
这个是一名用户扔给我的程序文件夹开始,我一步步摸清它的验证机制,直到最后把它完全攻破。
并写出了这份文章 ,主要是靠经验,纯属捡漏!!
---
准备工具:
DBG X64
go解析工具 :GoReSym
## 开始下载:解压得出一个文件夹
下载安装后,od报错,也不知道怎么分析,然后查看pe
【tu1.png】
看不到有用的 只知道是64位后面才知道是GO语言写的,用的是 前端用的Wails 框架
然后开始先抓包
【tu2.png】
然后知道了
- 它连的是 `ip:4002`,**全程明文 HTTP**!
- 登录时发出的请求长这样(就是这次抓到的原文):
```http
POST /heartbeat HTTP/1.1
host: ip:4002
content-type: application/json
{"content":"Var=2&card=00&mac=None&soft=%E9%9A%94%E7%A6%BB%E7%99%BD%E5%9B%BE&pid=5824"}
```
- 服务器回的是一段 Base64:
```
AAAAAAAAAAAAAAAApnLBr6BD+K1BSoPcBuc6tN0rs1Fwtw+Xq+uwwdRe3XX7X*****
```
---
前期没什么有用的东西,不过后面dbg调试的时候 看到一些特征,感觉是 go语言,那如果是go语言
Go编译器通常会把所有函数名压在二进制里(pclntab 表),而且是“明文”
也就是说,哪怕没有源码,函数名全在文件里躺着,而且也没有什么加密,然后请求返回Base64 乱码,说明请求不加密、响应加密。想伪造响应,必须先搞明白这段 Base64 的加密方式。
## 第二阶段:解密base64
我们把那段 Base64 存下来(比如存成 `resp.bin`),先猜:
```powershell
# 看前 12 个字节
::FromBase64String((Get-Content resp.bin))
```
得到的结果:解码后**前 12 个字节全是 00。
【tu3.png】
这条结果说明什么呢:这是 AES-GCM 加密的典型结构——`前12字节 = nonce(随机数)+ 后面 = 密文和校验标签`。正常软件 nonce 是随机的,这里全零——作者图省事,固定了 nonce。
说明响应用的是"标准 AES-GCM",就差一把钥匙。
---
## 第三阶段:回到x64 dbg,把"钥匙"找出来
我们既然知道是GO语言开发的,然后用到一个解析工具 :GoReSym
作者开源地址:怕违规,你们自己去git搜索一下
现在知道了要干什么:找到 AES 密钥是怎么来的。这时候第一阶段发现的"Go 函数名全在"就成了知识库咯。
# .1 从函数名里找线索
Go 程序的函数名藏在一种叫 **pclntab** 的符号表里——注意:**x64dbg 自带的“符号”功能不认这种格式**(它只认微软的 PDB),所以直接在里面搜是搜不到的,要用**Go语言专门的解析工具**:
- 开源小工具 **GoReSym**(命令行跑一下,导出全部函数名 + 地址清单)
这里看官方说明直接就能解析出来
```
GoReSym.exe -json func_table.txt -d -p xxx.exe
```
然后我们拿到一个函数清单的文件,搜关键词:
- 搜 `Generate` → 出现 `GenerateKey`、`GenerateRequestParams`
- 搜 `Aes` → 出现 `AesGcmDecrypt`
- 搜 `Machine` → 出现 `GetMachineCode`
【tu4.png】
| 函数名 | 一眼看出的作用 |
|---|---|
| `GenerateKey` | 生成密钥(就是它!) |
| `AesGcmDecrypt` | AES-GCM 解密(解密服务器响应) |
| `GenerateRequestParams` | 组装请求参数 |
| `GetMachineCode` | 取机器码 |
然后都取出来了,就不一一截图了!!
这些关键词说明:验证流程的画布已经完整了——`生成密钥 → 组装请求 → 发心跳 → 解密响应`。现在只需要钻进 `GenerateKey` 看密钥怎么算。因为名字已被解析出来,偏移已换算好,直接拿去用
我们打开调试器
1. 在 x64dbg 里按 `Ctrl+G`,输入函数地址(这里是 `0x1403AE000` 的相对位置,回车。
【tu5.png】
2. 然后跑起程序断点,等他停下来,然后断到达函数开头后往下看,到这段代码
```
00000001403AE000 | 4C:8D6424 E0 | lea r12,qword ptr ss: |
00000001403AE005 | 4D:3B66 10 | cmp r12,qword ptr ds: |
00000001403AE009 | 0F86 0B010000 | jbe 棱镜工作台.1403AE11A |
00000001403AE00F | 55 | push rbp |
00000001403AE010 | 48:89E5 | mov rbp,rsp |
00000001403AE013 | 48:81EC 98000000 | sub rsp,0x98 |
00000001403AE01A | 48:898424 A8000000 | mov qword ptr ss:,rax |
00000001403AE022 | 48:89CA | mov rdx,rcx |
00000001403AE025 | 48:C1F9 3F | sar rcx,0x3F |
00000001403AE029 | 48:89C6 | mov rsi,rax |
00000001403AE02C | 48:B8 057CF36AE259D148 | mov rax,0x48D159E26AF37C05 |
00000001403AE036 | 48:F7EA | imul rdx |
00000001403AE039 | 48:C1FA 0A | sar rdx,0xA |
00000001403AE03D | 48:29CA | sub rdx,rcx |
00000001403AE040 | 48:69CA 100E0000 | imul rcx,rdx,0xE10 |
00000001403AE047 | 48:894C24 60 | mov qword ptr ss:,rcx |
00000001403AE04C | 44:0F117C24 70 | movups xmmword ptr ss:,xmm15|
00000001403AE052 | 44:0F11BC24 80000000 | movups xmmword ptr ss:,xmm15|
00000001403AE05B | 48:89F0 | mov rax,rsi |
00000001403AE05E | 66:90 | nop |
00000001403AE060 | E8 7BBACBFF | call <棱镜工作台.sub_140069AE0> |
00000001403AE065 | 48:8D0D 94902500 | lea rcx,qword ptr ds: | 解密
```
我们解读这段代码 :栈检测,Go运行时代码,业务还没开始……中间一大段:sub rsp、mov、imul、算术运算……
00000001403AE060 call sub_140069AE0 ;前面执行完SHA256或者其他前置逻辑
00000001403AE065 lea rcx,ds:; ←真正加载加密常量slice头,解密相关
```
所以我们在下面看到了他
```
00000001403AE065 | 48:8D0D 94902500 | lea rcx,qword ptr ds: | 解密
```
3. 我们直接跑过去看内存 ,ascii看到了以下内容
【tu6.png】
```
your-32-byte-secret-key-1234567890
```
嘿嘿嘿,AES 密钥的种子被硬编码在程序里。这样任何拿到这个 exe 的人,都能算出密钥
### 3.1 继续跟完整的密钥公式
然后光有种子不够,AES 密钥还得经过处理。但是继续看 `GenerateKey` 的逻辑(单步几条),注意到:
1.它调用了一次"获取时间"(时间戳,请求过 `/timestamp` 那个)
2. 时间戳经过一步运算(除以 3600 —— 也就是"取小时")之后,和种子拼接
3.拼接结果过一遍 SHA-256,得到 32 字节的 AES 密钥
【tu7.png】
然后得到的结论(先记为公式 A):
```
密钥 = SHA256( "your-32-byte-secret-key-1234567890" + 时间戳的小时形式 )
```
注意:这里拼接的是小时取整后的时间戳**(如 `1789045200`),不是原始秒、也不是小时数,因为踩了坑过来的!
这条我们总结:密钥是"种子 + 时间"算出来的,每小时变一次,但每个人都能算。到这一步,"解密"已经没有门槛了。
期待的小手,可以写出注册机了
---
## 第四阶段:AI写脚本验证 —— 真的能解密
把已知的的公式结果,交给豆包写,不吹不黑,豆包2.1的turbo 还是可以用的
### 4.1 写解密脚本
```python
import base64, hashlib
from Crypto.Cipher import AES
SEED = "your-32-byte-secret-key-1234567890"
def decrypt(b64_body, timestamp):
key = hashlib.sha256((SEED + str(timestamp // 3600 * 3600)).encode()).digest()
raw = base64.b64decode(b64_body)
cipher = AES.new(key, AES.MODE_GCM, nonce=raw[:12], mac_len=16)
return cipher.decrypt_and_verify(raw, raw[-16:])
# 用第二阶段抓到的真实响应 + 当时的服务器时间戳
print(decrypt(open("resp.bin").read(), 1789046645))
```
我的踩坑记录:第一次没解开,因为在"时间戳怎么拼"上猜错了一版(我让ai先试了"小时数"但是不行GG,应该用"小时取整后的时间戳")。改了拼接方式后:
得到的答案:解密成功,明文是:
```json
{"code": 1006, "expiration_time": "卡密不存在.", "remaining_time": "", "file_modified_time": "", "data": null}
```
这个时候我们有了 看懂响应 伪造响应的全部能力。`code=1006` 是"授权失败",跟界面上弹出的提示完全对上了
## 第五阶段:移花接木,欺骗客户端
首先 既然密钥能算,那造一个 `code=2000`(授权成功)的响应是分分钟的事。但是唯一的问题:怎么把这个假响应送到客户端手里?不可能一直开着抓包把?而且 客户端写死了连 `IP:4002`(硬编码 IP,不查域名——所以改 hosts 没用)
那就玩一个"地址占位":把这个 IP 绑到本机,客户端以为在连服务器,其实连的是我们,不用带端口:
```powershell
# 需要管理员。把目标 IP 绑到本机的 Loopback 接口(物理网卡完全不受影响)
netsh interface ip add address name="Loopback Pseudo-Interface 1" ip 255.255.255.255
```
顺便写个端口返回器:浏览器打开 `**:4002/timestamp`——如果此时本机服务器已经起来、你看到的是自己服务器返回的 `{"timestamp":...}`,说明流量已经"截胡"成功。
【tu8.png】
然后写一个 ,写个几十行的小服务,监听 4002,对客户端的一切请求都回"我们造的响应":
1 收到 `GET /timestamp` → 返回当前时间戳
2 收到 `POST /heartbeat` → 用 上面保留的 的公式造一个 `code=2000` 的加密响应返回
```
def cmd_demo(_args) -> int:
hr("DEMO · 伪造服务器 + 模拟请求客户端 ")
state = {"i": 0}
srv = start_server(0, state, days=3650, data={}) # 端口0随机的
port = srv.server_address
print(c(" 客户端 GET /timestamp (明文 HTTP, V-02)", "1;36"))
import urllib.request
ts_body = urllib.request.urlopen(f"http://127.0.0.1:{port}/timestamp", timeout=5).read()
server_ts = json.loads(ts_body)["timestamp"]
print(f" <- {{\"timestamp\":{server_ts}}}")
print(c(" 客户端 POST /heartbeatbody={{\"content\":\"Var=2&card=00&mac=None&...\"}}", "1;36"))
req = urllib.request.Request(
f"http://127.0.0.1:{port}/heartbeat",
data=json.dumps({"content": "Var=2&card=00&mac=None&soft=%E9%9A%94%E7%A6%BB%E7%99%BD"
"%E5%9B%BE&pid=8052"}).encode(),
headers={"Content-Type": "application/json"}, method="POST")
body = urllib.request.urlopen(req, timeout=5).read().decode()
print(f" <- {body[:60]}...({len(body)} B Base64, nonce=全零x12)")
```
然后我们跑一下,看看是否登录成功
【tu9.png】
完美 正常进入,因为我不会 DNF 我不知道如何用 就不测试了 ,就只是简单复现
#总结
为什么能成功破解 首先我 不是攻破了什么高深加密,而是作者把每一环都做"省"了——种子密钥写死、时间可预测、流量明文、响应无条件信任。加密算法再标准,钥匙插在锁上就等于没有锁。
建议修复思路(能防住我这种菜鸟):
1.首先流量上 TLS
2.授权改成服务器签名、客户端只留公钥
3.功能层加真实会话。
4.能加V,能加壳只管上
5.什么恶心都只管上,反正我后面不会再看了hhh
最后如果对你有学习帮助 请给我 点赞+评分 谢谢!
另外如果有困惑的程序 可以留言,我下课后 如果能玩的动
我会积极的写出一篇教程文章,努力活跃论坛气氛
练手程序下载地址:链接:https://share.weiyun.com/s6PECfJw 密码:isgkxa
调试请在虚拟机哦!!!
--- 大佬,这种无人直播的软件激活能破解吗
软件下载链接:https://pan.quark.cn/s/28d47f3dcab6
视频软件链接:https://www.bilibili.com/video/BV1kUYK66EBh go语言啊。 DNF这个游戏以前挺火的,有些科技作者确实赚了一大笔。前几天B站看新闻刷到过,现在这游戏官方好像都直接卖装备 礼包了(数值). 虽然看的不是特别明白,但是还是要谢谢 这游戏还用G?没啥玩头了。。早就不玩了。。。早些年还有点意思现在改的没谁了。。
这G有啥功能弄的这样还加密 是验证模块用go还是整体?业界水平提高了? go语言写的这玩意的确是十分少见 kilkilo502 发表于 2026-9-13 22:53
这游戏还用G?没啥玩头了。。早就不玩了。。。早些年还有点意思现在改的没谁了。。
这G有啥功能弄的这样还 ...
准确来说,是多开搬砖脚本, 先收藏再观看,养成好习惯 感谢分享,看的很有意思,以前也用G玩过DNF,结果被盗号了,哈哈 多谢lz的分析。清晰明了
页:
[1]
2