2025.12.6 Mini VN
2026-08-22 20:05:38

wav也能像png那样美丽吗

📎 相关:
| 2.音视频隐写
| 1.1LSB隐写
| 4.压缩包分析
| 1.图片隐写
| 4.1已知明文攻击(bkcrack)
| Misc tools

从Audacity读摩斯密码

![20251206Mini VN-01](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-01.png)

![20251206Mini VN-02](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-02.png)

fakeflag1 {**L**OOK AT THE FILE END}

010打开文件看文件尾

![20251206Mini VN-03](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-03.png)

fakeflag2{**S**ee the audiram}

右键看频谱图

![20251206Mini VN-04](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-04.png)

![20251206Mini VN-05](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-05.png)

fakeflag3{**B**e aware of the sentences intials}注意句子的首字母

LSB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import wave
import numpy as np
import os

# 方法:使用相对路径回到桌面找 你觉得wav美丽.wav
wav_path = os.path.join(os.path.expanduser("~"), "Desktop", "你觉得wav美丽.wav")

def extract_message_from_wav(wav_path, bit_to_extract=0):
with wave.open(wav_path, 'rb') as wav:
frames = wav.readframes(wav.getnframes())
data = np.frombuffer(frames, dtype=np.uint8)

message_bits = []
for byte in data:
bit = (byte >> bit_to_extract) & 1
message_bits.append(bit)
if len(message_bits) >= 8 and message_bits[-8:] == [0]*8:
break

message_bytes = [message_bits[i:i+8] for i in range(0, len(message_bits) - len(message_bits)%8, 8)]
extracted_message = ''
for byte in message_bytes:
char_code = int(''.join(map(str, byte)), 2)
if char_code == 0:
break
extracted_message += chr(char_code)

return extracted_message

print(extract_message_from_wav(wav_path))

![20251206Mini VN-06](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-06.png)

VNCTF{C@u5e_y0u_10ve_m3_dummy}

welcome.pdf

伪加密修复

![20251206Mini VN-07](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-07.png)

010打开pdf

![20251206Mini VN-08](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-08.png)

看到许多09``20构成的数据 推测是pdf隐写

wbstego pdf隐写

https://www.bailer.at/wbstego/

![20251206Mini VN-09](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-09.png)

![20251206Mini VN-10](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-10.png)

第一次用wbStego记一下使用步骤

![20251206Mini VN-11](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-11.png)

![20251206Mini VN-12](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-12.png)

![20251206Mini VN-13](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-13.png)
![20251206Mini VN-14](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-14.png)

![20251206Mini VN-15](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-15.png)
如果有密码 填密码 此处为空

![20251206Mini VN-16](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-16.png)

![20251206Mini VN-17](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-17.png)

接下来在桌面上看到welcome.pdf.txt

获得7z压缩包的密码fHsi9qhd#3

汉信码

![20251206Mini VN-18](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-18.png)

p图软件把他们拼在一起

![20251206Mini VN-19](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-19.jpg)

用中国编码app扫描

![20251206Mini VN-20](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-20.jpg)

得到半个flag VN{Un10n_7

盲水印

随后再对四张图片进行分析,StegSolve可以看到RGB各通道是有一点猫腻的,邻近像素之间有细微差异,并且部分通道有一定对称性

考虑使用盲水印

盲水印算法可以有效抵抗模糊,图片覆盖,椒盐噪声,亮度变化,裁剪带来的影响,甚至连截图也能提取出水印

四张图,每张图上有4个padding为2的Unicode,一共是16个,全部提取出来并手动补0

ref(APA): https://himekawa.top/2025/12/minivnctf2025-welcomepdf/

![20251206Mini VN-21](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-21.png)

\u0065\u0061\u0072\u006E\u005F\u0046\u0072\u0030\u0072\u006E\u0043\u004E\u0071\u0077\u0071\u007D(注意是先读第一张盲水印的正的一行,再读第二章盲水印正的一行这种)

![20251206Mini VN-22](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-22.png)

earn_Fr0rnCNqwq}

VNCTF{Un10n_7earn_Fr0rnCNqwq}

D u know Pyc?

https://www.hurkin.top/index.php/2025/d-u-know-pyc%ef%bc%9f%e5%ae%98%e6%96%b9write-up/

关于pyc

.pyc是python源代码编译后的字节码文件

![20251206Mini VN-23](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-23.jpg)

构建pyc头部

1.Magic Number

![20251206Mini VN-24](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-24.png)

备注里提到版本号是python 3.12.12

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# get_pyc_magic.py

