引子

本题我愿称之为 ”非常 6 + 1“,一题更比 6 题强,下图是题目概况:

challenge_info

运行 32 位程序 crackme.exe,弹出一个经典 CM 界面,如下图所示。点击 check 按钮时,若输入的序列号错误,程序退出。

challenge_info

管中窥豹

该程序没有 main 函数,在 sub_402D4C 里首先通过 PEB 找到 kernel32 模块,拿到两个关键函数 GetProcAddress 和 LoadLibraryA。借助它们找到本函数内所需的所有导入函数,并将一众函数指针保存到自定义结构体中:

1
2
3
4
5
6
7
8
9
struct custom
{
int kernel32_hModule;
HMODULE (__stdcall *LoadLibraryA)(LPCSTR lpLibFileName);
FARPROC (__stdcall *GetProcAddress)(HMODULE hModule, LPCSTR lpProcName);
// LocalAlloc
// ExitProcess
// ...
};

之后创建了一个事件 poision132(估计是用来防止多开的)与主对话框:

sub_402D4C

其中 GetModuleHandleW 的后四个参数实为 DialogBoxParamA 的参数,因为汇编里它们四个在调用 GetModuleHandleW 前就入栈了,IDA 没能分辨出来。

对话框对应的函数是这样处理 check 按下的:

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
else if ( v22 == 273 && a3 == 1002 )
{
v40 = custom;
v21 = (*(custom + 92))(a1, 1001); // GetDlgItem
if ( v21 )
{
for ( i = 0; i < 0x100; ++i )
input[i] = 0;
input_len = (*(v40 + 96))(v21); // GetWindowTextLengthA
if ( input_len <= 210 && input_len >= 1 )
{
(*(v40 + 100))(v21, input, 255); // GetWindowTextA
input_len = (*(v40 + 24))(input); // lstrlenA
if ( input_len >= 1 && input_len <= 128 )
{
for ( j = input_len; j < 128; ++j )
{
if ( j % 2 )
input[j] = 32;
else
input[j] = 127;
}
v39 = 1;
}
else
{
v39 = 0;
}
}
else
{
v39 = 0;
}
}
else
{
v39 = 0;
}
if ( v39 )
{
v48 = custom;
if ( (*(custom + 56))(0, crackme_exe_path, 255) )// GetModuleFileNameW
{
v27 = 104;
v28 = 0;
v29 = -72;
v30 = 0;
v31 = -1;
v32 = -48;
v19 = 0;
v36 = 0;
v8[17] = -1;
for ( k = 0; k < 0x44; ++k )
*(v8 + k) = 0;
v8[0] = 68;
for ( m = 0; m < 0x10; ++m )
*(&v42 + m) = 0;
if ( (*(v48 + 28))(crackme_exe_path, 0, 0, 0, 0, CREATE_SUSPENDED, 0, 0, v8, &v42) )// CreateProcessW
{
v6[0] = 65543;
if ( !(*(v48 + 32))(v43, v6) ) // GetThreadContext
goto LABEL_69;
v19 = v6[44];
v36 = (*(v48 + 44))(v42, 0, 128, 4096, 4);// VirtualAllocEx
if ( !v36 )
goto LABEL_69;
for ( n = 0; n < 0x80; n += v17 )
{
v17 = 0;
if ( !(*(v48 + 48))(v42, n + v36, &input[n], 128 - n, &v17) )// WriteProcessMemory
{
v16 = 0;
goto LABEL_45;
}
}
v16 = 1;
if ( !v16 )
goto LABEL_69;
LABEL_45:
v20 = (*(v48 + 20))(0); // GetModuleHandleW
v10 = *(v20 + 60) + v20;
v9 = v19 - *(v10 + 40);
v11 = sub_B31000 - v20;
v28 = v36;
v30 = sub_B31000 + v9 - v20;
for ( ii = 0; ii < 0xC; ii += v15 )
{
v15 = 0;
if ( !(*(v48 + 48))(v42, ii + v19, &v27 + ii, 12 - ii, &v15) )// WriteProcessMemory
{
v14 = 0;
goto LABEL_52;
}
}
v14 = 1;
LABEL_52:
if ( v14 )
{
(*(v48 + 36))(v43); // ResumeThread
v35 = 2;
if ( (*(v48 + 60))(v42, 30000) ) // WaitForSingleObject
{
(*(v48 + 64))(v42, 2); // TerminateProcess
}
else
{
v18 = 0;
if ( (*(v48 + 52))(v42, &v18) && v18 != 2 )// GetExitCodeProcess
v35 = v18;
}
if ( v35 == 2 )
{
strcpy(v25, "Unknown Error");
(*(v48 + 88))(0, v25, v25, 0); // MessageBoxA
(*(v48 + 16))(0); // ExitProcess
v13 = 0;
}
else
{
if ( v43 )
{
(*(v48 + 64))(v42, 2); // TerminateProcess
(*(v48 + 40))(v43); // CloseHandle
}
if ( v42 )
(*(v48 + 40))(v42); // CloseHandle
if ( v35 != 1 )
(*(v48 + 16))(0); // ExitProcess
v13 = v35;
}
v33 = v13;
}
else
{
LABEL_69:
v34 = 2;
strcpy(v24, "Unknown Error");
(*(v48 + 88))(0, v24, v24, 0); // MessageBoxA
(*(v48 + 16))(0); // ExitProcess
v12 = 0;
v33 = 0;
}
}
else
{
v33 = -1;
}
}
else
{
v33 = -1;
}
if ( v33 )
{
strcpy(v23, "SUCCESS!");
strcpy(v26, "INFO");
(*(custom + 88))(0, v23, v26, 0); // MessageBoxA
}
}
(*(custom + 16))(0); // ExitProcess
}

其行为主要是:

  1. 获得输入框内容,判断输入长度在 1 ~ 128 之间
  2. 新建一个子进程 crackme.exe 并挂起
  3. 在子进程中申请一片空间 A 并写入输入内容
  4. 将子进程 start 函数开头改为直接调用 sub_401000,并传入 A 的地址
  5. 启动子进程,等待子进程执行结束,获取其退出码,若为 1 则正确

故父进程只起到检查作用,只需关心子进程的核心逻辑 sub_401000。该函数形似 sub_402D4C,都是先找到一众函数地址,然后再执行自己的逻辑:

  1. 通过 Decompress 函数解压释放代码
  2. 调用前 6 个加密函数对输入进行加密
  3. 调用最后一个函数对输入进行最后一次加密的同时校验输入

sub_401000

因此,本题的题意应该就是分析这 7 种加密方式再写脚本还原了。

一叶障目

一开始我用 x32dbg 启动父进程,用 IDA 附加子进程。在跟进第一个加密函数后,天真的我按下了 F5,当听到电脑的风扇声时,我意识到问题没有那么简单。同时,我发现对同一组输入,输出的加密结果竟然每次都不一样,那八成是存在调试器检测了。

反正也反编译不出来,所以我交换了一下,用 IDA 与 x32dbg 来分别调试父、子进程,方便用插件隐藏调试器。这次对于同一组输入,输出果然固定了下来。

