a3341736201 发表于 2026-9-12 20:56

记录一次我把VB6 小游戏的存档扒光

本帖最后由 a3341736201 于 2026-9-12 21:52 编辑

# 记录一次我把VB6 小游戏的存档扒光

先说结论:这游戏一共俩文件,一个 几十KB 的 `Test.exe`,一个 俩排多来字节的 `log.txt`。就这点东西,我断断续续折腾了好几天,中间数次都想直接放弃,但是弄不出来,晚上睡觉都不得劲,上课的时候 都在想解密方法,最后是终于理顺了【作者在存档里埋了个校验和、一个彩蛋、两处随机数、还有对玩家姓名的一点小心思。200 个字符,信息密度顶我一整个周末】
写篇流水文章记录一下,文末有全套工具和完整格式表,想直接抄作业的往后翻。但是记得给我加评分
练手程序:::https://wwaxn.lanzout.com/ioBoQ47tw5eh 密码:7j8v
---

## 一、案发现场

游戏叫《进制转换Go》,一个练进制转换的小练习题集,VB6 做的,很怀旧。玩法就是弹个数,让你转成另一个进制打进去。

我对它动手的动机很朴素:想改分。

结果第一天就碰壁了。这存档长得实在不讲武德:

- 大小 250 字节上下飘(247~259 都见过)
- 但你按字符数,永远是 200 个字符 + 一个 CRLF
- 内容既不是明文也不是规整的二进制,而像是把三种东西硬揉在一起

而且它防篡改防得很认真:改任意一个字节,再进游戏读档,它直接甩你一句

```
数据校验错误,请选择重新开始
```

好嘛,那就走流程吧。先刷样本。

---

## 二、啥也不说,先刷他 19 份存档

手动点最稳:新建角色 → 起名字 → 退出 → 点"是"保存。我点到第七个的时候开始怀疑人生,后来写了个自动化脚本替我点,开始踩坑了,后面懒得自己写,直接豆包干(PowerShell + SendInput 模拟真实按键,这里略过不表,都是泪)。

最后攒出十九份有效样本,覆盖不同姓名、不同游玩程度。然后把文件拖进 Python,逐字符铺开对齐看。

第一眼发现的东西是这位:

```
第 27 位(数字块内计数)永远是 '1'。
```

换了几份全是 1。我一度兴奋:这不会是版本号吧?

后来发现,第 49 位也永远是 '1'。

再后来,把 19 份样本按列做统计,才发现真相远比版本号有喜感——这先按下不表,往后看。

---

## 三、啃最硬的骨头:20 个校验字符

文件最后 20 个字符是校验区。这玩意挡了我最久。

断断续续逆了一晚上,把算法抠出来了。核心长这样:

```
对 i = 1..20:
    acc = 0
    for j = i .. i+40:
      acc = (acc + 109 + Asc(S) % 67 + Asc(S) % 23 + Asc(S) % 19) % 96
    校验字符 = Chr(48 + acc)
```

S 是文件前 180 个字符。用 Python 抄一遍就能验证:

```python
def srem(v, d):
    # VB6 的 Mod 是"向零截断",余数符号跟着被除数走
    r = abs(v) % d
    return r if v >= 0 else -r

def checksum(codes):# codes: 前 180 个字符的字符码
    out = []
    for i in range(1, 21):
      acc = 0
      for j in range(i, i + 41):
            v1, v2, v3 = codes, codes, codes
            acc = srem(acc + 109 + srem(v1, 67) + srem(v2, 23) + srem(v3, 19), 96)
      out.append(48 + acc)
    return out
```

拿全部有效样本跑:**20/20 个校验字符全中**。

为了反向验证,我还做了几份"变异样本":单字节改动后,失配数的分布是这样的——

| 变异位置 | 校验区失配数 |
|---|---|
| 前缀区(改 1 字节) | 20 / 20 |
| 前缀区(另一处) | 15 |
| 校验区自己(只改 1 个) | 1 |

跟"这个位置被几个滑窗覆盖"的数学推算是完全对上的。到这里我基本可以宣布:**这校验算法已经姓我了**。

顺带说几个 VB6 的脏细节,坑了我一下午:

- `Asc()` 对双字节汉字返回的是 16 位编码(有符号),所以 `%67` 这类运算要做**有符号余数**;
- 生成的校验字符如果落在没法构成合法字符的值上,写进文件时会变成 `'?'`——不是报错,是静默替换,读档时也按 `'?'` 重算,所以依然自洽。

---

## 四、然后我发现了"你猜呢" 感觉被嘲笑了!

继续拆前面的部分。文件前 60 个字符是一坨单双字节混排的"乱码"。我寻思这大概就是加密后的姓名。