MAGIC_MAP = {
"1.5": "99020d0a",
"1.6": "58030d0a",
"2.0": "cf040d0a",
"2.1": "bf050d0a",
"2.2": "df060d0a",
"2.3": "e0070d0a",
"2.4": "f0080d0a",
"2.5": "f8090d0a",
"2.6": "d10a0d0a",
"2.7": "030b0d0a",
"3.0": "3b0b0d0a",
"3.1": "4f0b0d0a",
"3.2": "6d0c0d0a",
"3.3": "3e0d0d0a",
"3.4": "ee0d0d0a",
"3.5": "160e0d0a",
"3.6": "170e0d0a",
"3.7": "180e0d0a",
"3.8": "190e0d0a",
"3.9": "1a0e0d0a",
"3.10": "1b0e0d0a",
"3.11": "1c0e0d0a",
"3.12": "cb0d0d0a",
"3.13": "cc0d0d0a", # Python 3.13 (预览)
}

def main():
print("输入 Python 版本号(如 3.8、3.12),我告诉你 .pyc 前四字节!")
print("输入 'quit' 或 'q' 退出。\n")

while True:
version = input("请输入 Python 版本号: ").strip()

if version.lower() in ('quit', 'q', 'exit'):
print("👋 再见!")
break

if not version:
print("❌ 不能为空,请输入版本号(如 3.12)\n")
continue

# 只取主版本+次版本,比如 3.12.3 → 3.12
parts = version.split('.')
if len(parts) < 2:
print("❌ 格式错误!请使用类似 '3.8' 或 '3.12' 的格式\n")
continue

key = f"{parts[0]}.{parts[1]}"
magic = MAGIC_MAP.get(key)

if magic:
print(f"✅ Python {key} 的 .pyc 前四字节是: {magic}")
print(f" 字节形式: b'\\x{magic[0:2]}\\x{magic[2:4]}\\x{magic[4:6]}\\x{magic[6:8]}'\n")
else:
print(f"❓ 暂不支持 Python {key},或版本号有误。\n")

if __name__ == "__main__":
main()

于是python 3.12对应的前四字节是cb0d0d0a

2.Bit Field

![20251206Mini VN-25](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-25.png)

在python 3.12.12中,Bit Field只有两种状态

00 00 00 00 ——->

后面跟着的是Timestamp和Source Size,然后16位后hash 值置空

(就是全0,会被非预期,所以我手动将其改掉了,但是不会影响pyc的正常功能,对此感兴趣师傅可以自己去构建一下)

01 00 00 00——->

后面跟着的是hash值 至于Bit Field为00 00 00 00,决定了8-12位是TIMESTAMP or HASH

ref(APA): Hurkin.Welcome To Hurk1n’s Home!!!.https://www.hurkin.top. Retrieved 2026/1/8.

情况1:Bit Field=00 00 00 00

1
[魔数][Bit=0][时间戳][文件大小][CodeObject...]

不存在hash值

python只靠“时间+大小”判断是否要重新编译 容易出错

情况2:Bit Field=01 00 00 00

1
[魔数][Bit=1][时间戳][文件大小][16字节hash][CodeObject...]

存了 hash 值!

python 会对比:当前 .py 的 hash vs .pyc 里存的 hash

一样 → 用缓存

不一样 → 重新编译

为什么wp原文说hash 置空(全0)不会影响功能?

因为如果 Bit Field 是 00 00 00 00,python 就不会去读 hash

所以即使你在那个位置写 16 个 0x00,python 也视而不见

但如果 Bit Field 是 01 00 00 00,而 hash 是全 0,那就会出问题(因为真实代码的 hash 不可能是全 0)。

此处Bit Field为00 00 00 00

3.TimeStamp

从Bandzip看到的修改时间是压缩包保存的时间 而非py文件最后修改时间 不知道

表示为????????

4.File Size

![20251206Mini VN-26](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-26.png)

flag.py的原始大小是1411转为4字节的16进制表示是83050000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def dec_to_le_hex(value, byte_length=4):
"""
将十进制整数转为小端序的十六进制字符串(固定字节数)
"""
if value < 0:
raise ValueError("仅支持非负整数")
if value >= (256 ** byte_length):
raise OverflowError(f"数值 {value} 超出 {byte_length} 字节表示范围")
return value.to_bytes(byte_length, byteorder='little').hex()

def main():
try:
user_input = input("请输入一个十进制整数: ").strip()
decimal_num = int(user_input)
hex_str = dec_to_le_hex(decimal_num, 4)
print(f"小端序 4 字节十六进制表示: {hex_str}")
except ValueError as e:
print(f"输入错误: {e}")
except OverflowError as e:
print(f"数值过大: {e}")

