DASCTF 2024.8 wp re
Maze
三维迷宫,两个迷宫切换,TLS中塞了点东西,识别出来之后再写个路径算法即可
TLS中一段字节与程序的一段xor来恢复逻辑

140001A60(watchdogThreadMain)前面的一段逻辑用于反调试

我们可以修补到这:

执行0x90 ^ key[i]
1 2 3 4 5 6 7 8 9 10 11 12 13
| import ida_bytes
PATCH_ADDR = 0x140001B16 PATCH_HEX = ( "8b05184a000039050e4a000074648b05064a00008905044a0000" "833d0949000001750ac744242002000000eb08c7442420010000" "008b4424208905eb480000833de448000001750e488d059b4400" "004889442428eb0c488d05cd4600004889442428488d05c14600" "00488b4c2428488948c0" )
ida_bytes.patch_bytes(PATCH_ADDR, bytes.fromhex(PATCH_HEX)) print("watchdogThreadMain decrypted at 0x{:X}".format(PATCH_ADDR))
|


这里其实就是在切换地图(迷宫)
之后我们理解下迷宫就知道走法可以写脚本了
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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| from collections import deque from pathlib import Path import hashlib
def load_mazes(exe_path="Maze.exe"): data = Path(exe_path).read_bytes() base = 0x4200 a = data[base:base + 0x200] b = data[base + 0x240:base + 0x240 + 0x200] if len(a) != 0x200 or len(b) != 0x200: raise ValueError("Unexpected maze size") return [list(a), list(b)]
def slide(pos, maze, move): if move == 'a': while True: if pos % 8 - 1 < 0: return None if maze[pos - 1]: return pos pos -= 1 elif move == 'd': while True: if pos % 8 + 1 >= 8: return None if maze[pos + 1]: return pos pos += 1 elif move == 'w': while True: if pos % 64 - 8 < 0: return None if maze[pos - 8]: return pos pos -= 8 elif move == 's': while True: if pos % 64 + 8 >= 64: return None if maze[pos + 8]: return pos pos += 8 elif move == 'n': while True: if pos - 64 < 0: return None if maze[pos - 64]: return pos pos -= 64 elif move == 'u': while True: if pos + 64 >= 512: return None if maze[pos + 64]: return pos pos += 64 else: raise ValueError("Unknown move")
def find_path(mazes, start=0, goal=436): queue = deque([(start, 0)]) parents = {(start, 0): (None, None)} moves = ['a', 'd', 'w', 's', 'n', 'u'] while queue: pos, parity = queue.popleft() if pos == goal: path = [] state = (pos, parity) while parents[state][0] is not None: state, ch = parents[state] path.append(ch) return ''.join(reversed(path)) maze = mazes[parity] for ch in moves: new_pos = slide(pos, maze, ch) if new_pos is None: continue state = (new_pos, 1 - parity) if state not in parents: parents[state] = ((pos, parity), ch) queue.append(state) raise RuntimeError("No path found")
def main(): mazes = load_mazes() path = find_path(mazes) flag = hashlib.md5(path.encode()).hexdigest() print(f"path: {path}") print(f"flag: {flag}")
if __name__ == "__main__": main()
|
path: sdwusanwduawus
flag{1bb5fd78f2299f26ccc0630c5e7516b6}
ezCPP
通过 VirtualProtect 修改该派生类对象的 vtable 条目,使其指向两个自定义函数:sub_140001A60(抛出 runtime_error)和 sub_140001AA0(真正的“检查”函数)。异常路径 sub_140001A60 只在内存分配失败时使用,表面上属于反调试/反破坏保护。
1 2 3 4
| _BOOL8 sub_140001AA0() { return 5 / 0 != 0; }
|
sub_140001AA0 在进入时先执行一次 idiv 0;关键部分调用 sub_140001BA0,返回布尔值作为最终校验。

sub_140001BA0 逻辑较复杂:
- VirtualAlloc 一块 0x130 大小的可执行内存,把 src__0(长度 0x130,位于 .rdata)拷贝过去。
- catch 块里把该内存逐字节 XOR 0xDA 解密,得到一段自修改后的代码;随后用 (BeingDebugged) 作为参数调用。我们已用脚本解密出这段代码,确认它是一个 64 位函数,实现了 32 轮 XTEA 风格的运算,常量 0xF3E56、0x42CA4455、0x8E0AE93B、0xA569C4D0 作为 128bit key,Delta 为 0x523A855B。
- 整体结构符合典型 XTEA,使用 key table 和 delta 做 32 轮更新。
- 函数结果传入 sub_140001820 -> sub_1400029D0(转换为 std::string)。随后 sub_140001DE0 拷贝 src__1(21 字节字符串)到一个缓冲,并通过 sub_140002290 在必要时执行 (i%5+3) XOR 的互斥解密;再与刚生成的字符串比较 (sub_140002DF0)。若匹配则返回 true。
- 若失败,再进行一次几乎相同的流程,换用 sub_140001E80 拷贝的另一串(“6?<82>967 4:1=??468$\a”),走 (i%10+7) XOR 的解码,再比较。