把 60 个字符按位置 mod 3 分成三组,各自统计分布,看聚簇:

```
组0的中心 ≈ 45762
组1的中心 ≈ 50392
组2的中心 ≈ 50403
```

这三个数字看着眼熟。丢进 GBK 一查——

- 45762 = 猜
- 50392 = 呢
- 50403 = 你

**好家伙。**

填充料是"你猜呢"三个字,逐字做了 ±50 的随机偏移,重复 20 组硬凑满 60 个字符。

我又回去翻了翻 exe 的字符串表,果然在启动逻辑里躺着一句 `你猜呢`。行吧,作者是个有意思的人。这仨字我现在看着都觉得它在嘲笑我。

---

## 五、姓名到底藏哪了

彩蛋归彩蛋,玩家姓名还是得存的。继续对着样本找,发现规律很粗暴:

**每隔 5 个字符,插一个姓名原文汉字。** 完全没加密,明文。

比如你的名字叫"张伟",那么在某个 5 步长的位置序列上,会依次出现 `张`、`伟` 两个汉字;"测试小王"就是四个字各就各位。

不过这个序列的起点是随机的——它来自保存时生成的一个随机数 m(后面还会出场):

```
姓名插入链起点 = (4 − m) mod 60,m = Int(Rnd()*30) + 5
```

所以每次保存,汉字的位置都会换地方。之前我有一瞬间觉得"汉字怎么以大概 10 字节的间隔散布",答案就是这个——一个汉字占 2 字节,加上空隙正好。

---

## 六、数字块:一场精心设计的填空游戏

中间 120 位数字是重头戏。用列统计法一照(19 份样本逐列数,看哪列的值域窄得不像随机数),发现了 7 个"受控位":

| 位置(文件字符) | 内容 | 实测 |
|---|---|---|
| 第 78–79 位 | 随机字段 n 的两位十进制数 | n = Int(Rnd()*20)+8,全样本落在 |
| 第 87 位 | 恒为 `'1'` | 就是你最早发现的"第 27 位" |
| 第 96 位 | 姓名长度 | "测试小王"→4、"测试"→2 |
| 第 109 位 | 恒为 `'1'` | 同胞兄弟 |
| 第 116–117 位 | 随机字段 m 的两位十进制数 | m = Int(Rnd()*30)+5,落在 |

而其余的位置也不是自由发挥——反汇编里能直接看到它做了 6 段固定切片(代码里明晃晃写着 1/17、20/7、28/8、37/12、50/6、58/3 这几组字面量),切片之间正好被上面这 7 个位置隔开。拼满 60 位,一个不多一个不少。

好玩的是那个 m:我是先从不相关的线索(姓名链的起点)把 m 反推出来,再拿它去撞第 116–117 位那两位数字——**全中**。两条独立证据链咬死在同一个随机数上,这种瞬间是干这行最上头的时刻。

---

## 七、幽灵批次:S1 到 S8 的诡异事件

期间我用批处理脚本又造了 8 份样本(S1~S8),结果这 8 份行为怪怪的:

- 第 96 位(姓名长度)恒为 3
- 姓名链里全是没见过怪字

一度怀疑游戏还有隐藏分支。追了半天,把 S1 的姓名链字符按顺序连成字节:

```
E6 B5 8B E8 AF 95
```

眼熟。这是 **"测试"两个字的 UTF-8 编码**。

案情还原:那个批处理脚本存成了 UTF-8 无 BOM,PowerShell 5.1 按 GBK 读脚本,把 `"测试"` 读成了 `"娴嬭瘯"`,然后把这三个字当姓名输进游戏了。游戏很无辜地把它存了下来,长度 3,于是"第 96 位恒 3"。

我盯着屏幕笑了半分钟。合着不是游戏的隐藏机制,是我自己脚本的锅。

各位 PowerShell 5.1 用户记住了:**中文脚本要么存 GBK,要么带 BOM,不然你永远不知道自己输进去的是什么**。

---

## 八、最后一厘米:上调试器,逮到那个 0

前面还留了个尾巴:第二段数字块的地方,反汇编里有一段逻辑——当某个数 N 大于 0 时,会每隔 6 位写一个 `(n + N) % 10` 的受控数字。

但奇怪的是:所有没玩过游戏的存档里,这段逻辑像从来没生效过,数字块纯随机。

那 N 到底是多少?代码说:它来自某个控件的文本,用 `R8ValFromBstr` 解析。反正不是 0 就是什么别的。

我盯着"某个控件"四个字看了很久,最后决定不猜了,直接抓现行。

