下载附件后解压是一堆没有后缀名的文件在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 { bPK\x03\x04: zip, bPK\x05\x06: zip, # 空ZIP归档 bRar!\x1a\x07\x00: rar, # RAR4 bRar!\x1a\x07\x01\x00: rar, # RAR5 b7z\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(pathoutput_dir, pwdpwd_bytes) elif fmt rar: with rarfile.RarFile(filepath) as rf: rf.extractall(pathoutput_dir, pwdstr(password)) elif fmt 7z: with py7zr.SevenZipFile(filepath, moder, passwordstr(password)) as sz: sz.extractall(pathoutput_dir) else: raise ValueError(f无法识别的文件格式) return fmt def main(): os.makedirs(OUTPUT_DIR, exist_okTrue) # 匹配 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, casefoldTrue) def check_file(filepath: str): try: with open(filepath, r, encodingutf-8) as f: content f.read() match_data re.search(rdata([0-9A-Z]), content, re.IGNORECASE) match_crc re.search(rcrc32([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, encodingutf-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(encodingutf-8, errorsignore).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(rdata([A-Z2-7]), content) crc_match re.search(rcrc32([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, flushTrue) 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), repeatnum_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_okTrue) 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, encodingutf-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, encodingutf-8) as f: content f.read() match re.search(rdata([^\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, casefoldTrue) 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