2024春秋杯网络安全联赛冬季赛

upx2023

upx壳,原版upx -d脱不了,x64dbg手动调试脱壳然后scylla dump

F5直接得到主函数逻辑

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
char *v4; // rax
int v6[44]; // [rsp+20h] [rbp-60h] BYREF
__int64 v7[2]; // [rsp+D0h] [rbp+50h] BYREF
__int64 v8[2]; // [rsp+E0h] [rbp+60h] BYREF
__int64 v9[2]; // [rsp+F0h] [rbp+70h] BYREF
int v10; // [rsp+104h] [rbp+84h]
unsigned int Seed; // [rsp+108h] [rbp+88h]
int i; // [rsp+10Ch] [rbp+8Ch]

sub_40AAA0(argc, argv, envp);
Seed = time64(0i64);
srand(Seed);
sub_4479D0(v7);
cout(&qword_47E800, aInputYourFlag);
cin(&qword_47E4A0, v7);
sub_4478C0(v9, v7);
enc((__int64)v8, (__int64)v9);
sub_447E00((__int64)v7, (__int64)v8);
sub_447D50(v8);
sub_447D50(v9);
if ( str_len(v7) != 42 )
{
v3 = cout(&qword_47E800, aLenError);
((void (__fastcall *)(__int64))sub_464AC0)(v3);
exit(0);
}
qmemcpy(v6, dword_46A020, 0xA8ui64);
for ( i = 0; i <= 41; ++i )
{
v10 = rand() % 255;
v4 = (char *)str_index(v7, i);
if ( (v10 ^ *v4) != v6[i] )
exit(0);
}
sub_447D50(v7);
return 0;
}

enc对输入flag做加密,最后与随机数异或

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
__int64 *__fastcall enc(__int64 *a1, __int64 a2)
{
v7[5] = (__int64)v6;
v13 = 3;
nullsub_2(&v6[95]);
sub_447820(a1, &qword_47F000, &v8);
nullsub_3(&v8);
len = str_len(a2);
v11 = len - 1i64;
v7[1] = 0i64;
v2 = len;
v10 = v13 - 1i64;
v7[0] = v11;
v7[2] = len;
v7[3] = 0i64;
v3 = alloca(sub_40B290(((unsigned __int64)v13 * (unsigned __int128)(unsigned __int64)len) >> 64));
v9 = v7;
for ( i = 0; i < v13; ++i )
{
for ( j = 0; j < len; ++j )
*((_BYTE *)v9 + j + v2 * i) = 10;
}
v18 = 0;
v17 = 0;
for ( k = 0; k < len; ++k )
{
if ( !v18 || v13 - 1 == v18 )
v17 ^= 1u;
v4 = (_BYTE *)str_index(a2, k);
*((_BYTE *)v9 + k + v2 * v18) = *v4;
if ( v17 )
++v18;
else
--v18;
}
for ( m = 0; m < v13; ++m )
{
for ( n = 0; n < len; ++n )
{
if ( *((_BYTE *)v9 + n + v2 * m) != 10 )
sub_447F10(a1, *((_BYTE *)v9 + n + v2 * m));
}
}
return a1;
}

enc看起来比较麻烦,实际上是置换操作,将输入打乱,让输入的42个字符两两不同,把置换规则跑出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
inp = [i for i in range(42)]
l = len(inp)

ls = [0xff for _ in range(3*l)]

v18 = 0
v17 = 0
for i in range(l):
if ((v18 == 0) or (v18 == 2)):
v17 ^= 1
ls[i + l * v18] = inp[i]
if (v17):
v18 += 1
else:
v18 -= 1

while (0xff in ls):
ls.remove(0xff)

print(ls)
# [0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 2, 6, 10, 14, 18, 22, 26, 30, 34, 38]

随机数种子爆破

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
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <stdint.h>

