2026黄鹤杯杯网络安全人才创新大赛学生组(失序货栈)
2026/7/29 3:07:38 网站建设 项目流程

下载附件后解压是一堆没有后缀名的文件

在010看发现是压缩包

解压发现这些压缩包都被加密了

而且发现这些压缩包的大小都很小

那么就试一下crc爆破

就当我美滋滋查看1.txt的时候发现居然是

气晕了

既然这样,在010看了又不是伪加密,那我只能爆破一下了

我多爆破了几个发现密码就是文件编号

我突然想起来之前我师哥出题的时候就直接用文件名当过密码

打开txt观察发现里面有base32编码的内容,末尾还有crc32,众所周知crc32是传输数据中用来校验数据是否正确的,

所以说明要选出crc32与前面data的crc32一致的BOX

那么接下来先批量解压缩

import os import re import shutil import pyzipper import rarfile import py7zr # ==================== 配置区 ==================== SOURCE_DIR = r"./" # 压缩包所在文件夹路径 OUTPUT_DIR = r"./output" # 解压目标文件夹路径 # ================================================ # 文件头签名映射 (Magic Bytes) MAGIC_BYTES = { b'PK\x03\x04': 'zip', b'PK\x05\x06': 'zip', # 空ZIP归档 b'Rar!\x1a\x07\x00': 'rar', # RAR4 b'Rar!\x1a\x07\x01\x00': 'rar', # RAR5 b'7z\xbc\xaf\x27\x1c': '7z', } def detect_format(filepath): """通过读取文件头判断压缩格式""" with open(filepath, 'rb') as f: header = f.read(8) for magic, fmt in MAGIC_BYTES.items(): if header.startswith(magic): return fmt return None def extract_file(filepath, password, output_dir): """根据格式选择对应库进行解密解压""" fmt = detect_format(filepath) pwd_bytes = str(password).encode('utf-8') if fmt == 'zip': with pyzipper.AESZipFile(filepath) as zf: zf.extractall(path=output_dir, pwd=pwd_bytes) elif fmt == 'rar': with rarfile.RarFile(filepath) as rf: rf.extractall(path=output_dir, pwd=str(password)) elif fmt == '7z': with py7zr.SevenZipFile(filepath, mode='r', password=str(password)) as sz: sz.extractall(path=output_dir) else: raise ValueError(f"无法识别的文件格式") return fmt def main(): os.makedirs(OUTPUT_DIR, exist_ok=True) # 匹配 BOX-数字编号 格式 pattern = re.compile(r'^BOX-(\d+)$') files = [f for f in os.listdir(SOURCE_DIR) if os.path.isfile(os.path.join(SOURCE_DIR, f))] success, fail = 0, 0 for filename in sorted(files): match = pattern.match(filename) if not match: print(f"[跳过] {filename} - 文件名不符合 BOX-数字 格式") continue password = match.group(1) filepath = os.path.join(SOURCE_DIR, filename) try: fmt = extract_file(filepath, password, OUTPUT_DIR) print(f"[成功] {filename} | 密码: {password} | 格式: {fmt}") success += 1 except Exception as e: print(f"[失败] {filename} | 密码: {password} | 错误: {e}") fail += 1 print(f"\n{'='*40}") print(f"处理完成: 成功 {success} 个, 失败 {fail} 个") if __name__ == '__main__': main()

接下来筛选出校验crc成功的文件