在汇编层面进行一些分析后,我发现本题的每个加密函数中都存在非常多的无用代码,应该是用来做代码膨胀的,这直接导致 IDA 需要分析大量无用逻辑来生成伪代码。虽然一直等下去有可能能跑出来,但是我怕还没看到伪代码就该下一题了,于是就采用选择明文攻击及对输入下硬件访问断点(简称连蒙带猜)来找到和还原加密逻辑。

山重水复

第一次加密

  1. 找到 128 以内的质数作为下标数组,访问输入,对对应下标的字符进行查表替换
  2. 与已知数组逐字节异或

Python 描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def enc1(plain: bytes) -> bytes:
tbl = bytes.fromhex("b466efcad9ebb6423614b123b5abd400b0bb96e430a87e5e872daa0147a03dd2dae185f5ff0cfdad07aff2c8739446fce77f1570baf3085f0aa38d1fe6056dc44d31881799c6b26b831c80db6927fac22eec4b2f62a4d339bc6a4a8633bf929144b9cdacf6976ce9906554747609e249f0f9a2139a1632f4823f6f290b5a22b39c4e68d0c1e041ae6428d5048f9f78cc1d180d675be5487b19edd7dd55590e258e2c4012601e10bdc571f851c721c01b45e83ba1f76e2b8cb7d60fde35892a1a7d95d1723ca53411b8525c75ee9bf1fba96179c9203ec337817ccb5798dcbe243a586302d8ea4f43849d064c9efee3a7a68a0356938b7ace385326cfdf775d50")

pp = b"0123456789ABCDEF" * 8
ee = bytes.fromhex("3031157034f3365f3839418843994546307f32703435365f38394142439945b23031323334f3363738a34188434445b23031323334f336373839418543994546303132703435365f38a34142434445b2303132703435363738a3414243444546307f323334f3365f3839418843994546307f32333435363738394142434445b2")
idxs = []
for i in range(len(ee)):
if ee[i] != pp[i]:
idxs.append(i)

cc = list(pp)
cipher = list(plain)
for idx in idxs:
cc[idx] = tbl[pp[idx]]
cipher[idx] = tbl[plain[idx]]

rr = bytes.fromhex("0b3a177e1fc61b4a70304b8958ad0560115632373b7020731c1a5b55059e658c232e150c31cf3507288173885e5c76ba2200731d2ab0130124117b8501a47d0f3c250b7470021c4617a5434146434ebf2122256d2b101f1c17967a7f00030c096326535653985b2e47bac803d60ed8e597d281868bf4f3f0ebe6a2a7aaabb449")
for i in range(len(cc)):
cipher[i] ^= cc[i] ^ rr[i]

return bytes(cipher)

第二次加密

  1. 逐字节查表替换
  2. 前后 64 字节位置调换
  3. 按 32 字节划分为 4 组,1、3 组位置交换
  4. 与已知数组逐字节异或
  5. 2、4 组位置交换
  6. 与已知数组逐字节异或

Python 描述:

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
def enc2(plain: bytes) -> bytes:
tbl = bytes.fromhex("38393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637716e66655e61696b5d64676f5c606a7063685b6d5f626c8788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff72737475767778797a7b7c7d7e7f80818283848586")

pp = bytes.fromhex("5a69aef84e591d6a7a3a3ef22d6b781f40e463b16a23265316102e227058189d727d445b60503301225206f32b2f0bab7353224a7b2f15072e1b0efe746200706d765af221511a661d763636333033ae707174eb7a43191a1d450f08757071763294020102075d0e4db0bd78a3c8a59ac660d0d1daa7f5f6e1ecd7d0dfd8c958")
cc = list(pp)
cipher = list(plain)

for i in range(len(plain)):
cc[i] = tbl[pp[i]]
cipher[i] = tbl[plain[i]]

for i in range(0x40):
cc[i], cc[0x80-i-1] = cc[0x80-i-1], cc[i]
cipher[i], cipher[0x80-i-1] = cipher[0x80-i-1], cipher[i]

for i in range(0x20):
cc[i], cc[0x40+i] = cc[0x40+i], cc[i]
cipher[i], cipher[0x40+i] = cipher[0x40+i], cipher[i]

rr = bytes.fromhex("680567cabc021234390d1ed230593e9cc250360d412f2e553e2e386c711999bb8f646b9c500264121a186ac43ec4222d93415f434746dd02370b7402256ad50455bf8f97818915916565a6f4fad443f8868aefa6a5fcf10159470c16141db429977f1142703e7e7940727203b2288f6a2ff41971056669f0183c5c4013ae0458")
for i in range(len(cc)):
cipher[i] ^= cc[i] ^ rr[i]

cc = list(rr)
for i in range(0x20):
cc[0x20+i], cc[0x60+i] = cc[0x60+i], cc[0x20+i]
cipher[0x20+i], cipher[0x60+i] = cipher[0x60+i], cipher[0x20+i]

rr = bytes.fromhex("f6b0c1702bb6869da6bcba77bde9b92f51db99a5efa3a6d4b8b2bcc0c781342b14dd80ccd0a3e8c0e7c0f79a198205f8b46e99fe90e4ca51afb5e498ca74df8488615077606bf6758083411c1369ac087036514d500731f9b5a9b3efeee95eda7ddf9a71a7c3a6d1deddac03f60de8e65f8c918c97970fd1e3dea2d5db962af9")
for i in range(len(cc)):
cipher[i] ^= cc[i] ^ rr[i]

return bytes(cipher)

第三次加密

  1. 相邻字节两两交换位置
  2. 与已知数组逐字节错位异或
  3. 前后 64 字节位置调换
  4. 逐字节加 0x80

Python 描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def enc3(plain: bytes) -> bytes:
tbl = bytes.fromhex("c4189868c047179a23f21a2f75ffd837eb1c7d1861cdf1b74914c18590e1616d7bd4f82307d23443325c3c1aeddff337e4ae04e36d2506bd4ea1f351bd3f8ca1974465b774ac094bdb56552fe61bfc579daa9b47710921084a979b12601de096438c39b57b9bfe9e0e196ceb9a9632a14c5c7e3291bd9017d0db023a8f002eb2")
tbl = list(tbl)
cipher = list(plain)

for i in range(0, len(cipher), 2):
cipher[i], cipher[i+1] = cipher[i+1], cipher[i]

for i in range(len(tbl)):
tbl[i] ^= cipher[(0x75 + i) % 128]

for i in range(0x40):
tbl[i], tbl[0x80-i-1] = tbl[0x80-i-1], tbl[i]

for i in range(len(tbl)):
tbl[i] = (tbl[i] + 0x80) & 0xFF

return bytes(tbl)

第四次加密

与已知数组逐字节加

Python 描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def enc4(plain: bytes) -> bytes:
cipher = list(plain)

pp1 = bytes.fromhex("a53f0c50366abda69abc3ecf6f580d6be2286767b4b24360f7cdf44e9c88f5931131d090a4b71fd9e1e0957144ed5f7dbc2ceceece0a5291bf6db45b822f9587c5954189bf76d9d7bf713f0aa36ceeb414f393794788f7754208923f11de80144e78c441def8bb74decb3a47247b014001990f031fb08b788c3512a3361749d3")
cc1 = bytes.fromhex("a642115b4c77f1b5fdba36cb428a409f17f2309fdea03474e6d0f7519f8bf8961434d393a7ba22dce4e3987447f05876b525e5e7c7034b8ab866ad547b289082c0903c84ba71d4d2ba6c3a059e67f5bb1bfa9a804e8ffe7c490f994618e587144e78c441def8bb74decb3a472483094809a1170b27b89380943d1aab3e0c3ec8")
dis = []
for i in range(len(pp1)):
dis.append((cc1[i] - pp1[i]) & 0xFF)