uint8_t ans[] = {0x09, 0x63, 0xD9, 0xF6, 0x58, 0xDD, 0x3F, 0x4C, 0x0F, 0x0B, 0x98, 0xC6, 0x65, 0x21, 0x41, 0xED, 0xC4, 0x0B, 0x3A, 0x7B, 0xE5, 0x75, 0x5D, 0xA9, 0x31, 0x41, 0xD7, 0x52, 0x6C, 0x0A, 0xFA, 0xFD, 0xFA, 0x84, 0xDB, 0x89, 0xCD, 0x7E, 0x27, 0x85, 0x13, 0x08};

uint8_t ls[] = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 2, 6, 10, 14, 18, 22, 26, 30, 34, 38};


int main()
{
// time_t time_stamp = 0x647AA836;
time_t time_stamp = 0x64000000;
// i = 0x4dc62a
for (time_t i = 0; i < 0x1000000; i++)
{
srand(time_stamp + i);
uint8_t tmp_flag[43];
tmp_flag[42] = 0;
for (int i = 0; i < 42; i++)
{
tmp_flag[ls[i]] = ans[i] ^ (rand() % 0xff);
}
puts(tmp_flag);
}
return 0;
}

拿到flag

file_encryptor

F5发现有问题

故意触发异常

调试发现还没运行到main函数就挂了

看导入表有导入函数IsDebuggerPresent,交叉引用

TlsCallback_0里面有反调试

把jz patch成jmp

调试发现TlsCallback_0里面也有出发异常,pass之后跳到了loc_C41916

直接在0x00C418ED用jmp跳到loc_C41916

再运行main处触发异常,pass之后跳到loc_C619CC

patch一下

patch之后就能无痛F5了

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v3; // zf
void *Src; // [esp+24h] [ebp-34h]
HGLOBAL hResData; // [esp+28h] [ebp-30h]
HMODULE hModule; // [esp+2Ch] [ebp-2Ch]
HRSRC hResInfo; // [esp+30h] [ebp-28h]
DWORD Size; // [esp+34h] [ebp-24h]
DWORD i; // [esp+38h] [ebp-20h]
void *v11; // [esp+3Ch] [ebp-1Ch]

hModule = GetModuleHandleW(0);
hResInfo = FindResourceW(hModule, (LPCWSTR)0x65, L"DATA");
sub_1112140();
MEMORY[0x1E99782]();
if ( !v3 )
{
Size = SizeofResource(hModule, hResInfo);
hResData = LoadResource(hModule, hResInfo);
v11 = (void *)sub_1112156(Size);
if ( hResData )
{
Src = LockResource(hResData);
if ( Src )
{
memcpy(v11, Src, Size);
for ( i = 0; i < Size; ++i )
*((_BYTE *)v11 + i) ^= 0x33u;
dword_111541C = sub_1111CE0(v11, Size);
}
FreeResource(hResData);
}
j_j_free(v11);
}
((void (*)(void))loc_1111320)();
sub_1112000();
system("pause");
return 0;
}

仔细看有怪东西

1
2
3
4
5
6
7
8
9
10
11
  sub_1112140();
MEMORY[0x1E99782]();

void sub_1112140()
{
char *retaddr; // [esp+0h] [ebp+0h]

++retaddr;
}

((void (*)(void))loc_1221320)();

主函数和loc_1221320里有花指令,nop掉,再F5就正常了

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
void *Src; // [esp+24h] [ebp-34h]
HGLOBAL hResData; // [esp+28h] [ebp-30h]
HMODULE hModule; // [esp+2Ch] [ebp-2Ch]
HRSRC hResInfo; // [esp+30h] [ebp-28h]
DWORD Size; // [esp+34h] [ebp-24h]
DWORD i; // [esp+38h] [ebp-20h]
void *v10; // [esp+3Ch] [ebp-1Ch]