if __name__ == "__main__":
main()

0.pyc Header

于是构建的pyc头部为

CB 0D 0D 0A 00 00 00 00 ?? ?? ?? ?? 83 05 00 00

综上已知CB 0D 0D 0A 00 00 00 00 ?? ?? ?? ?? 83 05 00 00 8+4字节,且偏移量为12。可以进行明文攻击

ref(APA): Hurkin.Welcome To Hurk1n’s Home!!!.https://www.hurkin.top. Retrieved 2026/1/8.

字节 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
CB 0D 0D 0A 00 00 00 00 ?? ?? ?? ?? 83 05 00 00

什么是8+4字节

1
[已知8字节] [未知4字节] [已知4字节]

什么是偏移量为12

在整个数据块中,第12字节处的值是已知的,并且从那里开始有连续的4字节是已知的

明文爆破

cd C:\Users\13964\Desktop\pyc

![20251206Mini VN-27](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-27.png)

.\bkcrack.exe -C flag.zip -c attack_it.pyc -x 0 CB0D0D0A00000000 -x 12 83050000

![20251206Mini VN-28](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-28.png)

.\bkcrack.exe -C **flag.zip** -c **flag.py** -k **d7fe0787 b3af2704 99fa95dc** -d **flag_decrypted.py**

![20251206Mini VN-29](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-29.png)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
from base64 import b64encode as _B64E
def _u(x):
return x
def __r(s):
t = []
ln = len(s)
i = ln - 1
while i >= 0:
t.append(s[i])
i -= 1
return "".join(t)

def ___x(bts, kk):
k = kk ^ 0x00
return bytes((bb ^ k) & 0xFF for bb in bts)
def __rc4(k, d):
S = list(range(256))
j = 0
lk = len(k)
for i in range(256):
j = (j + S[i] + k[i % lk]) & 0xFF
S[i], S[j] = S[j], S[i]
out = bytearray()
i = 0
j = 0
garbage = 0
for ch in d:
i = (i + 1) & 0xFF
j = (j + S[i]) & 0xFF
S[i], S[j] = S[j], S[i]
idx = (S[i] + S[j]) & 0xFF
kbyte = S[idx]
garbage ^= (kbyte ^ i ^ j) & 0
out.append(ch ^ kbyte)
_ = garbage
return bytes(out)

def __main_like():
flag = "VNCTF{?????????}"
s1 = __r(flag)
dummy = (len(s1) ^ 0x13) & 0xFF
if dummy == -1:
print("unreachable:", dummy)
kxor = (0x10 ^ 0x27)
b1 = s1.encode("utf-8")
b2 = ___x(b1, kxor)
fname = "S3ct3t"
with open(fname, "rb") as f:
raw = f.read()
key = _u(raw).strip().replace(b"\r", b"\r").replace(b"\n", b"\n")
c = __rc4(key, b2)
c64 = _B64E(c).decode()
lab = "".join(chr(x) for x in [0x66, 0x69, 0x6e, 0x61, 0x6c])
print(lab, "=>", c64)
if __name__ == "__main__":
__main_like()

Sect3t.jpg

![20251206Mini VN-30](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-30.png)

your-secret-key-here

编写解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from base64 import b64decode

def __rc4(k, d):
# RC4 实现(与原脚本一致)
S = list(range(256))
j = 0
lk = len(k)
for i in range(256):
j = (j + S[i] + k[i % lk]) & 0xFF
S[i], S[j] = S[j], S[i]
out = bytearray()
i = 0
j = 0
for ch in d:
i = (i + 1) & 0xFF
j = (j + S[i]) & 0xFF
S[i], S[j] = S[j], S[i]
idx = (S[i] + S[j]) & 0xFF
kbyte = S[idx]
out.append(ch ^ kbyte)
return bytes(out)

# 已知数据
c64 = "adJts2ab4DvAn9Zu60bNyGrOAWj72B1/kg=="
key = b"your-secret-key-here"
kxor = 0x10 ^ 0x27 # 0x37

# Step 1: Base64 decode
c = b64decode(c64)

# Step 2: RC4 decrypt
b2 = __rc4(key, c)

# Step 3: XOR decrypt with kxor
b1 = bytes(bb ^ kxor for bb in b2)

# Step 4: Reverse string
flag = b1[::-1].decode("utf-8")

print("Flag:", flag)

VNCTF{W3lc0me_T0_V&N2025}

![20251206Mini VN-31](/img/obsidian/2025-12-06-mini-vn/20251206Mini VN-31.png)

上一页
2026-08-22 20:05:38
下一页