这是还原后的代码,dump到了一个单独的二进制文件反编译分析

字节异或


已经验证:
- sub_140001D70 的 XOR 方案对 src__1 生成的明文是 12055721120662551337。
- sub_140001E10 对第二串产生明文 17529248803287439874。
- 对应到上一步 XTEA 输出必须等于这两种明文之一(先比第一串,失败再比第二串)。
1 2 3 4 5 6 7 8 9 10 11
| src_1 = bytes.fromhex("32 36 35 33 32 34 36 34 37 35 33 32 33 34 32 36 35 36 35 30 03") src_3 = bytes.fromhex("36 3F 3C 38 32 3E 39 36 37 20 34 3A 31 3D 3F 3F 34 36 38 24 07") print(len(src_1)) def xor_decode(data, mod, offset): return bytes(((i % mod) + offset) ^ b for i, b in enumerate(data))
goal1 = xor_decode(src_1, mod=5, offset=3).rstrip(b"\x00").decode() goal2 = xor_decode(src_3, mod=10, offset=7).rstrip(b"\x00").decode()
print("target #1:", goal1) print("target #2:", goal2)
|
Exp:
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
| DELTA = 0xF3E56 KEY_BASE = [0x42CA4455, 0x8E0AE93B, 0xA569C4D0, 0x523A855B] TARGETS = [ "12055721120662551337", "17529248803287439874", ]
def tea_decrypt(result, being_debugged=0): a1 = (result >> 32) & 0xFFFFFFFF a2 = result & 0xFFFFFFFF key = KEY_BASE.copy() key[3] = being_debugged + 0x523A855B sum_ = (DELTA * 0x20) & 0xFFFFFFFF for _ in range(0x20): idx = (sum_ >> 11) & 3 a2 = (a2 - ((key[idx] + sum_) ^ (a1 + ((a1 >> 5) ^ ((a1 << 4) & 0xFFFFFFFF))))) & 0xFFFFFFFF sum_ = (sum_ - DELTA) & 0xFFFFFFFF idx = sum_ & 3 a1 = (a1 - ((key[idx] + sum_) ^ (a2 + ((a2 >> 5) ^ ((a2 << 4) & 0xFFFFFFFF))))) & 0xFFFFFFFF return (a1 << 32) | a2
def main(): for s in TARGETS: value = int(s) block = tea_decrypt(value) candidate = block.to_bytes(8, "big") print(f"target {s} -> {candidate!r}") print("\nflag input:", tea_decrypt(int(TARGETS[1])).to_bytes(8, "big").decode())
if __name__ == "__main__": main()
|
7up@fT3A
结果是错的,稍微短了点,应该有哪里漏了
忘记了 程序是根据是否被调试来进行解密的
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
| DELTA = 0xF3E56 KEY_BASE = [0x42CA4455, 0x8E0AE93B, 0xA569C4D0, 0x523A855B] TARGETS = [ "12055721120662551337", "17529248803287439874", ]
def tea_decrypt(result, being_debugged=0): result ^= being_debugged a1 = (result >> 32) & 0xFFFFFFFF a2 = result & 0xFFFFFFFF key = KEY_BASE.copy() key[3] = being_debugged + 0x523A855B sum_ = (DELTA * 0x20) & 0xFFFFFFFF for _ in range(0x20): idx = (sum_ >> 11) & 3 a2 = (a2 - ((key[idx] + sum_) ^ (a1 + ((a1 >> 5) ^ ((a1 << 4) & 0xFFFFFFFF))))) & 0xFFFFFFFF sum_ = (sum_ - DELTA) & 0xFFFFFFFF idx = sum_ & 3 a1 = (a1 - ((key[idx] + sum_) ^ (a2 + ((a2 >> 5) ^ ((a2 << 4) & 0xFFFFFFFF))))) & 0xFFFFFFFF return (a1 << 32) | a2
def main(): print("\nflag input:", tea_decrypt(int(TARGETS[0]),1).to_bytes(8, "big").decode()) print("\nflag input:", tea_decrypt(int(TARGETS[1]),0).to_bytes(8, "big").decode())
if __name__ == "__main__": main()
|
G0g3tTea7up@fT3A
DASCTF{G0g3tTea7up@fT3A}
FakeApple
TSCTF-J 2025 wp | Matriy’s blog
最后一题