hModule = GetModuleHandleW(0);
hResInfo = FindResourceW(hModule, (LPCWSTR)0x65, L"DATA");
if ( hResInfo )
{
Size = SizeofResource(hModule, hResInfo);
hResData = LoadResource(hModule, hResInfo);
v10 = (void *)sub_1222156(Size);
if ( hResData )
{
Src = LockResource(hResData);
if ( Src )
{
memcpy(v10, Src, Size);
for ( i = 0; i < Size; ++i )
*((_BYTE *)v10 + i) ^= 0x33u;
dword_122541C = sub_1221CE0(v10, Size);
}
FreeResource(hResData);
}
j_j_free(v10);
}
((void (*)(void))sub_1221320)();
sub_1222000();
system("pause");
return 0;
}

从资源节加载名字叫DATA的资源文件,然后异或0x33解密,调试然后dump下来,PE文件,看导入表可以看出来是加解密相关的dll

sub_1221CE0应该是加载该dll的函数,运行完进程里多了cryptsp.dll,应该就是导入的dll

继续往后分析,sub_1221320

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int sub_1221320()
{
if ( !sub_1221050() )
exit(0);
sub_1221000(&byte_122501C, &unk_1225018, strlen(&byte_122501C));
return sub_1221210(&unk_1225414, &unk_1225418, &byte_122501C, strlen(&byte_122501C));
}


int sub_1221050()
{
DWORD VolumeSerialNumber; // [esp+4h] [ebp-8h] BYREF

VolumeSerialNumber = 0;
GetVolumeInformationA(0, 0, 0, &VolumeSerialNumber, 0, 0, 0, 0);
if ( VolumeSerialNumber == 2108366133 )
return 2108366133;
else
return 0;
}

里面检测磁盘序号,nop掉

sub_1221000里面对字符串做操作,估计是密钥

sub_1221210比较怪

1
2
3
v7 = (int (__stdcall *)(_DWORD *, _DWORD, _DWORD, int, int))sub_1221BB0(dword_122541C, (LPCSTR)2);
v6 = (int (__stdcall *)(_DWORD, int, char *, _DWORD))sub_1221BB0(dword_122541C, (LPCSTR)0x1E);
v5 = (void (__stdcall *)(_DWORD, _DWORD))sub_1221BB0(dword_122541C, (LPCSTR)0x1C);

v7的内容

刚好是cryptsp.dll里的第2个导出函数CryptAcquireContextWsub_F71BB0应该是根据ordinal获取函数地址

把其他几个函数获取一下

1
2
3
CryptAcquireContextW = (int (__stdcall *)(int *, _DWORD, _DWORD, int, int))sub_971BB0(dword_97541C, (LPCSTR)2);
CryptSetKeyParam = (int (__stdcall *)(_DWORD, int, char *, _DWORD))sub_971BB0(dword_97541C, (LPCSTR)0x1E);
CryptReleaseContext = (void (__stdcall *)(_DWORD, _DWORD))sub_971BB0(dword_97541C, (LPCSTR)0x1C);

调用这几个函数应该是初始化密钥

接着分析sub_972000

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
void sub_F72000()
{
HANDLE hFindFile; // [esp+0h] [ebp-874h]
PWSTR ppszPath; // [esp+4h] [ebp-870h] BYREF
struct _WIN32_FIND_DATAW FindFileData; // [esp+8h] [ebp-86Ch] BYREF
WCHAR v3[260]; // [esp+258h] [ebp-61Ch] BYREF
WCHAR FileName[260]; // [esp+460h] [ebp-414h] BYREF
WCHAR pszDest[260]; // [esp+668h] [ebp-20Ch] BYREF

if ( !SHGetKnownFolderPath(&rfid, 0, 0, &ppszPath) )
{
if ( PathCombineW(pszDest, ppszPath, L"document") )
{
if ( PathCombineW(FileName, pszDest, L"*.*") )
{
hFindFile = FindFirstFileW(FileName, &FindFileData);
if ( hFindFile != (HANDLE)-1 )
{
do
{
if ( FindFileData.cFileName[0] != 46 )
{
PathCombineW(v3, pszDest, FindFileData.cFileName);
sub_F717E0(FindFileData.cFileName, v3);
}
}
while ( FindNextFileW(hFindFile, &FindFileData) );
FindClose(hFindFile);
}
}
}
CoTaskMemFree(ppszPath);
}
}

