Misc
谍影重重 6.0
Challenge
经过我国执法部门的努力,终于在今年十月提取出了张纪星(系杜撰名字,与现实人员无关)被捕前布置的监听设备中的加密信息,据本人供述其曾恢复过我国一份绝密情报。
flag为情报所提及的详细时间和地址的md5值,即flag{md5(x年x月x日x时x分于x地)}。
题目提示:本题依托于架空的时间线,取材自真实历史事件,请关注地点信息。
Solution

先看看协议分级,发现全是 UDP 流量

先查看第一个包的 payload,发现是以 80 开头的,这使我不禁想起前段时间的 WMCTF2025,参考当时写的 wp:Voice-hacker
先假设它是 RTP 流量,然后用同样的方法解析它的头部 80 80 76 38 99 59 48 23 88 48 19 ee:
- 80: 版本号(V=2)
- 80: 标记位(M=1),载荷类型 (Payload Type, PT) = 0
- 76 38: 序列号 (Sequence Number) = 30264
- 99 59 48 23: 时间戳 (Timestamp) = 2572765219
- 88 48 19 ee: 同步源标识符 (SSRC) = 0x884819EE
完美对上了,这就是 RTP 协议,右键第一条流量在 Decode As... 把端口 40000 的 UDP 流量解析为 RTP
然后往下滑,发现还有多个端口,把端口 40001 的也解析了

