SMC

什么是SMC

SMC(Self Modified Code),也就是自修改代码。它是在执行过程中修改自身指令的代码或数据,来组织别人直接静态分析,然后在动态运行程序时对代码进行解密,达到程序正常运行的效果。

VirtualProtect 函数通常用于代码自加密的场景。代码自加密是一种保护代码不被轻易逆向分析的技术,通过在程序运行时动态地修改代码的内存保护属性,使得代码在执行时可以被修改和执行,但在不执行时则不能被读取或修改。

1
2
3
4
5
6
BOOL VirtualProtect(
LPVOID lpAddress, // 要修改的内存起始地址
SIZE_T dwSize, // 要修改的字节数
DWORD flNewProtect, // 新的保护属性
PDWORD lpflOldProtect // 原始保护属性(输出)
);

它的作用是:修改指定内存区域的访问权限(如改为可读写可执行),并返回之前的保护属性。这是在Windows程序里。

mprotect()函数是在Linux 程序中来改变虚拟内存区域的属性的。

1
2
3
#include <sys/mman.h>

int mprotect(void *addr, size_t len, int prot);
参数 含义
addr 要修改保护属性的内存区域起始地址(必须是页对齐的)
len 要修改的内存长度(会按页大小对齐)
prot 新的保护属性(如下表)

这些可以组合使用,例如PROT_READ | PROT_WRITE 表示可读可写。

宏名 含义
PROT_NONE 无访问权限
PROT_READ 可读取
PROT_WRITE 可写入
PROT_EXEC 可执行

在程序中如果发现VirtualProtect 函数或mprotect()函数,那么极有可能是SMC。

SMC一般有两种破解方法。

第一种找到对代码或数据加密的函数后通过idapython写解密脚本。

第二种动态调试到SMC解密结束的地方dump出来。

例题

1.NSS-[网鼎杯 2020 青龙组]jocker

使用IDA打开后,我们进行分析,发现整个函数头有positive sp value has been detected, the output may be wrong!

这就说明了存在栈不平衡的问题,导致IDA无法生成代码,我们可以在IDA里打开栈指针Options->General->Stack pointer来帮助分析。在x64架构里rsp 是栈指针,它总是指向当前栈顶,在x86架构里esp是栈指针。

会发现有VirtualProtect 函数,那么这个大概率就是一个smc了。先进行分析,也就是要输入一个24位的flag,然后通过wrong(flag)进行加密,再通过omg(flag)进行比较,先看看加密函数。

这个加密逻辑很简单,就是分出奇偶分别进行加密。

这个检验逻辑也很简单,我们可以直接从unk_4030C0中读取出来。

需要注意的是它是24个int型的数据,读取之后写出exp,但是是错误的

1
2
3
4
5
6
7
8
9
10
v2=[0x66, 0x6B, 0x63, 0x64, 0x7F, 0x61, 0x67, 0x64, 0x3B, 0x56, 0x6B, 0x61, 0x7B, 0x26, 0x3B, 0x50, 0x63, 0x5F, 0x4D, 0x5A, 0x71, 0x0C, 0x37, 0x66]
print(len(v2))
flag=''
for i in range(len(v2)):
if((i&1)!=0):
flag+=chr(v2[i]+i)
else:
flag+=chr(v2[i]^i)
print(flag)
#flag{fak3_alw35_sp_me!!}

就是说嘛,这个是SMC,不会这么简单的。

1
2
if ( !VirtualProtect(encrypt, 0xC8u, 4u, &flOldProtect) )
exit(1);

意思是:将 encrypt 函数所在的内存区域设置为 PAGE_READWRITEEXECUTE(值是 4),允许后续对其进行修改。0xC8 是要修改的字节长度,即 200 字节。

1
2
for ( i = 0; i <= 186; ++i )
*((_BYTE *)encrypt + i) ^= 0x41u;

作用是:运行时对 encrypt 函数做 XOR 解密,之前程序通过 XOR 将 encrypt 加密存储,这里要先“解锁”。

1
2
if ( encrypt(Destination) )
sub_40159A(Destination);

这才是真正验证flag的代码。

我们可以在if ( encrypt(Destination) )这里下断点,然后动态调试看真正的加密方式。动态调试时,会提示我们输入flag,这里任意输只要长度是24就好了。然后直接查看汇编,找到encrypt函数是public __Z7encryptPc

点进去发现,IDA反汇编错误,下面有很大串的数据都没识别出来,我们就将函数头按U解除定义,然后从函数头一直选到0x00401630这里结束,按C让IDA强制重新解析成代码。

这样就好了,然后再在函数头按P进行重新定义

然后F5就能反编译了。下一个函数 loc_40159A也没有办法反编译,就在函数头0x0040159A处,U解除定义,再P重新定义一下就能反编译了。

这就是真正的加密函数了,分析一下,右边的是将flag[i]和Buffer[i]进行异或得到密文v2[i],我们可以从unk_403040处找到v2密文,也能直接点击Buffer得到这个字符串是'hahahaha_do_you_find_me?',那么我们就能得到前19位的flag,只要将v2[i]和Buffer[i]进行异或就好了。我们知道这个flag一共是24位,还有5位不知道,看左边的加密函数。这个v3密文正好是5位,但是这个函数并没有什么卵用,我们就进行猜测这个是flag与某个值进行异或得到了这个密文。已知flag最后一位一定是右括号”}”,那么我们将这个和密文异或就能得到某个值了。

写出exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
buffer='hahahaha_do_you_find_me?'
flag =''