import os import re import base64 import zlib def calc_crc32(raw_bytes: bytes) -> str: """计算标准CRC32,返回小写十六进制字符串""" crc_int = zlib.crc32(raw_bytes) & 0xFFFFFFFF return f"{crc_int:x}" def safe_base32decode(b32_str: str) -> bytes: """自动补填充符,兼容不带=的base32""" padding = (8 - len(b32_str) % 8) % 8 padded = b32_str + "=" * padding return base64.b32decode(padded, casefold=True) def check_file(filepath: str): try: with open(filepath, "r", encoding="utf-8") as f: content = f.read() match_data = re.search(r"data=([0-9A-Z]+)", content, re.IGNORECASE) match_crc = re.search(r"crc32=([0-9a-fA-F]+)", content) if not match_data or not match_crc: return None, "无法提取data或crc32字段" b32_data = match_data.group(1) target_crc = match_crc.group(1).lower() decoded = safe_base32decode(b32_data) real_crc = calc_crc32(decoded) return real_crc == target_crc, f"计算:{real_crc} 目标:{target_crc}" except Exception as e: return None, f"异常: {str(e)}" if __name__ == "__main__": work_dir = os.getcwd() matched_files = [] # 校验相等(通过) mismatch_files = [] # CRC不相等(失败) error_files = [] # 无法完成校验(解析异常、解码失败等) for filename in os.listdir(work_dir): if filename.startswith("BOX-"): full_path = os.path.join(work_dir, filename) if not os.path.isfile(full_path): continue ok, info = check_file(full_path) if ok is True: print(f"✅ 校验通过 | {filename} | {info}") matched_files.append(filename) elif ok is False: print(f"❌ 校验不匹配 | {filename} | {info}") mismatch_files.append(filename) else: print(f"⚠️ 校验失败(异常) | {filename} | {info}") error_files.append(filename) print("\n==================== 统计汇总 ====================") print(f"✅ CRC校验相符文件数量:{len(matched_files)}") print(f"❌ CRC校验不相符文件数量:{len(mismatch_files)}") print(f"⚠️ 未能完成校验的文件数量:{len(error_files)}") print("===================================================") print("\n【校验通过文件名清单】") for name in matched_files: print(name) # 可选:写入清单文件 with open("valid_box_list.txt", "w", encoding="utf-8") as fw: fw.write("===== 校验通过 =====\n") fw.write("\n".join(matched_files)) fw.write("\n\n===== CRC不匹配 =====\n") fw.write("\n".join(mismatch_files)) fw.write("\n\n===== 校验异常文件 =====\n") fw.write("\n".join(error_files))

但是我们可以看到除了校验成功的校验失败的,还有未完成校验的,我们的代码一共就定义了3个函数,检查文件的函数肯定不会有这样的报错,那么推测是有的data不能正常进行base32解密的

当我观察这些通过文件名清单时我突然有了个惊人的发现,1kb的文件中除了我用红色笔圈起来的文件,其他文件都是校验通过的文件

而且这些文件都是base32计算正常的

这时候就要思考一下这个题究竟想让我干什么,然后我就在一瞬间顿悟了

crc是用来校验的,而这些base32解码正常完完全全是因为校验失败的文件都只有1kb,那么这不正提示着我继续用之前失败的crc爆破,我之前的直觉没有错,只是用错了地方

接下来就是考虑怎么用crc爆破

data数据按常规想肯定是传输过程中出现了损坏,如果把数据想象为若干部分肯定是某个部分数据有误,所以导致crc校验失败,而我只需要修正这一小部分就行,也就是说我只需要修改对这一小部分crc校验就正确了,而我修改这一小部分的手段就是暴力枚举,既然要暴力枚举我就得尽量把数据分成的若干部分控制的尽量小,(还有在传输过程是二进制数据,所以爆破的对象是字节不是base32字符)所以我就假设是一字节错误,如果分为1字节不行,我们再进行扩大,2字节....3字节.....

先手动把红笔圈起来的文件复制到一个单独的文件夹

进行爆破