不难发现,这个流量文件里并不是所有包的 SSRC 都相同的,这就意味着并不是所有包都属于同一个音频流,需要根据 SSRC 来划分
先用 tshark 把 UDP 包的 Payload 提取出来方便稍后使用脚本处理:
tshark -r Data.pcap -T fields -e data > Data.txt由于里面的流量并非全部属于同一个音频流,因此需要根据 SSRC 划出多段音频,最后将它们按顺序拼接起来合并成同一个音频文件,这里在 Voice-hacker 的脚本的基础上进行修改。
这样简单的处理得到的结果效果并不好,出现了两个问题:音频的音量过小,音频长达 18 小时😰
在听了两三分钟后,发现音频中存在较长的空白片段,随便往后一划也很容易划到没有声音的地方,检查一下前面导出的 Data.txt ,可以发现:
8000c6445d9424d7842e8e8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8000c6455d942577842e8e8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8000c6465d942617842e8e8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff里面存在大量类似这样的片段,因此我们可以先做一个预处理,修改 Data.txt,删除里面所有完全空白的片段:
kept_lines_count = 0removed_lines_count = 0 with open('Data.txt', 'r', encoding='utf-8') as infile, open('Data_new.txt', 'w', encoding='utf-8') as outfile: for line in infile: stripped_line = line.strip() if not stripped_line: removed_lines_count += 1 continue header_length = 24 # RTP头部的12字节 is_filler_payload = False if len(stripped_line) > header_length: payload = stripped_line[header_length:] if payload and all(char == 'f' for char in payload): is_filler_payload = True if is_filler_payload: removed_lines_count += 1 continue outfile.write(line) kept_lines_count += 1Data.txt 从原来的 1.04 GB 缩小成了 195 MB(看来掺水挺严重
下面是完善后的导出脚本:
import waveimport structimport osfrom collections import defaultdict # --- G.711 μ-law to 16-bit Linear PCM Decoder ---# 这是一个标准的查找表,用于将8位的μ-law字节解码为16位的线性采样值_ULAW_DECODE_TABLE = [ -32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956, -23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764, -15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412, -11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316, -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, -876, -844, -812, -780, -748, -716, -684, -652, -620, -588, -556, -524, -492, -460, -428, -396, -372, -356, -340, -324, -308, -292, -276, -260, -244, -228, -212, -196, -180, -164, -148, -132, -120, -112, -104, -96, -88, -80, -72, -64, -56, -48, -40, -32, -24, -16, -8, 0, 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, 876, 844, 812, 780, 748, 716, 684, 652, 620, 588, 556, 524, 492, 460, 428, 396, 372, 356, 340, 324, 308, 292, 276, 260, 244, 228, 212, 196, 180, 164, 148, 132, 120, 112, 104, 96, 88, 80, 72, 64, -56, -48, -40, -32, -24, -16, -8, 0] def decode_ulaw_to_pcm16(ulaw_data): """将一整段 G.711 u-law 字节数据解码为 16-bit 线性 PCM 字节数据""" pcm_frames = [] for ulaw_byte in ulaw_data: # 从查找表中获取对应的16位PCM值 pcm_sample = _ULAW_DECODE_TABLE[ulaw_byte] # 将16位整数打包成2个字节(小端序) pcm_frames.append(struct.pack('<h', pcm_sample)) return b''.join(pcm_frames) def amplify_and_clip_pcm16(pcm_data, factor): """ 放大16位PCM数据的音量,并进行削波处理。 :param pcm_data: 16位PCM字节数据 :param factor: 放大系数 (例如 2.0 表示放大2倍) :return: 放大后的16位PCM字节数据 """ # 将字节数据解包成16位整数列表 samples = struct.unpack(f'<{len(pcm_data) // 2}h', pcm_data) amplified_samples = [] for sample in samples: amplified_sample = int(sample * factor) # 削波处理: 确保值在16位有符号整数范围内 if amplified_sample > 32767: amplified_sample = 32767 elif amplified_sample < -32768: amplified_sample = -32768 amplified_samples.append(amplified_sample) # 将处理后的整数列表打包回字节 return struct.pack(f'<{len(amplified_samples)}h', *amplified_samples) def write_wav(filename, pcm_data, channels, sampwidth, framerate): """一个辅助函数,用于将PCM数据写入WAV文件""" try: with wave.open(filename, "wb") as wav_file: wav_file.setnchannels(channels) wav_file.setsampwidth(sampwidth) wav_file.setframerate(framerate) wav_file.writeframes(pcm_data) except Exception as e: print(f"写入文件 {filename} 时出错: {e}") def main(): input_filename = "Data_new.txt" output_dir = "output" combined_filename = "combined_audio.wav" amplification_factor = 50.0 # 音量放大系数 # WAV文件标准参数 CHANNELS = 1 SAMPWIDTH = 2 # 16-bit -> 2 bytes FRAMERATE = 8000 # --- 准备工作 --- # 创建输出目录 os.makedirs(output_dir, exist_ok=True) with open(input_filename, "r") as f: lines = [line.strip() for line in f if line.strip()] # --- 步骤 1: 根据SSRC对RTP包进行分组 --- streams = defaultdict(list) stream_order = [] for line in lines: if len(line) < 24: continue ssrc = line[16:24] payload_hex = line[24:] if not payload_hex: continue if ssrc not in streams: stream_order.append(ssrc) streams[ssrc].append(bytes.fromhex(payload_hex)) if not streams: print("未在文件中找到有效的RTP数据包。") return print(f"处理完成,共找到 {len(stream_order)} 个不同的音频流。") print("SSRC 出现顺序:", stream_order) # --- 步骤 2: 逐个处理流,导出片段并准备合并 --- final_pcm_data_list = [] total_samples_processed = 0 for ssrc in stream_order: # 拼接属于同一个流的所有payload raw_ulaw_data = b''.join(streams[ssrc]) if not raw_ulaw_data: print(f"SSRC {ssrc} 没有有效的音频数据,已跳过。") continue # 解码为PCM pcm_data = decode_ulaw_to_pcm16(raw_ulaw_data) # 放大音量 amplified_pcm_data = amplify_and_clip_pcm16(pcm_data, amplification_factor) # 计算片段的开始时间 start_time_seconds = total_samples_processed / FRAMERATE # 创建片段文件名并导出 segment_filename = f"{start_time_seconds:.3f}s.wav" segment_filepath = os.path.join(output_dir, segment_filename) print(f"正在导出片段: {segment_filepath}") write_wav(segment_filepath, amplified_pcm_data, CHANNELS, SAMPWIDTH, FRAMERATE) # 为合并做准备 final_pcm_data_list.append(amplified_pcm_data) # 更新已处理的总采样数 num_samples_in_segment = len(amplified_pcm_data) // SAMPWIDTH total_samples_processed += num_samples_in_segment # --- 步骤 3: 合并所有片段并导出最终文件 --- if final_pcm_data_list: combined_pcm_data = b''.join(final_pcm_data_list) combined_filepath = os.path.join(output_dir, combined_filename) print(f"\n正在导出合并后的文件: {combined_filepath}") write_wav(combined_filepath, combined_pcm_data, CHANNELS, SAMPWIDTH, FRAMERATE) print("\n所有任务完成") else: print("\n没有可处理的音频数据") if __name__ == "__main__": main()用语音识别模型(我用的是 FunASR)识别导出的 combined_audio.wav 得到字幕文件
粗略看了下没啥信息,是由多条听起来有点逻辑的语句直接拼接起来的,然而实际上并没有任何意义
因为附件还给了个 Secret.7z 压缩包,联系到题目描述不难想到“监听设备中的加密信息”指的是这些录音,那么“绝密情报”就是 Secret.7z 了,进而可以推测音频中隐藏着 Secret.7z 的解压密码
看了下字幕文件只有 14207 行,索性直接一股脑丢给 Gemini 问问看有没有比较突兀的地方