v2=[0x0E, 0x0D, 0x09, 0x06, 0x13 ,0x05 ,0x58 ,0x56 ,0x3E ,0x06 ,0x0C ,0x3C ,0x1F ,0x57 ,0x14 ,0x6B ,0x57 ,0x59 ,0x0D ]
print(len(v2))
for i in range(len(v2)):
flag+= chr(v2[i]^ord(buffer[i]))
print(flag)

v3 ='%tp&:'
a=ord(':')^ord('}')
for i in range(len(v3)):
flag+=chr(ord(v3[i])^a)
print(flag)

#flag{d07abccf8a410c
#flag{d07abccf8a410cb37a}

2.[NSSRound#2 Able]findxenny

使用IDA打开程序后,找到main函数,进行分析

这个是将输入的flag放到v12里面,长度要大于等于12,并且将这个分成三份,分别放到v14,v15,v16里面。然后进入到一个函数里面,最后的if条件是进行检测判断,通过这三个函数来对flag的三部分进行检测。那我们要进入到sub_140011514()里面来看看它进行了什么操作,里面有没有关于这三个函数的信息。

发现了VirtualProtect 函数,这就说明了考察的是SMC,上面的数据都是加密后的,下面是解密的内容,我们可以通过动态调试的办法找到解密后的数据,最后的那三个函数就是main函数里进行校验flag的函数。我们在 qword_140029370qword_140029378qword_140029380都下上断点,进行调试。

只要输入大于等于12个字符就好了。

我们单步步入到 return处,然后点击这三个函数,会发现这里原本没有数据,这时候竟然有数据了

我们点进去后,光标原本所在的位置就是函数头,直接按P进行重新定义,然后F5,就能将这个函数编译出来。

另外两个函数也是用这个相同的方法进行反编译,就能知道校验的内容。

第二个函数

第三个函数

我们会发现第三个将它反过来就是 _x3nny,与提示输入的xenny有异曲同工之妙,这三个内容应该就是flag的内容,但是第一个和第三个反了,将它们反过来后进行拼接就是flag。这一步主要靠经验和猜测。

最后flag就是NSSCTF{oh_you_found_our_x3nny}

3.NSS-[HDCTF 2023]enc

放到IDA里面进行分析

这个题是两部分组成,第一部分通过TEA解密要找到密钥,输入密钥正确后,我们才能进入第二部分输入flag以及flag的验证。

发现sub_411302(v6)函数点开后不能正确的反编译。先看看TEA函数吧,sub_411523((int)&v7, (int)v9);输入的key在TEA加密后必须输出v7 == 1627184887 // 0x610B6A77,v8 == 37149676 // 0x0235AC4C这两个特定数值,这是密文,利用它来解密,会得到两个数据,其中一个是v8=4,那另外一个就是v7=key了。

就是简单的TEA,进行解密

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
#include <iostream>
#include <stdint.h>

void TEA_decrypt(uint32_t* v, uint32_t* k)
{
uint32_t v0 = v[0], v1 = v[1];
uint32_t delta = 0x61C88647;
uint32_t sum = -delta * 32;

for (int i = 0; i < 32; i++) {
v1 -= (k[3] + (v0 >> 5)) ^ (sum + v0) ^ (k[2] + 16 * v0);
v0 -= (k[1] + (v1 >> 5)) ^ (sum + v1) ^ (k[0] + 16 * v1);
sum += delta;
}

v[0] = v0;
v[1] = v1;
}

int main()
{
uint32_t key[4] = {18, 52, 86, 120};
uint32_t cipher[2] = {1627184887, 37149676};

TEA_decrypt(cipher, key);

std::cout << "Decrypted v0 = " << cipher[0] << std::endl;
std::cout << "Decrypted v1 = " << cipher[1] << std::endl;

return 0;
}
//Decrypted v0 = 3
//Decrypted v1 = 4

key等于3,我们在sub_411302(v6)函数处打上断点,进行动态调试。

我们在断点位置按F7进行单步步入看看汇编,发现与静态分析的有所不同,这里有很多数据都没有被正确识别出来

我们直接在函数头按P进行重新定义,让IDA重新识别。

然后按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
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
void __cdecl __noreturn sub_41D000(char *Str)
{
char v1; // [esp+0h] [ebp-558h]
size_t m; // [esp+190h] [ebp-3C8h]
BOOL v3; // [esp+19Ch] [ebp-3BCh]
int k; // [esp+1A8h] [ebp-3B0h]
int v5; // [esp+1B4h] [ebp-3A4h]
int v6; // [esp+1C0h] [ebp-398h]
int i; // [esp+1CCh] [ebp-38Ch]
int j; // [esp+1CCh] [ebp-38Ch]
int v9; // [esp+1CCh] [ebp-38Ch]
char v10; // [esp+1DBh] [ebp-37Dh]
char v11; // [esp+1DBh] [ebp-37Dh]
char v12[540]; // [esp+1E4h] [ebp-374h]
char v13[16]; // [esp+400h] [ebp-158h] BYREF
int v14; // [esp+418h] [ebp-140h]
char v15[264]; // [esp+424h] [ebp-134h] BYREF
char v16[40]; // [esp+52Ch] [ebp-2Ch] BYREF

__CheckForDebuggerJustMyCode(&unk_425036);
v16[0] = 15;
v16[1] = -108;
v16[2] = -82;
v16[3] = -14;
v16[4] = -64;
v16[5] = 87;
v16[6] = -62;
v16[7] = -32;
v16[8] = -102;
v16[9] = 69;
v16[10] = 55;
v16[11] = 80;
v16[12] = -11;
v16[13] = -96;
v16[14] = 94;
v16[15] = -53;
v16[16] = 44;
v16[17] = 22;
v16[18] = 40;
v16[19] = 41;
v16[20] = -2;
v16[21] = -1;
v16[22] = 51;
v16[23] = 70;
v16[24] = 14;
v16[25] = 87;
v16[26] = -126;
v16[27] = 34;
v16[28] = 82;
v16[29] = 38;
v16[30] = 43;
v16[31] = 110;
v16[32] = -28;
v16[33] = -126;
v16[34] = 36;
j_memset(v15, 0, 0x100u);
v14 = j_strlen(Str);
strcpy(v13, "you_are_master");
v12[531] = 0;
v5 = 0;
for ( i = 0; i < 256; ++i )
{
v12[i + 264] = i;
v12[i] = v13[i % j_strlen(v13)];
}
for ( j = 0; j < 256; ++j )
{
v5 = ((unsigned __int8)v12[j] + v5 + (unsigned __int8)v12[j + 264]) % 256;
v10 = v12[j + 264];
v12[j + 264] = v12[v5 + 264];
v12[v5 + 264] = v10;
}
v6 = 0;
v9 = 0;
for ( k = 0; k < v14; ++k )
{
v9 = (v9 + 1) % 256;
v6 = (v6 + (unsigned __int8)v12[v9 + 264]) % 256;
v11 = v12[v9 + 264];
v12[v9 + 264] = v12[v6 + 264];
v12[v6 + 264] = v11;
v15[k] = v12[((unsigned __int8)v12[v6 + 264] + (unsigned __int8)v12[v9 + 264]) % 256 + 264] ^ Str[k];
}
v3 = j_strlen(Str) == 35;
for ( m = 0; m < j_strlen(v16); ++m )
{
if ( v16[m] != v15[m] )
{
v3 = 0;
break;
}
}
if ( v3 )
sub_41114F("right!!!!", v1);
else
sub_41114F("please try agin~", v1);
}

这是标准的RC4对输入的flag进行加密,把加密后的结果v15与v16进行逐字节比较,如果完全匹配,就打印 right!!!!,那么v16就是密文了,密钥就是v13"you_are_master"

我们可以使用在线网站进行解密:https://www.toolhelper.cn/SymmetricEncryption/RC4

也可以使用脚本

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
def rc4_decrypt(ciphertext, key):
# Key-scheduling algorithm (KSA)
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + ord(key[i % len(key)])) % 256
S[i], S[j] = S[j], S[i]