懒得看win api了,直接调试看内容

遍历桌面上document文件夹里的所有文件,过滤.开头的文件

sub_F717E0是一些宽字符串转换

1
2
3
4
5
6
7
8
9
10
11
int __cdecl sub_F717E0(LPCWCH lpWideCharStr, LPCWSTR lpFileName)
{
int cbMultiByte; // [esp+Ch] [ebp-10h]
CHAR *lpMultiByteStr; // [esp+10h] [ebp-Ch]

cbMultiByte = WideCharToMultiByte(0xFDE9u, 0, lpWideCharStr, -1, 0, 0, 0, 0);
lpMultiByteStr = (CHAR *)sub_F72156(cbMultiByte);
WideCharToMultiByte(0xFDE9u, 0, lpWideCharStr, -1, lpMultiByteStr, cbMultiByte, 0, 0);
sub_F71640(&dword_F75414, &dword_F75418, lpMultiByteStr, strlen(lpMultiByteStr));
return sub_F713E0((int)&dword_F75414, (int)&dword_F75418, lpFileName);
}

sub_F71640是一些密码操作,没对文件进行操作,忽略掉

sub_F713E0里面有文件操作

1
2
3
4
5
6
7
8
9
10
11
12
13
if ( !ReadFile(hObject, lpBuffer, nNumberOfBytesToRead, &NumberOfBytesRead, 0) )
break;
FileSize -= NumberOfBytesRead;
if ( !CryptEncrypt(*a1, 0, FileSize == 0, 0, lpBuffer, &NumberOfBytesRead, dwBytes)
|| !WriteFile(hFile, lpBuffer, NumberOfBytesRead, &NumberOfBytesRead, 0) )
{
break;
}
if ( !FileSize )
{
v8 = 1;
break;
}

读文件,用密钥加密,再写回去

加解密密钥应该一样,直接把CryptEncrypt替换成CryptDecrypt(要nop掉一个参数)就能解密

flag{7sa963fa-91a6-4371-bl7b-225102y789a0}

coos

main函数输入,enc加密,末尾每8字节跟密文比较

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
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
__CheckForDebuggerJustMyCode(&unk_10E2016);
printf("%d\n", dword_10E05D0[0]);
vm_init1();
vm_init2();
v11 = 0;
j_memset(Str, 0, 0x64u);
xor_66(::Str);
printf(::Str, v4);
xor_66(::Str);
xor_66(aC);
scanf(aC, Str);
xor_66(aC);
if ( j_strlen(Str) != 32 )
exit(0);
v11 = Str;
v8 = 0;
memset(v9, 0, 28);
check(Str, &v8);
for ( i = 0; i < 4; ++i )
{
v6 = dword_10E0578[i];
if ( v9[2 * i - 1] != dword_10DE280[2 * v6] || v9[2 * i] != dword_10DE284[2 * v6] )
exit(0);
}
xor_66(&byte_10DF5C4);
printf(&byte_10DF5C4, v5);
xor_66(&byte_10DF5C4);
return 0;
}

密文的比较只有4次,手动运行dump一下

1
ans = [0x9c149c0fce40e599, 0xdf4c680dcd759c04, 0xbbafb056e52e3dc0, 0x8e299c86b8f527cb]

enc里面是虚拟机,先初始化虚拟机指令,将输入数据每8字节用虚拟机进行加密