for i in range(len(cipher)):
cipher[i] += dis[i]
cipher[i] &= 0xFF

return bytes(cipher)

第五次加密

  1. 前 64 字节倒序排列
  2. 逐字节查表替换
  3. 与已知数组逐字节异或

Python 描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def enc5(plain: bytes) -> bytes:
cipher = []

for i in range(0x30, -1, -0x10):
for j in range(0xF, -1, -1):
cipher.append(plain[i+j])
cipher += list(plain[0x40:])
tbl = bytes.fromhex("d5a04a5de8a503a8dd2d980a4d8f901234023d5545b73f2cbabc07c70186df532961253e09217b85b60895300bb09e6bdbf9af51844950d210ebac2b48c56a74cdfd1d314bd6f3b381367e9c582f4c6c0513635a419b387c6519d047c4927a6fefb15783271a71d8fa2ae142a28ceaff166400561e374328ab06c9c3d3a614aeb5e6969a9d5f2615f223a7a34e8772ec208059a440f70ce7bb33ede2ee0f8eb20e3c916824754660a17932b8738b76d11fda8d22f00dc011c2e0665ed79ffe88dc7d8aca4404d417c61b94f654694f6de5be3b6ecbf18278f4def5bd2ece67c835ad7752c17fb99339f8e418bfd9cfb489cc5c1cfcaa3a7062fb5be3a99997e9")

for i in range(0x80):
cipher[i] = tbl[cipher[i]]

pp = bytes.fromhex("9620b6c3418b71c2a79c5d17937f210d436589b31ebb52c12e256660a46e84450c62a3b21370e5b91e840e67b2db5c2cb2cda71df65066990dcc285847021d46dc20489d6664cb3b66a2aca58ed8aa5ec75bedb54cec97d3361233f3ba7f15454cab44fd67625e1e67f6acb3099a2d812d3c2c0a85c2a4b540c507b86a4d6ac6")
cc = bytes.fromhex("1e76d27f22bf0ba060c447584fa9b895954c4a32bdc046d036cd250181466efc2f002f48486ec74947015bcc5249878f274d2cfb1ab6a9b9a362ca9cff8f586ac125846ea7c2acf7c225d7662ca5b39e5387ca66b8687d953a4d09b09436291f92e9a38ace7ce6cdd1ba7464e488dc9118143ec808ec822e4438538100d068b3")
for i in range(len(cipher)):
cipher[i] ^= pp[i] ^ cc[i]
return bytes(cipher)

第六次加密

与已知数组逐字节异或

Python 描述:

1
2
3
4
5
6
def enc6(plain: bytes) -> bytes:
p = bytes.fromhex("cf00fa1ac6497c69840b0c74e6d8c2ad50e23957b7b581e678aac75be30e61d895f5d38858be754f4dcf567f9f1cf0cc89fed6aee83cf43e20c8c8fadbc6f2cbd16eca9559b795199d171848be19fa92a30f49b7dc3ec2f54599d28e29a38546bc459e3b969792d0fb06532ac838a4c8fd72055c12d398bbd1f5ad497a5a831b")
c = bytes.fromhex("5a125abad541706a858bf87f4354c5ab409259b7e73511f6589a87ab23de41f885c5d2855fbf7a4d44df76ef7fec30fc005fe6ee885cb41e60e8880a1b16d2ebe11efa65eabc97298c061940be6770a8132f0907acaedbc505dbf2e563b7ad50eec6ee66e6f7b240eb0603bad868f498ed52956c62f3a8cbe1863b497658ae6b")
tbl = [p[i] ^ c[i] for i in range(len(p))]

return bytes([tbl[i] ^ plain[i] for i in range(len(plain))])

第七次加密及校验

  1. 逐字节查表替换 × 7
  2. 加密结束,与目标数组比较