# Pseudo-random generation algorithm (PRGA)
i = j = 0
keystream = []
for _ in range(len(ciphertext)):
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
K = S[(S[i] + S[j]) % 256]
keystream.append(K)

# Decrypt by XORing
return bytes([c ^ k for c, k in zip(ciphertext, keystream)])


if __name__ == "__main__":
# Encrypted values (converted to unsigned 8-bit integers)
v16 = [
15, 148, 174, 242, 192, 87, 194, 224, 154, 69,
55, 80, 245, 160, 94, 203, 44, 22, 40, 41,
254, 255, 51, 70, 14, 87, 130, 34, 82, 38,
43, 110, 228, 130, 36
]

key = "you_are_master"
decrypted = rc4_decrypt(v16, key)
print("Recovered flag:")
print(decrypted.decode())

4.NSS-[羊城杯 2021]BabySmc

这道题对于目前的我来讲还挺吃力的,不仅考了SMC,还考察了对于base64的理解,我当时写的时候就没有看出来base64。参考了一位师傅的博客才明白了,不得不说,师傅写的真好!!! https://blog.csdn.net/xiao__1bai/article/details/120342289

使用IDA打开,然后分析主函数

v4里面存放的是用户输入的flag,知道lpAddress和 qword_7FF62CC1AD88分别是起始和结束地址。然后看看sub_7FF62CBF1E30()函数。

发现这里有VirtualProtect 函数,修改了起始地址和结束地址的权限,进行了解密,所以这个是数据的解密函数。

我们接着看被IDA错误识别的汇编

发现这里有很多大块的没有被识别出来的数据,不是花指令的话,那就是smc了。看到上面调用了解密函数,我们可以使用动态调试dump出来。在解密函数处打上断点,进行调试分析。提示输入flag时,随便输就好了,这个不影响后续的分析。我们按F8进行单步步过,这时候会出现一个弹窗,意思是是否让IDA自己根据RIP生成代码,这里选no,因为IDA不知道哪里是代码,哪里是数据,如果根据RIP进行分析,就可能会错误的把数据当成代码,从而整个main函数结构就被拆分了。

然后我们看到了这里有更多的大块数据,没有正确识别出来

我们直接从Main函数头开始选中,一直选到第一个retn为止