vm_main是个比较简单的虚拟机

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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
int __cdecl vm_main(int instruction, int ins_len)
{
__CheckForDebuggerJustMyCode(&unk_10E2016);
pc = 0;
while ( 1 )
{
dword_10DFB70 = pc;
result = pc;
if ( pc >= ins_len )
return result;
switch ( *(instruction + 4 * pc) )
{
case 1:
movq(&reg0, reg1, SHIDWORD(reg1));
++pc;
break;
case 2:
movq(&reg0, reg2, SHIDWORD(reg2));
++pc;
break;
case 3:
movq(&reg0, reg3, SHIDWORD(reg3));
++pc;
break;
case 4:
movq(&reg1, reg0, SHIDWORD(reg0));
++pc;
break;
case 5:
movq(&reg1, reg2, SHIDWORD(reg2));
++pc;
break;
case 6:
movq(&reg1, reg3, SHIDWORD(reg3));
++pc;
break;
case 7:
movq(&reg2, reg0, SHIDWORD(reg0));
++pc;
break;
case 8:
movq(&reg2, reg1, SHIDWORD(reg1));
++pc;
break;
case 9:
movq(&reg2, reg3, SHIDWORD(reg3));
++pc;
break;
case 0xA:
movq(&reg3, reg0, SHIDWORD(reg0));
++pc;
break;
case 0xB:
movq(&reg3, reg1, SHIDWORD(reg1));
++pc;
break;
case 0xC:
movq(&reg3, reg2, SHIDWORD(reg2));
++pc;
break;
case 0xD: // loadiq 加载4字节
v3 = *(instruction + 4 * pc + 4);
movq(&reg3, v3, SHIDWORD(v3));
pc += 2;
break;
case 0xE: // nop
++pc;
break;
case 0xF: // nop
++pc;
break;
case 0x10:
pushq(reg1, SHIDWORD(reg1));
++pc;
break;
case 0x11:
shlq(&reg0, *(instruction + 4 * pc + 4));
pc += 2;
break;
case 0x12:
popq(&reg2);
++pc;
break;
case 0x13:
++pc;
break;
case 0x14:
shrq(&reg1, *(instruction + 4 * pc + 4));
pc += 2;
break;
case 0x15:
popq(&reg3);
++pc;
break;
case 0x16:
++pc;
break;
case 0x17:
++pc;
break;
case 0x18:
addq(&reg0, reg2, SHIDWORD(reg2));
++pc;
break;
case 0x19:
case 0x1C:
pushq(reg0, SHIDWORD(reg0));
++pc;
break;
case 0x1A:
case 0x1E:
shrq(&reg0, *(instruction + 4 * pc + 4));
pc += 2;
break;
case 0x1B:
++pc;
break;
case 0x1D:
++pc;
break;
case 0x1F:
++pc;
break;
case 0x20:
++pc;
break;
case 0x21:
++pc;
break;
case 0x22:
nop();
++pc;
break;
case 0x23:
popq(&reg1);
++pc;
break;
case 0x24:
addq(&reg1, reg0, SHIDWORD(reg0));
++pc;
break;
case 0x25:
popq(&reg0);
++pc;
break;
case 0x26:
v4 = *(instruction + 4 * pc + 4);
xorq(&reg0, v4, SHIDWORD(v4));
pc += 2;
break;
case 0x27:
pushq(reg2, SHIDWORD(reg2));
++pc;
break;
case 0x28:
xorq(&reg0, reg1, SHIDWORD(reg1));
++pc;
break;
case 0x29:
v5 = *(instruction + 4 * pc + 4);
xorq(&reg1, v5, SHIDWORD(v5));
pc += 2;
break;
case 0x2A:
addq(&reg1, reg2, SHIDWORD(reg2));
++pc;
break;
case 0x2B:
xorq(&reg1, reg2, SHIDWORD(reg2));
++pc;
break;
case 0x2C:
pc += 2;
break;
case 0x2D:
addq(&reg0, reg1, SHIDWORD(reg1));
++pc;
break;
case 0x2F:
pc += 2;
break;
case 0x30:
sub_10D1177(*(instruction + 4 * pc + 4));
sub_10D12C1(*(instruction + 4 * pc + 8));
break;
case 0x31:
pc += 2;
break;
case 0x32:
++pc;
break;
case 0x33:
pc += 2;
break;
case 0x34:
++pc;
break;
case 0x35:
++pc;
break;
case 0x36:
++pc;
break;
case 0x37:
v6 = *(instruction + 4 * pc + 4);
andq(&reg0, v6, SHIDWORD(v6));
pc += 2;
break;
case 0x38:
++pc;
break;
case 0x39:
v7 = *(instruction + 4 * pc + 4);
movq(&reg2, v7, SHIDWORD(v7));
pc += 2;
break;
case 0x3A:
v8 = *(instruction + 4 * pc + 4);
movq(&reg1, v8, SHIDWORD(v8));
pc += 2;
break;
case 0x3B:
++pc;
break;
case 0x3C:
++pc;
break;
case 0x3D:
shlq(&reg2, reg0);
++pc;
break;
case 0x3E:
pc += 2;
break;
case 0x3F:
++pc;
break;
case 0x40:
++pc;
break;
case 0x41:
pc += 2;
break;
case 0x42:
v9 = *(instruction + 4 * pc + 4);
movq(&reg4, v9, SHIDWORD(v9));
pc += 2;
break;
case 0x43:
movq(&reg1, q_input, SHIDWORD(q_input));
++pc;
break;
case 0x44:
shrq(&reg1, reg0);
++pc;
break;
case 0x45:
movq(&reg1, dword_10DE000[2 * reg0], dword_10DE000[2 * reg0 + 1]);
++pc;
break;
case 0x46:
shlq(&reg3, *(instruction + 4 * pc + 4));
pc += 2;
break;
case 0x47:
shlq(&reg1, reg3);
++pc;
break;
case 0x48:
addq(&reg4, reg1, SHIDWORD(reg1));
++pc;
break;
case 0x49:
addq(&reg2, 1, 0);
++pc;
break;
case 0x4A:
shrq(&reg1, reg2);
++pc;
break;
case 0x4B:
movq(&reg1, dword_10DE080[2 * reg2], dword_10DE080[2 * reg2 + 1]);
++pc;
break;
case 0x4C:
shlq(&reg0, reg1);
++pc;
break;
case 0x4D:
addq(&reg4, reg0, SHIDWORD(reg0));
++pc;
break;
case 0x4E:
reg0 = 0i64;
reg1 = 0i64;
reg3 = 0i64;
reg4 = 0i64;
v10 = *&byte_10E03A8[8 * dword_10E05C8];
v11 = *&byte_10E03A8[8 * dword_10E05C8++ + 4];
q_input = sub_10D1307(q_input, SHIDWORD(q_input), v10, v11);
++pc;
break;
case 0x4F:
q_input = sub_10D1334(q_input);
++pc;
break;
case 0x50:
q_input = sub_10D127B(q_input);
++pc;
if ( dword_10E05C8 == 31 )
dword_10E05C8 = 0;
break;
default:
continue;
}
}
}