回到字幕文件找到这一段

定位到这个片段发现确实是在念数字,人工识别得到:
651466314514271616614214660701456661601411451426071146666014214371656514214470尝试使用这串数字作为密码解压缩包失败了,观察发现字符的范围是 0-7,因此推测这里要八进制转字符
用动态规划算法切成若干个长度为 2 或 3 的八进制段寻找可能的解:
s = "651466314514271616614214660701456661601411451426071146666014214371656514214470" from functools import lru_cache @lru_cache(None)def dp(i): if i == len(s): return [[]] # 分割到末尾返回空解 res = [] for l in (2, 3): # 尝试2或3位八进制段 if i + l <= len(s): part = s[i:i+l] val = int(part, 8) # 按八进制解析 if 32 <= val <= 126: # 判断是否可打印 for tail in dp(i + l): res.append([val] + tail) return res solutions = dp(0)if solutions: for vals in solutions: decoded = "".join(chr(v) for v in vals) print(decoded)发现有且仅有一个解:5f3eb916bf08e610aeb09f60bc955bd8
这个解就是压缩包的解压密码,解开压缩包后得到 绝密录音.mp3
绝密录音.mp3 内存储了一段对话(其中A是普通话,B是粤语):
A:表兄,近日可好?上回托您带的廿四旦秋茶,家母嘱咐务必在辰时正过三刻前送到,切记用金丝锦盒装妥,此处潮气重,莫让干货受了霉,若赶得及时可赶得菊花开前便可让铺子开张。B:一切安好,我会按照要求准备好秋茶,我该送到何地?A:送至双鲤湖西岸南山茶铺,放右边第二个橱柜,莫放错。B:我已知悉,你在那边可还安好?A:一切安好,希望你我二人早日相见。B:指日可待,茶叶送到了,但是晚了时日,茶铺看来只能另寻良辰吉日了。你在那边千万保重!-
“廿四”指的是
24日 -
“辰时”指的是上午 7~9时,“辰时正”指的是
8时 -
“三刻”指的是
45分 -
地点是对话中提到的
双鲤湖西岸南山茶铺
双鲤湖位于福建省金门,结合这些信息可以找到1949年10月24日发起的金门战役,因此年份是 1949年
连起来就是 1949年10月24日8时45分于双鲤湖西岸南山茶铺
FLAG
flag{2a97dec80254cdb5c526376d0c683bdd}The_Interrogation_Room
Challenge
Reminder:
- Complete all rounds to get the flag (or a gift).
- Any invalid token terminates the session.
- Spaces must be added on both sides of ’(’ and ’)’.
Solution
本题的核心是一个逻辑推理挑战,我们需要在25轮游戏中的每一轮都成功推断出服务器在后台生成的8个未知的布尔秘密值(S0 到 S7),挑战规则如下:
- 查询机会:每轮有 17 次提问机会
- 查询方式:提问是通过发送一个由白名单内操作符(
['==','(',')','S0','S1','S2','S3','S4','S5','S6','S7','0','1','and','or'])组成的逻辑表达式 - 核心障碍:在17个回答中,服务器会精确地说谎 2 次(即返回与真实计算结果相反的布尔值)
- 目标:利用这17个可能包含错误的回答反推出唯一正确的 8 个秘密值
这个问题本质上是一个纠错码问题,我们需要设计一个信息冗余的查询系统,使得即便信息在传输过程中出现了2个比特的错误也依然能够恢复出原始的8比特信息。
为了尽可能地提高成功率,我们要设计一个能够消除绝大多数歧义性的查询策略。
==操作符的特性:在布尔逻辑中,A == B等价于XNOR(异或非)。当链式使用时(如S0 == S1 == S2),它会检查参与运算的变量中值为True的个数是奇数还是偶数,这种校验方式比or或and提供了更强的数学约束。
因此我们可以构建一个基于奇偶校验的编码系统,使用 == 操作符来实现奇偶校验,利用全部17次查询来构建一个强大的校验矩阵。
设计查询集(17个问题)
设计如下查询组合以最大化信息获取和冗余度:
-
8 个直接查询:
直接查询S0,S1, …,S7。
这为我们提供了含有最多2个错误的原始数据。 -
9 个奇偶校验查询:
设计 9 个不同的互相重叠的秘密子集并对它们进行==链式查询,这些查询充当了纠错码中的“校验位”,用于精确定位错误。text# 例如:S0 == S1 == S2 == S3S4 == S5 == S6 == S7S0 == S2 == S4 == S6 # 偶数位... (以及其他精心挑选的组合)这个查询集确保其最小汉明距离足够大,足以纠正2个比特的错误。
解码与暴力破解
在获得17个回答后采取以下步骤进行解码:
-
遍历所有可能性:由于秘密总共只有 8 位,所以只存在
2^8 = 256种可能的组合,可以进行暴力破解。 -
验证每个候选解:对 256 种可能的秘密组合,执行以下验证:
a. 假设候选解为真:假设当前遍历到的组合就是囚犯心中的真实秘密。
b. 计算理想答案:基于这个假设计算出我们设计的 17 个查询的全部正确答案。
c. 比较并计算差异:将这 17 个理想答案与服务器返回的 17 个回答逐一比较,计算出它们之间有多少个不一致(即汉明距离)。
d. 寻找匹配:根据题目规则,真实的秘密组合所产生的理想答案,与服务器的回答之间的汉明距离必须精确等于2。
处理极少数的歧义情况
实验证明,存在极小概率的情况会导致找到不止一个满足条件的候选解,处理方案如下:
- 如果只找到一个解,那么它就是正确答案。
- 如果找到多个解,脚本会记录一个警告,并猜测第一个解作为答案提交。
- 如果猜测错误,服务器会断开连接。此时要重新运行脚本,重跑几次总有一次能成功通过 25 轮。
from pwn import *from hashlib import sha256import stringimport itertoolsfrom functools import reduce # --- Config ---context.log_level = 'info'HOST = '39.106.45.147'PORT = 39009 # --- PoW Solver ---def solve_pow(p): p.recvuntil(b'sha256(XXXX+') suffix = p.recvuntil(b')', drop=True).decode() p.recvuntil(b'== ') target_hash = p.recvline().strip().decode() log.info(f"Solving PoW: sha256(XXXX+{suffix}) == {target_hash}") for prefix in itertools.product(string.ascii_letters + string.digits, repeat=4): prefix_str = "".join(prefix) guess = (prefix_str + suffix).encode() if sha256(guess).hexdigest() == target_hash: log.success(f"PoW solved! XXXX = {prefix_str}") p.sendlineafter(b'Give me XXXX: ', prefix_str.encode()) return log.error("PoW failed!") exit(1) # --- Helper for Parity Calculation ---def calculate_parity(booleans): if not booleans: return True return reduce(lambda a, b: a == b, booleans) # --- Main Logic for a Single Round ---def solve_round(p): log.info("Starting new round with Parity Code query set...") questions = [] # 1. 8 direct queries for i in range(8): questions.append(f"S{i}") # 2. 9 parity check queries for maximum error correction capability parity_indices = [ [0, 1, 2, 3], [4, 5, 6, 7], [0, 1, 4, 5], [2, 3, 6, 7], [0, 2, 4, 6], [1, 3, 5, 7], [0, 3, 5], [1, 2, 6], [0, 4, 7] ] for indices in parity_indices: questions.append(" == ".join([f"S{i}" for i in indices])) responses = [] for i, q in enumerate(questions): # On the first question of a round, check the received preamble for the gift received_data = p.sendlineafter(b"Ask your question:", q.encode(), timeout=5) if i == 0 and b"Here is a gift for you:" in received_data: for line in received_data.strip().split(b'\n'): if b"Here is a gift for you:" in line: log.warning(f"GIFT RECEIVED: {line.decode()}") p.recvuntil(b"Prisoner's response: ") res = p.recvline().strip().decode().replace("!", "") responses.append(res == 'True') possible_solutions = [] for i in range(256): candidate_secrets = [(i >> j) & 1 == 1 for j in range(8)] true_results = candidate_secrets[:] for indices in parity_indices: vals_for_parity = [candidate_secrets[k] for k in indices] parity_val = calculate_parity(vals_for_parity) true_results.append(parity_val) distance = sum(1 for j in range(17) if responses[j] != true_results[j]) if distance == 2: possible_solutions.append(candidate_secrets) # Handle the rare but real cases of ambiguity if len(possible_solutions) >= 1: if len(possible_solutions) > 1: log.warning(f"AMBIGUITY DETECTED: Found {len(possible_solutions)} solutions. Guessing the first one.") else: log.success(f"Found unique solution: {possible_solutions[0]}") solution = possible_solutions[0] else: # len == 0 log.error("FATAL: Found NO possible solutions. The logic is flawed or the server is inconsistent.") p.interactive() exit(1) solution_str = " ".join(map(str, map(int, solution))) p.sendlineafter(b"Now reveal the true secrets (1 for true, 0 for false):", solution_str.encode()) # --- Main Connection Handler ---def main(): try: p = remote(HOST, PORT) solve_pow(p) for i in range(25): log.info(f"--- Starting Round {i+1}/25 ---") solve_round(p) # Check for success or failure to prevent hanging on a closed socket response = p.recvline(timeout=3) if b"laughs triumphantly" in response: log.error("Round failed, likely due to an unlucky ambiguous case.") log.error("The server has closed the connection. Please restart the script.") return log.success("All rounds completed! Receiving flag...") p.interactive() except EOFError: log.error("Connection closed unexpectedly. This can happen after a failed round.") log.warning("Please re-run the script.") if __name__ == "__main__": main()[x] Opening connection to 39.106.45.147 on port 39009[x] Opening connection to 39.106.45.147 on port 39009: Trying 39.106.45.147[+] Opening connection to 39.106.45.147 on port 39009: Done[*] Solving PoW: sha256(XXXX+gkNcEHeSaUjh8lbR) == 18cc96fea5027c239eb6e8374633cd6986b661039506d41d3fd917319d955b33[+] PoW solved! XXXX = s4zb...[*] --- Starting Round 11/25 ---[*] Starting new round with Parity Code query set...[!] GIFT RECEIVED: Here is a gift for you: NDM0MTUyMmQzMTM1ZTdhYTgxZTU4N2JiZTZhZGE1ZTY5ZWFhMmRlNzgzYmRlNzgxYWJlNTljYjBlNWI4YTYyZDM2NDg1MjQ4NTQ0ZTQzMzA0MjM0Mzk0YzRmNDg1NjQzMzMzMDUzMzgzNw[+] Found unique solution: [True, True, True, True, False, True, True, False]...[+] All rounds completed! Receiving flag...[*] Switching to interactive modeThe prisoner scowls as you expose his lies. 'Very well, ask your next round of questions then.'The prisoner slumps in defeat: 'Alright, you win! I'll tell you everything.' He confesses all his secrets and reveals the hidden location of flag{42b7aa34-00c7-4c4a-88c2-c91e9ee9b315}' As he signs the confession, you notice a coded message hidden in his handwriting that leads you to the ultimate prize.[*] Interrupted[*] Closed connection to 39.106.45.147 port 39009FLAG
flag{42b7aa34-00c7-4c4a-88c2-c91e9ee9b315}Personal Vault
Challenge
My friend created a vault for each process, unfortunately we haven’t contacted for years, and this vault thing crashed my pc when I tried checking other’s secret? Please help me with this
附件下载 提取码(GAME)
Solution
非预期