然后按C,选择Force进行强制分析,然后按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
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
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
char v4; // sp
char v5; // cl
__int64 v6; // rcx
__int64 v8; // rax
__int64 v9; // rcx
unsigned __int128 v10; // rax
unsigned __int64 v11; // rdi
unsigned __int64 v12; // r9
int v13; // r8d
int v14; // r10d
unsigned __int64 v15; // rsi
__int64 v16; // rax
__int64 v17; // rdx
int v18; // r11d
int v19; // r13d
int v20; // ebx
int v21; // r12d
__int64 v22; // r13
int v23; // r11d
__int64 v24; // rbx
__int64 v25; // r11
int v26; // r12d
__int64 v27; // rbx
__int64 v28; // r11
int v29; // r12d
__int64 v30; // r13
__int64 v31; // rbx
__int64 v32; // r11
int v33; // r12d
__int64 v34; // rbx
__int64 v35; // r11
int v36; // r12d
__int64 v37; // r13
__int64 v38; // rbx
__int64 v39; // r11
int v40; // r12d
__int64 v41; // rbx
__int64 v42; // r11
int v43; // r12d
__int64 v44; // r13
__int64 v45; // rbx
__int64 v46; // r11
int v47; // r12d
__int64 v48; // rbx
__int64 v49; // r11
int v50; // r12d
__int64 v51; // r13
__int64 v52; // rbx
__int64 v53; // r11
int v54; // r12d
__int64 v55; // rbx
__int64 v56; // r11
int v57; // r12d
__int64 v58; // r13
__int64 v59; // rbx
__int64 v60; // r11
int v61; // r12d
__int64 v62; // rbx
__int64 v63; // r11
int v64; // r12d
__int64 v65; // r13
int v66; // r12d
__int64 v67; // r12
__int64 v68; // r13
__int64 v69; // r13
unsigned __int64 v70; // rax
__int64 i; // r9
__int64 v72; // rdx
int v73; // esi
int v74; // r8d
int v75; // r10d
__int64 v76; // rcx
char v77; // al
char v78; // r9
int v79; // r9d
int v80; // eax
char v81; // r10
char v82; // r8
const char *v83; // rsi
char *v84; // rdi
bool v85; // cf
unsigned __int8 v86; // dl
int v87; // eax
const char *v88; // rcx
__int64 v89; // rcx
unsigned __int64 v90; // rcx
__int128 v92[4]; // [rsp+0h] [rbp-1C8h] BYREF
char v93[256]; // [rsp+40h] [rbp-188h] BYREF
__int128 v94[4]; // [rsp+140h] [rbp-88h]
__m128i v95; // [rsp+180h] [rbp-48h] BYREF
__int128 v96; // [rsp+190h] [rbp-38h]
__int128 v97; // [rsp+1A0h] [rbp-28h]
__int64 v98; // [rsp+1B0h] [rbp-18h]

