1355 字
7 分钟
2026 御网杯 线上 像素中的秘密 image_05.zip Writeup

像素中的秘密 image_05.png — 详细 Writeup#

题目信息#

项目内容
附件image_05_2.zip → 解压得到 image_05.png
文件大小245 字节 (0xF5)
题目类型Misc — PNG 文件尾部隐写 + LCG 流密码 + Base62 编码
Flagflag{file_carving_skill}

1. PNG 文件结构分析#

1.1 完整 Hex Dump#

$ xxd image_05.png
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR
00000010: 0000 0040 0000 0040 0802 0000 0025 0be6 ...@...@.....%..
00000020: 8900 0000 7c49 4441 5478 9cd5 ce41 1100 ....|IDATx...A..
00000030: 2000 c3b0 52ff 9e87 081e 5ca3 2067 1b65 ...R.....\. g.e
00000040: 1227 7112 2771 1227 7112 2771 1227 7112 .'q.'q.'q.'q.'q.
00000050: 2771 1227 7112 2771 1227 7112 2771 1227 'q.'q.'q.'q.'q.'
00000060: 7112 2771 1227 7112 2771 1227 7112 2771 q.'q.'q.'q.'q.'q
00000070: 1227 7112 2771 1227 7112 2771 1227 7112 .'q.'q.'q.'q.'q.
00000080: 2771 1227 7112 2771 1227 7112 2771 1227 'q.'q.'q.'q.'q.'
00000090: 7112 2771 1227 7112 2771 1227 71fe 0ebc q.'q.'q.'q.'q...
000000a0: ba72 aa03 7d65 2883 7300 0000 0049 454e .r..}e(.s....IEN
000000b0: 44ae 4260 8200 0000 0069 cb34 419d 2dc8 D.B`.....i.4A.-.
000000c0: 55e9 4ba2 788d 485e 3c7b 34fc a298 62fa U.K.x.H^<{4...b.
000000d0: e7b7 b6d2 d511 59b7 beab 50c6 0a75 3b5e ......Y...P..u;^
000000e0: 2540 9f72 2974 43c6 6de8 275a f19c 4b2e %@.r)tC.m.'Z..K.
000000f0: b590 af42 b9 ...B.

1.2 PNG 块 (Chunk) 逐块解析#

PNG 文件由 文件签名 (8字节) + 若干数据块 组成,每个块结构为:

┌──────────┬──────────┬──────────────┬──────────┐
│ 长度 (4B) │ 类型 (4B) │ 数据 (N字节) │ CRC32(4B) │
└──────────┴──────────┴──────────────┴──────────┘

逐块遍历:

块序号类型偏移数据长度CRC32说明
—签名0x00008—89 50 4E 47 0D 0A 1A 0A PNG魔数
1IHDR0x0008130x250BE689图像头:64×64像素,8位色深
2IDAT0x00211240x65288373压缩图像数据
3IEND0x00A900xAE426082PNG结束标志块
⚠隐藏0x00B564—❗ IEND后不应有数据

关键发现:PNG 规范要求 IEND 是文件的最后一个块,但此文件 IEND 块结束于 0xB4,其后仍有 64 字节 额外数据,说明存在隐写。


2. 拆分隐藏数据 (File Carving)#

IEND 块后 64 字节完整 hex:

0x00B5: 00 00 00 00 69 CB 34 41 9D 2D C8 55 E9 4B A2 78 ....i.4A.-.U.K.x
0x00C5: 8D 48 5E 3C 7B 34 FC A2 98 62 FA E7 B7 B6 D2 D5 .H^<{4...b......
0x00D5: 11 59 B7 BE AB 50 C6 0A 75 3B 5E 25 40 9F 72 29 .Y...P..u;^%@.r)
0x00E5: 74 43 C6 6D E8 27 5A F1 9C 4B 2E B5 90 AF 42 B9 tC.m.'Z..K....B.

数据结构:

┌─────────────────────┬──────────────────────┬────────────────────────────────┐
│ 填充 (4字节) │ 种子 Seed (4字节) │ 密文 Ciphertext (56字节) │
│ 0x00 0x00 0x00 0x00 │ 0x69 0xCB 0x34 0x41 │ 0x9D 0x2D 0xC8 0x55 ... 0xB9 │
│ 无意义 │ 大端序 = 0x69CB3441 │ LCG-XOR 加密后的 Base62 文本 │
└─────────────────────┴──────────────────────┴────────────────────────────────┘
字段字节范围长度值含义
填充[0:4]400 00 00 00无意义填充
种子[4:8]469 CB 34 41LCG 初始种子
密文[8:64]569D 2D C8 55 ... 42 B9XOR 加密数据

种子按大端序 (Big-Endian) 解析:

0x69CB3441 = 6×16⁷ + 9×16⁶ + C×16⁵ + B×16⁴ + 3×16³ + 4×16² + 4×16¹ + 1×16⁰
= 1774924865

3. LCG 流密码解密#

3.1 LCG 参数#

使用 Numerical Recipes 标准线性同余生成器参数:

参数符号值说明
乘数 (multiplier)a1664525—
增量 (increment)c1013904223—
模数 (modulus)m2³² (0xFFFFFFFF)32位无符号整数截断

3.2 递推公式#

state₀ = seed = 0x69CB3441
stateₙ₊₁ = (a × stateₙ + c) mod 2³²
= (1664525 × stateₙ + 1013904223) & 0xFFFFFFFF
keyₙ = stateₙ₊₁ & 0xFF ← 取低8位作为密钥字节
plainₙ = cipherₙ ⊕ keyₙ ← XOR解密

3.3 密钥流计算过程(前8步 + 最后4步)#

步骤LCG Statekeyctpt明文字符
10xACEB80AC0xAC0x9D0x31'1'
20x6A75041B0x1B0x2D0x36'6'
30xAAA5EABE0xBE0xC80x76'v'
40xEEF193050x050x550x50'P'
50xFAC468A00xA00xE90x49'I'
60x71B8037F0x7F0x4B0x34'4'
70xAB92BAD20xD20xA20x70'p'
80xA7D41C090x090x780x71'q'
………………
530xBB2C79900x900x900x00'\x00'
540x55307FAF0xAF0xAF0x00'\x00'
550x5ADC29420x420x420x00'\x00'
560x997F57B90xB90xB90x00'\x00'

有趣的现象:最后4步的密钥字节恰好等于密文字节,XOR 结果为零字节(\x00),作为填充使用。实际有效 Base62 字符串为前 52 字节对应的 52 个字符(去除尾部 \x00 后为 33 个有效字符)。

3.4 解密代码#

A = 1664525
C = 1013904223
MASK = 0xFFFFFFFF
seed = int.from_bytes(raw_data[4:8], 'big') # seed = 0x69CB3441
cur_seed = seed
decrypted = bytearray()
for b in raw_data[8:]: # 遍历56字节密文
cur_seed = (A * cur_seed + C) & MASK # LCG迭代
decrypted.append(b ^ (cur_seed & 0xFF)) # 取低8位 XOR 解密
# 去除尾部空字节
base62_str = decrypted.rstrip(b'\x00').decode('ascii')
# base62_str = "16vPI4pqYkxq33FsdItrG9pL5jAc3GLk9"

3.5 解密结果#

16vPI4pqYkxq33FsdItrG9pL5jAc3GLk9

全部由 0-9 a-z A-Z 组成 → Base62 编码特征。


4. Base62 解码#

4.1 字符集(⚠ 本题特殊顺序)#

0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
└─0-9─┘└───────── a-z ──────────┘└───────── A-Z ──────────┘

注意:本题字符集顺序为 数字 → 小写字母 → 大写字母,而非常见的「数字→大写→小写」。这是本题的关键坑点,使用标准 Base62 解码工具会得到错误结果。

4.2 解码原理#

Base62 本质是 62 进制数 → 大整数 → 字节序列:

"16vPI4pqYkxq33FsdItrG9pL5jAc3GLk9"
│ (62进制→整数)
▼
2511413510841791937195854758407182524397597805993880480893
│ (转大端序字节)
▼
666c61677b66696c655f63617276696e675f736b696c6c7d
│ (UTF-8解码)
▼
flag{file_carving_skill}

4.3 解码细节#

输入字符串 (33字符): "16vPI4pqYkxq33FsdItrG9pL5jAc3GLk9"
逐字符累加计算大整数:
'1' → num = 0*62 + 1 = 1
'6' → num = 1*62 + 6 = 68
'v' → num = 68*62 + 57 = 4273
'P' → num = 4273*62 + 51 = 264977
...
'9' → num = ... = 2511413510841791937195854758407182524397597805993880480893
大整数: 2511413510841791937195854758407182524397597805993880480893
转大端序字节 (24 bytes):
0x66 0x6C 0x61 0x67 0x7B 0x66 0x69 0x6C
0x65 0x5F 0x63 0x61 0x72 0x76 0x69 0x6E
0x67 0x5F 0x73 0x6B 0x69 0x6C 0x6C 0x7D
UTF-8 解码:
f l a g { f i l e _ c a r v i n g _ s k i l l }

5. 完整解题流程#

image_05_2.zip
│
▼ 解压
image_05.png (245 bytes)
│
▼ xxd 查看 / PNG块遍历
IEND 块 (0xA9-0xB4) 后有 64 字节隐藏数据
│
▼ 提取: data[0xB5:]
┌────────────────────────────────────┐
│ 填充(4B) │ Seed(4B) │ 密文(56B) │
│ 00000000 │ 69CB3441 │ 9D2DC855... │
└────────────────────────────────────┘
│
▼ LCG 解密 (a=1664525, c=1013904223, m=2³²)
Base62 字符串: "16vPI4pqYkxq33FsdItrG9pL5jAc3GLk9"
│
▼ Base62 解码 (0-9a-zA-Z)
flag{file_carving_skill}

6. 涉及知识点#

知识点说明
PNG 文件格式文件签名、IHDR/IDAT/IEND 块结构、CRC32 校验
File Carving从正常文件数据之后提取隐藏的额外数据
线性同余生成器 (LCG)Numerical Recipes 标准参数 (a=1664525, c=1013904223)
XOR 流密码伪随机密钥流与密文逐字节 XOR
Base62 编码62 进制编码,字符集顺序为 0-9a-zA-Z(小写在大写前)

7. Flag#

flag{file_carving_skill}

谢谢除草鸡的阅读!

2026 御网杯 线上 像素中的秘密 image_05.zip Writeup
https://nan.sajunan.cn/posts/像素中的除草鸡/
作者
Inui Sajuna
发布于
2026-07-17
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取