0x4E调用了虚拟机,指令长度只有5,进行两次异或操作

1
2
3
4
5
6
7
8
# push q_input
# push qword_8D03A8[i]

# pop reg0
# pop reg1
# xor reg0 reg1
# xor reg0 0x0000000000000033
# mov q_input, reg0

0x4F里面调用虚拟机,指令长度较长,不过很多重复指令,当循环操作处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# mov reg2, 0
# mov reg4, 0
# mov reg2, 0
# for _ in range(16):
# mov reg0, reg2
# shl reg0, 2
# mov reg1, q_input
# shr reg1, reg0
# mov reg0, reg1
# and reg0, 0xf
# mov reg1, qword_8CE000[reg0]
# mov reg3, reg2
# shl reg3, 2
# shl reg1, reg3
# add reg4, reg1
# add reg2, 1
# mov q_input, reg4

0x50里面调用虚拟机操作,跟0x4F类似,循环操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# mov reg4, 0
# mov reg2, 0
# mov reg4, 0
# mov reg2, 0
# for _ in range(64):
# mov reg1, q_input
# shr reg1, reg2
# mov reg0, reg1
# and reg0, 1
# mov reg1, qword_8CE080[reg2]
# shl reg0, reg1
# add reg4, reg0
# add reg2, 1
# mov q_input, reg4
# add reg2, 1

分析第一次调用vm_main的指令,实际上依然是循环,vm_main返回之后,还调用了一次虚拟机,做最后一次处理

1
2
3
4
5
6
7
8
# push q_input
# push qword_8D03A8[31]