sub_7FF62CBF1EB0(0i64, 0i64, envp);
v95 = 0i64;
v96 = 0i64;
v97 = 0i64;
sub_7FF62CBF1D40("Input Your Flag : ");
sub_7FF62CBF1DC0("%46s", v95.m128i_i8);
lpAddress = &loc_7FF62CBF1085;
qword_7FF62CC1AD88 = (__int64)&loc_7FF62CBF1D00;
sub_7FF62CBF1E30();
v3 = 16i64;
do
{
v92[v3 + 3] = 0i64;
v92[v3 + 2] = 0i64;
v92[v3 + 1] = 0i64;
v92[v3] = 0i64;
v3 -= 4i64;
}
while ( v3 * 16 );
v94[0] = xmmword_7FF62CC0E340;
v94[1] = xmmword_7FF62CC0E350;
v94[2] = xmmword_7FF62CC0E360;
v94[3] = xmmword_7FF62CC0E370;
v5 = v4 + 0x80;
v6 = v5 & 0xF;
if ( !_BitScanForward((unsigned int *)&v8, (unsigned int)_mm_movemask_epi8(_mm_cmpeq_epi8((__m128i)0i64, v95)) >> v6) )
v8 = sub_7FF62CBF2340(v6, &v95.m128i_i8[v6]);
v9 = v8;
v10 = 0xAAAAAAAAAAAAAAABui64 * (unsigned __int128)(unsigned __int64)v8;
v11 = *((_QWORD *)&v10 + 1) >> 1;
if ( *((_QWORD *)&v10 + 1) >> 1 )
{
LODWORD(v12) = 0;
LODWORD(v10) = 1;
v13 = 0;
v14 = 0;
v15 = *((_QWORD *)&v10 + 1) >> 5;
if ( *((_QWORD *)&v10 + 1) >> 5 )
{
do
{
v16 = v13;
v17 = v14;
v18 = v95.m128i_i8[v13 + 1];
v19 = 16 * (v95.m128i_i8[v13] & 3);
v20 = v95.m128i_i8[v13 + 2];
v93[v14] = *((_BYTE *)v94 + (v95.m128i_i8[v13] >> 2)) ^ 0xA6;
v93[v14 + 1] = *((_BYTE *)v94 + ((v18 >> 4) | v19)) ^ 0xA3;
v93[v14 + 2] = *((_BYTE *)v94 + ((v20 >> 6) | (4 * (v18 & 0xF)))) ^ 0xA9;
v21 = v95.m128i_i8[v13 + 3] & 3;
v22 = v95.m128i_i8[v13 + 3] >> 2;
v93[v14 + 3] = *((_BYTE *)v94 + (v20 & 0x3F)) ^ 0xAC;
v23 = v95.m128i_i8[v13 + 4];
v93[v14 + 4] = *((_BYTE *)v94 + v22) ^ 0xA6;
LODWORD(v22) = v23;
v24 = v95.m128i_i8[v13 + 5] & 0x3F;
v25 = (v95.m128i_i8[v13 + 5] >> 6) | (4 * (v23 & 0xF));
v93[v14 + 5] = *((_BYTE *)v94 + (((int)v22 >> 4) | (16 * v21))) ^ 0xA3;
LODWORD(v22) = v95.m128i_i8[v13 + 6];
v93[v14 + 6] = *((_BYTE *)v94 + v25) ^ 0xA9;
v93[v14 + 7] = *((_BYTE *)v94 + v24) ^ 0xAC;
LODWORD(v25) = v95.m128i_i8[v13 + 7];
v93[v14 + 8] = *((_BYTE *)v94 + ((int)v22 >> 2)) ^ 0xA6;
v26 = v25;
v27 = v95.m128i_i8[v13 + 8] & 0x3F;
v28 = (int)((v95.m128i_i8[v13 + 8] >> 6) | (4 * (v25 & 0xF)));
v93[v14 + 9] = *((_BYTE *)v94 + (int)((v26 >> 4) | (16 * (v22 & 3)))) ^ 0xA3;
v93[v14 + 10] = *((_BYTE *)v94 + v28) ^ 0xA9;
v29 = v95.m128i_i8[v13 + 9] & 3;
v30 = v95.m128i_i8[v13 + 9] >> 2;
v93[v14 + 11] = *((_BYTE *)v94 + v27) ^ 0xAC;
LODWORD(v28) = v95.m128i_i8[v13 + 10];
v93[v14 + 12] = *((_BYTE *)v94 + v30) ^ 0xA6;
LODWORD(v30) = v28;
v31 = v95.m128i_i8[v13 + 11] & 0x3F;
v32 = (int)((v95.m128i_i8[v13 + 11] >> 6) | (4 * (v28 & 0xF)));
v93[v14 + 13] = *((_BYTE *)v94 + (((int)v30 >> 4) | (16 * v29))) ^ 0xA3;
LODWORD(v30) = v95.m128i_i8[v13 + 12];
v93[v14 + 14] = *((_BYTE *)v94 + v32) ^ 0xA9;
v93[v14 + 15] = *((_BYTE *)v94 + v31) ^ 0xAC;
LODWORD(v32) = v95.m128i_i8[v13 + 13];
v93[v14 + 16] = *((_BYTE *)v94 + ((int)v30 >> 2)) ^ 0xA6;
v33 = v32;
v34 = v95.m128i_i8[v13 + 14] & 0x3F;
v35 = (int)((v95.m128i_i8[v13 + 14] >> 6) | (4 * (v32 & 0xF)));
v93[v14 + 17] = *((_BYTE *)v94 + (int)((v33 >> 4) | (16 * (v30 & 3)))) ^ 0xA3;
v93[v14 + 18] = *((_BYTE *)v94 + v35) ^ 0xA9;
v36 = v95.m128i_i8[v13 + 15] & 3;
v37 = v95.m128i_i8[v13 + 15] >> 2;
v93[v14 + 19] = *((_BYTE *)v94 + v34) ^ 0xAC;
LODWORD(v35) = *((char *)&v96 + v13);
v93[v14 + 20] = *((_BYTE *)v94 + v37) ^ 0xA6;
LODWORD(v37) = v35;
v38 = *((_BYTE *)&v96 + v13 + 1) & 0x3F;
v39 = (int)((*((char *)&v96 + v13 + 1) >> 6) | (4 * (v35 & 0xF)));
v93[v14 + 21] = *((_BYTE *)v94 + (((int)v37 >> 4) | (16 * v36))) ^ 0xA3;
LODWORD(v37) = *((char *)&v96 + v13 + 2);
v93[v14 + 22] = *((_BYTE *)v94 + v39) ^ 0xA9;
v93[v14 + 23] = *((_BYTE *)v94 + v38) ^ 0xAC;
LODWORD(v39) = *((char *)&v96 + v13 + 3);
v93[v14 + 24] = *((_BYTE *)v94 + ((int)v37 >> 2)) ^ 0xA6;
v40 = v39;
v41 = *((_BYTE *)&v96 + v13 + 4) & 0x3F;
v42 = (int)((*((char *)&v96 + v13 + 4) >> 6) | (4 * (v39 & 0xF)));
v93[v14 + 25] = *((_BYTE *)v94 + (int)((v40 >> 4) | (16 * (v37 & 3)))) ^ 0xA3;
v93[v14 + 26] = *((_BYTE *)v94 + v42) ^ 0xA9;
v43 = *((_BYTE *)&v96 + v13 + 5) & 3;
v44 = *((char *)&v96 + v13 + 5) >> 2;
v93[v14 + 27] = *((_BYTE *)v94 + v41) ^ 0xAC;
LODWORD(v42) = *((char *)&v96 + v13 + 6);
v93[v14 + 28] = *((_BYTE *)v94 + v44) ^ 0xA6;
LODWORD(v44) = v42;
v45 = *((_BYTE *)&v96 + v13 + 7) & 0x3F;
v46 = (int)((*((char *)&v96 + v13 + 7) >> 6) | (4 * (v42 & 0xF)));
v93[v14 + 29] = *((_BYTE *)v94 + (((int)v44 >> 4) | (16 * v43))) ^ 0xA3;
LODWORD(v44) = *((char *)&v96 + v13 + 8);
v93[v14 + 30] = *((_BYTE *)v94 + v46) ^ 0xA9;
v93[v14 + 31] = *((_BYTE *)v94 + v45) ^ 0xAC;
LODWORD(v46) = *((char *)&v96 + v13 + 9);
v93[v14 + 32] = *((_BYTE *)v94 + ((int)v44 >> 2)) ^ 0xA6;
v47 = v46;
v48 = *((_BYTE *)&v96 + v13 + 10) & 0x3F;
v49 = (int)((*((char *)&v96 + v13 + 10) >> 6) | (4 * (v46 & 0xF)));
v93[v14 + 33] = *((_BYTE *)v94 + (int)((v47 >> 4) | (16 * (v44 & 3)))) ^ 0xA3;
v93[v14 + 34] = *((_BYTE *)v94 + v49) ^ 0xA9;
v50 = *((_BYTE *)&v96 + v13 + 11) & 3;
v51 = *((char *)&v96 + v13 + 11) >> 2;
v93[v14 + 35] = *((_BYTE *)v94 + v48) ^ 0xAC;
LODWORD(v49) = *((char *)&v96 + v13 + 12);
v93[v14 + 36] = *((_BYTE *)v94 + v51) ^ 0xA6;
LODWORD(v51) = v49;
v52 = *((_BYTE *)&v96 + v13 + 13) & 0x3F;
v53 = (int)((*((char *)&v96 + v13 + 13) >> 6) | (4 * (v49 & 0xF)));
v93[v14 + 37] = *((_BYTE *)v94 + (((int)v51 >> 4) | (16 * v50))) ^ 0xA3;
LODWORD(v51) = *((char *)&v96 + v13 + 14);
v93[v14 + 38] = *((_BYTE *)v94 + v53) ^ 0xA9;
v93[v14 + 39] = *((_BYTE *)v94 + v52) ^ 0xAC;
LODWORD(v53) = *((char *)&v96 + v13 + 15);
v93[v14 + 40] = *((_BYTE *)v94 + ((int)v51 >> 2)) ^ 0xA6;
v54 = v53;
v55 = *((_BYTE *)&v97 + v13) & 0x3F;
v56 = (int)((*((char *)&v97 + v13) >> 6) | (4 * (v53 & 0xF)));
v93[v14 + 41] = *((_BYTE *)v94 + (int)((v54 >> 4) | (16 * (v51 & 3)))) ^ 0xA3;
v93[v14 + 42] = *((_BYTE *)v94 + v56) ^ 0xA9;
v57 = *((_BYTE *)&v97 + v13 + 1) & 3;
v58 = *((char *)&v97 + v13 + 1) >> 2;
v93[v14 + 43] = *((_BYTE *)v94 + v55) ^ 0xAC;
LODWORD(v56) = *((char *)&v97 + v13 + 2);
v93[v14 + 44] = *((_BYTE *)v94 + v58) ^ 0xA6;
LODWORD(v58) = v56;
v59 = *((_BYTE *)&v97 + v13 + 3) & 0x3F;
v60 = (int)((*((char *)&v97 + v13 + 3) >> 6) | (4 * (v56 & 0xF)));
v93[v14 + 45] = *((_BYTE *)v94 + (((int)v58 >> 4) | (16 * v57))) ^ 0xA3;
LODWORD(v58) = *((char *)&v97 + v13 + 4);
v93[v14 + 46] = *((_BYTE *)v94 + v60) ^ 0xA9;
v93[v14 + 47] = *((_BYTE *)v94 + v59) ^ 0xAC;
LODWORD(v60) = *((char *)&v97 + v13 + 5);
v93[v14 + 48] = *((_BYTE *)v94 + ((int)v58 >> 2)) ^ 0xA6;
v61 = v60;
v62 = *((_BYTE *)&v97 + v13 + 6) & 0x3F;
v63 = (int)((*((char *)&v97 + v13 + 6) >> 6) | (4 * (v60 & 0xF)));
v93[v14 + 49] = *((_BYTE *)v94 + (int)((v61 >> 4) | (16 * (v58 & 3)))) ^ 0xA3;
v93[v14 + 50] = *((_BYTE *)v94 + v63) ^ 0xA9;
v64 = *((_BYTE *)&v97 + v13 + 7) & 3;
v65 = *((char *)&v97 + v13 + 7) >> 2;
v93[v14 + 51] = *((_BYTE *)v94 + v62) ^ 0xAC;
LODWORD(v63) = *((char *)&v97 + v13 + 8);
v93[v14 + 52] = *((_BYTE *)v94 + v65) ^ 0xA6;
LODWORD(v62) = *((char *)&v97 + v13 + 9);
v93[v14 + 53] = *((_BYTE *)v94 + (((int)v63 >> 4) | (16 * v64))) ^ 0xA3;
v66 = v62;
v12 = (unsigned int)(v12 + 1);
v13 += 48;
v14 += 64;
LOBYTE(v65) = *((_BYTE *)v94 + (v62 & 0x3F));
LODWORD(v62) = *((char *)&v97 + v16 + 10);
v93[v17 + 55] = v65 ^ 0xAC;
LODWORD(v65) = *((char *)&v97 + v16 + 11);
v93[v17 + 54] = *((_BYTE *)v94 + (int)((v66 >> 6) | (4 * (v63 & 0xF)))) ^ 0xA9;
v93[v17 + 56] = *((_BYTE *)v94 + ((int)v62 >> 2)) ^ 0xA6;
LODWORD(v63) = (int)v65 >> 4;
v67 = *((_BYTE *)&v97 + v16 + 12) & 0x3F;
v68 = (int)((*((char *)&v97 + v16 + 12) >> 6) | (4 * (v65 & 0xF)));
v93[v17 + 57] = *((_BYTE *)v94 + (int)(v63 | (16 * (v62 & 3)))) ^ 0xA3;
LOBYTE(v62) = *((_BYTE *)v94 + v68);
v69 = *((char *)&v97 + v16 + 13) >> 2;
LODWORD(v63) = *((_BYTE *)&v97 + v16 + 13) & 3;
v93[v17 + 58] = v62 ^ 0xA9;
v93[v17 + 59] = *((_BYTE *)v94 + v67) ^ 0xAC;
LOBYTE(v67) = *((_BYTE *)v94 + v69);
LODWORD(v69) = *((char *)&v97 + v16 + 14);
LODWORD(v62) = v69 & 0xF;
LODWORD(v63) = ((int)v69 >> 4) | (16 * v63);
LODWORD(v69) = *((char *)&v97 + v16 + 15);
v93[v17 + 60] = v67 ^ 0xA6;
LOBYTE(v67) = *((_BYTE *)v94 + (int)v63) ^ 0xA3;
LOBYTE(v63) = *((_BYTE *)v94 + (((int)v69 >> 6) | (4 * (int)v62))) ^ 0xA9;
LOBYTE(v62) = *((_BYTE *)v94 + (v69 & 0x3F)) ^ 0xAC;
v93[v17 + 61] = v67;
v93[v17 + 62] = v63;
v93[v17 + 63] = v62;
}
while ( v12 < v15 );
LODWORD(v10) = 16 * v12 + 1;
}
v70 = (unsigned int)(v10 - 1);
for ( i = 3 * (int)v70; v70 < v11; v93[v72 + 3] = v73 )
{
v72 = 4 * (int)v70;
v70 = (unsigned int)(v70 + 1);
v73 = v95.m128i_i8[i + 1];
v74 = 16 * (v95.m128i_i8[i] & 3);
v93[v72] = *((_BYTE *)v94 + (v95.m128i_i8[i] >> 2)) ^ 0xA6;
v75 = v95.m128i_i8[i + 2];
i += 3i64;
v93[v72 + 1] = *((_BYTE *)v94 + ((v73 >> 4) | v74)) ^ 0xA3;
LOBYTE(v74) = *((_BYTE *)v94 + ((v75 >> 6) | (4 * (v73 & 0xF)))) ^ 0xA9;
LOBYTE(v73) = *((_BYTE *)v94 + (v75 & 0x3F)) ^ 0xAC;
v93[v72 + 2] = v74;
}
}
v76 = v9 - 3 * v11;
if ( v76 == 1 )
{
v77 = v95.m128i_i8[3 * v11];
v93[4 * v11 + 2] = 49;
v93[4 * v11 + 3] = 52;
v78 = *((_BYTE *)v94 + (unsigned __int8)(16 * (v77 & 3))) ^ 0xA3;
v93[4 * v11] = *((_BYTE *)v94 + (v77 >> 2)) ^ 0xA6;
v93[4 * v11 + 1] = v78;
v93[4 * v11 + 4] = 0;
}
else if ( v76 == 2 )
{
v79 = v95.m128i_i8[3 * v11 + 1];
v80 = v95.m128i_i8[3 * v11];
v81 = *((_BYTE *)v94 + 4 * (v79 & 0xFu)) ^ 0xA9;
v82 = *((_BYTE *)v94 + ((v79 >> 4) | (16 * (v80 & 3)))) ^ 0xA3;
v93[4 * v11] = *((_BYTE *)v94 + (v80 >> 2)) ^ 0xA6;
v93[4 * v11 + 1] = v82;
v93[4 * v11 + 2] = v81;
v93[4 * v11 + 3] = 52;
v93[4 * v11 + 4] = 0;
}
else
{
v93[4 * v11] = 0;
}
v83 = "H>oQn6aqLr{DH6odhdm0dMe`MBo?lRglHtGPOdobDlknejmGI|ghDb<4";
v84 = v93;
while ( 1 )
{
v85 = (unsigned __int8)*v84 < (unsigned int)*v83;
if ( *v84 != *v83 )
break;
if ( !*v84 )
goto LABEL_21;
v86 = v84[1];
v85 = v86 < (unsigned int)v83[1];
if ( v86 != v83[1] )
break;
v84 += 2;
v83 += 2;
if ( !v86 )
{
LABEL_21:
v87 = 0;
goto LABEL_23;
}
}
v87 = v85 ? -1 : 1;
LABEL_23:
v88 = "No.\r\n";
if ( !v87 )
v88 = "Yes.\r\n";
sub_7FF62CBF1D40(v88);
sub_7FF62CBF6B38("pause");
v89 = v98;
v98 = 0i64;
v90 = (unsigned __int64)v92 ^ v89;
if ( v90 == _security_cookie )
return 0;
else
return sub_7FF62CBF1D40(v90);
}

