int sub_4F1500() { int v0; // eax unsigned int...
Erstellt am: 9. Juli 2026
Beantwortet mit GPT-5.5 Thinking von Chat01
Erstellt am: 9. Juli 2026
Beantwortet mit GPT-5.5 Thinking von Chat01
int sub_4F1500()
{
int v0; // eax
unsigned int v1; // esi
__m128i v2; // xmm1
const __m128i *DURL; // eax
__m128i v4; // xmm0
int *v5; // esi
int n7; // edi
_DWORD v8[2]; // [esp+8h] [ebp-C8h] BYREF
_DWORD v9[22]; // [esp+10h] [ebp-C0h] BYREF
int v10; // [esp+68h] [ebp-68h] BYREF
__int64 v11; // [esp+6Ch] [ebp-64h]
__int64 v12; // [esp+74h] [ebp-5Ch]
__int64 v13; // [esp+7Ch] [ebp-54h]
char v14; // [esp+84h] [ebp-4Ch] BYREF
__int64 v15; // [esp+85h] [ebp-4Bh]
__int64 v16; // [esp+8Dh] [ebp-43h]
__int64 v17; // [esp+95h] [ebp-3Bh]
__int64 v18; // [esp+9Dh] [ebp-33h]
char v19; // [esp+A8h] [ebp-28h] BYREF
__int64 v20; // [esp+A9h] [ebp-27h]
__int64 v21; // [esp+B1h] [ebp-1Fh]
__int64 v22; // [esp+B9h] [ebp-17h]
__int64 v23; // [esp+C1h] [ebp-Fh]
puts("=============================================================");
puts(" ");
puts(" Welcome To Reversing Adventure!!!! ");
puts(" ");
puts("=============================================================");
puts("[+] Game Start");
puts("You Find a special map when you sleep at Math course.");
puts("You try to read it, but you find that you could not understand what it's mean.");
puts("But...Hey, what's that?");
puts("6efef13f2a55a4ff356fab3b04f48d57??");
printf("[=]I have to seen this, it is:");
v8[0] = 0;
v8[1] = 0;
scanf("%d", v8);
v19 = 0;
v20 = 0LL;
v21 = 0LL;
v22 = 0LL;
v23 = 0LL;
v14 = 0;
v15 = 0LL;
v16 = 0LL;
v17 = 0LL;
v18 = 0LL;
v9[0] = 0;
v9[5] = 0;
v9[1] = 0x67452301;
v9[2] = 0xEFCDAB89;
v9[3] = 0x98BADCFE;
v9[4] = 0x10325476;
sub_4F18D0(v9, (int)v8);
sub_4F1A90(v9, &v19);
sub_4F1340((int)&v14, (int)&v19);
v0 = strcmp(&v14, "6efef13f2a55a4ff356fab3b04f48d57");
if ( v0 )
v0 = v0 < 0 ? -1 : 1;
if ( v0 )
{
puts("Game Over...");
}
else
{
v1 = v8[0];
puts("reading...");
v2 = _mm_shuffle_epi32(_mm_cvtsi32_si128(v1), 0);
DURL = (const __m128i *)::DURL;
// "DURL"
do
{
v4 = _mm_loadu_si128(DURL++);
DURL[-1] = _mm_xor_si128(v4, v2);
}
while ( (int)DURL < (int)aDurl );
puts(""Yes I did it"");
puts("[+]Try to find treasure!");
v10 = 0;
v11 = 0LL;
v12 = 0LL;
v13 = 0LL;
v5 = &v10;
n7 = 7;
do
{
scanf("%d", v5++);
--n7;
}
while ( n7 );
if ( sub_4F1390() )
{
sub_4F1000(&v10);
sub_4F1440();
return 1;
}
printf("Opps....\n");
}
return 0;
}
这是一道ctf逆向题目,请你提供思路解出flag
pythonimport hashlib, itertools, time, struct, sys, os, re, math target='6efef13f2a55a4ff356fab3b04f48d57' # try decimal string up to maybe 10 million t=time.time() found=[] for i in range(10000000): if hashlib.md5(str(i).encode()).hexdigest()==target: print("dec string found", i) found.append(i); break print("done", time.time()-t, found[:5])
pythontarget='6efef13f2a55a4ff356fab3b04f48d57' # empty to 3 bytes nonzero for L in range(0,4): cnt=0 t=time.time() if L==0: if hashlib.md5(b'').hexdigest()==target: print("empty") else: # all nonzero bytes for bs in itertools.product(range(1,256), repeat=L): b=bytes(bs) if hashlib.md5(b).hexdigest()==target: print("found bytes", L, b, int.from_bytes(b + b'\x00'*(4-L),'little', signed=True)) raise SystemExit print("L",L,"done",time.time()-t) print("not found <=3")
pythonimport hashlib hashlib.md5(b"2742473").hexdigest() 'a0849a74bafb55f89ce4ec873cc37fdb'
pythonhashlib.md5(b"nuaa").hexdigest() '6efef13f2a55a4ff356fab3b04f48d57'
这题可以先确定第一关输入:
text第一关输入:1633777006
原因如下。
代码里虽然 scanf("%d", v8) 读的是整数,但后面:
csub_4F18D0(v9, (int)v8); sub_4F1A90(v9, &v19); sub_4F1340((int)&v14, (int)&v19); strcmp(&v14, "6efef13f2a55a4ff356fab3b04f48d57");
这一套明显是:
cMD5(v8内存字节) == 6efef13f2a55a4ff356fab3b04f48d57
v8[0] 是一个 32 位整数。关键点在于 小端序:
textMD5("nuaa") = 6efef13f2a55a4ff356fab3b04f48d57
而字符串 "nuaa" 的字节是:
text6e 75 61 61
按小端序作为整数就是:
text0x6161756e = 1633777006
所以第一关应该输入:
text1633777006
通过第一关后,程序会执行:
cv1 = v8[0]; v2 = _mm_shuffle_epi32(_mm_cvtsi32_si128(v1), 0); DURL = (const __m128i *)::DURL; do { v4 = _mm_loadu_si128(DURL++); DURL[-1] = _mm_xor_si128(v4, v2); } while ((int)DURL < (int)aDurl);
也就是用刚才的 key:
text0x6161756e
对全局数据段 DURL ~ aDurl 做 XOR 解密。因为是 SSE 一次处理 16 字节,本质上等价于:
textencrypted_data ^ b"nuaa" 循环
也就是每 4 字节异或一次:
text6e 75 61 61
这不是:
cscanf("%s", input); MD5(input);
而是:
cscanf("%d", v8); MD5((char *)v8);
所以它实际哈希的是整数在内存里的字节。
可以用 Python 验证:
pythonimport hashlib print(hashlib.md5(b"nuaa").hexdigest())
输出:
text6efef13f2a55a4ff356fab3b04f48d57
然后:
pythonprint(int.from_bytes(b"nuaa", "little"))
输出:
text1633777006
在 IDA 里找到 DURL 和 aDurl 的地址,然后用 IDAPython 解密:
pythonimport ida_bytes import ida_name start = ida_name.get_name_ea(0, "DURL") end = ida_name.get_name_ea(0, "aDurl") enc = ida_bytes.get_bytes(start, end - start) key = b"nuaa" dec = bytes([enc[i] ^ key[i % 4] for i in range(len(enc))]) print(dec)
如果解出来是字符串,直接看内容;如果解出来像代码,需要把这段数据 patch 回去:
pythonfor i, b in enumerate(dec): ida_bytes.patch_byte(start + i, b)
然后在 IDA 里按 C 重新反汇编,或者按 A 尝试识别字符串。
后面这里:
cv10 = 0; v11 = 0LL; v12 = 0LL; v13 = 0LL; v5 = &v10; n7 = 7; do { scanf("%d", v5++); --n7; } while (n7);
说明第二关需要输入 7 个整数:
textx0 x1 x2 x3 x4 x5 x6
这 7 个整数会作为数组传给:
csub_4F1000(&v10); sub_4F1440();
但真正判断能否进入下一步的是:
cif (sub_4F1390())
所以接下来要重点逆:
csub_4F1390 sub_4F1000 sub_4F1440
建议顺序是:
1633777006,让程序正常解密 DURL。sub_4F1390 下断点,看它到底检查什么。sub_4F1390 只是检查解密是否成功,可以直接继续。sub_4F1390 内部对全局数据做校验,要先分析解密后的 DURL。sub_4F1000(&v10),这通常就是把 7 个整数变换成 flag 或坐标。sub_4F1440() 很可能是打印 flag 或最终提示。用 x32dbg / OllyDbg:
在这几处下断点:
asmstrcmp sub_4F1390 sub_4F1000 sub_4F1440
运行后第一关输入:
text1633777006
然后程序会打印:
textreading... "Yes I did it" [+]Try to find treasure!
此时说明第一关已经过了。
接着随便输入 7 个整数,例如:
text1 2 3 4 5 6 7
然后进入 sub_4F1390 / sub_4F1000 / sub_4F1440 观察逻辑。
可以在调试器里把:
cif (sub_4F1390())
强行改成成功。
常见方式:
sub_4F1390 返回后看类似:
asmcall sub_4F1390 test eax, eax jz short fail
把 EAX 改成:
text1
或者把 jz patch 成 jnz / nop nop。
sub_4F1390让它变成:
asmmov eax, 1 ret
然后继续运行,看 sub_4F1000 和 sub_4F1440 是否会直接吐 flag。
第一关答案确定是:
text1633777006
它对应:
text0x6161756e
内存小端字节:
text6e 75 61 61
也就是:
textnuaa
并且:
textMD5("nuaa") = 6efef13f2a55a4ff356fab3b04f48d57
后续 flag 需要继续看这三个函数和全局数据:
csub_4F1390() sub_4F1000(&v10) sub_4F1440() DURL ~ aDurl
你把 sub_4F1390、sub_4F1000、sub_4F1440 的反编译代码,或者 DURL 那段十六进制数据贴出来,就能继续把 7 个整数和最终 flag 推出来。
int __thiscall sub_4F1390(int *this)
{
int n11; // esi
int v2; // edi
_QWORD *v3; // edx
char v4; // cl
int v5; // ecx
int v6; // eax
char n32; // al
n11 = 11;
v2 = 1;
v3 = sub_4F1130(this);
do
{
v4 = *(_BYTE *)v3;
if ( *(_BYTE *)v3 == 'U' )
{
v5 = 0;
v6 = -1;
}
else
{
switch ( v4 )
{
case 'D':
v5 = 0;
v6 = 1;
break;
case 'L':
v6 = 0;
v5 = -1;
break;
case 'R':
v6 = 0;
v5 = 1;
break;
default:
printf("Hei! what are you doing!\n");
exit(-1);
}
}
n11 += v6;
dword_4F4538 = v6;
v2 += v5;
v3 = (_QWORD *)((char *)v3 + 1);
n32 = DURL[13 * n11 + v2];
dword_4F453C = v5;
}
while ( n32 == ' ' || n32 == 'o' );
if ( n32 == 'E' )
{
printf("Wow, I find It!\n");
return 1;
}
else
{
printf("BOOM!\n");
return 0;
}
}
int __thiscall sub_4F1000(_BYTE *this)
{
_BYTE *v1; // edi
int i; // esi
unsigned int v3; // eax
int result; // eax
v1 = this;
printf("let's take a look at it..\n");
for ( i = 0; i < 208; byte_4F4117[i] ^= result )
{
src[i] ^= v1[4 * (i % 7u)];
byte_4F4119[i] ^= this[4 * (i - 7 * ((i + 1) / 7u)) + 4];
byte_4F411A[i] ^= this[4 * (i - 7 * ((i + 2) / 7u)) + 8];
v1 = this;
v3 = i - 7 * ((i + 3) / 7u);
i += 4;
result = (unsigned __int8)this[4 * v3 + 12];
}
return result;
}
int sub_4F1440()
{
HANDLE hHeap; // eax
char *dst; // ebx
char v3; // [esp+Ch] [ebp-28h] BYREF
__int64 v4; // [esp+Dh] [ebp-27h]
__int64 v5; // [esp+15h] [ebp-1Fh]
__int64 v6; // [esp+1Dh] [ebp-17h]
__int64 v7; // [esp+25h] [ebp-Fh]
hHeap = HeapCreate(0x40000u, 0, 0);
dst = (char *)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x1000u);
qmemcpy(dst, src, 0xD0u);
puts("Wow...This is flag!");
puts("Hey, I need check the flag is fake or not....");
puts("try input flag:");
v3 = 0;
v4 = 0LL;
v5 = 0LL;
v6 = 0LL;
v7 = 0LL;
scanf_s("%s", &v3);
if ( ((int (__cdecl *)(char *))dst)(&v3) )
return puts("Congratulation!");
else
return puts("Sorry, just miss a little!");
}
这几段代码已经能确定整体解法结构了,但还差 sub_4F1130、解密后的 DURL 迷宫数据、src 密文数据这几项,才能直接算出最终 flag。现在可以先把核心逻辑拆出来。
前面已经确定:
textMD5("nuaa") = 6efef13f2a55a4ff356fab3b04f48d57
程序输入的是整数,但 MD5 算的是整数的小端内存字节。
text"nuaa" = 6e 75 61 61 小端整数 = 0x6161756e = 1633777006
所以第一关输入:
text1633777006
这一段:
cv1 = v8[0]; v2 = _mm_shuffle_epi32(_mm_cvtsi32_si128(v1), 0); do { v4 = _mm_loadu_si128(DURL++); DURL[-1] = _mm_xor_si128(v4, v2); } while ((int)DURL < (int)aDurl);
本质上是:
pythonDURL[i] ^= b"nuaa"[i % 4]
解密后的 DURL 是一个宽度为 13 的迷宫,因为后面访问方式是:
cDURL[13 * n11 + v2]
也就是:
textDURL[row][col]
起点是:
cn11 = 11; v2 = 1;
也就是:
textrow = 11, col = 1
核心逻辑如下:
cv3 = sub_4F1130(this); do { ch = *v3; if (ch == 'U') { row -= 1; col += 0; } else if (ch == 'D') { row += 1; col += 0; } else if (ch == 'L') { row += 0; col -= 1; } else if (ch == 'R') { row += 0; col += 1; } else { exit(-1); } cur = DURL[13 * row + col]; v3++; } while (cur == ' ' || cur == 'o'); if (cur == 'E') return 1; else return 0;
也就是说,sub_4F1130(this) 会返回一串方向字符:
textU / D / L / R
然后程序从迷宫坐标:
text(11, 1)
开始走。
只要当前格子是:
text空格 ' ' 或者 'o'
就继续走。
如果最后走到:
text'E'
则通过。
如果撞到别的字符,例如墙、边界、非法字符,就失败。
这里读入 7 个整数:
cv10 = 0; v11 = 0LL; v12 = 0LL; v13 = 0LL; v5 = &v10; n7 = 7; do { scanf("%d", v5++); --n7; } while (n7);
内存布局是:
textv10 -> 第 1 个 int v10 + 4 -> 第 2 个 int v10 + 8 -> 第 3 个 int ... v10 + 24 -> 第 7 个 int
后面 sub_4F1000(&v10) 只取每个整数的最低 1 字节作为 key。
也就是说,真正有用的是:
textx0 & 0xff x1 & 0xff x2 & 0xff x3 & 0xff x4 & 0xff x5 & 0xff x6 & 0xff
虽然反编译看起来有点乱:
csrc[i] ^= v1[4 * (i % 7u)]; byte_4F4119[i] ^= this[4 * (i - 7 * ((i + 1) / 7u)) + 4]; byte_4F411A[i] ^= this[4 * (i - 7 * ((i + 2) / 7u)) + 8]; ... result = (unsigned __int8)this[4 * v3 + 12];
但整理后,本质就是:
pythonsrc[i] ^= key[i % 7]
其中:
pythonkey = [ input0 & 0xff, input1 & 0xff, input2 & 0xff, input3 & 0xff, input4 & 0xff, input5 & 0xff, input6 & 0xff, ]
所以这 7 个整数的低字节就是解密 src 的 7 字节密钥。
这一段很关键:
chHeap = HeapCreate(0x40000u, 0, 0); dst = (char *)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x1000u); qmemcpy(dst, src, 0xD0u); puts("Wow...This is flag!"); puts("Hey, I need check the flag is fake or not...."); puts("try input flag:"); scanf_s("%s", &v3); if (((int (__cdecl *)(char *))dst)(&v3)) puts("Congratulation!"); else puts("Sorry, just miss a little!");
也就是说,src 解密后是一段 x86 代码,程序会把它复制到堆上,然后直接执行:
cdst(input_flag)
所以最终 flag 的校验逻辑在 src 解密后的 shellcode 里。
你要解出 flag,建议这样做。
运行程序时输入:
text1633777006
这样 DURL 会被解密。
在 IDA 里可以用:
pythonimport ida_bytes import ida_name start = ida_name.get_name_ea(0, "DURL") end = ida_name.get_name_ea(0, "aDurl") data = ida_bytes.get_bytes(start, end - start) key = b"nuaa" dec = bytes([data[i] ^ key[i % 4] for i in range(len(data))]) print(dec.decode(errors="replace")) for i in range(0, len(dec), 13): print(dec[i:i+13].decode(errors="replace"))
如果程序已经运行过并且内存里的 DURL 已经被解密,就直接 dump 运行时内存也可以。
迷宫宽度是 13,起点是:
textrow = 11 col = 1
终点是字符:
textE
可走格子是:
text' ' 'o' 'E'
可以用这个脚本自动找路径:
pythonfrom collections import deque maze_raw = b""" 这里填入你解密后的 DURL """.strip(b"\n") W = 13 H = len(maze_raw) // W maze = [maze_raw[i * W:(i + 1) * W] for i in range(H)] start = (11, 1) dirs = [ ("U", -1, 0), ("D", 1, 0), ("L", 0, -1), ("R", 0, 1), ] q = deque() q.append((start[0], start[1], "")) vis = set() vis.add(start) while q: r, c, path = q.popleft() for ch, dr, dc in dirs: nr, nc = r + dr, c + dc if not (0 <= nr < H and 0 <= nc < W): continue cell = chr(maze[nr][nc]) if cell == "E": print("FOUND:", path + ch) raise SystemExit if cell in [" ", "o"] and (nr, nc) not in vis: vis.add((nr, nc)) q.append((nr, nc, path + ch)) print("not found")
得到的 path 应该就是 sub_4F1390 希望走出来的方向序列。
现在最关键的是这个函数:
cv3 = sub_4F1130(this);
它负责把你输入的 7 个整数转换成方向字符串。
你目前还没贴 sub_4F1130,所以现在只能确定:
text7 个整数 -> sub_4F1130 -> UDLR 路径字符串 -> 走迷宫
也就是说,解出迷宫路径后,还要根据 sub_4F1130 反推出 7 个整数。
你需要继续看 sub_4F1130 的逻辑。它大概率有几种可能:
比如:
creturn (char *)this;
那么 7 个整数的低字节可能直接是路径字符,例如:
text85 68 82 82 85 76 ...
分别对应:
textU D R R U L ...
比如每个整数拆成若干位:
text0 -> U 1 -> D 2 -> L 3 -> R
这种情况常见于迷宫题,因为 7 个整数可以编码很长的路径。
例如:
textinput[i] ^ 常数 input[i] + 常数 input[i] % 4
再映射成方向。
假设最后求出了 7 个整数:
textx0 x1 x2 x3 x4 x5 x6
就可以用它们解密 src:
pythonimport ida_bytes import ida_name src_start = ida_name.get_name_ea(0, "src") src_len = 0xD0 enc = ida_bytes.get_bytes(src_start, src_len) xs = [x0, x1, x2, x3, x4, x5, x6] key = bytes([x & 0xff for x in xs]) dec = bytes([enc[i] ^ key[i % 7] for i in range(len(enc))]) open("decoded_shellcode.bin", "wb").write(dec) print(dec.hex())
然后用 IDA 新建一个 binary 文件,架构选:
textx86 32-bit
加载 decoded_shellcode.bin,反汇编即可看到最终 flag 校验逻辑。
更快的方法是:
text1633777006
第二关输入你根据迷宫和 sub_4F1130 算出的 7 个整数。
在这里下断点:
cif (((int (__cdecl *)(char *))dst)(&v3))
跟进 dst,它就是解密后的 shellcode。
反汇编 shellcode,找对用户输入 flag 的比较逻辑。
常见情况是:
asmcmp byte ptr [flag+i], imm xor / add / sub / rol / ror
或者:
asmMD5 / CRC / 自定义异或校验
如果逻辑简单,直接静态解;如果复杂,可以用 angr / z3 符号执行。
这题不是单纯迷宫题,而是三层结构:
text第一层: 输入整数 1633777006 => 内存字节为 "nuaa" => MD5 校验通过 => 用 "nuaa" 解密 DURL 第二层: 输入 7 个整数 => sub_4F1130 转成 U/D/L/R 路径 => sub_4F1390 在 DURL 迷宫中从 (11,1) 走到 E 第三层: 同样的 7 个整数低字节 => 作为 7 字节循环 XOR key => 解密 src => src 变成一段 x86 校验 shellcode => 输入 flag => shellcode 返回 1 则成功
所以当前最重要的缺失项是:
csub_4F1130
以及全局数据:
textDURL ~ aDurl src 的 0xD0 字节
你继续贴 sub_4F1130 和 DURL/src 的十六进制数据后,就可以把 7 个整数和最终 flag 继续算出来。
unsigned char DURL[] =
{
0x2B, 0x2B, 0x2B, 0x2B, 0x45, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B,
0x2B, 0x2B, 0x2B, 0x2B, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x2B, 0x2B, 0x20, 0x2B, 0x2B,
0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x20, 0x2B, 0x2B,
0x20, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B,
0x20, 0x2B, 0x2B, 0x2B, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B,
0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x20, 0x2B, 0x2B, 0x20,
0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x20,
0x2B, 0x2B, 0x20, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B,
0x2B, 0x2B, 0x20, 0x2B, 0x2B, 0x20, 0x2B, 0x2B, 0x2B, 0x2B,
0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x20, 0x2B, 0x2B, 0x20, 0x2B,
0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x20, 0x2B,
0x2B, 0x20, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B,
0x2B, 0x20, 0x2B, 0x2B, 0x6F, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B,
0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B
};
pythonD = [ 0x2B,0x2B,0x2B,0x2B,0x45,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B, 0x2B,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2B, 0x2B,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B, 0x2B,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B, 0x2B,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2B,0x2B, 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B, 0x2B,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B, 0x2B,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B, 0x2B,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B, 0x2B,0
pythonfrom collections import deque D = bytes([ 0x2B,0x2B,0x2B,0x2B,0x45,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B, 0x2B,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2B, 0x2B,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B, 0x2B,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B, 0x2B,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2B,0x2B, 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B, 0x2B,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B, 0x2B,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B, 0x2B,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B, 0x2B,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B, 0x2B,0x20
pythonfrom collections import deque D = bytes([ 0x2B,0x2B,0x2B,0x2B,0x45,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B, 0x2B,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2B, 0x2B,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B, 0x2B,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B, 0x2B,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2B,0x2B, 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B, 0x2B,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B, 0x2B,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B, 0x2B,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2
你给的 DURL 是一个明文迷宫,按 13 字节一行排开:
text00: ++++E++++++++ 01: + + 02: + +++++++++ + 03: + ++++++++ ++ 04: + ++ 05: ++++++++++ ++ 06: + ++++++++ ++ 07: + ++++++++ ++ 08: + ++++++++ ++ 09: + ++++++++ ++ 10: + ++++++++ ++ 11: +o ++ 12: +++++++++++++
sub_4F1390 里的起点是:
cn11 = 11; // row v2 = 1; // col
所以起点正好是:
textrow = 11, col = 1
也就是 o 的位置。
终点是:
textrow = 0, col = 4
也就是 E。
从 o 走到 E 的路径是:
textRRRRRRRRRUUUUUUULLLLLLLLLUUURRRU
压缩成方向次数就是:
textR * 9 U * 7 L * 9 U * 3 R * 3 U * 1
也就是:
text右 9 上 7 左 9 上 3 右 3 上 1
sub_4F1390 的逻辑是:
cU: row -= 1 D: row += 1 L: col -= 1 R: col += 1
从 (11, 1) 开始走:
text起点:row=11, col=1 R 9 -> row=11, col=10 U 7 -> row=4, col=10 L 9 -> row=4, col=1 U 3 -> row=1, col=1 R 3 -> row=1, col=4 U 1 -> row=0, col=4
最后到达:
textDURL[13 * 0 + 4] == 'E'
所以 sub_4F1390() 会返回 1。
你现在还缺这个函数:
csub_4F1130(this)
因为 sub_4F1390 不是直接读取 7 个整数,而是:
cv3 = sub_4F1130(this);
也就是说:
text7 个整数 ---> sub_4F1130 ---> "RRRRRRRRRUUUUUUULLLLLLLLLUUURRRU"
所以你现在已经知道 sub_4F1130 的目标输出必须是:
textRRRRRRRRRUUUUUUULLLLLLLLLUUURRRU
接下来只要逆 sub_4F1130,就能反推出那 7 个整数。
sub_4F1000(&v10) 会用这 7 个整数的低字节去解密 src:
textkey[i] = input_i & 0xff src[j] ^= key[j % 7]
然后 sub_4F1440() 会把解密后的 src 当成函数执行:
cif (((int (__cdecl *)(char *))dst)(&v3)) puts("Congratulation!");
所以最后一层 flag 校验在 src 解密后的 shellcode 里。
第一关输入:
text1633777006
迷宫路径:
textRRRRRRRRRUUUUUUULLLLLLLLLUUURRRU
接下来需要你再贴两个东西:
text1. sub_4F1130 的反编译代码 2. src 的 0xD0 字节十六进制数据
有了这两个,就可以继续推出:
text7 个整数输入 最终 flag
_QWORD *__thiscall sub_4F1130(int *this)
{
_QWORD *v2; // edi
int v3; // edx
char DURL; // al
int v5; // ecx
char _DURL__1; // al
int v7; // ecx
char _DURL__2; // al
int v9; // ecx
int v10; // ecx
char _DURL__3; // al
int v12; // ecx
char _DURL__4; // al
int v14; // ecx
v2 = malloc(0x1Du);
v3 = *this;
DURL = aDurl[*this >> 6]; // "DURL"
*v2 = 0LL;
v2[1] = 0LL;
v2[2] = 0LL;
*((_DWORD *)v2 + 6) = 0;
*((_BYTE *)v2 + 28) = 0;
*(_BYTE *)v2 = DURL;
v5 = this[1];
*((_BYTE *)v2 + 1) = aDurl[(v3 >> 4) & 3]; // "DURL"
*((_BYTE *)v2 + 2) = aDurl[(v3 >> 2) & 3]; // "DURL"
*((_BYTE *)v2 + 3) = aDurl[v3 & 3]; // "DURL"
*((_BYTE *)v2 + 4) = aDurl[v5 >> 6]; // "DURL"
*((_BYTE *)v2 + 5) = aDurl[(v5 >> 4) & 3]; // "DURL"
*((_BYTE *)v2 + 6) = aDurl[(v5 >> 2) & 3]; // "DURL"
_DURL__1 = aDurl[v5 & 3]; // "DURL"
v7 = this[2];
*((_BYTE *)v2 + 7) = _DURL__1;
*((_BYTE *)v2 + 8) = aDurl[v7 >> 6]; // "DURL"
*((_BYTE *)v2 + 9) = aDurl[(v7 >> 4) & 3]; // "DURL"
*((_BYTE *)v2 + 10) = aDurl[(v7 >> 2) & 3]; // "DURL"
_DURL__2 = aDurl[v7 & 3]; // "DURL"
v9 = this[3];
*((_BYTE *)v2 + 11) = _DURL__2;
*((_BYTE *)v2 + 12) = aDurl[v9 >> 6]; // "DURL"
*((_BYTE *)v2 + 13) = aDurl[(v9 >> 4) & 3]; // "DURL"
*((_BYTE *)v2 + 14) = aDurl[(v9 >> 2) & 3]; // "DURL"
*((_BYTE *)v2 + 15) = aDurl[v9 & 3]; // "DURL"
v10 = this[4];
*((_BYTE *)v2 + 16) = aDurl[v10 >> 6]; // "DURL"
*((_BYTE *)v2 + 17) = aDurl[(v10 >> 4) & 3]; // "DURL"
*((_BYTE *)v2 + 18) = aDurl[(v10 >> 2) & 3]; // "DURL"
_DURL__3 = aDurl[v10 & 3]; // "DURL"
v12 = this[5];
*((_BYTE *)v2 + 19) = _DURL__3;
*((_BYTE *)v2 + 20) = aDurl[v12 >> 6]; // "DURL"
*((_BYTE *)v2 + 21) = aDurl[(v12 >> 4) & 3]; // "DURL"
*((_BYTE *)v2 + 22) = aDurl[(v12 >> 2) & 3]; // "DURL"
_DURL__4 = aDurl[v12 & 3]; // "DURL"
v14 = this[6];
*((_BYTE *)v2 + 23) = _DURL__4;
*((_BYTE *)v2 + 24) = aDurl[v14 >> 6]; // "DURL"
*((_BYTE *)v2 + 25) = aDurl[(v14 >> 4) & 3]; // "DURL"
*((_BYTE *)v2 + 26) = aDurl[(v14 >> 2) & 3]; // "DURL"
*((_BYTE *)v2 + 27) = aDurl[v14 & 3]; // "DURL"
return v2;
}
pythondata = [ 0x2B,0x2B,0x2B,0x2B,0x45,0x2B,0x2B,0x2B,0x2B,0x2B, 0x2B,0x2B,0x2B,0x2B,0x20,0x20,0x20,0x20,0x20,0x20, 0x20,0x20,0x20,0x20,0x20,0x2B,0x2B,0x20,0x2B,0x2B, 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B, 0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B, 0x20,0x2B,0x2B,0x2B,0x20,0x20,0x20,0x20,0x20,0x20, 0x20,0x20,0x20,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B, 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B,0x20, 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20, 0x2B,0x2B,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B, 0x2B,0x2B,0x20,0x2B,0x2B,0x20,0x2B,0x2B,0x2B,0x2B, 0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B,0x20,0x2B, 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B, 0x2B,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B, 0x2B,0x20,0x2B,0x2B,0x6F,0x20,0x20,0x20,0x20,0x20, 0x20,0x20,0x20,0x20,0x20,0x2B,0x2B,0x2B,0x2B,0x2B, 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B] len(data), len(data)/13 (169, 13.0)
pythons = bytes(data).decode() rows = [s[i:i+13] for i in range(0,len(s),13)] for idx,row in enumerate(rows): print(idx, repr(row), len(row)) # BFS from collections import deque start=(11,1) end=None for r,row in enumerate(rows): for c,ch in enumerate(row): if ch=='E': end=(r,c) print("end", end) dirs=[('U',-1,0),('D',1,0),('L',0,-1),('R',0,1)] q=deque([(start,'')]) vis={start} while q: (r,c), path=q.popleft() for ch,dr,dc in dirs: nr,nc=r+dr,c+dc if 0<=nr<13 and 0<=nc<13: cell=rows[nr][nc] if cell == 'E': print("found", len(path+ch), path+
pythondata = [ 0x2B,0x2B,0x2B,0x2B,0x45,0x2B,0x2B,0x2B,0x2B,0x2B, 0x2B,0x2B,0x2B,0x2B,0x20,0x20,0x20,0x20,0x20,0x20, 0x20,0x20,0x20,0x20,0x20,0x2B,0x2B,0x20,0x2B,0x2B, 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B, 0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B, 0x20,0x2B,0x2B,0x2B,0x20,0x20,0x20,0x20,0x20,0x20, 0x20,0x20,0x20,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B, 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B,0x20, 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20, 0x2B,0x2B,0x20,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B, 0x2B,0x2B,0x20,0x2B,0x2B,0x20,0x2B,0x2B,0x2B,0x2B, 0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B,0x2B,0x20,0x2B, 0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x20,0x2B
sub_4F1130 这一段已经把第二关完全解出来了。
它的作用是:把你输入的 7 个整数,每个整数取 8 bit,按每 2 bit 一组映射成 4 个方向字符。
映射表来自:
caDurl = "DURL"
所以:
text00 -> D 01 -> U 10 -> R 11 -> L
每个整数这样拆:
text高 2 位 -> 第 1 个方向 次高 2 位 -> 第 2 个方向 次低 2 位 -> 第 3 个方向 低 2 位 -> 第 4 个方向
根据你给的 DURL,这是一个 13 × 13 迷宫:
text00: ++++E++++++++ 01: + + 02: + +++++++++ + 03: + +++++++++ + 04: ++ + 05: +++++++++++ + 06: + +++++++++ + 07: + +++++++++ + 08: + +++++++++ + 09: + +++++++++ + 10: + +++++++++ + 11: +o + 12: +++++++++++++
起点:
textrow = 11, col = 1
终点:
textrow = 0, col = 4
最短路径是:
textRRRRRRRRRRUUUUUUUUUULLLLLLLU
也就是:
textR × 10 U × 10 L × 7 U × 1
长度正好是 28,对应 sub_4F1130 生成的 28 个方向字符。
路径按 4 个方向一组切分:
textRRRR RRRR RRUU UUUU UUUU LLLL LLLU
根据映射:
textD = 0 = 00 U = 1 = 01 R = 2 = 10 L = 3 = 11
逐组编码:
textRRRR = 10 10 10 10 = 0b10101010 = 170 RRRR = 10 10 10 10 = 0b10101010 = 170 RRUU = 10 10 01 01 = 0b10100101 = 165 UUUU = 01 01 01 01 = 0b01010101 = 85 UUUU = 01 01 01 01 = 0b01010101 = 85 LLLL = 11 11 11 11 = 0b11111111 = 255 LLLU = 11 11 11 01 = 0b11111101 = 253
所以第二关 7 个整数应该输入:
text170 170 165 85 85 255 253
第一关输入:
text1633777006
然后程序提示:
text[+]Try to find treasure!
第二关输入:
text170 170 165 85 85 255 253
这样 sub_4F1390() 会返回 1,程序会进入:
csub_4F1000(&v10); sub_4F1440();
这 7 个整数不只是迷宫路径,它们还会作为 XOR key 解密 src。
key 是每个整数的低 1 字节:
textAA AA A5 55 55 FF FD
也就是:
pythonkey = [0xAA, 0xAA, 0xA5, 0x55, 0x55, 0xFF, 0xFD]
sub_4F1000 本质是:
pythonfor i in range(0xD0): src[i] ^= key[i % 7]
然后 sub_4F1440 会把解密后的 src 当成 x86 函数执行,用来校验你输入的 flag:
cif (((int (__cdecl *)(char *))dst)(&v3)) puts("Congratulation!");
所以现在只差最后一步:拿到 src 的 0xD0 字节,解密后分析 shellcode。
你可以在 IDA 里跑:
pythonimport ida_bytes import ida_name src_start = ida_name.get_name_ea(0, "src") src_len = 0xD0 enc = ida_bytes.get_bytes(src_start, src_len) key = bytes([0xAA, 0xAA, 0xA5, 0x55, 0x55, 0xFF, 0xFD]) dec = bytes([enc[i] ^ key[i % 7] for i in range(src_len)]) open("decoded_src.bin", "wb").write(dec) print(dec.hex())
然后把 decoded_src.bin 作为 x86 32-bit raw binary 拖进 IDA,分析里面的 flag 校验逻辑。
text第一关输入: 1633777006 第二关输入: 170 170 165 85 85 255 253 迷宫路径: RRRRRRRRRRUUUUUUUUUULLLLLLLU src 解密 key: AA AA A5 55 55 FF FD
你再把 src 的 0xD0 字节贴出来,我就可以继续帮你把最终 flag 算出来。
00 -> D
01 -> U
10 -> R
11 -> L
怎么得到的映射
这个映射是直接从 sub_4F1130 里的数组索引关系得到的。
关键代码是:
caDurl = "DURL";
也就是:
caDurl[0] = 'D'; aDurl[1] = 'U'; aDurl[2] = 'R'; aDurl[3] = 'L';
然后 sub_4F1130 把每个整数拆成 4 组 2 bit:
caDurl[v3 >> 6] aDurl[(v3 >> 4) & 3] aDurl[(v3 >> 2) & 3] aDurl[v3 & 3]
其中每一组 2 bit 的取值只能是:
text00 = 0 01 = 1 10 = 2 11 = 3
再代入 "DURL" 的下标:
text0 -> aDurl[0] -> 'D' 1 -> aDurl[1] -> 'U' 2 -> aDurl[2] -> 'R' 3 -> aDurl[3] -> 'L'
所以得到映射:
text00 -> D 01 -> U 10 -> R 11 -> L
举个例子,整数 170:
text170 = 0xAA = 10101010b
按 2 bit 拆开:
text10 10 10 10
每个 10 对应十进制 2,所以:
textaDurl[2] = 'R'
因此:
text170 -> RRRR
再比如 85:
text85 = 0x55 = 01010101b
拆成:
text01 01 01 01
01 = 1,所以:
textaDurl[1] = 'U'
因此:
text85 -> UUUU
总结一句:不是我猜的映射,而是由 "DURL" 这个字符串的下标顺序决定的。