Python 描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def enc7(plain: bytes) -> bytes:
tbl1 = bytes.fromhex("588d51e8d4d9a5c53094c6bab325c238a4c8aaf74e2e603b3ab2a975a12316fb64e42478322631434bbcb8ac639b7e871b42b59e612a866e4cd2355ad6e9b91db77fa8537bd7de799a745f0bf001ade24a3d98651e6cd8dbabe5442221cb507615565c2972896d418c8e19e1919df2063f1a7d978a8392dc77f936cf48a0c78214e3963984b188284611afa352fe10f457f1e0ca547ca27049a72db6d090da7a67c3bd1cf6f52b9308691359624feaced3eceb5ed10e04204d0f8bbbc9bf03059ccc40dfef853e3cf3ffd50a272fee8f6a1ffd955d817355dde75b6f18c4ae450d689fc012edcd0c33f8fc2c003747bee634a666b0b49971806b07fac1170209")
tbl2 = bytes.fromhex("35b3a22a08d483add97fb6c277933814aa1341488d79eca0f6529df8a64d30fa6124b557658ef9f0d1c3c41acd252f676aa44c17686682c7cc6343e987dd8cb2ea3f7cfc6d115db8b1bd562cc186b044056236a8399427ae7b80df73e5e233e3d3cba7859b9991d0006921e66e9806372b8bf42e95a3cf0c7119530410c96b3aee1b88e12397d6f2d54fe4be4270ffe8de5c7db9f126a9843bced2bcc6d83d501c81db2d6045493e587692c512fb9c54ba070290ef477e75af18bb51f5209a9631349f599e46bfa1ca89014a5a1f1e5bf78fb7d7fe5ee01528e7dcda725f55030d64744b29c04e78ab40226f1d09f3a516c8fd6cac8a0fed0a3c327a0b0eebb4")
tbl3 = bytes.fromhex("7f9458886cfd2dafe88538c7287bebeeb925894fd2053a0cde8ffccb65ad45c959e12b2f4372c381db8eab010677075e74bc03628c543d3718e279840a307e2e83f95746d15b1a1f116820a76495710b5627a350bbc102e035b582d333ca4a0eef26f413b786acc83ed8759d233b934ca15ab6a0048b8a551922be61e69690c099c69ed7603241f087e9aa175f7c4b1b0d40ffb34269e410faa9d62cf5edbad9480f4ef378d014f7a66eaea870669a52801d34366b5da4ecdf0891ce9c4992a221cc2a6df8bd514d6ae55ce767b0f2f12412989b29b4b197fbc453cdda44e3dd1e6f31dc3cc576ead5f673c28d3f1ccf477aa50039fe7db215d4b863bf9f1609")
tbl4 = bytes.fromhex("4f8a646ddee94796105c220be55999d3319533ff1821ab3d2d78291e7758844db99ff851357d2b666ec2b15e2cdc5fd742ae98371fe2439cd94bcadfec9761398334676345366270243f877e467ca203d47394a41da6bb1add8cebd6d8be93feb419a832002a0a11c94ced28f3fc8ea0a1f675c0ce0754fbb512ea914e9dc7746014ac8f385ac152501c539bf1c4e36a4a41b2e8e19071b3cc0c57f9d2ada523bd7615effdbcf5af85dae068f4c8fa69d5256b273b891655db9ee448b69aee80f7170406b0921bcfbf6f86727bf05db713ba050f01e6aa2fc35b7a026ccda32ea9c53c568882e78d3e8120cb0d79f244653a260830a7b84940d07fc6d18b0e09")
tbl5 = b'\x1d\xbcs,\xdb\x81\xe3\x04\x06U\x8dF\xfcQ\x83\x00G\x14v-C\xf8\xc6\x10l\x11\x1c\xc0\x96V\x99\xbd\xd7\xeb\xa8\t\xc4=\x0ci\xea\xfd.j\xbf\x947!\x0fo(\x1f\xa7\x1bg6\x13\xd9\xff\xe8d0?\xf4q+\x8c\\p\'m@2\xa0z#\xcct\x871\xc1DN\x80&c\x07\x12W\x91<`h|w\xb7\x84\xae\x9c\xc9\xc2\xd0\xc3\x97\xf1B%y$\xc8\x02\xca/\xe4\xf0Or\x92\x0b\xfb\xa3n\xe7\x1eT\xc7\xa4J\xe0\xd5\x86\xb1\xf7\x8bY\xd2\x89\xf5\x08E\n:\x93\xd4\xd6}\xf9{\x85\x9d\xfe\xd3"L;~\xbe\xab\xb0\xb9\xa9\x88\xa6\xda\xbb\xf6\x1a\x05\xde\x18>\x8e\xb4\xfa\xba\xb3\xb8\xb6k\x0eK5\xcb\xec\xdc\x17\x8f \xf2*eb\x82\xf3\xa2\xcf^\xb5\xe6\xce\x9ff\xe9PR\xef[\x9e\xdd\x8a\xe2Iau\xed8\xd8\xd1\xe1)\x15\x98ZS\x01\xa5M\xcd\xc5\x90]\r9\x7f4\xdf_\x16\x03\xee\xa1\xad\x9b\xaf\xb23X\xe5\x19HA\xaa\x95\xac\x9ax'
tbl6 = b'V\xd6x\xdc\x8fs\xcf\x8e\x0b\x14\xaa\xfa?=\xc0\xf1)\xde\x8b:t\xb1n\x8cH\xc5\xc4R\xdd.\xd76yD\x7f\xe1\xc1\x99\xd4\x9a\xd8h\xa52WC\x17\xa2\xdf\x87\x82\xf8\xe6\xe7\xb5\x004\xb2\xc9\rX\xaeK\xad\xc3\xc7\x88\x91\n\x051dz>Mb\x06m\xa4\xc6\xe3Bji\xa3\xcc\xb0\x92a\xbf\x8a!Q\x07\xa7-&A\x10\x08\x19\xe2\x1ckr\xca\xb4\x18\x94U\xdaJ\xf3Y^"\x0cE\xbe\xfbG@}\xd0]*\xf2\x0eO/\xcd\x90\x03\x97\xff<\x968\x15\x80\xec\x0f\xc2\x1b\xb3\xeb\x9b~I\xaf{g\x9d$\x86\tf\xf7\\;\xd3\xfd\x04o\x1f\xd9\xb8\xa8ev\x8d\x95\xbb\xb9l\xb7\xee\x01\xf4 `\x1au\xf65\x89%\xbd\x11\x83,\xbcF\xd5\xcb\xab(w\'\xd2\xc8\xe0\xb6N\xd1\x98\xe9\xcep\xe4P+\xe8\xa1#\xa6\xfe\xedS\x9c\xa9\x127Z[\x81\xbaLT\xf9\x9e\xdb3\xea\x939\x1d\x02qc\x16\x85\x1e\x9f|\xac\xf0\xf5\x84\xef\xa00\xe5\x13_\xfc'
tbl7 = b' ;\xec\xbe\xb6\x87\x85\x9fS\xefW\xe0\xc3\x01\xf8:\xe7AKr)\x0bl\x04y\xfc\x18\xb4g\xce\xf0\x0eZ4\x9e\xf6\x94\xacXo\xf3\xc42\x8aU\x9c\x07\xb0e\xe3\x9bRQ\xc7\xddc\xa9\x12\x0f\x95\x80Y\x1a\xcf{0\xa8\xab1/a\x16\x1cMi"p\xcbu<\xd3t\x91\xf1O\xa5\xe2\xfd\xbd\x11m6+\xaa\xd5\x90\xe4\x8d\xda\xb8\xa2\xaf\x1d\x98\x1f\x88*I\x82x\x99\xebGf\xca?\xa6J&\xdfD\x7f\x13\xa4\xb1\x02z\xc9\x9d\x00\xd6^\xbaC\xc2\x03\xff(5\xbcdq\xdc\xa3[\xd4\xee\x83\xf7F-k}\xaeb\xd9\xa7,\xc8\r\xa1\x8ej\xe8`n\xb2@%h\xf4\xb7\xb3w\xc18\xc0\xd8\xd1\xfe\xb9\'\xbfE]=\x19\xf5\xcc9\xde\x81\x9a3\xc5s\x15\\P\x89N\xa0!$\xdb\xbbV\x17_\xad\xf2\x8c\x8f\x96\x97\xe5T\x86\t\x1bB\x10H\xb5\xf9~|\x05\xd7>\x067\xe6\x93\xfa\xe9v\n\x1e\xed\x0c.\xcd\xfb\x08\xea\x84\x92\xd0\xc6L\xe1#\xd2\x14\x8b'

cipher = list(plain)
tbls = [tbl1, tbl2, tbl3, tbl4, tbl5, tbl6, tbl7]
for tbl in tbls:
for i in range(len(cipher)):
cipher[i] = tbl[cipher[i]]
return bytes(cipher)

target = bytes([121, 23, 66, 107, 59, 80, 122, 227, 70, 208, 222, 78, 36, 167, 138, 106, 105, 208, 2, 6, 240, 39, 36, 189, 192, 187, 227, 30, 9, 163, 151, 48, 60, 182, 235, 104, 144, 9, 208, 234, 17, 242, 196, 96, 165, 203, 195, 252, 69, 251, 92, 83, 192, 128, 58, 153, 89, 111, 47, 84, 74, 217, 14, 106, 52, 222, 210, 236, 175, 74, 11, 164, 138, 182, 250, 147, 31, 4, 16, 68, 238, 228, 214, 158, 244, 69, 18, 77, 55, 60, 240, 17, 248, 200, 68, 118, 99, 16, 115, 45, 104, 215, 157, 47, 195, 132, 42, 182, 204, 181, 55, 97, 79, 15, 22, 188, 187, 140, 83, 39, 238, 119, 170, 31, 156, 194, 24, 222])

assert enc7(enc6(enc5(enc4(enc3(enc2(enc1(user_input))))))) == target

抛砖引玉

第七个函数比较小,当时一不小心反编译出来,就花了点时间写去混淆的 IDAPython 脚本。原理是基于模式匹配,识别出其中的 13 种无用代码的模式,再将它们 nop 掉。脚本在该函数上效果很好,运行后基本只剩下核心逻辑,代码如下(运行前要先创建加密函数,拿到函数的起始、结束地址):

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
import re