用 64 位 Python 写了个迷你调试器去调这个 32 位小程序(中间踩的 WOW64 坑足够单开一篇:`DEBUG_EVENT` 的 union 在 64 位下偏移是 16 不是 12、32 位代码的断点异常码是 `0x4000001F` 不是 `0x80000003`、`WOW64_CONTEXT` 里 Eip 在 0xB8……),在写档函数里那个 `fstp ` 后面下了个断点。

游戏被我骗着正常操作了一遍,关窗、确认、写档——啪,断点命中。

```
N = = 0.0
被解析的文本 = "0"   (直接从 BSTR 里读出来的)
```

**N = 0,实锤。** 没玩过的存档里,这个数字就是 0,那个 `N > 0` 的分支压根不执行,所以第二段数字块是 100% 纯随机——之前拟合不上的谜题到此闭合。

顺带这也解释了那两个恒 `'1'`:它们是 `Chr(Len(该显示文本)+48)` 算出来的,显示文本现在是单字符 `"0"`,所以是 `'1'`。

**先立个预言**:只要有办法让那个显示的数字变成两位数(比如玩一会儿让它涨上去),这两处就应该变成 `'2'`。评论区交给你们验证,验证错了欢迎来打我脸。

---

## 九、完整格式表(抄作业区)

梳理一下,200 个字符 = 60 + 120 + 20:

| 区间 | 内容 |
|---|---|
| 第 1–60 字符 | "你猜呢"三字的 ±50 随机编码 × 20 组 + 姓名明文每 5 位一个(起点随机) |
| 第 61–180 字符 | 两段 60 位数字流;第一段 = 6 段切片 + 7 处插入(两个随机字段的十进制、姓名长度、两个恒 '1')|
| 第 181–200 字符 | 20 个校验字符(滑窗 41 次累加 mod 96,见上文算法)|

校验算法就不重复贴了,往上翻第三节。

配套工具我也写好了(Python,无依赖):

```
python save_tool.py check你的.log   # 校验完整性
python save_tool.py dump   你的.log   # 铺开看 60/120/20 三段明细
python save_tool.py checkdir 目录      # 批量校验
```

拿游戏里存的档直接跑,PASS/FAIL 一目了然。改过字节的档全被检出,和游戏里的报错行为完全一致。

比如把其中一份铺开看:

```
数字块 120 位:
   +0: 623520763250404991058165131970542164515823961895110782034367
   + 60: 466692762256324092454907233323774056792546334429025152172574
   受控位 idx=17 (文件第 78 字符): 1← n 的十位
   受控位 idx=26 (文件第 87 字符): 1← 恒 '1'
   受控位 idx=35 (文件第 96 字符): 4← 姓名长度
   解码: n==10 (在 范围内 ✓)   m==34 (在 范围内 ✓)
校验块: fNYz?PgJ@2OQe4P?GKUC
校验:PASS(20/20 校验字符一致)——数据完整
```
当然还有改分器一并打包给你们 在一个压缩包!!


---

## 十、后记

回头看看这 200 个字符:

一个彩蛋("你猜呢")、一个校验和、两处随机数、一处姓名长度,还有一段给"玩过的玩家"准备的受控数字——但这游戏主要是给没玩过的人做练习的,那段逻辑大部分人一辈子也碰不到几次。

你说这算加密吗?算不上,全场最大的"密钥"是三个字的彩蛋。

你说这不加密吧,它又能防住所有想改分数的人。

这大概就是 VB6 年代小游戏作者的一种浪漫:明明就是个练习小程序,存档还是要给你整上全套仪式感。

最后,感谢这位不知名的作者,也感谢你没给游戏加壳——不然这篇文章就没有后半段了。

---

最后,感谢关注+评分,期待有更多好的作品文章能够带给大家,谢谢!

BCDHY 发表于 2026-9-12 21:03

太强了{:1_921:}思路方法和过程都很详细,受教了{:1_893:}

Turbo86 发表于 2026-9-12 21:12

感谢大佬

zdyzzgdnnyx 发表于 2026-9-12 21:29

lz,如果用ce行不行,这个好难啊

a3341736201 发表于 2026-9-12 21:39

zdyzzgdnnyx 发表于 2026-9-12 21:29
lz,如果用ce行不行,这个好难啊

没试过,不过理论 可以,改了之后 ,保存,校验只要对了,就可以成功

semiuel 发表于 2026-9-12 21:45

很久以前我们用FPE2000修改游戏。

fqhs 发表于 2026-9-12 22:18

这么厉害?受教了

why12 发表于 2026-9-13 07:06

这个可以,思路清晰!

vtf007 发表于 2026-9-13 11:30

学习下思路,受益匪浅

nig0414 发表于 2026-9-13 12:17

感谢思路!让我想起之前没改成的一个游戏,回头有时间再改改看
页: [1] 2
查看完整版本: 记录一次我把VB6 小游戏的存档扒光