在这个AI能杀穿CTF的时代怀着不知道出什么题好的心理且同时还得给NCTF出题的情况下出了个musc签到题()

可以看到流量包后面还跟着一个压缩包,提取出来

image-20260208152146156

可以看出压缩包中的so_ez是一个sslkeylog

image-20260208152237659

导入wireshark解密流量

image-20260208152337700

可以看到流量中有大量的HTTP2请求,仔细观察可以发现,其中WINDOW_UPDATE帧的Window Size Increment字段的值总是65536或65537

image-20260208152741697
image-20260208152800713

将65536记作0,65537记作1,可提取出一个字符串,即为压缩包密码

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
62
# -*- coding: utf-8 -*-
import subprocess
import base64
import os

def solve_challenge(pcap_file, keylog_file):
print(f"[*] Analyzing {pcap_file} with {keylog_file}...")

tshark_cmd = [
"tshark",
"-r", pcap_file,
"-o", f"tls.keylog_file:{keylog_file}",
"-Y", "http2.window_update.window_size_increment",
"-T", "fields",
"-e", "http2.window_update.window_size_increment"
]

try:
result = subprocess.run(tshark_cmd, capture_output=True, check=True)
output = result.stdout.decode('utf-8', errors='ignore').strip()

if not output:
print("[-] No HTTP/2 Window Update frames found.")
err_msg = result.stderr.decode('utf-8', errors='ignore')
if err_msg:
print(f"[!] Tshark Stderr: {err_msg}")
return

lines = output.split('\n')
bits = ""
for val in lines:
val = val.strip()
if val == "65536":
bits += "0"
elif val == "65537":
bits += "1"

print(f"[*] Extracted {len(bits)} bits.")

byte_data = bytearray()
for i in range(0, len(bits), 8):
byte_str = bits[i:i+8]
if len(byte_str) == 8:
byte_data.append(int(byte_str, 2))

try:
raw_decoded = base64.b64decode(byte_data)
password = raw_decoded.decode('utf-8')
print("-" * 30)
print(f"[+] Found password: {password}")
print("-" * 30)
except Exception as e:
print(f"[-] Decode failed. Bitstream might be incomplete.")
print(f"[*] Raw data as string: {byte_data.decode('utf-8', errors='ignore')}")

except Exception as e:
print(f"[!] Runtime Error: {e}")

if __name__ == "__main__":
PCAP_PATH = "attachment.pcapng"
KEYLOG_PATH = "so_ez"
solve_challenge(PCAP_PATH, KEYLOG_PATH)
image-20260208152959744

解得一个二维码,但是无法扫描,图片的备注中有一个式子

image-20260209231433779

可以看到这个二维码还是有较大块的连在一起的白色区域的,说明这里其实mask有问题。这里是一个自定义的mask,用的式子即为 \[ ij\%2+(i+j)\%3 \] 这里放出出题时生成二维码用的脚本,是通过劫持qrcode库的mask_func函数实现的

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
import qrcode
import qrcode.util

def generate_custom_mask_qr(data, filename="flag.png"):
def my_custom_mask(i, j):
value = (i * j) % 2 + (i + j) % 3
return value == 0
original_mask_func = qrcode.util.mask_func

def hijacked_mask_func(pattern):
return my_custom_mask
qrcode.util.mask_func = hijacked_mask_func

try:
qr = qrcode.QRCode(
version=5,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
mask_pattern=0
)

qr.add_data(data)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
img.save(filename)
print(f"应用公式: (i * j) % 2 + (i + j) % 3 == 0")

finally:
qrcode.util.mask_func = original_mask_func

if __name__ == "__main__":
content = "flag{Y0U_F0UND_Th3_fl48!!_922a24f585ac8e4bacd7}"
generate_custom_mask_qr(content)

现在要做的是对flag.png用自定义的mask异或回去,然后用正确的mask(0-7都可以,使用哪个mask对扫码并没有影响,只是是否最优的问题,所以这里用个mask0就行)再异或后即可正常扫描

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
62
63
64
65
66
67
68
69
70
71
72
73
import qrcode
from PIL import Image
from pyzbar.pyzbar import decode

def decode_custom_mask_qr(image_path):
print(f"[*] 正在处理: {image_path}")

img = Image.open(image_path).convert('L')
width, height = img.size
qr_version = 5
border = 4
qr = qrcode.QRCode(version=qr_version, border=0)
qr.add_data("placeholder")
qr.make(fit=False)
matrix_size = len(qr.modules)
box_size = width // (matrix_size + border * 2)
def is_functional(r, c, size, version):
if (r < 7 and c < 7) or (r < 7 and c >= size - 7) or (r >= size - 7 and c < 7):
return True
if r == 6 or c == 6:
return True
align_pos = qrcode.util.pattern_position(version)
for row_pos in align_pos:
for col_pos in align_pos:
if not ((row_pos < 7 and col_pos < 7) or
(row_pos < 7 and col_pos >= size - 7) or
(row_pos >= size - 7 and col_pos < 7)):
if abs(r - row_pos) <= 2 and abs(c - col_pos) <= 2:
return True
if (r < 9 and c < 9) or (r < 9 and c >= size - 8) or (r >= size - 8 and c < 9):
return True
return False

def custom_mask(i, j):
return ((i * j) % 2 + (i + j) % 3) == 0

def standard_mask_0(i, j):
return (i + j) % 2 == 0

new_img = Image.new("1", (width, height), "white")
pixels = new_img.load()
src_pixels = img.load()

for x in range(width):
for y in range(height):
pixels[x, y] = src_pixels[x, y]

print("[*] 开始执行 XOR 逆向转换...")

for r in range(matrix_size):
for c in range(matrix_size):
if not is_functional(r, c, matrix_size, qr_version):
if custom_mask(r, c) != standard_mask_0(r, c):
px_start = (c + border) * box_size
py_start = (r + border) * box_size
for px in range(px_start, px_start + box_size):
for py in range(py_start, py_start + box_size):
current_val = src_pixels[px, py]
pixels[px, py] = 0 if current_val > 128 else 255

fixed_filename = "fixed_standard_mask_qr.png"
new_img.save(fixed_filename)
print(f"[*] 修复完成: {fixed_filename}")

decoded_objects = decode(Image.open(fixed_filename))
if decoded_objects:
for obj in decoded_objects:
print(f"\nSUCCESS! 内容: {obj.data.decode('utf-8')}")
else:
print("\nFAILED. 依然无法解码。")

if __name__ == "__main__":
decode_custom_mask_qr("flag.png")
image-20260209235619230