def nop(ea: int, size: int):
for i in range(size):
patch_byte(ea + i, 0x90)

def printable(hex_str: str):
return 32 <= int(hex_str, 16) < 127

def deobfus(start: int, end: int):
cur = start
while cur < end:
asm1 = generate_disasm_line(cur, 0)
asm2 = generate_disasm_line(cur+get_item_size(cur), 0)

m1 = re.match(r"mov +\[ebp\+var_(.+)\], *1000h", asm1)
m2 = re.match(r"lea +eax, *\[ebp\+var_(.+)\]", asm2)
if m1 and m2 and m1.group(1) == m2.group(1):
print(f"found pattern-1 at: {hex(cur)}")
asm3 = generate_disasm_line(cur + 0xd4 - 6, 0)
if re.match(r"mov +\[ebp\+var_(.+)\], *eax", asm3):
nop(cur, 0xd4)
cur += 0xd4
continue

m1 = re.match(r"mov +\[ebp\+var_(.+)\], *(.{2})h", asm1)
m2 = re.match(r"mov +\[ebp\+var_(.+)\], *(.{2})h", asm2)
if m1 and m2 and int(m1.group(1), 16) - int(m2.group(1), 16) == 1 and printable(m1.group(2)) and printable(m2.group(2)):
if get_bytes(cur + 0x1d7 - 2, 2) == b'\xeb\xc7':
print(f"found pattern-2 at: {hex(cur)}")
nop(cur, 0x1d7)
cur += 0x1d7
continue
elif get_bytes(cur + 0x1bb - 2, 2) == b'\xeb\xc7':
print(f"found pattern-2 at: {hex(cur)}")
nop(cur, 0x1bb)
cur += 0x1bb
continue
elif get_bytes(cur + 0x12d - 5, 5) == b'\xE9\x5A\xFF\xFF\xFF':
print(f"found pattern-11 at: {hex(cur)}")
nop(cur, 0x12d)
cur += 0x12d
continue

m1 = re.match(r"and +\[ebp\+var_(.+)\], *0", asm1)
m2 = re.match(r"lea +eax, *\[ebp\+var_(.+)\]", asm2)
if m1 and m2 and m1.group(1) == m2.group(1):
asm3 = generate_disasm_line(cur + 0xb5 - 7, 0)
if re.match(r"or +\[ebp\+var_(.+)\], *0FFFFFFFFh", asm3):
print(f"found pattern-3 at: {hex(cur)}")
nop(cur, 0xb5)
cur += 0xb5
continue

m1 = re.match(r"and +\[ebp\+var_.+\], *0", asm1)
m2 = re.match(r"and +\[ebp\+var_.+\], *0", asm2)
asm3 = generate_disasm_line(cur + 0x73, 0)
m3 = re.match(r"cmp +\[ebp\+var_.+\], *0Ah", asm3)
if m1 and m2 and m3:
if get_bytes(cur + 0x15c - 5, 5) == b"\xE9\x0A\xFF\xFF\xFF":
print(f"found pattern-4 at: {hex(cur)}")
nop(cur, 0x15c)
cur += 0x15c
continue

m1 = re.match(r"and +\[ebp\+var_.+\], *0", asm1)
m2 = re.match(r"mov +\[ebp\+var_.+\], *1", asm2)
asm3 = generate_disasm_line(cur + 0x90, 0)
m3 = re.match(r"cmp +\[ebp\+var_.+\], *5", asm3)
if m1 and m2 and m3:
if get_bytes(cur + 0x192 - 5, 5) == b"\xE9\xF1\xFE\xFF\xFF":
print(f"found pattern-5 at: {hex(cur)}")
nop(cur, 0x192)
cur += 0x192
continue

if m1 and m2 and get_bytes(cur + 0x98, 2) == b"\x72\xC1":
asm3 = generate_disasm_line(cur + 0xb5 - 0xa, 0)
if re.match(r"mov +\[ebp\+var_.+\], *1", asm3):
print(f"found pattern-12 at: {hex(cur)}")
nop(cur, 0xb5)
cur += 0xb5
continue

m1 = re.match(r"mov +\[ebp\+var_.+\], *0Ah", asm1)
m2 = re.match(r"mov +\[ebp\+var_.+\], *2", asm2)
asm3 = generate_disasm_line(cur + 0x7a, 0)
m3 = re.match(r"cmp +\[ebp\+var_.+\], *9", asm3)
if m1 and m2 and m3:
if get_bytes(cur + 0x109 - 5, 5) == b"\xE9\x64\xFF\xFF\xFF":
print(f"found pattern-6 at: {hex(cur)}")
nop(cur, 0x109)
cur += 0x109
continue

m1 = re.match(r"mov +\[ebp\+var_.+\], *1", asm1)
m2 = re.match(r"mov +\[ebp\+var_.+\], *3", asm2)
asm3 = generate_disasm_line(cur + 0x92, 0)
if m1 and m2 and "jg" in asm3:
if get_bytes(cur + 0x14f - 5, 5) == b"\xE9\x37\xFF\xFF\xFF":
print(f"found pattern-7 at: {hex(cur)}")
nop(cur, 0x14f)
cur += 0x14f
continue

m1 = re.match(r"mov +\[ebp\+var_.+\], *1", asm1)
m2 = re.match(r"mov +\[ebp\+var_.+\], *5", asm2)
asm3 = generate_disasm_line(cur + 0xc8, 0)
m3 = re.match(r"mov +\[ebp\+var_.+\], *2", asm3)
if m1 and m2 and m3:
if get_bytes(cur + 0x198 - 5, 5) == b"\xE9\xFE\xFE\xFF\xFF":
print(f"found pattern-8 at: {hex(cur)}")
nop(cur, 0x198)
cur += 0x198
continue

m1 = re.match(r"mov +\[ebp\+var_.+\], *20h", asm1)
m2 = re.match(r"mov +\[ebp\+var_.+\], *6Dh", asm2)
asm3 = generate_disasm_line(cur + 0x5c, 0)
m3 = re.match(r"cmp +\[ebp\+var_.+\], *3", asm3)
if m1 and m2 and m3:
if get_bytes(cur + 0xf7 - 5, 5) == b"\xE9\x58\xFF\xFF\xFF":
print(f"found pattern-9 at: {hex(cur)}")
nop(cur, 0xf7)
cur += 0xf7
continue

m1 = re.match(r"and +\[ebp\+var_.+\], *0", asm1)
if m1 and get_bytes(cur + 0xe, 2) == b"\xEB\x0D" and get_bytes(cur + 0xc6, 5) == b"\xB9\x00\x04\x00\x00":
asm3 = generate_disasm_line(cur + 0x136 - 6, 0)
if re.match(r"mov +\[ebp\+var_.+\], *eax", asm3):
print(f"found pattern-10 at: {hex(cur)}")
nop(cur, 0x136)
cur += 0x136
continue

m1 = re.match(r"mov +\[ebp\+var_.+\], *3", asm1)
m2 = re.match(r"mov +\[ebp\+var_.+\], *2", asm2)
if m1 and m2 and get_bytes(cur + 0x4c, 2) == b'\x73\x34':
if get_bytes(cur + 0x82 - 2, 2) == b"\xEB\xBE":
print(f"found pattern-13 at: {hex(cur)}")
nop(cur, 0x82)
cur += 0x82
continue