进行分析代码,这个代码量有点多欸。通过代码的大概形式我们可以知道这个类似base64加密,其中

这些>>2 , >>4 , >>6 , &3 , &0xF , &0x3F , *16就是<<4,(v*(2^n)==v<<n)这看起来就像是base64从3 * 8,到4 * 6的实现。

举个例子

1
2
3
4
5
6
7
原始字节流:      [11111111] [10101010] [01010101]
位数编号: 00000000 00000001 00000010 (共3字节)
总共24位,把它们分为4组,每组6位:
1组: 位0-5 → (data[0] >> 2)
2组: 位6-11 → ((data[0] & 0x3) << 4) | (data[1] >> 4)
3组: 位12-17 → ((data[1] & 0xf) << 2) | (data[2] >> 6)
4组: 位18-23 → (data[2] & 0x3f)

‘>>2’ # 取出高6位
& 0x3 # 保留最后两位(低2位)
<< 4 # 左移填充到高位(准备拼接)
& 0xf # 取中间4位(低4位)
<< 2 # 左移再准备拼接
& 0x3f # 最终保留6位结果

但是这里还有不同的就是这里多了异或操作^0xA6 , ^0xA3 , ^0xA9 , ^0xAc,还有这个还是个变表,真正的表是 v94

v94[0] = xmmword_7FF62CC0E340; v94[1] = xmmword_7FF62CC0E350;
v94[2] = xmmword_7FF62CC0E360; v94[3] = xmmword_7FF62CC0E370;

