Loading... # N1CTF 2025 wp 这re题出的,一个随机数(怎么还有misc标签),一个臭VMP(和vm有啥关系没搞懂),一个不需要安装就可以逆的驱动(还玩了把misc找压缩包隐藏文件),唯一有点看头的题目算是`True Operator Master`,出的挺复杂,但还是可以记录下的 ## True Operator Master 8字节一组加密,每次传入16字节的密钥 ~~~c // bad sp value at call has been detected, the output may be wrong! // positive sp value has been detected, the output may be wrong! void sub_7FF621DFEDD7() { int v0; // [rsp+28h] [rbp-C8h] BYREF int v1; // [rsp+2Ch] [rbp-C4h] _DWORD v2[4]; // [rsp+30h] [rbp-C0h] BYREF _DWORD v3[12]; // [rsp+40h] [rbp-B0h] BYREF char v4[64]; // [rsp+70h] [rbp-80h] BYREF char Str[56]; // [rsp+B0h] [rbp-40h] BYREF int j; // [rsp+E8h] [rbp-8h] int i; // [rsp+ECh] [rbp-4h] sub_7FF621DFF040(); puts("Input your flag: "); v2[0] = 0xB4B3522E; v2[1] = 0x23DDF732; v2[2] = 0x14C23B21; v2[3] = 0xF3F3ACB4; sub_7FF621E000C0("%48s"); if ( strlen(Str) != 48 ) { puts("Wrong length!"); sub_7FF621DFFC40(0LL); } sub_7FF621DCD3E8((__int64)Str, (__int64)v3, 48); for ( i = 0; i <= 11; i += 2 ) { v0 = v3[i]; v1 = v3[i + 1]; ((void (__fastcall *)(int *, _DWORD *))sub_7FF621DCD5AC)(&v0, v2); v3[i] = v0; v3[i + 1] = v1; } sub_7FF621DCD4B5((__int64)v3, (__int64)v4, 48); for ( j = 0; j <= 47; ++j ) { if ( v4[j] != byte_7FF621E01000[j] ) { puts("Wrong flag!"); sub_7FF621DFFC40(0LL); } } puts("Right flag!"); sub_7FF621DFFC40(0LL); JUMPOUT(0x7FF621DFEF74LL); } ~~~ sub_7FF621DCD5AC是加密函数,双击发现`Decompilation failure:7FF621DCD5AC: stack frame is too big`,转到汇编视图看下,可以看到出现了大量cmp和跳转指令 很难分析就是了,想到利用ida自身的trace把一轮加密函数走过的汇编trace记录导出来,在进入函数前下断点,勾选`Enable tracing`,然后下一行出来后再下断点勾选`Disable tracing`,在下一行设置断点(跑一轮就可以停下来了,反正加密都一样)  停下来后打开Tracing window,右键导出到txt,稍微分析了下发现里面存在大量控制流混淆,所以我直接尝试python去除存在`"cmp [rbp+160h+var_"`的一行,减少了非常多混杂代码  接着从头开始看,发现trace进入加密函数后又调用了很多基本操作运算函数,但是里面有花指令和反调试,而且非常多相同操作函数  ~~~c __int64 __fastcall sub_7FF621D9FAE5(__int64 a1, __int64 a2) { return a2 & a1; } ~~~ 看反编译可能看着是and操作,但是看看汇编代码就知道有猫腻 ~~~assembly .text:00007FF621D9FAE5 .text:00007FF621D9FAE5 ; =============== S U B R O U T I N E ======================================= .text:00007FF621D9FAE5 .text:00007FF621D9FAE5 ; Attributes: bp-based frame .text:00007FF621D9FAE5 .text:00007FF621D9FAE5 ; __int64 __fastcall sub_7FF621D9FAE5(__int64, __int64) .text:00007FF621D9FAE5 sub_7FF621D9FAE5 proc near ; CODE XREF: sub_7FF621DCD5AC+1046↓p .text:00007FF621D9FAE5 ; DATA XREF: .pdata:00007FF621E04DDC↓o ... .text:00007FF621D9FAE5 55 push rbp .text:00007FF621D9FAE6 48 89 E5 mov rbp, rsp .text:00007FF621D9FAE9 48 83 EC 10 sub rsp, 10h .text:00007FF621D9FAED 50 push rax .text:00007FF621D9FAEE 53 push rbx .text:00007FF621D9FAEF 51 push rcx .text:00007FF621D9FAF0 52 push rdx .text:00007FF621D9FAF1 41 50 push r8 .text:00007FF621D9FAF3 48 89 C8 mov rax, rcx .text:00007FF621D9FAF6 48 31 DB xor rbx, rbx .text:00007FF621D9FAF9 48 83 F3 01 xor rbx, 1 .text:00007FF621D9FAFD E8 00 00 00 00 call $+5 .text:00007FF621D9FB02 48 83 F3 01 xor rbx, 1 .text:00007FF621D9FB06 48 83 FB 01 cmp rbx, 1 .text:00007FF621D9FB0A 74 04 jz short loc_7FF621D9FB10 .text:00007FF621D9FB0C 48 21 D0 and rax, rdx .text:00007FF621D9FB0F C3 retn .text:00007FF621D9FB10 ; --------------------------------------------------------------------------- .text:00007FF621D9FB10 .text:00007FF621D9FB10 loc_7FF621D9FB10: ; CODE XREF: sub_7FF621D9FAE5+25↑j .text:00007FF621D9FB10 B8 30 00 00 00 mov eax, 30h ; '0' .text:00007FF621D9FB15 65 48 8B 00 mov rax, gs:[rax] .text:00007FF621D9FB19 48 8B 40 60 mov rax, [rax+60h] .text:00007FF621D9FB1D 0F B6 40 02 movzx eax, byte ptr [rax+2] .text:00007FF621D9FB21 83 F8 00 cmp eax, 0 .text:00007FF621D9FB24 41 58 pop r8 .text:00007FF621D9FB26 5A pop rdx .text:00007FF621D9FB27 59 pop rcx .text:00007FF621D9FB28 5B pop rbx .text:00007FF621D9FB29 58 pop rax .text:00007FF621D9FB2A 48 89 C8 mov rax, rcx .text:00007FF621D9FB2D 75 0E jnz short loc_7FF621D9FB3D .text:00007FF621D9FB2F 48 89 D3 mov rbx, rdx .text:00007FF621D9FB32 48 31 D2 xor rdx, rdx .text:00007FF621D9FB35 48 F7 F3 div rbx .text:00007FF621D9FB38 48 89 D0 mov rax, rdx .text:00007FF621D9FB3B EB 03 jmp short loc_7FF621D9FB40 .text:00007FF621D9FB3D ; --------------------------------------------------------------------------- .text:00007FF621D9FB3D .text:00007FF621D9FB3D loc_7FF621D9FB3D: ; CODE XREF: sub_7FF621D9FAE5+48↑j .text:00007FF621D9FB3D 48 21 D0 and rax, rdx .text:00007FF621D9FB40 .text:00007FF621D9FB40 loc_7FF621D9FB40: ; CODE XREF: sub_7FF621D9FAE5+56↑j .text:00007FF621D9FB40 48 83 C4 10 add rsp, 10h .text:00007FF621D9FB44 5D pop rbp .text:00007FF621D9FB45 C3 retn .text:00007FF621D9FB45 sub_7FF621D9FAE5 endp ; sp-analysis failed ~~~ 00007FF621D9FAFD处调用了call,执行到retn就会返回到00007FF621D9FB02处,我们分析下rbx值,最开始异或自身设置为了0,第一次异或1得到1,cmp发现相等不跳转执行到retn回到00007FF621D9FB02再异或1,得到0,此时比较发现不等则跳转到loc_7FF621D9FB10。loc_7FF621D9FB10处继续分析发现利用gs:[30h]获取了TIB,然后利用偏移值60h获取了PEB,PEB+2正是BeingDebugged标志位,调试的时候为1,利用pop恢复初始rax、rdx等值,cmp后发现不等就会跳转loc_7FF621D9FB3D,此时rax and后是最终值,返回rax。没调试的时候没有跳转,此时计算的是div操作。 分析其他函数可知套路一样,所有的运算函数都是存在一个反调试,检测到调试会走虚假的计算操作。 此时拿到的trace实际上是假的,因此需要patch去除所有反调试,然后在所有操作函数的真正计算汇编指令上下断点实现加密trace 首先尝试patch反调试,提取前面分析的反调试pattern(字节串),匹配所有地址,然后patch `movzx eax, byte ptr [rax+2]`为`xor eax, eax`,从而保证eax时终为0 ~~~python # IDA9 patch script # 查找字节序列 65 48 8B 00 48 8B 40 60 0F B6 40 02 # 将 movzx eax, byte ptr [rax+2] (4 bytes) 替换为: # xor eax, eax (31 C0) 2 bytes # nop (90) 1 byte # nop (90) 1 byte # 共 4 bytes,长度保持不变。 import ida_bytes import idautils import idc import idaapi PATTERN = b'\x65\x48\x8B\x00\x48\x8B\x40\x60\x0F\xB6\x40\x02' PAT_LEN = len(PATTERN) PREFIX_LEN = 8 # 前两条指令共 8 bytes (65 48 8B 00 | 48 8B 40 60) MOVZX_OFFSET = PREFIX_LEN # movzx 的偏移相对于 pattern 起点 # replacement: xor eax,eax ; nop ; nop REPLACEMENT = b'\x31\xC0\x90\x90' # 4 bytes to match original length patched = 0 ea = 0x7FF621D91450 while ea <= 0x7FF621DCD3E4 - PAT_LEN: data = ida_bytes.get_bytes(ea, PAT_LEN) if not data: ea += 1 continue if data == PATTERN: movzx_ea = ea + MOVZX_OFFSET # Safety checks: ensure disasm at movzx_ea looks like movzx mnem = idc.print_insn_mnem(movzx_ea).lower() if 'movzx' not in mnem: print("[WARN] pattern matched at 0x{:X} but mnemonic is '{}' (expected movzx)".format(movzx_ea, mnem)) # still proceed if you want; here we skip to be safe ea += 1 continue # patch bytes ida_bytes.patch_bytes(movzx_ea, REPLACEMENT) ea = movzx_ea + len(REPLACEMENT) else: ea += 1 ~~~ 接着匹配所有真实计算的汇编代码,需要把跳转函数的字节包含进来(\x75\x0\*),不然会匹配到前面的虚假计算,把所有出现的操作提取出来 ~~~ \x75\x05\x48\x31\xd0 \x48\x31\xd0 xor \x75\x05\x48\x01\xd0 \x48\x01\xd0 add \x75\x05\x48\x21\xd0 \x48\x21\xd0 and \x75\x0E\x48\x89\xD3\x48\x31\xD2\x48\xF7\xF3 \x48\xF7\xF3 div \x75\x05\x48\x29\xd0 \x48\x29\xd0 sub \x75\x05\x48\x09\xd0 \x48\x09\xd0 or \x75\x07\x88\xd1\x48\xd3\xe0 \x48\xd3\xe0 shl \x75\x07\x88\xd1\x48\xd3\xe8 \x48\xd3\xe8 shr ~~~ ~~~python # IDA9: 找到 pattern1 的每个出现位置,并在该出现位置的字节里定位 pattern2,打印 pattern2 的地址(每行一个地址,16 进制) import ida_bytes import idautils import idc import idaapi # 请在这里放入要搜索的 (pattern1, pattern2) 列表 patterns = [ (b'\x75\x05\x48\x31\xd0', b'\x48\x31\xd0'), # xor 示例 (b'\x75\x05\x48\x01\xd0', b'\x48\x01\xd0'), # add 示例 (b'\x75\x05\x48\x21\xd0', b'\x48\x21\xd0'), # and 示例 (b'\x75\x0E\x48\x89\xD3\x48\x31\xD2\x48\xF7\xF3', b'\x48\xF7\xF3'), # div 示例 (b'\x75\x05\x48\x29\xd0', b'\x48\x29\xd0'), # sub 示例 (b'\x75\x05\x48\x09\xd0', b'\x48\x09\xd0'), # or 示例 (b'\x75\x07\x88\xd1\x48\xd3\xe0', b'\x48\xd3\xe0'), # shl 示例 (b'\x75\x07\x88\xd1\x48\xd3\xe8', b'\x48\xd3\xe8'), # shr 示例 ] def find_pattern_addresses(pattern1, pattern2): found_addrs = [] p1_len = len(pattern1) ea = 0x7FF6CCCD1450 # 遍历段内可能的起始位置 last = 0x7FF6CCD0D3E6 - p1_len while ea <= last: data = ida_bytes.get_bytes(ea, p1_len) if not data: ea += 1 continue if data == pattern1: # 在该匹配块中找 pattern2 # 读取匹配块(有时 pattern2 比 p1 长或短,这里在 p1 范围内查找) # 如果希望在匹配起始地址之后的一段更长范围里查找,可修改读取长度 idx = data.find(pattern2) if idx != -1: addr_of_p2 = ea + idx found_addrs.append(addr_of_p2) # 如果 pattern2 可能在 pattern1 后紧随的字节中,需要扩大读取区域。 # 这里保持简单:只在匹配到的 pattern1 bytes 中查找 pattern2。 # 跳过到匹配块末尾继续搜索,避免重叠重复匹配 ea += p1_len else: ea += 1 return found_addrs # 执行并打印(每行一个地址,16 进制) for i, (p1, p2) in enumerate(patterns): addrs = find_pattern_addresses(p1, p2) for a in addrs: print(hex(a)) ~~~ 得到所有地址后去下条件断点,断点非常多当然不能人工下,这里我利用了一个我才写好的插件TraceHelper,能够根据输入的地址自动下断点,在调试过程中打印该地址的计算符号、操作数以及结果。插件还是挺方便,但暂时不考虑开源,还很不完善  Enable后我们在所有计算地址上下好了断点,跑一轮就可以打印所有计算操作,可以看到输入的1111、2222已转化为了0x31313131和0x32323232,每一轮2508行,观察发现正好是114轮加密,提取第一轮和最后一轮我们来看看 ~~~ # round 1 div 0x0, 0x7 = 0x0 sub 0x12345679, 0x0 = 0x12345679 div 0x0, 0x4 = 0x0 shl 0x32323232, 0x0 = 0x32323232 shr 0x32323232, 0x0 = 0x32323232 xor 0x32323232, 0xf3f3acb4 = 0xc1c19e86 xor 0x32323232, 0xb4b3522e = 0x8681601c and 0x32323232, 0x12345679 = 0x12301230 xor 0x8681601c, 0x12301230 = 0x94b1722c xor 0xc1c19e86, 0x94b1722c = 0x5570ecaa sub 0x31313131, 0x5570ecaa = 0xffffffffdbc04487 div 0x0, 0x5 = 0x0 div 0x0, 0x6 = 0x0 shr 0xdbc04487, 0x0 = 0xdbc04487 shl 0xdbc04487, 0x0 = 0xdbc04487 xor 0xdbc04487, 0x23ddf732 = 0xf81db3b5 xor 0xdbc04487, 0x14c23b21 = 0xcf027fa6 or 0xdbc04487, 0x12345679 = 0xdbf456ff xor 0xcf027fa6, 0xdbf456ff = 0x14f62959 xor 0xf81db3b5, 0x14f62959 = 0xeceb9aec add 0x32323232, 0xeceb9aec = 0x11f1dcd1e add 0x0, 0x1 = 0x1 # round 114 sub 0x4a270f1e, 0xbe6d41ef = 0xffffffff8bb9cd2f div 0x71, 0x7 = 0x10 div 0x71, 0x4 = 0x1c shl 0x9bf0fe6b, 0x1 = 0x137e1fcd6 shr 0x9bf0fe6b, 0x1 = 0x4df87f35 xor 0x37e1fcd6, 0xf3f3acb4 = 0xc4125062 xor 0x4df87f35, 0xb4b3522e = 0xf94b2d1b and 0x9bf0fe6b, 0x8bb9cd2f = 0x8bb0cc2b xor 0xf94b2d1b, 0x8bb0cc2b = 0x72fbe130 xor 0xc4125062, 0x72fbe130 = 0xb6e9b152 sub 0xc277c497, 0xb6e9b152 = 0xb8e1345 div 0x71, 0x5 = 0x16 div 0x71, 0x6 = 0x12 shr 0xb8e1345, 0x3 = 0x171c268 shl 0xb8e1345, 0x5 = 0x171c268a0 xor 0x171c268, 0x23ddf732 = 0x22ac355a xor 0x71c268a0, 0x14c23b21 = 0x65005381 or 0xb8e1345, 0x8bb9cd2f = 0x8bbfdf6f xor 0x65005381, 0x8bbfdf6f = 0xeebf8cee xor 0x22ac355a, 0xeebf8cee = 0xcc13b9b4 add 0x71, 0x1 = 0x72 add 0x9bf0fe6b, 0xcc13b9b4 = 0x16804b81f ~~~ 可以借助ai和手动分析同构出来(div那里绕了很久才发现不是div是mod,条件断点没打印余数) ~~~python import sys KEY_ARRAY = [ 0x00000000, 0x522e687e, 0x557b0378, 0x8244d52c, 0x5c448f8c, 0x629e21b2, 0xb28619aa, 0x82f3746e, 0xac1ddac0, 0xa0477863, 0x03a5c138, 0xb18205b0, 0x58ba85ec, 0x405032ee, 0x9d9a9360, 0x44e4d3f9, 0xfef76750, 0xd47e15c7, 0xcd00870e, 0xf5bf8ede, 0x3dd89734, 0x811de12d, 0xcfc2d132, 0xd1b4661a, 0xb99590e8, 0xf7c98504, 0x3adcd4e8, 0xd53d1764, 0x0784fe94, 0x1aa44ff5, 0xfb007960, 0x086be4a1, 0x8f467020, 0x3d705192, 0x6c11a02c, 0xd9e012d7, 0x3cf36d58, 0xb0a3f503, 0x4ea925ce, 0x0d4448a8, 0xc91e9f48, 0x9fc1f8c5, 0x17b238ac, 0xd908c9c2, 0x5a364984, 0x083e74da, 0x1551c22c, 0x5648e3d9, 0x0567f3b0, 0x6fc3e73b, 0x5f4d0634, 0xecdc1821, 0xf41daca8, 0x0a0275ab, 0xa5b01538, 0x6a96ddcb, 0x2c7e3c18, 0xba8a813d, 0x470e6262, 0x2c15f05f, 0x975d4b08, 0x312acb81, 0xbeb43a46, 0x77bc5005, 0xa3d90400, 0x1d1518aa, 0x0bac2d6e, 0xa8874213, 0xacd31ba4, 0xe5998224, 0x33e23c44, 0x8569cbb1, 0xb39090a8, 0x3e74de2d, 0x1db030f4, 0x5d73b653, 0xc86c7cc0, 0x7d302415, 0xd067862e, 0x0150c238, 0x4b0aecd0, 0xa6d2992b, 0xd1526be6, 0xc90ac785, 0xda2aaa4c, 0x761487ee, 0xe1e0412e, 0xd7ea059b, 0x7a7d8870, 0xbd88af69, 0x1291d1aa, 0xfcf88f42, 0x4dddfd14, 0x55e22263, 0xc2d76ad2, 0xf226a96b, 0x21d18fc0, 0x4300cfba, 0x81fcc5d2, 0x33778bcd, 0x21f5bf28, 0xab0f8127, 0xb774aaa0, 0x3e161403, 0x4668bb30, 0x96142323, 0x24ac5204, 0xbc86c121, 0x9302378c, 0xa759b71a, 0x5aa1f3be, 0x1b2456b7, 0xdbab4c90, 0xbe6d41ef ] # 您提供的 4 个密钥,作为算法中的魔法常数 K = [ 0xB4B3522E, # K[0] 0x23DDF732, # K[1] 0x14C23B21, # K[2] 0xF3F3ACB4 # K[3] ] def to_u32(val): """模拟 32 位无符号整数环绕""" return val & 0xFFFFFFFF def encrypt(v1, v2): """ 根据 op_trace.txt 复现的 114 轮加密算法 :param v1: 32 位整数 (左半块) :param v2: 32 位整数 (右半块) :return: (加密后的 v1, 加密后的 v2) """ # 确保输入是 32 位的 v1 = to_u32(v1) v2 = to_u32(v2) # 密钥调度的初始值 key_base = 0x12345679 # --- 开始 114 轮迭代 --- for i in range(114): # 1. 密钥生成 (Key Schedule) # 轮密钥是基于上一轮的密钥减去 KEY_ARRAY 中的一个值 key_base = to_u32(key_base - KEY_ARRAY[i]) round_key = key_base # 2. F1 函数 (作用于 v2) s1 = i % 4 # (来自 div 0x..., 0x4) s2 = i % 7 # (来自 div 0x..., 0x5) a = to_u32(v2 << s2) ^ K[3] # b = to_u32(v2 >> s1) ^ K[0] # c = v2 & round_key # f1_result = a ^ (b ^ c) # # 3. 更新 v1 (左半块) v1 = to_u32(v1 - f1_result) # 4. F2 函数 (作用于 *新的* v1) s3 = i % 5 # (来自 div 0x..., 0x6) s4 = i % 6 # (来自 div 0x..., 0x7) d = to_u32(v1 >> s3) ^ K[1] # e = to_u32(v1 << s4) ^ K[2] # f = v1 | round_key # f2_result = d ^ (e ^ f) # # 5. 更新 v2 (右半块) v2 = to_u32(v2 + f2_result) return v1, v2 # --- 主程序 --- if __name__ == "__main__": # 检查 KEY_ARRAY 长度是否正确 if len(KEY_ARRAY) != 114: print(f"错误:KEY_ARRAY 长度为 {len(KEY_ARRAY)},应为 114。") sys.exit(1) # 您的输入 "11112222" input_v1 = 0x31313131 input_v2 = 0x32323232 print(f"输入: v1=0x{input_v1:08x}, v2=0x{input_v2:08x}") print(f"密钥 (K): {[hex(k) for k in K]}") print("开始执行 114 轮加密...") final_v1, final_v2 = encrypt(input_v1, input_v2) print("-" * 30) print(f"加密完成!") print(f"最终 v1: 0x{final_v1:08x}") print(f"最终 v2: 0x{final_v2:08x}") print("-" * 30) # --- 验证 --- # 根据 op_trace.txt 的最后几行 # (在 i=113 / 0x71 轮次结束时) # sub 0xc277c497, 0xb6e9b152 = 0xb8e1345 (这是 final_v1) # add 0x9bf0fe6b, 0xcc13b9b4 = 0x16804b81f (这是 final_v2, 32 位截断为 0x6804b81f) expected_v1 = 0xb8e1345 expected_v2 = 0x6804b81f print("验证 (对照 trace 文件末尾):") print(f"预期 v1: 0x{expected_v1:08x}") print(f"预期 v2: 0x{expected_v2:08x}") if final_v1 == expected_v1 and final_v2 == expected_v2: print("\n[+] 验证成功!Python 复现与 trace 结果一致。") else: print("\n[-] 验证失败!Python 复现与 trace 结果不一致。") ~~~ 很好,现在可以交给ai放心逆向了,需要注意的是加密前后有个换位操作,解密前我们要先换下位,然后解密后还要再换个位 ~~~python KEY_ARRAY = [ 0x00000000, 0x522e687e, 0x557b0378, 0x8244d52c, 0x5c448f8c, 0x629e21b2, 0xb28619aa, 0x82f3746e, 0xac1ddac0, 0xa0477863, 0x03a5c138, 0xb18205b0, 0x58ba85ec, 0x405032ee, 0x9d9a9360, 0x44e4d3f9, 0xfef76750, 0xd47e15c7, 0xcd00870e, 0xf5bf8ede, 0x3dd89734, 0x811de12d, 0xcfc2d132, 0xd1b4661a, 0xb99590e8, 0xf7c98504, 0x3adcd4e8, 0xd53d1764, 0x0784fe94, 0x1aa44ff5, 0xfb007960, 0x086be4a1, 0x8f467020, 0x3d705192, 0x6c11a02c, 0xd9e012d7, 0x3cf36d58, 0xb0a3f503, 0x4ea925ce, 0x0d4448a8, 0xc91e9f48, 0x9fc1f8c5, 0x17b238ac, 0xd908c9c2, 0x5a364984, 0x083e74da, 0x1551c22c, 0x5648e3d9, 0x0567f3b0, 0x6fc3e73b, 0x5f4d0634, 0xecdc1821, 0xf41daca8, 0x0a0275ab, 0xa5b01538, 0x6a96ddcb, 0x2c7e3c18, 0xba8a813d, 0x470e6262, 0x2c15f05f, 0x975d4b08, 0x312acb81, 0xbeb43a46, 0x77bc5005, 0xa3d90400, 0x1d1518aa, 0x0bac2d6e, 0xa8874213, 0xacd31ba4, 0xe5998224, 0x33e23c44, 0x8569cbb1, 0xb39090a8, 0x3e74de2d, 0x1db030f4, 0x5d73b653, 0xc86c7cc0, 0x7d302415, 0xd067862e, 0x0150c238, 0x4b0aecd0, 0xa6d2992b, 0xd1526be6, 0xc90ac785, 0xda2aaa4c, 0x761487ee, 0xe1e0412e, 0xd7ea059b, 0x7a7d8870, 0xbd88af69, 0x1291d1aa, 0xfcf88f42, 0x4dddfd14, 0x55e22263, 0xc2d76ad2, 0xf226a96b, 0x21d18fc0, 0x4300cfba, 0x81fcc5d2, 0x33778bcd, 0x21f5bf28, 0xab0f8127, 0xb774aaa0, 0x3e161403, 0x4668bb30, 0x96142323, 0x24ac5204, 0xbc86c121, 0x9302378c, 0xa759b71a, 0x5aa1f3be, 0x1b2456b7, 0xdbab4c90, 0xbe6d41ef ] K = [ 0xB4B3522E, # K[0] 0x23DDF732, # K[1] 0x14C23B21, # K[2] 0xF3F3ACB4 # K[3] ] def to_u32(val): """模拟 32 位无符号整数环绕""" return val & 0xFFFFFFFF def generate_round_keys(): """ 预先计算加密算法使用的所有 114 个轮密钥。 """ keys = [] key_base = 0x12345679 for i in range(114): key_base = to_u32(key_base - KEY_ARRAY[i]) keys.append(key_base) return keys # --- 逆向求解(解密)函数 --- def decrypt(ct_v1, ct_v2): """ encrypt() 函数的逆向实现(解密)。 :param ct_v1: 32 位密文 (左半块) :param ct_v2: 32 位密文 (右半块) :return: (解密后的 v1, 解密后的 v2) """ # 1. 预先计算所有 114 个轮密钥 all_round_keys = generate_round_keys() v1 = to_u32(ct_v1) v2 = to_u32(ct_v2) # 2. --- 以相反的顺序 (113 -> 0) 进行 114 轮迭代 --- for i in reversed(range(114)): # 获取当前轮的密钥 round_key = all_round_keys[i] # 3. 逆向 F2 函数和 v2 的更新 # 加密: v2 = to_u32(v2 + f2_result) # 解密: v2 = to_u32(v2 - f2_result) # a. 计算 F2 (注意:F2 使用的是 *更新后* 的 v1) s3 = i % 5 s4 = i % 6 d = to_u32(v1 >> s3) ^ K[1] # v1 是本轮开始时的 v1_out e = to_u32(v1 << s4) ^ K[2] f = v1 | round_key f2_result = d ^ (e ^ f) # b. 恢复 v2 v2 = to_u32(v2 - f2_result) # 4. 逆向 F1 函数和 v1 的更新 # 加密: v1 = to_u32(v1 - f1_result) # 解密: v1 = to_u32(v1 + f1_result) # a. 计算 F1 (注意:F1 使用的是 *恢复后* 的 v2) s1 = i % 4 s2 = i % 7 a = to_u32(v2 << s2) ^ K[3] # v2 是刚刚恢复的 v2_in b = to_u32(v2 >> s1) ^ K[0] c = v2 & round_key f1_result = a ^ (b ^ c) # b. 恢复 v1 v1 = to_u32(v1 + f1_result) # 5. 返回完全恢复的 v1, v2 return v1, v2 if __name__ == "__main__": cmp = [0xEC6185B2, 0xD47844B8, 0x00EB9EE7, 0x17C6A398, 0xF6CF87F7, 0x04AE0512, 0xA7F53F10, 0x6A1A83DB, 0x9F52BFEF, 0xC933183C, 0x9830FD51, 0xAB852172] for i in range(len(cmp)): cmp[i] = ((cmp[i]>>16)|(cmp[i]<<16))&0xffffffff for i in range(0, len(cmp), 2): cmp[i:i+2] = decrypt(cmp[i], cmp[i+1]) flag = [int.to_bytes(cmp[i], 4, 'little') for i in range(len(cmp))] for i in range(len(flag)): flag[i] = flag[i].decode() for j in [1, 3, 2, 0]: print(flag[i][j], end="") ~~~ 得到`flag{C_L4nGu4g3_1Nt_c14s5_0P3r4t0r_Ov3r1o4D1nG?}` 最后修改:2025 年 11 月 03 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