cur += get_item_size(cur)

start_ea = 0x028ADA1C
end_ea = 0x028E5FC1

deobfus(start_ea, end_ea)
del_items(start_ea, 0, end_ea)
create_insn(start_ea)
add_func(start_ea)

不过在其他 6 个函数上用的效果不是很好,因为其他函数中还有许多 call 无用 API 的模式没有考虑。它们肯定都可以通过模式识别进行定位并去除,考虑到未知的工作量,赛中就没有再写下去了。脚本放在这里,希望能起到抛砖引玉的作用。

柳暗花明

将每个加密函数的逆函数写出来再逐个调用就得到正确的序列号了,代码如下:

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
def dec1(cipher: bytes) -> bytes:
plain = list(cipher)
tbl = bytes.fromhex("b466efcad9ebb6423614b123b5abd400b0bb96e430a87e5e872daa0147a03dd2dae185f5ff0cfdad07aff2c8739446fce77f1570baf3085f0aa38d1fe6056dc44d31881799c6b26b831c80db6927fac22eec4b2f62a4d339bc6a4a8633bf929144b9cdacf6976ce9906554747609e249f0f9a2139a1632f4823f6f290b5a22b39c4e68d0c1e041ae6428d5048f9f78cc1d180d675be5487b19edd7dd55590e258e2c4012601e10bdc571f851c721c01b45e83ba1f76e2b8cb7d60fde35892a1a7d95d1723ca53411b8525c75ee9bf1fba96179c9203ec337817ccb5798dcbe243a586302d8ea4f43849d064c9efee3a7a68a0356938b7ace385326cfdf775d50")

pp = b"0123456789ABCDEF" * 8
ee = bytes.fromhex("3031157034f3365f3839418843994546307f32703435365f38394142439945b23031323334f3363738a34188434445b23031323334f336373839418543994546303132703435365f38a34142434445b2303132703435363738a3414243444546307f323334f3365f3839418843994546307f32333435363738394142434445b2")
idxs = []
for i in range(len(ee)):
if ee[i] != pp[i]:
idxs.append(i)

cc = list(pp)
for idx in idxs:
cc[idx] = tbl[pp[idx]]

rr = bytes.fromhex("0b3a177e1fc61b4a70304b8958ad0560115632373b7020731c1a5b55059e658c232e150c31cf3507288173885e5c76ba2200731d2ab0130124117b8501a47d0f3c250b7470021c4617a5434146434ebf2122256d2b101f1c17967a7f00030c096326535653985b2e47bac803d60ed8e597d281868bf4f3f0ebe6a2a7aaabb449")
for i in range(len(cc)):
plain[i] ^= cc[i] ^ rr[i]

for idx in idxs:
plain[idx] = tbl.index(plain[idx])

return bytes(plain)

def dec2(cipher: bytes) -> bytes:
plain = list(cipher)
tbl = bytes.fromhex("38393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637716e66655e61696b5d64676f5c606a7063685b6d5f626c8788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff72737475767778797a7b7c7d7e7f80818283848586")

rr = bytes.fromhex("f6b0c1702bb6869da6bcba77bde9b92f51db99a5efa3a6d4b8b2bcc0c781342b14dd80ccd0a3e8c0e7c0f79a198205f8b46e99fe90e4ca51afb5e498ca74df8488615077606bf6758083411c1369ac087036514d500731f9b5a9b3efeee95eda7ddf9a71a7c3a6d1deddac03f60de8e65f8c918c97970fd1e3dea2d5db962af9")
cc = list(bytes.fromhex("680567cabc021234390d1ed230593e9cc250360d412f2e553e2e386c711999bb8f646b9c500264121a186ac43ec4222d93415f434746dd02370b7402256ad50455bf8f97818915916565a6f4fad443f8868aefa6a5fcf10159470c16141db429977f1142703e7e7940727203b2288f6a2ff41971056669f0183c5c4013ae0458"))
for i in range(0x20):
cc[0x20+i], cc[0x60+i] = cc[0x60+i], cc[0x20+i]

for i in range(len(plain)):
plain[i] ^= cc[i] ^ rr[i]
for i in range(0x20):
plain[0x20+i], plain[0x60+i] = plain[0x60+i], plain[0x20+i]

rr = bytes.fromhex("680567cabc021234390d1ed230593e9cc250360d412f2e553e2e386c711999bb8f646b9c500264121a186ac43ec4222d93415f434746dd02370b7402256ad50455bf8f97818915916565a6f4fad443f8868aefa6a5fcf10159470c16141db429977f1142703e7e7940727203b2288f6a2ff41971056669f0183c5c4013ae0458")
pp = bytes.fromhex("5a69aef84e591d6a7a3a3ef22d6b781f40e463b16a23265316102e227058189d727d445b60503301225206f32b2f0bab7353224a7b2f15072e1b0efe746200706d765af221511a661d763636333033ae707174eb7a43191a1d450f08757071763294020102075d0e4db0bd78a3c8a59ac660d0d1daa7f5f6e1ecd7d0dfd8c958")
cc = list(pp)

for i in range(len(plain)):
cc[i] = tbl[pp[i]]
for i in range(0x40):
cc[i], cc[0x80-i-1] = cc[0x80-i-1], cc[i]
for i in range(0x20):
cc[i], cc[0x40+i] = cc[0x40+i], cc[i]

for i in range(len(cc)):
plain[i] ^= cc[i] ^ rr[i]

for i in range(0x20):
plain[i], plain[0x40+i] = plain[0x40+i], plain[i]
for i in range(0x40):
plain[i], plain[0x80-i-1] = plain[0x80-i-1], plain[i]

for i in range(len(plain)):
plain[i] = tbl.index(plain[i])

return bytes(plain)

def dec3(cipher: bytes) -> bytes:
plain = list(cipher)
tbl = bytes.fromhex("c4189868c047179a23f21a2f75ffd837eb1c7d1861cdf1b74914c18590e1616d7bd4f82307d23443325c3c1aeddff337e4ae04e36d2506bd4ea1f351bd3f8ca1974465b774ac094bdb56552fe61bfc579daa9b47710921084a979b12601de096438c39b57b9bfe9e0e196ceb9a9632a14c5c7e3291bd9017d0db023a8f002eb2")

for i in range(len(plain)):
plain[i] = (plain[i] - 0x80) & 0xFF

for i in range(0x40):
plain[i], plain[0x80-i-1] = plain[0x80-i-1], plain[i]

ret = [0] * 128
for i in range(len(tbl)):
ret[(0x75 + i) % 128] = plain[i] ^ tbl[i]

for i in range(0, len(ret), 2):
ret[i], ret[i+1] = ret[i+1], ret[i]

return bytes(ret)

def dec4(cipher: bytes) -> bytes:
plain = list(cipher)