大致分析后,知道**”H>oQn6aqLr{DH6odhdm0dMe`MBo?lRglHtGPOdobDlknejmGI|ghDb<4”**这就是加密后的结果,我们对其要进行解密,解密脚本参考了师傅写的。哎,看来我得好好看看base64了……

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
res="H>oQn6aqLr{DH6odhdm0dMe`MBo?lRglHtGPOdobDlknejmGI|ghDb<"
key=[0xA6,0xA3,0xA9,0xAC]
flag=[]
for i in range(len(res)):
flag+=[ord(res[i]) ^ key[i%len(key)]]
print(flag)
table=[0xE4, 0xC4, 0xE7, 0xC7, 0xE6, 0xC6, 0xE1,
0xC1, 0xE0, 0xC0, 0xE3, 0xC3, 0xE2, 0xC2, 0xED,
0xCD, 0xEC,0xCC, 0xEF, 0xCF, 0xEE, 0xCE, 0xE9, 0xC9,
0xE8, 0xC8, 0xEB, 0xCB, 0xEA, 0xCA, 0xF5, 0xD5, 0xF4,
0xD4, 0xF7, 0xD7, 0xF6, 0xD6, 0xF1, 0xD1, 0xF0, 0xD0,
0xF3, 0xD3, 0xF2, 0xD2, 0xFD, 0xDD, 0xFC, 0xDC, 0xFF,
0xDF, 0x95, 0x9C, 0x9D, 0x92, 0x93, 0x90, 0x91, 0x96,
0x97, 0x94, 0x8A, 0x8E]
result=""
#flag根据再table中的索引值全部转化为二进制,放入table中,不够6位的需要补齐
for i in flag:
result+=('{:0>6}'.format(bin(table.index(i)).replace("0b","")))
for i in range(0,len(result),8):
print(chr(int(result[i:i+8],2)),end="")
#SangFor{XSAYT0u5DQhaxveIR50X1U13M-pZK5A0}

5.NewStar-SMc_math

这个是ELF64位的程序,使用IDA打开,进行分析

我们看到了mprotect()函数这就很明显是一个SMC了,这个函数是对encrypt进行修改权限,要求输入的flag长度为28,并且这个encrypt就是最后的加密验证函数,我们可以使用ELF文件动调来看,先下断点到if ( (unsigned int)((__int64 (__fastcall *)(char *))encrypt)(s) )这一行,此时是解密完成的状态。这里主要介绍第二种方法,使用解密脚本。我们也可以使用IDAPython 进行解密,知道解密方法是和0x3E进行异或,并且知道了解密的长度,现在要找到起始地址就ok了。

IDAPython脚本如下

1
2
3
4
5
6
7
8
9
# Patch encrypt 函数加密内容
start = 0x11E9 # encrypt 函数起始地址
length = 0x3D6 # 总共需要解密的长度

for i in range(length):
b = ida_bytes.get_byte(start + i)
ida_bytes.patch_byte(start + i, b ^ 0x3E)

print("Decrypt done.")

在IDA中打开 File->Script command…->将IDA改成python

运行后,我们发现这就发生了很大的变化,原来大块的数据都没有了。我们从函数头一直选中到函数尾也就是retn的位置,然后按C强制进行分析。

再在函数头先U解除定义一下,再P重新定义,就能F5进行反编译了

方程式,使用z3求解

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
from z3 import *

# 声明7个未知的64位无符号整数变量
v = [BitVec(f'v{i}', 64) for i in range(7)]

s = Solver()

# 添加约束
s.add(5 * (v[1] + v[0]) + 4 * v[2] + 6 * v[3] + v[4] + 9 * v[6] + 2 * v[5] == 0xD5CC7D4FF)
s.add(4 * v[6] + 3 * v[3] + 6 * v[2] + 10 * v[1] + 9 * v[0] + 9 * v[5] + 3 * v[4] == 0x102335844B)
s.add(9 * v[4] + 4 * (v[3] + v[2]) + 5 * v[1] + 4 * v[0] + 3 * v[6] + 10 * v[5] == 0xD55AEABB9)
s.add(9 * v[1] + 5 * v[0] + 9 * v[6] + 2 * (v[2] + 2 * v[3] + 5 * v[4] + v[5]) == 0xF89F6B7FA)
s.add(5 * v[4] + 9 * v[3] + 7 * v[0] + 2 * v[1] + v[2] + 3 * v[6] + 9 * v[5] == 0xD5230B80B)
s.add(8 * v[6] + 6 * v[3] + 10 * v[2] + 5 * v[1] + 6 * v[0] + 3 * v[5] + 9 * v[4] == 0x11E28ED873)
s.add(v[0] + 4 * (v[2] + v[1] + 2 * v[3]) + 9 * v[4] + v[5] + 3 * v[6] == 0xB353C03E1)

# 求解
if s.check() == sat:
model = s.model()
result = [model[v[i]].as_long() for i in range(7)]
print("解为:")
for i, val in enumerate(result):
print(f"v{i} = {val} (0x{val:016X})")

# 将每个 64 位整数转换为 8 字节字符串(小端顺序)
flag = b''.join(val.to_bytes(8, 'little') for val in result)
# 去除非 ASCII 或不可打印字符(如果需要)
try:
print("\n拼接后的字符串为:")
print(flag.decode('ascii', errors='ignore')) # 或 'replace' 替换非法字符
except Exception as e:
print("转换为字符串失败:", e)
else:
print("无解")
# 解为:
#v0 = 1734437990 (0x0000000067616C66)
#v1 = 1596998779 (0x000000005F30447B)
#v2 = 1601515609 (0x000000005F753059)
#v3 = 1999662667 (0x0000000077306E4B)
#v4 = 1129149279 (0x00000000434D735F)
#v5 = 1148073055 (0x00000000446E345F)
#v6 = 2100517471 (0x000000007D335A5F)
#拼接后的字符串为:
#flag{D0_Y0u_Kn0w_sMC_4nD_Z3}