import os import re import base64 import binascii import itertools from pathlib import Path from typing import Optional, Tuple, List def parse_box_file(filepath: Path) -> Tuple[Optional[str], Optional[bytes], Optional[int]]: """解析 BOX 文件,返回 (原始BOX编号, 解码后的二进制数据, 目标CRC32整数)""" try: content = filepath.read_text(encoding='utf-8', errors='ignore').strip() except Exception as e: print(f" [!] 读取失败: {e}") return None, None, None # 提取 BOX 编号、data 和 crc32 box_match = re.search(r'(BOX-\d+)', content) data_match = re.search(r'data=([A-Z2-7=]+)', content) crc_match = re.search(r'crc32=([0-9a-fA-F]{8})', content) if not box_match or not data_match or not crc_match: return None, None, None try: binary_data = base64.b32decode(data_match.group(1)) except Exception as e: print(f" [!] Base32 解码失败: {e}") return None, None, None box_id = box_match.group(1) target_crc = int(crc_match.group(1), 16) return box_id, binary_data, target_crc def brute_force_bytes( original: bytes, target_crc: int, max_errors: int = 2 ) -> Optional[Tuple[bytearray, List[Tuple[int, int, int]], int]]: """对二进制字节数组进行暴力枚举修复""" n = len(original) target = target_crc & 0xFFFFFFFF if (binascii.crc32(original) & 0xFFFFFFFF) == target: return bytearray(original), [], 0 positions = list(range(n)) for num_errors in range(1, max_errors + 1): print(f" 枚举 {num_errors} 字节... ", end="", flush=True) count = 0 for pos_combo in itertools.combinations(positions, num_errors): original_vals = [original[p] for p in pos_combo] for val_combo in itertools.product(range(256), repeat=num_errors): if val_combo == tuple(original_vals): continue test = bytearray(original) for p, v in zip(pos_combo, val_combo): test[p] = v count += 1 if (binascii.crc32(bytes(test)) & 0xFFFFFFFF) == target: changes = [(p, original[p], v) for p, v in zip(pos_combo, val_combo)] print(f"✅ 成功! (尝试 {count:,} 次)") return test, changes, num_errors print(f"❌ 未命中 ({count:,} 次)") return None def main(): search_dir = "." # ← BOX 文件所在目录 output_dir = "./recovered" max_byte_errors = 2 # ← 最大枚举字节数 os.makedirs(output_dir, exist_ok=True) files = sorted([ f for f in Path(search_dir).iterdir() if f.name.startswith("BOX-") and f.suffix == "" and f.is_file() ]) print(f"📁 找到 {len(files)} 个 BOX 文件 | 最大枚举字节数: {max_byte_errors}\n") success = 0 for fp in files: print(f"▶ {fp.name}") box_id, binary_data, target_crc = parse_box_file(fp) if box_id is None or binary_data is None or target_crc is None: print(" [!] 解析失败,跳过\n") continue print(f" 数据长度: {len(binary_data)} 字节 | 目标CRC: {target_crc:08x}") result = brute_force_bytes(binary_data, target_crc, max_byte_errors) if result is not None: fixed_data, changes, err_count = result # ★ 核心修改:重新 Base32 编码并按原格式拼接字符串 fixed_b32 = base64.b32encode(bytes(fixed_data)).decode('ascii') output_content = f"{box_id} | data={fixed_b32} | crc32={target_crc:08x}" # 以文本模式写入,保持与原文件完全一致的格式 out_path = Path(output_dir) / f"{fp.name}.txt" out_path.write_text(output_content, encoding='utf-8') print(f" 💾 已按原格式保存: {out_path}") for p, old, new in changes: print(f" byte[{p}]: 0x{old:02x} -> 0x{new:02x}") success += 1 else: print(f" ❌ {max_byte_errors} 字节范围内未找到匹配") print() print(f"\n{'='*50}") print(f"🏁 完成: {success}/{len(files)} 个文件修复成功") if __name__ == "__main__": main()

现在我们得到了27个校验的正确文件

因为题目中说有局部重叠,联系到文件也就是说前一个文件和后一个有局部重叠,而依靠这一点就能还原出正确顺序,很重要的是着了的局部重叠指的不是base32而是原始二进制数据,因为有的base32后有等于号,但开头没有有等于号的