pp1 = bytes.fromhex("a53f0c50366abda69abc3ecf6f580d6be2286767b4b24360f7cdf44e9c88f5931131d090a4b71fd9e1e0957144ed5f7dbc2ceceece0a5291bf6db45b822f9587c5954189bf76d9d7bf713f0aa36ceeb414f393794788f7754208923f11de80144e78c441def8bb74decb3a47247b014001990f031fb08b788c3512a3361749d3")
cc1 = bytes.fromhex("a642115b4c77f1b5fdba36cb428a409f17f2309fdea03474e6d0f7519f8bf8961434d393a7ba22dce4e3987447f05876b525e5e7c7034b8ab866ad547b289082c0903c84ba71d4d2ba6c3a059e67f5bb1bfa9a804e8ffe7c490f994618e587144e78c441def8bb74decb3a472483094809a1170b27b89380943d1aab3e0c3ec8")
dis = []
for i in range(len(pp1)):
dis.append((cc1[i] - pp1[i]) & 0xFF)

for i in range(len(plain)):
plain[i] -= dis[i]
plain[i] &= 0xFF

return bytes(plain)

def dec5(cipher: bytes) -> bytes:
plain = list(cipher)
tbl = bytes.fromhex("d5a04a5de8a503a8dd2d980a4d8f901234023d5545b73f2cbabc07c70186df532961253e09217b85b60895300bb09e6bdbf9af51844950d210ebac2b48c56a74cdfd1d314bd6f3b381367e9c582f4c6c0513635a419b387c6519d047c4927a6fefb15783271a71d8fa2ae142a28ceaff166400561e374328ab06c9c3d3a614aeb5e6969a9d5f2615f223a7a34e8772ec208059a440f70ce7bb33ede2ee0f8eb20e3c916824754660a17932b8738b76d11fda8d22f00dc011c2e0665ed79ffe88dc7d8aca4404d417c61b94f654694f6de5be3b6ecbf18278f4def5bd2ece67c835ad7752c17fb99339f8e418bfd9cfb489cc5c1cfcaa3a7062fb5be3a99997e9")

pp = bytes.fromhex("9620b6c3418b71c2a79c5d17937f210d436589b31ebb52c12e256660a46e84450c62a3b21370e5b91e840e67b2db5c2cb2cda71df65066990dcc285847021d46dc20489d6664cb3b66a2aca58ed8aa5ec75bedb54cec97d3361233f3ba7f15454cab44fd67625e1e67f6acb3099a2d812d3c2c0a85c2a4b540c507b86a4d6ac6")
cc = bytes.fromhex("1e76d27f22bf0ba060c447584fa9b895954c4a32bdc046d036cd250181466efc2f002f48486ec74947015bcc5249878f274d2cfb1ab6a9b9a362ca9cff8f586ac125846ea7c2acf7c225d7662ca5b39e5387ca66b8687d953a4d09b09436291f92e9a38ace7ce6cdd1ba7464e488dc9118143ec808ec822e4438538100d068b3")
for i in range(len(cipher)):
plain[i] ^= pp[i] ^ cc[i]

for i in range(0x80):
plain[i] = tbl.index(plain[i])

ret = []
for i in range(0x30, -1, -0x10):
for j in range(0xF, -1, -1):
ret.append(plain[i+j])
ret += plain[0x40:]
return bytes(ret)

def dec6(plain: bytes) -> bytes:
p = bytes.fromhex("cf00fa1ac6497c69840b0c74e6d8c2ad50e23957b7b581e678aac75be30e61d895f5d38858be754f4dcf567f9f1cf0cc89fed6aee83cf43e20c8c8fadbc6f2cbd16eca9559b795199d171848be19fa92a30f49b7dc3ec2f54599d28e29a38546bc459e3b969792d0fb06532ac838a4c8fd72055c12d398bbd1f5ad497a5a831b")
c = bytes.fromhex("5a125abad541706a858bf87f4354c5ab409259b7e73511f6589a87ab23de41f885c5d2855fbf7a4d44df76ef7fec30fc005fe6ee885cb41e60e8880a1b16d2ebe11efa65eabc97298c061940be6770a8132f0907acaedbc505dbf2e563b7ad50eec6ee66e6f7b240eb0603bad868f498ed52956c62f3a8cbe1863b497658ae6b")
tbl = [p[i] ^ c[i] for i in range(len(p))]

return bytes([tbl[i] ^ plain[i] for i in range(len(plain))])