# pop reg0
# pop reg1
# xor reg0 reg1
# xor reg0 0x0000000000000033
# mov q_input, reg0

完整的enc指令如下:

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
# mov reg2, 0

# for i in range(31):
# mov reg0, 0
# mov reg1, 0
# mov reg3, 0
# mov reg4, 0

# push q_input
# push qword_8D03A8[i]

# pop reg0
# pop reg1
# xor reg0 reg1
# xor reg0 0x0000000000000033
# mov q_input, reg0

# mov reg2, 0
# mov reg4, 0
# mov reg2, 0
# for _ in range(16):
# mov reg0, reg2
# shl reg0, 2
# mov reg1, q_input
# shr reg1, reg0
# mov reg0, reg1
# and reg0, 0xf
# mov reg1, qword_8CE000[reg0]
# mov reg3, reg2
# shl reg3, 2
# shl reg1, reg3
# add reg4, reg1
# add reg2, 1
# mov q_input, reg4

# mov reg4, 0
# mov reg2, 0
# mov reg4, 0
# mov reg2, 0
# for _ in range(64):
# mov reg1, q_input
# shr reg1, reg2
# mov reg0, reg1
# and reg0, 1
# mov reg1, qword_8CE080[reg2]
# shl reg0, reg1
# add reg4, reg0
# add reg2, 1
# mov q_input, reg4
# add reg2, 1

# push q_input
# push qword_8D03A8[31]

# pop reg0
# pop reg1
# xor reg0 reg1
# xor reg0 0x0000000000000033
# mov q_input, reg0

用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
34
35
36
37
38
flag = "589ab5c1f0394baaaec4cdc462bdd60c"
q_inputs = []
q_crypted = []
for i in range(0, 32, 8):
q_inputs.append(int("0x" + flag[i:i+8][::-1].encode().hex(), 16))

for q_input in q_inputs:
for i in range(31):
q_input ^= qword_8D03A8[i]
q_input ^= 0x33

reg4 = 0
for j in range(16):
reg0 = j << 2
reg1 = q_input >> reg0
reg0 = reg1 & 0xf
reg1 = qword_8CE000[reg0]
reg3 = j << 2
reg1 = reg1 << reg3
reg4 += reg1
q_input = reg4

reg4 = 0
for j in range(64):
reg1 = q_input >> j
reg0 = reg1 & 1
reg1 = qword_8CE080[j]
reg0 = reg0 << reg1
reg4 += reg0
q_input = reg4

q_input ^= qword_8D03A8[31]
q_input ^= 0x33

q_crypted.append(q_input)

for q in q_crypted:
print(hex(q))

不难看出是SPN加密,逆向出解密

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
ans = [0x9c149c0fce40e599, 0xdf4c680dcd759c04, 0xbbafb056e52e3dc0, 0x8e299c86b8f527cb]
q_flag = []

for q_input in q_crypted:
q_input ^= 0x33
q_input ^= qword_8D03A8[31]

for i in range(30, -1, -1):
reg4 = q_input
q_input = 0
for j in range(63, -1, -1):
reg0 = reg4 >> qword_8CE080[j]
reg1 = reg0 & 1
q_input = (q_input << 1) | reg1

reg4 = q_input
q_input = 0
for j in range(15, -1, -1):
reg3 = j << 2
reg1 = reg4 >> reg3
reg1 = reg1 & 0xf
reg0 = qword_8CE000.index(reg1)
q_input = (q_input << 4) | reg0

q_input ^= 0x33
q_input ^= qword_8D03A8[i]
q_flag.append(q_input)

for q in q_flag:
print(q.to_bytes(8, 'little').decode() , end='')

# a9d99caef9ae999a299129c91299fc95

解密得到flaga9d99caef9ae999a299129c91299fc95


2024春秋杯网络安全联赛冬季赛
https://blog.noxke.icu/2024/01/25/ctf_wp/2024春秋杯网络安全联赛冬季赛/
作者
noxke
发布于
2024年1月25日
许可协议