import os import re import glob import base64 def parse_and_decode(filepath): """从文件中提取 data= 内容并解码为二进制""" try: with open(filepath, 'r', encoding='utf-8') as f: content = f.read() match = re.search(r'data=([^\s|]+)', content) if match: raw_b32 = match.group(1).strip() # 去除 Base32 填充符后再解码 clean_b32 = raw_b32.rstrip('=') # base64.b32decode 要求输入长度为 8 的倍数,手动补回正确的 padding pad_len = (8 - len(clean_b32) % 8) % 8 clean_b32 += '=' * pad_len return base64.b32decode(clean_b32, casefold=True) except Exception as e: print(f"❌ 处理失败 {filepath}: {e}") return None def find_binary_overlap(a: bytes, b: bytes, min_overlap: int = 8) -> int: """ 查找 a 的后缀与 b 的前缀在二进制层面的最大重叠字节数 """ max_possible = min(len(a), len(b)) # 从最大可能长度向下搜索,找到即返回 for length in range(max_possible, min_overlap - 1, -1): if a[-length:] == b[:length]: return length return 0 def assemble_boxes(directory: str): pattern = os.path.join(directory, "BOX-*") files = sorted(glob.glob(pattern)) if not files: print("未找到 BOX-* 文件") return print(f"📂 找到 {len(files)} 个文件,正在解码...") file_data = {} for fp in files: fname = os.path.basename(fp) decoded = parse_and_decode(fp) if decoded is not None: file_data[fname] = decoded print(f" ✅ {fname}: {len(decoded)} bytes") else: print(f" ⚠️ {fname}: 解码失败,跳过") filenames = list(file_data.keys()) n = len(filenames) # 计算所有两两之间的二进制重叠度 print("\n🔍 正在计算二进制重叠关系...") overlaps = {} for i in range(n): for j in range(n): if i != j: ov = find_binary_overlap(file_data[filenames[i]], file_data[filenames[j]]) if ov > 0: overlaps[(filenames[i], filenames[j])] = ov print(f" 发现 {len(overlaps)} 组有效重叠关系") if not overlaps: print("❌ 未找到任何二进制重叠!") print("💡 提示:如果文件切割不在字节边界,可能存在 bit-shift,需要额外处理") return # 贪心组装 fragments = [[f] for f in filenames] file_to_frag = {f: idx for idx, f in enumerate(filenames)} merged = 0 while len([f for f in fragments if f]) > 1 and overlaps: best_pair = None best_ov = -1 for (a, b), ov in overlaps.items(): fa = file_to_frag.get(a) fb = file_to_frag.get(b) if fa is not None and fb is not None and fa != fb: if fragments[fa][-1] == a and fragments[fb][0] == b: if ov > best_ov: best_ov = ov best_pair = (a, b, fa, fb) if best_pair is None: break a, b, fa, fb = best_pair print(f" 🔗 {a} → {b} (重叠 {best_ov} bytes)") fragments[fa].extend(fragments[fb]) fragments[fb] = [] for f in fragments[fa]: file_to_frag[f] = fa merged += 1 overlaps = {k: v for k, v in overlaps.items() if file_to_frag.get(k[0]) != file_to_frag.get(k[1])} # 输出结果 final_chain = [f for frag in fragments if frag for f in frag] print("\n" + "=" * 60) print(f"✅ 组装完成!合并 {merged} 次,序列包含 {len(final_chain)} 个文件") print("=" * 60) for idx, fname in enumerate(final_chain, 1): size = len(file_data[fname]) print(f" {idx:2d}. {fname} ({size} bytes)") # 验证:拼接完整二进制并检查 full_binary = bytearray() for i, fname in enumerate(final_chain): d = file_data[fname] if i == 0: full_binary.extend(d) else: prev_fname = final_chain[i - 1] ov = find_binary_overlap(file_data[prev_fname], d) full_binary.extend(d[ov:]) print(f"\n📦 拼接后总大小: {len(full_binary)} bytes") print(f"💾 已保存为 assembled_output.bin") with open(os.path.join(directory, "assembled_output.bin"), "wb") as out: out.write(full_binary) return final_chain if __name__ == "__main__": TARGET_DIR = "." assemble_boxes(TARGET_DIR)

打开发现是 zlib (deflate) 压缩流

最后解压缩找到flag

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询