FLAG
flag{personal_vault_seems_a_little_volatile_innit}Reverse
butterfly
Challenge
(空)
Solution
入口链路与主函数定位
- 入口链路:start → __startup_libc_wrapper(0x4041B0) → cpu_feature_init_ifunc(0x403200) → call_main_trampoline(0x4021F0) → main(0x4018D0)
- 通过字符串与调用关系可见 main 打印 Usage/Encoding/Encoded size/%s.key 等信息,确认其为核心逻辑。
main@0x4018D0(核心流程)
// 参数检查if (argc != 3) { printf("Usage: %s <input_file> <output_file>\n", argv[0]); printf("Example: %s plaintext.txt encoded.dat\n", argv[0]); return 1;} in = fopen(argv[1], "rb"); // sub_405540fseek(in, 0, SEEK_END); // sub_407480(..., 2)n = ftell_like(in); // sub_405640fseek(in, 0, SEEK_SET); // sub_407480(..., 0) buf = malloc(n + 8); // sub_412620read_n = fread_like(buf, 1, n, in); // sub_41CC80fclose_like(in); // sub_405180 if (read_n != n) error("File read failed"); // 关键:MMX 块变换(逐 8 字节)mmx_loop(buf, n, key8 = first8bytes_of_key_material); // 写出编码结果ok = write_file(argv[2], buf, n); // sub_401CA0if (ok) { // 生成 key 文件名并写出 32 字节 key 材料 snprintf("%s.key", argv[2]); // sub_4777A0 write_file(key_path, key_material_32, 32); // sub_401CA0} free(buf); // sub_412CF0return 0;MMX 编码循环的关键指令(0x401A49—0x401A73):
movq mm0, [rax] ; 加载 8 字节数据块movq mm1, [rsp+var_138]; 加载 8 字节 key(由 "MMXEncode2024" 派生/缓存)pxor mm0, mm1 ; x ^= key8movq mm2, mm0psllw mm2, 8 ; mm2 = x << 8(以 16-bit lane 为单位)psrlw mm0, 8 ; mm0 = x >> 8(以 16-bit lane 为单位)por mm0, mm2 ; x = swap16(x)(每 16 位内交换高低字节)movq mm2, mm0psllq mm0, 1 ; x = rol1(x)(64 位整体左移1位)psrlq mm2, 3Fh ; 取原最低位做循环por mm0, mm2 ; 合成循环左移paddb mm0, mm1 ; x += key8(按字节求和 mod 256)movq [rax], mm0 ; 写回结论(每个 8 字节块 x → y):
- y = add8( rol1( swap16( x XOR key8 ) ), key8 )
- 尾余(<8 字节)未进入 MMX 循环,保持原样。
sub_401CA0(写文件封装)
int write_file(const char* path, const void* data, size_t n) { f = fopen(path, "wb"); // sub_405540 if (!f) { log("Error: Cannot create file %s"); return 0; } written = fwrite_like(data, 1, n, f); // sub_440140(..., f) fclose_like(f); // sub_405180 if (written != n) { log("Error: File write failed"); return 0; } return 1;}功能:安全写文件并校验长度;失败路径打印错误。
sub_405540(fopen 包装)
伪代码要点:
- sub_412620 分配 FILE 结构;sub_40BF90 初始化;sub_407830 做额外设置。
- sub_407E10(v3, a1, “rb”/“wb”, 1) 实际为 _IO_new_file_fopen 路径。
- 成功则返回已初始化的文件对象指针;失败则清理并返回 0。
功能:glibc _IO 层封装的 fopen-like。
sub_405640(获取长度/定位配合)
- 在互斥/线程本地存储保护下调用 sub_4057E0(a1, 0, 1, 0) 等,配合 fseek/ftell 语义。
- 返回“当前位置或大小”,与 main 的两次 sub_407480(…2/0) 配合可得文件长度。
功能:获取“文件长度”或“当前位置”的封装。
sub_407480(文件定位封装)
- 在锁保护下更新流的 owner/tid 与递归计数。
- 最终调用 sub_4057E0(a1, a2, n2, 3),n2=2/0 分别对应 SEEK_END/SEEK_SET。
功能:fseek/rewind 等价封装。
sub_41CC80(读取封装)
// 溢出与区间检查if (n != 0 && a3 != 0 && (n*a3 overflows || n7_6 < n*a3)) sub_41CB80(...); // 读前加锁/进入 tcache/arena 体系// ...nread_total = sub_40B820(stream, buf, n*a3, ...); // 实质 fread// 读后解锁/递归计数回退return nread_total == n*a3 ? n : nread_total / a3;功能:带线程/arena 管理的 fread 等价封装(静态 glibc 影子实现)。
import sysfrom pathlib import Path def ror64_1_le(block8: bytes) -> bytes: # rotate-right by 1 bit on the 64-bit little-endian value v = int.from_bytes(block8, 'little') v = ((v >> 1) | ((v & 1) << 63)) & ((1 << 64) - 1) return v.to_bytes(8, 'little') def swap_each_16bit_bytes(b: bytes) -> bytes: # swap bytes within each 16-bit lane: [b0 b1][b2 b3]... -> [b1 b0][b3 b2]... ba = bytearray(b) for i in range(0, len(ba), 2): if i + 1 < len(ba): ba[i], ba[i+1] = ba[i+1], ba[i] return bytes(ba) def per_byte_sub(a: bytes, key8: bytes) -> bytes: return bytes(((a[i] - key8[i % 8]) & 0xFF) for i in range(len(a))) def per_byte_xor(a: bytes, key8: bytes) -> bytes: return bytes((a[i] ^ key8[i % 8]) for i in range(len(a))) def decrypt_block(block8: bytes, key8: bytes) -> bytes: # Encryption: c = paddb( rol64( swap16( x ^ key )), key ) # Decryption: x = ( swap16( ror64( c - key )) ) ^ key t1 = per_byte_sub(block8, key8) t2 = ror64_1_le(t1) t3 = swap_each_16bit_bytes(t2) plain = per_byte_xor(t3, key8) return plain def load_key8(key_path: Path) -> bytes: # Try to read first 8 bytes from key file, fallback to b"MMXEncod" fallback = b"MMXEncode2024"[:8] try: data = key_path.read_bytes() if len(data) >= 8: return data[:8] except Exception: pass return fallback def main(): enc_path = Path("encode.dat") key_path = Path("encode.dat.key") if not enc_path.exists(): print("encode.dat not found", file=sys.stderr) sys.exit(1) key8 = load_key8(key_path) enc = enc_path.read_bytes() out = bytearray() n = len(enc) # process full 8-byte blocks full_blocks = (n // 8) for i in range(full_blocks): blk = enc[i*8:(i+1)*8] out.extend(decrypt_block(blk, key8)) # tail bytes remain unchanged (encrypter仅对满8字节块处理) tail = enc[full_blocks*8:] if tail: out.extend(tail) # print to stdout (binary-safe) sys.stdout.buffer.write(bytes(out)) if __name__ == "__main__": main()FLAG
flag{butter_fly_mmx_encode_7778167}