def dec7(cipher: bytes) -> bytes:
tbl1 = bytes.fromhex("588d51e8d4d9a5c53094c6bab325c238a4c8aaf74e2e603b3ab2a975a12316fb64e42478322631434bbcb8ac639b7e871b42b59e612a866e4cd2355ad6e9b91db77fa8537bd7de799a745f0bf001ade24a3d98651e6cd8dbabe5442221cb507615565c2972896d418c8e19e1919df2063f1a7d978a8392dc77f936cf48a0c78214e3963984b188284611afa352fe10f457f1e0ca547ca27049a72db6d090da7a67c3bd1cf6f52b9308691359624feaced3eceb5ed10e04204d0f8bbbc9bf03059ccc40dfef853e3cf3ffd50a272fee8f6a1ffd955d817355dde75b6f18c4ae450d689fc012edcd0c33f8fc2c003747bee634a666b0b49971806b07fac1170209")
tbl2 = bytes.fromhex("35b3a22a08d483add97fb6c277933814aa1341488d79eca0f6529df8a64d30fa6124b557658ef9f0d1c3c41acd252f676aa44c17686682c7cc6343e987dd8cb2ea3f7cfc6d115db8b1bd562cc186b044056236a8399427ae7b80df73e5e233e3d3cba7859b9991d0006921e66e9806372b8bf42e95a3cf0c7119530410c96b3aee1b88e12397d6f2d54fe4be4270ffe8de5c7db9f126a9843bced2bcc6d83d501c81db2d6045493e587692c512fb9c54ba070290ef477e75af18bb51f5209a9631349f599e46bfa1ca89014a5a1f1e5bf78fb7d7fe5ee01528e7dcda725f55030d64744b29c04e78ab40226f1d09f3a516c8fd6cac8a0fed0a3c327a0b0eebb4")
tbl3 = bytes.fromhex("7f9458886cfd2dafe88538c7287bebeeb925894fd2053a0cde8ffccb65ad45c959e12b2f4372c381db8eab010677075e74bc03628c543d3718e279840a307e2e83f95746d15b1a1f116820a76495710b5627a350bbc102e035b582d333ca4a0eef26f413b786acc83ed8759d233b934ca15ab6a0048b8a551922be61e69690c099c69ed7603241f087e9aa175f7c4b1b0d40ffb34269e410faa9d62cf5edbad9480f4ef378d014f7a66eaea870669a52801d34366b5da4ecdf0891ce9c4992a221cc2a6df8bd514d6ae55ce767b0f2f12412989b29b4b197fbc453cdda44e3dd1e6f31dc3cc576ead5f673c28d3f1ccf477aa50039fe7db215d4b863bf9f1609")
tbl4 = bytes.fromhex("4f8a646ddee94796105c220be55999d3319533ff1821ab3d2d78291e7758844db99ff851357d2b666ec2b15e2cdc5fd742ae98371fe2439cd94bcadfec9761398334676345366270243f877e467ca203d47394a41da6bb1add8cebd6d8be93feb419a832002a0a11c94ced28f3fc8ea0a1f675c0ce0754fbb512ea914e9dc7746014ac8f385ac152501c539bf1c4e36a4a41b2e8e19071b3cc0c57f9d2ada523bd7615effdbcf5af85dae068f4c8fa69d5256b273b891655db9ee448b69aee80f7170406b0921bcfbf6f86727bf05db713ba050f01e6aa2fc35b7a026ccda32ea9c53c568882e78d3e8120cb0d79f244653a260830a7b84940d07fc6d18b0e09")
tbl5 = b'\x1d\xbcs,\xdb\x81\xe3\x04\x06U\x8dF\xfcQ\x83\x00G\x14v-C\xf8\xc6\x10l\x11\x1c\xc0\x96V\x99\xbd\xd7\xeb\xa8\t\xc4=\x0ci\xea\xfd.j\xbf\x947!\x0fo(\x1f\xa7\x1bg6\x13\xd9\xff\xe8d0?\xf4q+\x8c\\p\'m@2\xa0z#\xcct\x871\xc1DN\x80&c\x07\x12W\x91<`h|w\xb7\x84\xae\x9c\xc9\xc2\xd0\xc3\x97\xf1B%y$\xc8\x02\xca/\xe4\xf0Or\x92\x0b\xfb\xa3n\xe7\x1eT\xc7\xa4J\xe0\xd5\x86\xb1\xf7\x8bY\xd2\x89\xf5\x08E\n:\x93\xd4\xd6}\xf9{\x85\x9d\xfe\xd3"L;~\xbe\xab\xb0\xb9\xa9\x88\xa6\xda\xbb\xf6\x1a\x05\xde\x18>\x8e\xb4\xfa\xba\xb3\xb8\xb6k\x0eK5\xcb\xec\xdc\x17\x8f \xf2*eb\x82\xf3\xa2\xcf^\xb5\xe6\xce\x9ff\xe9PR\xef[\x9e\xdd\x8a\xe2Iau\xed8\xd8\xd1\xe1)\x15\x98ZS\x01\xa5M\xcd\xc5\x90]\r9\x7f4\xdf_\x16\x03\xee\xa1\xad\x9b\xaf\xb23X\xe5\x19HA\xaa\x95\xac\x9ax'
tbl6 = b'V\xd6x\xdc\x8fs\xcf\x8e\x0b\x14\xaa\xfa?=\xc0\xf1)\xde\x8b:t\xb1n\x8cH\xc5\xc4R\xdd.\xd76yD\x7f\xe1\xc1\x99\xd4\x9a\xd8h\xa52WC\x17\xa2\xdf\x87\x82\xf8\xe6\xe7\xb5\x004\xb2\xc9\rX\xaeK\xad\xc3\xc7\x88\x91\n\x051dz>Mb\x06m\xa4\xc6\xe3Bji\xa3\xcc\xb0\x92a\xbf\x8a!Q\x07\xa7-&A\x10\x08\x19\xe2\x1ckr\xca\xb4\x18\x94U\xdaJ\xf3Y^"\x0cE\xbe\xfbG@}\xd0]*\xf2\x0eO/\xcd\x90\x03\x97\xff<\x968\x15\x80\xec\x0f\xc2\x1b\xb3\xeb\x9b~I\xaf{g\x9d$\x86\tf\xf7\\;\xd3\xfd\x04o\x1f\xd9\xb8\xa8ev\x8d\x95\xbb\xb9l\xb7\xee\x01\xf4 `\x1au\xf65\x89%\xbd\x11\x83,\xbcF\xd5\xcb\xab(w\'\xd2\xc8\xe0\xb6N\xd1\x98\xe9\xcep\xe4P+\xe8\xa1#\xa6\xfe\xedS\x9c\xa9\x127Z[\x81\xbaLT\xf9\x9e\xdb3\xea\x939\x1d\x02qc\x16\x85\x1e\x9f|\xac\xf0\xf5\x84\xef\xa00\xe5\x13_\xfc'
tbl7 = b' ;\xec\xbe\xb6\x87\x85\x9fS\xefW\xe0\xc3\x01\xf8:\xe7AKr)\x0bl\x04y\xfc\x18\xb4g\xce\xf0\x0eZ4\x9e\xf6\x94\xacXo\xf3\xc42\x8aU\x9c\x07\xb0e\xe3\x9bRQ\xc7\xddc\xa9\x12\x0f\x95\x80Y\x1a\xcf{0\xa8\xab1/a\x16\x1cMi"p\xcbu<\xd3t\x91\xf1O\xa5\xe2\xfd\xbd\x11m6+\xaa\xd5\x90\xe4\x8d\xda\xb8\xa2\xaf\x1d\x98\x1f\x88*I\x82x\x99\xebGf\xca?\xa6J&\xdfD\x7f\x13\xa4\xb1\x02z\xc9\x9d\x00\xd6^\xbaC\xc2\x03\xff(5\xbcdq\xdc\xa3[\xd4\xee\x83\xf7F-k}\xaeb\xd9\xa7,\xc8\r\xa1\x8ej\xe8`n\xb2@%h\xf4\xb7\xb3w\xc18\xc0\xd8\xd1\xfe\xb9\'\xbfE]=\x19\xf5\xcc9\xde\x81\x9a3\xc5s\x15\\P\x89N\xa0!$\xdb\xbbV\x17_\xad\xf2\x8c\x8f\x96\x97\xe5T\x86\t\x1bB\x10H\xb5\xf9~|\x05\xd7>\x067\xe6\x93\xfa\xe9v\n\x1e\xed\x0c.\xcd\xfb\x08\xea\x84\x92\xd0\xc6L\xe1#\xd2\x14\x8b'

plain = list(cipher)
tbls = [tbl1, tbl2, tbl3, tbl4, tbl5, tbl6, tbl7]
for tbl in tbls[::-1]:
for i in range(len(plain)):
plain[i] = tbl.index(plain[i])
return bytes(plain)


target = bytes([121, 23, 66, 107, 59, 80, 122, 227, 70, 208, 222, 78, 36, 167, 138, 106, 105, 208, 2, 6, 240, 39, 36, 189, 192, 187, 227, 30, 9, 163, 151, 48, 60, 182, 235, 104, 144, 9, 208, 234, 17, 242, 196, 96, 165, 203, 195, 252, 69, 251, 92, 83, 192, 128, 58, 153, 89, 111, 47, 84, 74, 217, 14, 106, 52, 222, 210, 236, 175, 74, 11, 164, 138, 182, 250, 147, 31, 4, 16, 68, 238, 228, 214, 158, 244, 69, 18, 77, 55, 60, 240, 17, 248, 200, 68, 118, 99, 16, 115, 45, 104, 215, 157, 47, 195, 132, 42, 182, 204, 181, 55, 97, 79, 15, 22, 188, 187, 140, 83, 39, 238, 119, 170, 31, 156, 194, 24, 222])

decs = [dec1, dec2, dec3, dec4, dec5, dec6, dec7]
for func in decs[::-1]:
target = func(target)
print(target.decode())

得到一串神奇 flag:

1
:*D#O+_I3;`}0NfP-=2/+Y"_>A8S6]L|4G;UHiA5mnol^k;#OhW2!UEJf0"7?Dt5m{CqE*AZr~$1(zW@TBXKL&2r?+3kwxC90O'%&PyVo~)'Q%Z@6b}REKF[cgFe/-?I

PS. 本题解同样发表在看雪论坛,并有幸登上看雪公众号