吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1236|回复: 9
上一主题 下一主题
收起左侧

[原创] 破解DxF多开搬砖脚本验证分析从零开始,看完你也会!

[复制链接]
跳转到指定楼层
楼主
a3341736201 发表于 2026-9-13 20:34 回帖奖励
本帖最后由 a3341736201 于 2026-9-14 14:36 编辑

GO语言开发的 DxF外挂验证分析从零拆解,看完你也会!

这个是一名用户扔给我的程序文件夹开始,我一步步摸清它的验证机制,直到最后把它完全攻破。
并写出了这份文章 ,主要是靠经验,纯属捡漏!!


准备工具:
DBG X64
go解析工具 :GoReSym

开始下载:解压得出一个文件夹

下载安装后,od报错,也不知道怎么分析,然后查看pe


【tu1.png】
看不到有用的 只知道是64位后面才知道是GO语言写的,用的是 前端用的Wails 框架
然后开始先抓包

【tu2.png】
然后知道了

  • 它连的是 ip:4002全程明文 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),先猜:

# 看前 12 个字节
[Convert]::FromBase64String((Get-Content resp.bin))[0..11]

得到的结果:解码后**前 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 → 出现 GenerateKeyGenerateRequestParams
  • 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:[rsp-0x20]         |
    00000001403AE005 | 4D:3B66 10               | cmp r12,qword ptr ds:[r14+0x10]         |
    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:[rsp+0xA8],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:[rsp+0x60],rcx         |
    00000001403AE04C | 44:0F117C24 70           | movups xmmword ptr ss:[rsp+0x70],xmm15  |
    00000001403AE052 | 44:0F11BC24 80000000     | movups xmmword ptr ss:[rsp+0x80],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:[0x140607100]      | 解密

    我们解读这段代码 :栈检测,Go运行时代码,业务还没开始……中间一大段:sub rsp、mov、imul、算术运算……
    00000001403AE060   call sub_140069AE0      ;前面执行完SHA256或者其他前置逻辑
    00000001403AE065   lea rcx,ds:[0x140607100]; ←真正加载加密常量slice头,解密相关

    所以我们在下面看到了他

    00000001403AE065 | 48:8D0D 94902500         | lea rcx,qword ptr ds:[0x140607100]      | 解密

  3. 我们直接跑过去看内存 ,ascii看到了以下内容 【tu6.png】
    your-32-byte-secret-key-1234567890

嘿嘿嘿,AES 密钥的种子被硬编码在程序里。这样任何拿到这个 exe 的人,都能算出密钥

3.1 继续跟完整的密钥公式

然后光有种子不够,AES 密钥还得经过处理。但是继续看 GenerateKey 的逻辑(单步几条),注意到:

1.它调用了一次"获取时间"(时间戳,请求过 /timestamp 那个)

  1. 时间戳经过一步运算(除以 3600 —— 也就是"取小时")之后,和种子拼接
    3.拼接结果过一遍 SHA-256,得到 32 字节的 AES 密钥
    【tu7.png】

    然后得到的结论(先记为公式 A):
密钥 = SHA256( "your-32-byte-secret-key-1234567890" + 时间戳的小时形式 )

注意:这里拼接的是小时取整后的时间戳**(如 1789045200),不是原始秒、也不是小时数,因为踩了坑过来的!
这条我们总结:密钥是"种子 + 时间"算出来的,每小时变一次,但每个人都能算。到这一步,"解密"已经没有门槛了。
期待的小手,可以写出注册机了


第四阶段:AI写脚本验证 —— 真的能解密

把已知的的公式结果,交给豆包写,不吹不黑,豆包2.1的turbo 还是可以用的

4.1 写解密脚本

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[12:-16], raw[-16:])

# 用第二阶段抓到的真实响应 + 当时的服务器时间戳
print(decrypt(open("resp.bin").read(), 1789046645))

我的踩坑记录:第一次没解开,因为在"时间戳怎么拼"上猜错了一版(我让ai先试了"小时数"但是不行GG,应该用"小时取整后的时间戳")。改了拼接方式后:

得到的答案:解密成功,明文是:

{"code": 1006, "expiration_time": "卡密不存在.", "remaining_time": "", "file_modified_time": "", "data": null}

这个时候我们有了 看懂响应 伪造响应的全部能力。code=1006 是"授权失败",跟界面上弹出的提示完全对上了

第五阶段:移花接木,欺骗客户端

首先 既然密钥能算,那造一个 code=2000(授权成功)的响应是分分钟的事。但是唯一的问题:怎么把这个假响应送到客户端手里?不可能一直开着抓包把?而且 客户端写死了连 IP:4002(硬编码 IP,不查域名——所以改 hosts 没用)
那就玩一个"地址占位":把这个 IP 绑到本机,客户端以为在连服务器,其实连的是我们,不用带端口:

# 需要管理员。把目标 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[1]

    print(c("  [1] 客户端 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("  [2] 客户端 POST /heartbeat  body={{\"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

调试请在虚拟机哦!!!

免费评分

参与人数 5吾爱币 +5 热心值 +2 收起 理由
weidechan + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
lsb2pojie + 1 + 1 我很赞同!
zcyx + 1 我很赞同!
hero0189 + 1 用心讨论,共获提升!
_Bitter + 1 + 1 感谢最近正研究go语言咋分析呢

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
忧郁之子 发表于 2026-9-13 20:49
虽然看的不是特别明白,但是还是要谢谢
3#
kilkilo502 发表于 2026-9-13 22:53
这游戏还用G?没啥玩头了。。早就不玩了。。。早些年还有点意思现在改的没谁了。。
这G有啥功能弄的这样还加密
4#
BrutusScipio 发表于 2026-9-13 23:59
5#
xiaoyixian 发表于 2026-9-14 01:51
go语言写的这玩意的确是十分少见
6#
a19852 发表于 2026-9-14 09:26
go语言啊。 DNF这个游戏以前挺火的,有些科技作者确实赚了一大笔。前几天B站看新闻刷到过,现在这游戏官方好像都直接卖装备 礼包了(数值).
7#
 楼主| a3341736201 发表于 2026-9-14 09:46 |楼主
kilkilo502 发表于 2026-9-13 22:53
这游戏还用G?没啥玩头了。。早就不玩了。。。早些年还有点意思现在改的没谁了。。
这G有啥功能弄的这样还 ...

准确来说,是多开搬砖脚本,
8#
wuaipipixia888 发表于 2026-9-14 22:31
先收藏再观看,养成好习惯
9#
li000yu 发表于 2026-9-14 22:39
感谢分享,看的很有意思,以前也用G玩过DNF,结果被盗号了,哈哈
10#
blyizhi 发表于 2026-9-15 08:37
多谢lz的分析。清晰明了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-9-15 11:03

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表