# N1CTF Junior 2026 WriteUp

# Reverse 方向:

# MaybeAndroid

题目提供了一个 Android APK。安装后发现是一个 “Python 脚本运行器”,内置了几个 Python 脚本。 核心目标是获取 flag_check.py 中的 flag。直接运行 flag_check.py 会提示:“该脚本仅限 VIP 用户使用”,且该脚本是一个校验脚本,需要输入正确的 flag 才能通过,而不是直接输出 flag。

通过反编译 APK 定位到 MainActivity.kt

42

从而可以发现 app 的逻辑:Python 脚本存放在 Assets 目录;点击运行脚本时,会检查文件名。如果是 flag_check.py (VIP_SCRIPTS),则调用 VipManager.isVip (context) 检查权限;如果不是 VIP,弹出提示并拦截运行;脚本运行器界面是 read-only(只读)的 Text 组件,无法直接在 App 内修改代码。

为了运行脚本,首先需要成为 VIP。定位到 com.example.maybeandroid.VipManager 类,分析 activate 方法:

43

从而编写 Python 脚本解密 AES 密文:

1
2
3
4
5
6
7
8
9
10
11
12
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
key = b"8888888888888888"
iv = bytes(range(16))
ciphertext =
base64.b64decode("ZlZNZBpzLDK7C4yfjrQcGTlqAAr5EotPbAj+0eC9w0MHcOesj
Cs4nB/qgrcQFuxI")
cipher = AES.new(key, AES.MODE_CBC, iv)
vip_code = unpad(cipher.decrypt(ciphertext),
AES.block_size).decode()
print(vip_code)

获得 VIP 激活码为:F4E52DFB41CCC32F8FFFC340A3804383

获得 VIP 后,可以查看到 flag_check.py 的源码:

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
import sys
import os

print("--- 开始运行 ---")


a = open("😇","r")


b = a ^ "😋"
s = a ^ "🫨"
d = a ^ "😁"


c = (((a ^ "😋") << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢" << "😢") == "😃")


j = (((a ^ "😋")) == "😃")
for i in range(c):
j = (j + s[i] + (d << "😢")) % c
s[i], s[j] = s[j], s[i]


i, j = (((a ^ "😋")) == "😃"), (((a ^ "😋")) == "😃")
e = a ^ "😤"
flag_str = ""
print("正在计算 Flag...")


for _ in range(100):
try:
i = (i + 1) % c
j = (j + s[i]) % c
s[i], s[j] = s[j], s[i]
k = s[(s[i] + s[j]) % c]

cipher_byte = (e << "😢")
if cipher_byte is None: break

flag_str += chr(cipher_byte ^ k)
except:
break

print("\n--------------------------------")
print("FLAG 是:")
print(flag_str)
print("--------------------------------\n")

保存文件后,使用 MT 管理器对 APK 进行 V1+V2 签名,卸载旧应用,安装修改后的使用 MaybeAndroid.apk,

最后输入激活码激活 VIP。打开脚本运行器,选择 flag_check.py 并运行,观察日志输出。

44

取前 32 位 MD5 哈希值得到:

flag{5f19b83de29bd46e9e02f7f88bfb4ea2}

# Microsoft VS Code

首先看到主逻辑:

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// Hidden C++ exception states: #wind=1
int __fastcall main(int argc, const char **argv, const char **envp)
{
HCRYPTKEY hKey; // [rsp+40h] [rbp-B8h] BYREF
HCRYPTPROV phProv; // [rsp+48h] [rbp-B0h] BYREF
DWORD pdwDataLen; // [rsp+50h] [rbp-A8h] BYREF
BYTE *v7; // [rsp+58h] [rbp-A0h]
int v8; // [rsp+60h] [rbp-98h]
int v9; // [rsp+64h] [rbp-94h]
int v10; // [rsp+68h] [rbp-90h]
DWORD dwFlags; // [rsp+6Ch] [rbp-8Ch]
DWORD dwBufLen; // [rsp+70h] [rbp-88h]
int v13; // [rsp+74h] [rbp-84h]
size_t MaxCount; // [rsp+78h] [rbp-80h]
void *Src; // [rsp+80h] [rbp-78h]
void *v16; // [rsp+88h] [rbp-70h]
void *v17; // [rsp+90h] [rbp-68h]
BYTE v18[32]; // [rsp+98h] [rbp-60h] BYREF
BYTE pbData[48]; // [rsp+B8h] [rbp-40h] BYREF

phProv = 0;
hKey = 0;
v8 = byte_14003C018[0];
v10 = (int)byte_14003C018[0] >> (unk_14003C012
+ byte_14003C018[0]
- byte_14003C014[2]
- unk_14003C010 / (__int16)byte_14003C018[3]
- byte_14003C014[0]);
v9 = unk_14003C011;
dwFlags = v10 << (byte_14003C018[0] * (unk_14003C010 / (int)unk_14003C011) / unk_14003C011);
if ( !CryptAcquireContextA(
&phProv,
0,
0,
byte_14003C014[0] / (int)byte_14003C018[0] + byte_14003C018[4] - unk_14003C010 - unk_14003C011,
dwFlags) )
return 1;
memset(&pbData[1], 0, 0x2Bu);
pbData[0] = byte_14003C014[0] - byte_14003C014[2];
pbData[1] = byte_14003C018[4] - byte_14003C018[2];
*(_WORD *)&pbData[2] = 0;
*(_DWORD *)&pbData[4] = unk_14003C011 + ((byte_14003C018[2] - unk_14003C007) << unk_14003C008) - unk_14003C001;
*(_DWORD *)&pbData[8] = byte_14003C018[6] - unk_14003C00E - (unk_14003C011 - unk_14003C001);
memcpy(&pbData[12], &unk_14003C000, *(int *)&pbData[8]);
if ( CryptImportKey(
phProv,
pbData,
unk_14003C003
^ byte_14003C018[6]
^ byte_14003C018[7]
& (unk_14003C010
^ (unsigned __int8)(byte_14003C018[6] & byte_14003C018[0])),
0,
0,
&hKey) )
{
if ( CryptSetKeyParam(hKey, byte_14003C018[4] / (int)byte_14003C018[6], &::pbData, 0) )
{
sub_14000AE70(v18);
sub_1400090A0(&qword_14003D5E0, "Enter your flag: ");
sub_14000A910(&qword_14003D540, v18);
if ( unknown_libname_70(v18) > (unsigned __int64)byte_14003C018[7] )
sub_14000E920(v18, byte_14003C018[7], 0);
v7 = (BYTE *)j__malloc_base(byte_14003C018[6] - unk_14003C00E - (unk_14003C011 - unk_14003C001));
MaxCount = unknown_libname_70(v18);
Src = (void *)sub_14000D7E0(v18);
memcpy(v7, Src, MaxCount);
pdwDataLen = byte_14003C018[6] - unk_14003C00E - (unk_14003C011 - unk_14003C001);
dwBufLen = byte_14003C018[6] - unk_14003C00E;
if ( CryptEncrypt(hKey, 0, 1, 0, v7, &pdwDataLen, dwBufLen) )
{
if ( pdwDataLen == byte_14003C018[6] - unk_14003C00E
&& !memcmp(v7, &unk_14003C030, byte_14003C018[6] - unk_14003C00E) )
{
v16 = (void *)sub_1400090A0(&qword_14003D5E0, "Correct!");
_CallMemberFunction0(v16, sub_14000A650);
}
else
{
v17 = (void *)sub_1400090A0(&qword_14003D5E0, "Wrong!");
_CallMemberFunction0(v17, sub_14000A650);
}
free(v7);
CryptDestroyKey(hKey);
CryptReleaseContext(phProv, 0);
exit(0);
}
CryptDestroyKey(hKey);
CryptReleaseContext(phProv, 0);
v13 = 1;
sub_14000BB10(v18);
return v13;
}
else
{
CryptDestroyKey(hKey);
CryptReleaseContext(phProv, 0);
return 1;
}
}
else
{
CryptReleaseContext(phProv, 0);
return 1;
}
}

密文:

45

同时因为 CryptSetKeyParam 函数用于自定义会话密钥操作的各个方面。此函数设置的值不会保存到内存中,并且只能在单个会话中使用。(可能跟 AES 相关)

其实就是初始化密钥的,可以确定 iv:

46

key 不是写死在输入里,而是运行时拼出来的,原始 key 材料在 .data 里,0x14003C000,内容是 0x00..0x1F 共 32 字节,在这里 (sub_140008150) 会调用 sub_1400012F0 生成两个字符串

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
// Hidden C++ exception states: #wind=4
__int64 sub_140008150()
{
unsigned __int64 MaxCount; // [rsp+30h] [rbp-118h]
int v2[2]; // [rsp+38h] [rbp-110h]
int i; // [rsp+40h] [rbp-108h]
char *Block; // [rsp+48h] [rbp-100h]
char *v5; // [rsp+58h] [rbp-F0h]
int v6; // [rsp+60h] [rbp-E8h]
void *v7; // [rsp+68h] [rbp-E0h]
unsigned __int64 v8; // [rsp+70h] [rbp-D8h]
int v9; // [rsp+90h] [rbp-B8h]
int v10; // [rsp+98h] [rbp-B0h]
_BYTE v11[24]; // [rsp+C0h] [rbp-88h] BYREF
_BYTE v12[24]; // [rsp+D8h] [rbp-70h] BYREF
_BYTE v13[32]; // [rsp+F0h] [rbp-58h] BYREF
_BYTE v14[32]; // [rsp+110h] [rbp-38h] BYREF

*(_QWORD *)v2 = sub_140007CD0();
MaxCount = sub_140007CF0((unsigned int)v2[0]);
v6 = sub_140007F90();
Block = (char *)j__malloc_base(MaxCount * *(_QWORD *)v2 - MaxCount);
v7 = j__malloc_base(MaxCount * *(_QWORD *)v2 - MaxCount);
memcpy(Block, &riid, MaxCount * MaxCount);
memcpy(&Block[MaxCount * MaxCount], &riid.Data2, MaxCount);
memcpy(&Block[MaxCount + MaxCount * MaxCount], &riid.Data3, MaxCount);
memcpy(&Block[*(_QWORD *)v2 - MaxCount / MaxCount], riid.Data4, MaxCount * MaxCount * MaxCount);
v5 = (char *)j__malloc_base((*(_QWORD *)v2 - MaxCount) * (*(_QWORD *)v2 - MaxCount / MaxCount));
sub_1400012F0(Block, v5);
sub_14000AE20(v14, v5, MaxCount * MaxCount * (MaxCount + *(_QWORD *)v2));
sub_14000AE20(v13, &v5[MaxCount * MaxCount * (MaxCount + *(_QWORD *)v2)], MaxCount + *(_QWORD *)v2);
sub_140007FC0((unsigned int)v12, (unsigned int)v14, v2[0], MaxCount, v6);
sub_140007FC0((unsigned int)v11, (unsigned int)v13, v2[0], MaxCount, v6);
v9 = sub_14000DDE0(v11);
v10 = sub_14000DDE0(v12);
sub_140007980(v10, v9, v2[0], MaxCount, v6, v7);
for ( i = 0; i < 16; ++i )
{
v8 = *(_QWORD *)v2
+ ((*(_QWORD *)v2 - MaxCount * MaxCount) << (LOBYTE(v2[0])
- (unsigned __int8)MaxCount * (unsigned __int8)MaxCount
+ LOBYTE(v2[0])
- (unsigned __int8)MaxCount))
+ ((*(_QWORD *)v2 - MaxCount) << (LOBYTE(v2[0])
- (unsigned __int8)MaxCount
* (unsigned __int8)MaxCount
* (unsigned __int8)MaxCount
+ (unsigned __int8)MaxCount * (LOBYTE(v2[0]) - (unsigned __int8)MaxCount)))
+ (MaxCount << ((unsigned __int8)MaxCount * (unsigned __int8)MaxCount * LOBYTE(v2[0])
- (unsigned __int8)MaxCount * (unsigned __int8)MaxCount * (unsigned __int8)MaxCount))
+ (*(_QWORD *)v2 << ((unsigned __int8)MaxCount
+ (unsigned __int8)MaxCount * (unsigned __int8)MaxCount * LOBYTE(v2[0])
- LOBYTE(v2[0])))
- ((MaxCount * MaxCount) << (LOBYTE(v2[0])
- (unsigned __int8)MaxCount * (unsigned __int8)MaxCount
+ LOBYTE(v2[0])
- (unsigned __int8)MaxCount
- (unsigned __int8)MaxCount))
+ MaxCount
+ *(_QWORD *)v2
- MaxCount * MaxCount;
*(_BYTE *)(v8 + i) ^= *((_BYTE *)v7 + i);
}
free(Block);
free(v5);
sub_14000BBD0(v11);
sub_14000BBD0(v12);
sub_14000BB10(v13);
return sub_14000BB10(v14);
}

47

会调用 build_registry_strings_gmp 生成两个字符串,然后 read_registry_value_16bytes 读取注册表

然后在 sub_140007980 这个函数打开了注册表读取 HKEY_USERS\SOFTWARE\Microsoft\Windows NT\CurrentVersion 下 ProductName 的前 16 字节

48

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
void __fastcall sub_140007980(const WCHAR *a1, const WCHAR *a2, int a3, int a4, int a5, void *Dst)
{
_BYTE cbData[12]; // [rsp+44h] [rbp-24h] BYREF
LPBYTE lpData; // [rsp+50h] [rbp-18h]
HKEY hKey; // [rsp+58h] [rbp-10h]

*(_DWORD *)&cbData[8] = 0;
*(_QWORD *)cbData = (unsigned int)(a4 * (a3 - a4 * a4 * a4 - a5) * (a3 << a4));
lpData = (LPBYTE)j__malloc_base(a3 * a3);
hKey = (HKEY)(a3
- a4 * a4 * a4
+ ((a3 - a4 * a4 * a4 - a4) << (a4 * a4 * a3 - (a4 << a4) + a4 * a4 * a4 - a4 * a4))
+ a4
+ (a4 << (a4 * a4 * a3 - (a4 << a4) + a4 * a4 * a4 - a4 * a4 - a4)));
if ( !RegOpenKeyExW(
hKey,
a1,
0,
a3 - a4 * a4 + a4 * a3 * a3 * a3 * a3 + a4 * a3 * a3 * a3 * a3 * a3 - a4 * (a4 << a4) * (a4 << a4),
(PHKEY)&cbData[4]) )
{
RegQueryValueExW(*(HKEY *)&cbData[4], a2, 0, 0, lpData, (LPDWORD)cbData);
memcpy(Dst, lpData, 0x10u);
RegCloseKey(*(HKEY *)&cbData[4]);
free(lpData);
}
}

16 字节会 XOR 到 0x14003C010,而 g_dyn_bytes 实际上是 g_seed_bytes + 0x10,也就是 key 的后 16 字节。

最终 CryptImportKey 里把 g_seed_bytes(被 XOR 后)拷贝到 key_blob [12..],并设置 keylen=32,所以 AES-256 key 就是:

key [0..15] = g_seed_bytes [0..15] // 固定 00..0F

key[16..31] = g_seed_bytes[16..31] XOR ProductName[0..15

这里就是 key_blob

49

最后的 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
35
36
37
38
from Crypto.Cipher import AES

G_SEED = bytes(range(32))

G_IV = bytes([0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88, 0x77,
0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00])

G_CIPHER = bytes([
0xDE, 0x58, 0x46, 0x70, 0xEB, 0xC7, 0x47, 0x06, 0x62, 0xEA,
0xF6, 0x49, 0x03, 0xB8, 0x8E, 0x35,
0xF1, 0xF8, 0x8F, 0x4E, 0x18, 0xD0, 0x8B, 0xD2, 0x5C, 0xF3,
0x53, 0x2F, 0x9F, 0x36, 0xE4, 0x99,
0xBC, 0xE0, 0x15, 0xCF, 0x95, 0x54, 0x36, 0xD3, 0x8F, 0x9F,
0x06, 0x8F, 0xCF, 0x8B, 0x3F, 0x0B,
])
DEFAULT_PRODUCT = "Windows "
def derive_key(product_name: str) -> bytes:
name_bytes = product_name.encode("utf-16le")
name_bytes = (name_bytes + b"\x00" * 16)[:16]
tail = bytes(a ^ b for a, b in zip(G_SEED[16:], name_bytes))
return G_SEED[:16] + tail

def decrypt(product_name: str = DEFAULT_PRODUCT) -> bytes:
key = derive_key(product_name)
pt = AES.new(key, AES.MODE_CBC, iv=G_IV).decrypt(G_CIPHER)
pad = pt[-1]
if 1 <= pad <= 16 and pt.endswith(bytes([pad]) * pad):
pt = pt[:-pad]
return pt.split(b"\x00", 1)[0]

if __name__ == "__main__":
import sys
product = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_PRODUCT
print(decrypt(product).decode("ascii", errors="replace"))




运行得:flag{M1cr0SOf7_V5_C0dE,d0_Y0U_Kn0W??}

# It's_Wizard_Time

首先运行程序得到

50

从而根据输出定位到函数 Il11:

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
__int64 Il11()
{
int v0; // eax
_BYTE v2[64]; // [rsp+20h] [rbp-60h] BYREF
_BYTE v3[64]; // [rsp+60h] [rbp-20h] BYREF
_DWORD v4[8]; // [rsp+A0h] [rbp+20h] BYREF
_BYTE v5[340]; // [rsp+C0h] [rbp+40h] BYREF
int v6; // [rsp+214h] [rbp+194h]
unsigned int v7; // [rsp+218h] [rbp+198h]
int i; // [rsp+21Ch] [rbp+19Ch]

i1iII1();
1i1III1llilli1ll1l1ll1li(65001);
iiII11lIliI(&unk_14000C608, "Welcome to the Magic Spell Tutorial Course at the Magic Academy!\n");
iiII11lIliI(&unk_14000C688, "You'll practice spells here, correct spells'll show amazing effects.\n");
iiII11lIliI(&unk_14000C730, "In the first class, you need to learn simple spells related to five basic elements.\n");
iiII11lIliI(&unk_14000C7C8, "These five basic elements are - water, fire, earth, wind, and ether.\n");
iiII11lIliI(&unk_14000C860, "You need to master five spells at the same time to complete this lesson.\n");
for ( i = 0; i <= 4; ++i )
{
I111111I(v2, 64, byte_14000C8B0, (unsigned int)(i + 1));
I111111I(v3, 64, "Input your magic string line%d: ", i + 1);
iiII11lIliI(v2, v3);
v0 = iIIi1illlIl1i(&v5[65 * i], 65);
v4[i] = v0;
}
iiII11lIliI(&unk_14000C928, "Let's check your learning results.\n");
v6 = iI1iii1l1ilIi(v5, v4);
v7 = 0;
if ( v6 == 130 )
{
iiII11lIliI(&unk_14000CA30, "Wow, your grades are perfect! Let's see what you'll get.\n");
v7 = 17;
}
else
{
iiII11lIliI(&unk_14000C9A0, "Um... It may not look perfect, but why not having a try for it?\n");
v7 = (int)((double)(10 * v6) / 130.0 + 7.0);
}
IlIlIi1II(&unk_14000CA80);
IlIlIi1II(&unk_14000CB78);
1l1liil();
1l1liil();
iiII11lIliI(&unk_14000CBF2, "IT'S WIZARD TIME!\n");
Ii1liIilliI(2000);
1Il11ilIlI1iIii1(v7);
Ii1liIilliI(2000);
i11liIiii111l111iil();
IlIlIi1II(&unk_14000CC10);
1l1liil();
return 0;
}

通过分析可知,iI1iii1l1ilIi(校验函数)和 1Il11ilIlI1iIii1(生成函数)是两个关键函数:

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
__int64 __fastcall iI1iii1l1ilIi(__int64 a1, __int64 a2)
{
_QWORD v3[131]; // [rsp+20h] [rbp-60h] BYREF
unsigned int v4; // [rsp+43Ch] [rbp+3BCh]
char v5; // [rsp+443h] [rbp+3C3h]
int v6; // [rsp+444h] [rbp+3C4h]
int n; // [rsp+448h] [rbp+3C8h]
int m; // [rsp+44Ch] [rbp+3CCh]
unsigned int v9; // [rsp+450h] [rbp+3D0h]
int k; // [rsp+454h] [rbp+3D4h]
int j; // [rsp+458h] [rbp+3D8h]
int i; // [rsp+45Ch] [rbp+3DCh]

Il111l11();
for ( i = 0; i <= 4; ++i )
{
if ( (unsigned __int8)IilIlii1l1lIii(65LL * i + a1) != 1 )
{
IlIlIi1II(&unk_14000C530);
11ll();
}
}
I1Iiil(v3, 0, 1040);
for ( j = 0; j <= 4; ++j )
{
v6 = *(_DWORD *)(4LL * j + a2);
if ( v6 > 63 )
{
IlIlIi1II(&unk_14000C560);
11ll();
}
for ( k = 0; k < v6; ++k )
{
v5 = *(_BYTE *)(a1 + 65LL * j + k);
v4 = v5 - 97;
if ( v4 >= 0x1A )
{
IlIlIi1II(&unk_14000C590);
11ll();
}
1ii[64 * (__int64)j + k] = v4 + 1;
v3[26 * j + (int)v4] += 1LL << k;
}
}
v9 = 0;
for ( m = 0; m <= 4; ++m )
{
for ( n = 0; n <= 25; ++n )
{
if ( v3[26 * m + n] == I1iiIi[26 * m + n] )
++v9;
}
}
return v9;
}

可以看出这是一个经典的 “字符位置位图 “(Character Position Bitmap)校验逻辑,因此通过分析 iI1iii1l1ilIi 函数,可以完全还原出那 5 个正确的咒语字符串。

通过关键代码

51

可以知道程序计算了一个巨大的数组 v3。对于每一个字母(比如 'a'),它用一个 64 位的整数来记录这个字母在字符串中出现的所有位置。

最后,程序检查:

1
if ( v3[26 * m + n] == I1iiIi[26 * m + n] )

这意味着 I1iiIi 这个全局数组里,存储了标准答案的位图。

52

所以 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def solve_magic_spells():
data = [
0, 0, 0x0A0881420800883, 0x4014080154000, 0x8000000000000,
0x540200401540, 0x2281042AA210, 0x5000080A000008, 0,
0x1000011000004,
0, 0x20, 0, 0x2040000000, 0, 0x2000000000000,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0x40000100009, 0, 0x100208000040, 0x2A08491421480, 0,
0x20000080004,
0x5412042840902, 0x4000020, 0, 0, 0, 0x85820016200,
0, 0, 0, 0x100200010, 0, 0, 0, 0x8000,
0, 0, 0, 0, 0, 0, 0x501200808100402,
0, 0x1802D163005060, 0x20000000000000, 1, 0x52490808010,
0x40080000010000, 0x84100004002800, 0, 0x2028000002A0284,
0,
0x400200040100, 0, 0x400008, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0x2A020100800, 0, 0, 0x54484288102,
0x0B030540A4, 0, 0, 0, 0x58C22648, 0,
0, 0, 0x1000001010, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0x0C003803000718, # 10 dup(0) + last val
0, 0x50124824042A003, 0x20000000000000, 0x40000000000000,
0x0A4120040880,
0x10000000, 0x282400404814024, 0, 0x8000000, 0,
0x800000301000, 0, 0x10110080080000, 0, 0, 0,
0x40, 0, 0, 0, 0, 0, 0, 0, 0 # 8 dup(0)
]
if len(data) < 130:
data.extend([0] * (130 - len(data)))
print("--- 魔法咒语书 ---")
for i in range(5):
chars = [''] * 65
max_len = 0
for char_idx in range(26):
bitmap = data[i * 26 + char_idx]
current_char = chr(ord('a') + char_idx)

for bit_pos in range(64):
if (bitmap >> bit_pos) & 1:
chars[bit_pos] = current_char
if bit_pos + 1 > max_len:
max_len = bit_pos + 1

spell = "".join(chars[:max_len])
print(f"咒语 {i+1}: {spell}")

if __name__ == '__main__':
solve_magic_spells()

运行得到 5 条咒语

53

依次输入程序后得到

54

可以看出是 317427355F77317A3452645F37314D33,将其转为 ASCII 码是:1t'5_w1z4Rd_71M3

所以 flag{1t'5_w1z4Rd_71M3}

# find_my_time

首先通过搜索字符串定位到函数 sub_140002E80

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
__int64 __fastcall sub_140002E80(QApplication *a1, int *a2, __int64 a3, int a4, __int64 a5, __int64 a6, char *a7)
{
__int64 v7; // rdx
__int64 v8; // rbp
unsigned int v9; // esi
volatile signed __int32 *v11; // [rsp+28h] [rbp-70h] BYREF
char v12; // [rsp+30h] [rbp-68h] BYREF
_BYTE v13[88]; // [rsp+40h] [rbp-58h] BYREF

LODWORD(a7) = a4;
QApplication::QApplication(a1, a2, &a7, (unsigned int)&v12);
v11 = (volatile signed __int32 *)QString::fromAscii_helper(a1, (const char *)a2, 17);
QIcon::QIcon(a1, (const QString *)&v11);
QApplication::setWindowIcon(a1, (const QIcon *)&v11);
QIcon::~QIcon(a1);
if ( !*v11 || *v11 != -1 && !_InterlockedSub(v11, 1u) )
QArrayData::deallocate(a1, &v11, 2, v11, 8);
QMainWindow::QMainWindow(a1, &v11, 0, v13, 0);
v8 = sub_14007A2A0(a1, &v11, v7, 256);
sub_14006C560(a1, &v11, 0, v8);
QMainWindow::setCentralWidget(a1, (QWidget *)&v11);
v11 = (volatile signed __int32 *)QString::fromAscii_helper(a1, (const char *)&v11, 12);
QWidget::setWindowTitle(a1, (const QString *)&v11);
if ( !*v11 || *v11 != -1 && !_InterlockedSub(v11, 1u) )
QArrayData::deallocate(a1, &v11, 2, v11, 8);
QWidget::show(a1);
v9 = QApplication::exec(a1);
QMainWindow::~QMainWindow(a1);
QApplication::~QApplication(a1);
return v9;
}

可以看出该函数创建 了 QApplication、QMainWindow,构造主控件 sub_14006C560,进入事件循环

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
__int64 __fastcall sub_14006C560(QPixmap *a1, __int64 a2, __int64 a3, __int64 a4)
{
__int64 v5; // rdx
__int64 v6; // rdx
__int64 v7; // rdx
int v8; // eax
int v9; // r8d
int v10; // r9d
__int64 v11; // rdx
__int64 v12; // rdx
__int64 v13; // rax
__int64 v14; // rdx
__int64 v15; // r12
__int64 v16; // rdx
__int64 v17; // rax
_BYTE v19[8]; // [rsp+58h] [rbp-A30h] BYREF
volatile signed __int32 *v20; // [rsp+60h] [rbp-A28h] BYREF
__int64 v21; // [rsp+68h] [rbp-A20h]
char v22[16]; // [rsp+70h] [rbp-A18h] BYREF
_QWORD Block[3]; // [rsp+80h] [rbp-A08h] BYREF
__int64 v24; // [rsp+98h] [rbp-9F0h]

QWidget::QWidget(a1, a2, a3, a4, 0);
*(_QWORD *)(a4 + 64) = 0;
*(_QWORD *)a4 = &off_14016F9A0;
*(_QWORD *)(a4 + 16) = off_14016FB50;
*(_WORD *)(a4 + 48) = 0;
*(_QWORD *)(a4 + 56) = a4 + 72;
*(_QWORD *)(a4 + 88) = a4 + 104;
*(_QWORD *)(a4 + 120) = a4 + 136;
*(_BYTE *)(a4 + 72) = 0;
*(_QWORD *)(a4 + 96) = 0;
*(_BYTE *)(a4 + 104) = 0;
*(_QWORD *)(a4 + 128) = 0;
*(_BYTE *)(a4 + 136) = 0;
*(_QWORD *)(a4 + 152) = a4 + 168;
*(_QWORD *)(a4 + 160) = 0;
*(_BYTE *)(a4 + 168) = 0;
QPixmap::QPixmap(a1);
sub_140022920(a1, a2, v5, a4 + 184);
strcpy(v22, "default");
v20 = (volatile signed __int32 *)v22;
v21 = 7;
sub_140074D50(Block, &v20, &v20, Block);
if ( v20 != (volatile signed __int32 *)v22 )
j_j_free_2(Block);
sub_1400037B0(Block, &v20, v6, &v20);
v8 = sub_140074FA0(Block, &v20, v7, Block);
sub_140005070((unsigned int)Block, (unsigned int)&v20, v8, (unsigned int)&v20, v9, v10);
sub_140022BE0(Block, &v20, &v20, a4 + 184);
sub_1400032B0(Block, &v20, v11, &v20);
nullsub_4(Block, &v20, v12, Block);
v20 = (volatile signed __int32 *)QString::fromAscii_helper((QString *)Block, (const char *)&v20, 15);
QPixmap::QPixmap(Block, &v20, &v20, Block, 0, 0);
v13 = *(_QWORD *)(a4 + 240);
*(_QWORD *)(a4 + 240) = v24;
v24 = v13;
QPixmap::~QPixmap((QPixmap *)Block);
if ( !*v20 || *v20 != -1 && !_InterlockedSub(v20, 1u) )
QArrayData::deallocate(Block, &v20, 2, v20, 8);
if ( (unsigned __int8)QPixmap::isNull((QPixmap *)Block) )
{
QWidget::setFixedSize((QWidget *)Block, (unsigned int)&v20, 1920);
}
else
{
Block[0] = QPixmap::size((QPixmap *)Block);
QWidget::setFixedSize(Block, &v20, Block, a4);
}
v15 = sub_14007A2A0(Block, &v20, v14, 32);
QTimer::QTimer(Block, &v20, a4, v15);
*(_QWORD *)(a4 + 248) = v15;
v21 = 0;
v20 = (volatile signed __int32 *)QTimer::timeout;
Block[0] = sub_14006C430;
Block[1] = 0;
v17 = sub_14007A2A0(Block, &v20, v16, 32);
*(_DWORD *)v17 = 1;
*(_QWORD *)(v17 + 24) = 0;
*(__m128i *)(v17 + 8) = _mm_unpacklo_epi64(
(__m128i)(unsigned __int64)sub_14006CE00,
(__m128i)(unsigned __int64)sub_14006C430);
QObject::connectImpl(Block, &v20, v15, v19, &v20, a4);
QMetaObject::Connection::~Connection((QMetaObject::Connection *)Block);
return QTimer::start((QTimer *)Block, (unsigned int)&v20);
}

sub_14006C560,主控件构造加载了资源图,创建 QTimer,连接 timeout 到 sub_14006C430

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
void __fastcall sub_14006C430(QTimer *a1, __int64 a2, __int64 a3, __int64 a4)
{
int v4; // ebx
unsigned __int64 v5; // rsi
__int64 v6; // [rsp+0h] [rbp-C8h]
__int64 v7; // [rsp+8h] [rbp-C0h]
int v8; // [rsp+10h] [rbp-B8h]
int v9; // [rsp+18h] [rbp-B0h]
struct _FILETIME SystemTimeAsFileTime; // [rsp+20h] [rbp-A8h] BYREF
int v11; // [rsp+28h] [rbp-A0h]
int v12; // [rsp+30h] [rbp-98h]
int v13; // [rsp+38h] [rbp-90h]
int v14; // [rsp+40h] [rbp-88h]
__int64 *v15; // [rsp+48h] [rbp-80h]
int v16; // [rsp+50h] [rbp-78h]
__int64 v17; // [rsp+58h] [rbp-70h] BYREF
int v18; // [rsp+60h] [rbp-68h]
__int64 *v19; // [rsp+68h] [rbp-60h]
int v20; // [rsp+70h] [rbp-58h]
__int64 v21; // [rsp+78h] [rbp-50h] BYREF
int v22; // [rsp+80h] [rbp-48h]
__int64 *v23; // [rsp+88h] [rbp-40h]
int v24; // [rsp+90h] [rbp-38h]
__int64 v25; // [rsp+98h] [rbp-30h] BYREF
int v26; // [rsp+A0h] [rbp-28h]
__int64 v27; // [rsp+A8h] [rbp-20h]
int v28; // [rsp+B0h] [rbp-18h]
__int64 v29; // [rsp+B8h] [rbp-10h]
__int64 v30; // [rsp+C0h] [rbp-8h]

v4 = a4;
if ( *(_BYTE *)(a4 + 48) && *(_BYTE *)(a4 + 49) )
{
QTimer::stop(a1);
}
else
{
v5 = 0;
GetSystemTimeAsFileTime(&SystemTimeAsFileTime);
if ( *(unsigned __int64 *)&SystemTimeAsFileTime > 0x19DB1DED53E7FFFLL )
v5 = *(unsigned __int64 *)&SystemTimeAsFileTime / 0x989680 - 0x2B6109100LL;
sub_1400017C0(a1, v5, v5, &SystemTimeAsFileTime);
sub_140002160(
(_DWORD)a1,
v5,
(unsigned int)&SystemTimeAsFileTime,
v5,
v4 + 48,
v4 + 184,
v6,
v7,
v8,
v9,
SystemTimeAsFileTime.dwLowDateTime,
v11,
v12,
v13,
v14,
(_DWORD)v15,
v16,
v17,
v18,
(_DWORD)v19,
v20,
v21,
v22,
(_DWORD)v23,
v24,
v25,
v26,
v27,
v28,
v29,
v30);
QWidget::update(a1);
if ( v23 != &v25 )
j_j_free_2(a1);
if ( v19 != &v21 )
j_j_free_2(a1);
if ( v15 != &v17 )
j_j_free_2(a1);
}
}

  • GetSystemTimeAsFileTime → 计算 UTC 秒 ts
  • 调用 sub_1400017C0 做时间判定 + 生成关键参数
  • 调用 sub_140002160 根据判定结果更新内部字符串 / 状态
  • QWidget::update 触发绘制

重点是 sub_1400017C0:时间 → 多重素数筛选

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
__int64 __fastcall sub_1400017C0(__int64 a1, __int64 a2, unsigned __int64 a3, __int64 a4)
{
unsigned int v4; // ebx
unsigned __int64 v7; // rdx
unsigned __int64 v8; // rcx
__int64 i; // rax
unsigned __int64 v10; // rcx
int v11; // r13d
unsigned int v12; // r11d
__int64 v13; // rsi
__int64 v14; // rdi
unsigned __int64 v15; // r14
unsigned __int64 v16; // rcx
unsigned __int64 v17; // rdx
unsigned __int64 v18; // r13
unsigned __int64 v19; // r15
__m128i *v20; // rax
_DWORD *v22; // rdx
int v23; // eax
__int64 v24; // rdx
__int64 v25; // rdx
__int64 v26; // rdx
__int64 v27; // rdx
__int64 v28; // rdx
char *v29; // rdi
char *v30; // rsi
size_t v31; // rax
unsigned int v32; // ebx
_QWORD *v33; // rdx
_BYTE *v34; // rdx
_BYTE *v35; // rax
__int64 v36; // rdx
__int64 v37; // rdx
__int64 v38; // rdx
__int64 v39; // rdx
_QWORD *v40; // rax
unsigned int v41; // [rsp+68h] [rbp-110h]
__m128i v42; // [rsp+70h] [rbp-108h] BYREF
int v43; // [rsp+80h] [rbp-F8h]
char v44[12]; // [rsp+84h] [rbp-F4h] BYREF
char Str[32]; // [rsp+90h] [rbp-E8h] BYREF
_QWORD *v46; // [rsp+B0h] [rbp-C8h] BYREF
__int64 v47; // [rsp+B8h] [rbp-C0h]
_QWORD v48[2]; // [rsp+C0h] [rbp-B8h] BYREF
__int64 v49; // [rsp+D0h] [rbp-A8h] BYREF
__int64 v50; // [rsp+D8h] [rbp-A0h]
_BYTE v51[32]; // [rsp+F0h] [rbp-88h] BYREF
_QWORD v52[13]; // [rsp+110h] [rbp-68h] BYREF

v4 = 1970;
*(_QWORD *)(a4 + 8) = a3;
*(_QWORD *)(a4 + 40) = a4 + 56;
*(_QWORD *)(a4 + 72) = a4 + 88;
*(_QWORD *)(a4 + 104) = a4 + 120;
*(_BYTE *)a4 = 0;
*(_QWORD *)(a4 + 32) = 0;
*(_QWORD *)(a4 + 48) = 0;
*(_BYTE *)(a4 + 56) = 0;
v7 = a3 / 0x15180;
*(_QWORD *)(a4 + 80) = 0;
*(_BYTE *)(a4 + 88) = 0;
*(_QWORD *)(a4 + 112) = 0;
*(_BYTE *)(a4 + 120) = 0;
*(_OWORD *)(a4 + 16) = 0;
while ( 1 )
{
if ( (v4 & 3) != 0 || (v8 = 366, __ROR4__(-1030792151 * v4, 2) <= 0x28F5C28u) )
v8 = (__ROR4__(-1030792151 * v4, 4) < 0xA3D70Bu) + 365LL;
if ( v7 < v8 )
break;
if ( v4 == 10000 )
return a4;
v7 -= v8;
++v4;
}
if ( v4 != 10000 )
{
for ( i = 1; ; ++i )
{
v12 = i;
v13 = (unsigned int)i;
if ( (_DWORD)i == 2 )
{
if ( (v4 & 3) == 0 )
goto LABEL_19;
LABEL_15:
if ( v4 % 0x190 )
{
while ( v7 > 0x1B )
{
++i;
v7 -= 28LL;
v13 = (unsigned int)i;
v12 = i;
if ( (_DWORD)i != 2 )
goto LABEL_10;
if ( (v4 & 3) == 0 )
{
LABEL_19:
while ( v4 % 0x64 )
{
if ( v7 <= 0x1C )
goto LABEL_34;
++i;
v7 -= 29LL;
v13 = (unsigned int)i;
v12 = i;
if ( (_DWORD)i != 2 )
{
v10 = dword_14007D1FC[i];
v11 = dword_14007D1FC[i];
if ( v7 >= v10 )
goto LABEL_11;
goto LABEL_23;
}
}
goto LABEL_15;
}
}
}
else
{
while ( v7 > 0x1C )
{
++i;
v7 -= 29LL;
v13 = (unsigned int)i;
v12 = i;
if ( (_DWORD)i != 2 )
goto LABEL_10;
if ( (v4 & 3) == 0 )
goto LABEL_19;
}
}
LABEL_34:
v13 = v12;
v14 = (unsigned int)(v7 + 1);
goto LABEL_25;
}
LABEL_10:
v10 = dword_14007D1FC[i];
v11 = dword_14007D1FC[i];
if ( v7 < v10 )
break;
LABEL_11:
if ( v12 == 12 )
break;
v7 -= v10;
}
LABEL_23:
v14 = (unsigned int)(v7 + 1);
if ( (int)v14 <= 0 || (int)v14 > v11 )
return a4;
LABEL_25:
v15 = a3 % 0x15180 / 0xE10;
v16 = a3 % 0x15180 % 0xE10;
*(__m128i *)(a4 + 16) = _mm_unpacklo_epi64(
_mm_unpacklo_epi32(_mm_cvtsi32_si128(v4), _mm_cvtsi32_si128(v13)),
_mm_unpacklo_epi32(_mm_cvtsi32_si128(v14), _mm_cvtsi32_si128(v15)));
v17 = v16 / 0x3C;
*(_DWORD *)(a4 + 32) = v16 / 0x3C;
v18 = v16 / 0x3C;
v16 %= 0x3Cu;
*(_DWORD *)(a4 + 36) = v16;
v19 = v16;
if ( (unsigned __int8)sub_140001540(v14, v13, v17, (int)v4) )
{
v20 = &v42;
v43 = 11;
v42 = _mm_loadu_si128((const __m128i *)&xmmword_14007D230);
while ( v20->m128i_i32[0] != (_DWORD)v13 )
{
v20 = (__m128i *)((char *)v20 + 4);
if ( v44 == (char *)v20 )
return a4;
}
if ( (_DWORD)v14 != 1 )
{
v41 = v4 % 0x64;
if ( (unsigned __int8)sub_140001540(v14, v13, v44, (int)v14) )
{
if ( (_DWORD)v13 != 2 || ((v4 & 3) != 0 || (v22 = (_DWORD *)v41, v23 = 29, !v41)) && (v23 = 29, v4 % 0x190) )
{
v22 = dword_14007D200;
v23 = dword_14007D200[(int)v13 - 1];
}
if ( v23 >= (int)v14
&& (unsigned int)(v15 - 2) <= 0x15
&& (unsigned __int8)sub_140001540(v14, v13, v22, a3 % 0x15180 / 0xE10)
&& (unsigned int)(v18 - 2) <= 0x39
&& (unsigned __int8)sub_140001540(v14, v13, v24, v18)
&& (unsigned int)(v19 - 2) <= 0x39
&& (unsigned __int8)sub_140001540(v14, v13, v25, v19)
&& (unsigned __int8)sub_140001540(v14, v13, v26, (int)v14 + 100LL * (int)v13 + 10000LL * (int)v4)
&& (unsigned __int8)sub_140001540(v14, v13, v27, 100 * v18 + v19 + 10000 * v15) == 1
&& a3 > 1
&& (unsigned __int8)sub_140001540(v14, v13, v28, a3) )
{
v29 = Str;
sub_14005DE00((unsigned int)Str, v13, 32, (unsigned int)Str, (unsigned int)"%04d%02d%02d%02d%02d%02d", v4);
v30 = (char *)v48;
v46 = v48;
v31 = strlen(Str);
v52[0] = v31;
v32 = v31;
if ( v31 > 0xF )
{
v40 = (_QWORD *)sub_140078840(Str, v48, v52, &v46, 0);
v46 = v40;
v48[0] = v52[0];
}
else
{
if ( v31 == 1 )
{
LOBYTE(v48[0]) = Str[0];
LABEL_53:
v33 = v46;
v47 = v52[0];
*((_BYTE *)v46 + v52[0]) = 0;
if ( (unsigned __int8)sub_1400014B0(v29, v30, v33, &v46) )
{
v35 = v46;
v34 = (char *)v46 + v47;
while ( v35 != v34 )
{
if ( *v35++ == 48 )
goto LABEL_61;
}
v30 = (char *)&v49;
sub_140079790(v29, &v49, a3, &v49);
v29 = v51;
sub_140001700((unsigned int)v51, (unsigned int)&v49, (_DWORD)v46, (unsigned int)v51, v47, v49);
sub_140001700((unsigned int)v51, (unsigned int)&v49, v49, (unsigned int)v52, v50, (_DWORD)v46);
if ( (unsigned __int8)sub_1400014B0(v51, &v49, v36, v51)
&& (unsigned __int8)sub_1400014B0(v51, &v49, v37, v52) )
{
*(_BYTE *)a4 = 1;
sub_140078730(v51, &v49, &v46, a4 + 40);
sub_140078730(v51, &v49, v51, a4 + 72);
sub_140078730(v51, &v49, v52, a4 + 104);
}
sub_1400762B0(v51, &v49, v37, v52);
sub_1400762B0(v51, &v49, v38, v51);
sub_1400762B0(v51, &v49, v39, &v49);
}
LABEL_61:
sub_1400762B0(v29, v30, v34, &v46);
return a4;
}
if ( !v31 )
goto LABEL_53;
v40 = v48;
}
qmemcpy(v40, Str, v32);
v30 = &Str[v32];
v29 = (char *)v40 + v32;
goto LABEL_53;
}
}
}
}
}
return a4;
}

  • 把 FILETIME 转成 year,month,day,hour,min,sec

  • 月份必须在表 xmmword_14007D230 + var_F8=11(即 {2,3,5,7,11})

  • 日 / 时 / 分 / 秒要过 sub_140001540(素性检查)

  • YYYYMMDD、HHMMSS、YYYYMMDDHHMMSS 都要是素数

  • 字符串里不能含字符 '0'(遍历 YYYYMMDDHHMMSS,若见 '0' 直接失败)

  • 还要求:ts 是素数、YYYYMMDDHHMMSS||ts 和 ts||YYYYMMDDHHMMSS 也是素数

  • sub_140001540:把数字转十进制字符串 → sub_1400047F0 → GMP 的素性判定

  • sub_140002160:满足条件后才进入 “解密显示”

    – sub_140001E70 /sub_140001EE0:从 .rdata 里 XOR 解出两段十进制密文

    – 以 p=YYYYMMDDHHMMSS、q=p||ts、r=ts||p 组 RSA

    – 以 e=ts 做 m = c^d mod n(内部用 GMP 大数函数)

    – sub_140001F50 导出字节流,sub_1400015B0 生成可显示字符串(不可打印字节

    变成 \xNN)

    – 结果分别写到控件两个字符串槽位

  • sub_14006BD20:paintEvent,把两段字符串画到背景图上

通过后,构造三个大素数 p=YYYYMMDDHHMMSS、q=p||ts、r=ts||p,用 n=pqr、e=ts 对两个内置密文做 RSA 反解(密文是 XOR 过的十进制串)

结果字节流再转成可显示字符串,可以给出爆破 exp

还要满足一串素性检查:YYYYMMDD、HHMMSS、YYYYMMDDHHMMSS、timestamp、YYYYMMDDHHMMSS||timestamp、timestamp||YYYYMMDDHHMMSS 都要是素数

代码里还有一个字符串里不能含 0 的检查点(紧跟在 YYYYMMDDHHMMSS 的素性判断之

后)

  1. 月份表是 {2,3,5,7,11},但无 0 其实只剩 11 月。
  2. 解密密文需要原始密文数据,它在 exe 的 .rdata 中,经过 XOR 混淆:XOR key = 51 - dword_14017C040 通过函数 sub_140001E70 /sub_140001EE0 可知 key 只能让结果为数字串用脚本验证 key=51 才解出可用密文
  3. 用上述约束枚举时间 → 计算 p/q/r → RSA 解密 → 输出字符串

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
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import datetime
import pefile
import math
import struct
import itertools

def is_prime(n):
if n < 2: return False
if n in (2, 3): return True
if n % 2 == 0: return False
small_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]
for p in small_primes:
if n % p == 0: return n == p

d, s = n - 1, 0
while d % 2 == 0:
s += 1
d //= 2
for a in small_primes:
if n <= a: break
x = pow(a, d, n)
if x == 1 or x == n - 1:
continue
for _ in range(s - 1):
x = (x * x) % n
if x == n - 1:
break
else:
return False
return True

def has_no_zeros(val):
return '0' not in str(val)

def int_to_clean_bytes(n):
length = (n.bit_length() + 7) // 8
if length == 0: return b''
raw_bytes = n.to_bytes(length, 'big')
return bytes(b for b in raw_bytes if b != 0)

def render_readable(data):
result = []
for b in data:
if 0x20 <= b <= 0x7E:
result.append(chr(b))
else:
result.append(f"\\x{b:02X}")
return "".join(result)
def load_encrypted_data(filepath='./find_my_time_c.exe'):
try:
pe = pefile.PE(filepath)
except FileNotFoundError:
exit(1)

base = pe.OPTIONAL_HEADER.ImageBase
offset_enc1 = 0x14007D1C0 - base
offset_enc2 = 0x14007D160 - base
enc1_bytes = pe.get_data(offset_enc1, 63)
enc2_bytes = pe.get_data(offset_enc2, 64)
KEY = 51

def xor_and_parse(data, suffix_bytes=b''):
decrypted_chars = ''.join(chr(b ^ KEY) for b in data)
decrypted_chars += suffix_bytes.decode('ascii')
return int(decrypted_chars)

suffix = chr(KEY ^ 7) + chr(KEY ^ 6)
c1 = xor_and_parse(enc1_bytes)
c2 = xor_and_parse(enc2_bytes, suffix_bytes=suffix.encode())
return c1, c2

def generate_valid_components():
def get_primes(start, end, width=2):
return [
x for x in range(start, end)
if is_prime(x) and has_no_zeros(f"{x:0{width}d}")
]

months = [11]
days = get_primes(2, 32)
hours = get_primes(2, 24)
mins = get_primes(2, 60)
secs = get_primes(2, 60)
valid_times = []
for h, m, s in itertools.product(hours, mins, secs):
time_val = h * 10000 + m * 100 + s
if is_prime(time_val):
valid_times.append((h, m, s))
years = get_primes(1970, 10000, width=4)
return years, months, days, valid_times

def solve(c1, c2):
years, months, days, valid_times = generate_valid_components()
candidates = []

for y, mo, d in itertools.product(years, months, days):
try:
datetime.date(y, mo, d)
except ValueError:
continue

ymd_int = y * 10000 + mo * 100 + d
if not is_prime(ymd_int):
continue
for h, m, s in valid_times:
dt = datetime.datetime(y, mo, d, h, m, s,
tzinfo=datetime.timezone.utc)
ts = int(dt.timestamp())

if ts <= 1 or not is_prime(ts):
continue
dt_str = f"{y:04d}{mo:02d}{d:02d}{h:02d}{m:02d}{s:02d}"
if not has_no_zeros(dt_str): continue
if not is_prime(int(dt_str)): continue
ts_str = str(ts)
if not is_prime(int(dt_str + ts_str)): continue
if not is_prime(int(ts_str + dt_str)): continue
candidates.append((dt, ts, dt_str, ts_str))
print(f"candidates: {len(candidates)}")
for dt, ts, dt_str, ts_str in candidates:
p = int(dt_str)
q = int(dt_str + ts_str)
r = int(ts_str + dt_str)

n = p * q * r
phi = (p - 1) * (q - 1) * (r - 1)

try:
d_inv = pow(ts, -1, phi)
except ValueError:
continue

m1 = pow(c1, d_inv, n)
m2 = pow(c2, d_inv, n)
s1 = render_readable(int_to_clean_bytes(m1))
s2 = render_readable(int_to_clean_bytes(m2))

print(f"{dt} {ts} {dt_str}")
print(f"s1: {s1}")
print(f"s2: {s2}")

if __name__ == '__main__':
c1_val, c2_val = load_encrypted_data()
solve(c1_val, c2_val)

运行得到:

55

最后的 flag 为:flag{4Kr0s5_Th0u54nDs_0f_yE4Rs_u_f1nd_m3_1nTh3_sE4_oF_7imE}