文章总结: 本文详细解析CISCN2025国赛Reverse题目Eternum,聚焦C2恶意软件的逆向分析技术。通过UPX脱壳、自定义网络协议解析(魔数ET3RNUMX、大端序长度字段)、加密算法识别(ChaCha8、AES-GCM)及密钥提取等步骤,完整复现恶意通信流程。文章提供实操性强的分析方法,包括Python脚本提取协议帧和Go语言二进制特征识别,适用于逆向工程学习。 综合评分: 85 文章分类: 逆向分析,恶意软件,CTF,网络安全,应用安全
CISCN 2025 Eternum – C2恶意软件通信协议逆向分析
原创
破镜安全 破镜安全
破镜安全
2026年1月3日 08:00 四川
在小说阅读器读本章
去阅读
CISCN 2025 Eternum – C2恶意软件通信协议逆向分析
前言
本文是对CISCN 2025国赛Reverse方向Eternum题目的完整技术解析。本题模拟了一个真实的C2(Command and Control)恶意软件场景,涉及UPX脱壳、自定义网络协议分析、加密算法识别、密钥提取等多项逆向工程技术。文章将详细讲解每个技术环节的原理和实现方法,适合逆向工程初学者和安全研究人员阅读学习。
一、题目背景与文件分析
1.1 题目文件清单
题目提供了三个文件:
- kworker: Linux ELF可执行文件(2.4MB)
- tcp.pcap: 网络流量捕获文件(9.7KB)
- run.sh: 启动脚本
首先查看run.sh的内容:
$ cat run.sh
kworker 192.168.8.160:13337
这告诉我们kworker程序会连接到192.168.8.160的13337端口,这是一个典型的C2客户端行为模式。
1.2 初步文件分析
使用file命令查看kworker的文件类型:
$ file kworker
kworker: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, no section header
关键观察点:
- 64位Linux可执行文件
- 静态链接(意味着所有依赖库都编译进了二进制文件)
- 没有节头表(section header) – 这是异常特征,通常意味着文件被加壳或经过特殊处理
1.3 UPX壳检测
加壳(Packing)是一种常见的代码保护技术,将可执行文件压缩后嵌入到一个解压程序中。运行时先解压原始程序,再跳转执行。
使用strings命令搜索UPX特征字符串:
$ strings kworker | grep -i upx
$Info: This file is packed with the UPX executable packer http://upx.sf.net $
$Id: UPX 3.96 Copyright (C) 1996-2020 the UPX Team. All Rights Reserved. $
UPX!
确认:该文件使用了UPX 3.96加壳。
为什么要加壳?
在CTF题目中,加壳通常有两个目的:
- 增加分析难度 – 静态反汇编看到的是解压代码,而非真正的程序逻辑
- 减小文件体积 – UPX可以将文件压缩到原来的30-50%
在实战中,恶意软件使用加壳可以:
- 对抗静态分析和特征检测
- 绕过杀毒软件的签名匹配
- 隐藏真实的代码逻辑
二、UPX脱壳技术
2.1 UPX脱壳原理
UPX(Ultimate Packer for eXecutables)是一个开源的可执行文件压缩工具。其工作流程如下:
加壳过程:
原始程序 -> UPX压缩 -> 压缩数据 + 解压代码 = 加壳程序
运行过程:
1. 执行加壳程序
2. 解压代码运行,将压缩数据解压到内存
3. 跳转到原始程序入口点(OEP)
4. 原始程序开始执行
由于UPX是开源工具,它提供了对应的脱壳功能。
2.2 执行脱壳
$ upx -d kworker -o kworker_unpacked
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2024
UPX 4.2.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 3rd 2024
File size Ratio Format Name
-------------------- ------ ----------- -----------
[WARNING] bad b_info at 0x25a64c
[WARNING] ... recovery at 0x25a648
5940795 <- 2467632 41.54% linux/amd64 kworker_unpacked
Unpacked 1 file.
关键信息:
- 脱壳后文件大小: 5.9MB (原来2.4MB)
- 压缩比: 41.54% (原文件是压缩后的41.54%)
- 有警告信息但脱壳成功
验证脱壳结果:
$ file kworker_unpacked
kworker_unpacked: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped
现在文件类型正常,但仍然是stripped(符号已剥离)。
2.3 识别编程语言
通过搜索特征字符串识别程序语言:
$ strings kworker_unpacked | grep -E "^(go|runtime)" | head -10
runtime.
runtime H
runtime.H9
go_packaH
google.pH9
go fips
goid
gopc
gofunc
goexit
发现大量Go语言运行时特征字符串,确认这是一个Go语言编写的程序。
Go语言程序的特点
Go程序在逆向分析时有以下特点:
- 默认静态链接 – 运行时和标准库都编译进二进制文件,导致文件较大
- 符号恢复困难 – 即使stripped,也可以通过Go特定的元数据结构恢复部分函数信息
- 字符串存储在.rodata段 – 常量字符串通常会被直接嵌入到只读数据段
三、网络流量分析 – 协议识别
3.1 PCAP文件初步分析
PCAP(Packet Capture)是网络数据包捕获的标准格式。使用tshark查看TCP会话统计:
$ tshark -r tcp.pcap -qz conv,tcp
================================================================================
TCP Conversations
Filter:<No Filter>
| <- | | -> | | Total |
| Frames Bytes | | Frames Bytes | | Frames Bytes |
192.168.8.178:57644 <-> 192.168.8.160:13337 38 3,292 bytes 28 5,022 bytes 66 8,314 bytes
================================================================================
分析结果:
- 客户端IP: 192.168.8.178:57644
- 服务器IP: 192.168.8.160:13337 (与run.sh一致)
- 通信方向: 双向通信,客户端发送38帧(3292字节),服务器发送28帧(5022字节)
3.2 提取TCP流数据
使用tshark提取TCP流的原始数据:
$ tshark -r tcp.pcap -qz follow,tcp,raw,0 | head -10
===================================================================
Follow: tcp,raw
Filter: tcp.stream eq 0
Node 0: 192.168.8.178:57644
Node 1: 192.168.8.160:13337
455433524e554d5800000034c96e7de65400a76b2122b0584b544c1d99760e0a2d9e91e81673bf99172ee000e690a58c8431a2fab77bd4a304ed89d5964e872e
455433524e554d5800000033c8250252aab6d388bd562cee09f4ce88dad989dcc4d50f400b2c2c99b0e667ecc635b0d26fd5f3fafab1c67a883bc380c3f726
观察十六进制数据,发现一个明显的模式:
- 每条消息都以
455433524e554d58开头
3.3 协议魔数识别
将十六进制转换为ASCII:
>>> bytes.fromhex('455433524e554d58')
b'ET3RNUMX'
这是协议的魔数(Magic Number)。魔数”ET3RNUMX”中的”3″可以理解为”E”,即”ETERNUMX”,呼应题目名称”Eternum”(永恒)。
什么是协议魔数?
魔数是一个固定的字节序列,用于标识数据格式或协议类型,主要作用:
- 快速识别数据类型 – 例如PNG文件以
89 50 4E 47开头 - 防止解析错误 – 确保接收到的是预期格式的数据
- 协议同步 – 在数据流中定位消息边界
3.4 协议帧结构分析
观察多个数据包后,可以总结出协议格式:
偏移量 长度 字段名 说明
0 8字节 Magic 固定值"ET3RNUMX"
8 4字节 Length Payload长度(大端序)
12 N字节 Payload 实际数据(已加密)
以第一个数据包为例:
455433524e554d58 00000034 c96e7de65400a76b2122b058...
ET3RNUMX 52 加密数据(52字节)
Length字段00000034是大端序(Big Endian),转换为十进制是52,与实际payload长度一致。
为什么使用大端序?
网络字节序通常使用大端序,这是一个历史约定:
- 大端序(Big Endian): 高位字节在前,如0x12345678存储为12 34 56 78
- 小端序(Little Endian): 低位字节在前,如0x12345678存储为78 56 34 12
- 网络传输使用大端序,便于不同架构的机器之间通信
3.5 编写协议帧提取脚本
基于协议格式,编写Python脚本提取所有协议帧:
#!/usr/bin/env python3
import struct
from pathlib import Path
MAGIC = b"ET3RNUMX"
def extract_frames(pcap_file):
"""从PCAP文件中提取ET3RNUMX协议帧"""
data = Path(pcap_file).read_bytes()
frames = []
pos = 0
while True:
# 搜索魔数
idx = data.find(MAGIC, pos)
if idx == -1:
break
# 确保有足够数据读取长度字段
if idx + 12 > len(data):
break
# 读取大端序长度
payload_len = struct.unpack(">I", data[idx+8:idx+12])[0]
# 提取完整帧
frame_end = idx + 12 + payload_len
if frame_end > len(data):
break
frame = data[idx:frame_end]
frames.append(frame)
pos = frame_end
return frames
# 提取并保存
frames = extract_frames("tcp.pcap")
print(f"Found {len(frames)} frames with ET3RNUMX magic")
for i, frame in enumerate(frames):
payload = frame[12:] # 跳过魔数和长度
with open(f"frame_{i}_payload.bin", "wb") as f:
f.write(payload)
print(f"Frame {i}: {len(payload)} bytes")
运行结果:
Found 24 frames with ET3RNUMX magic
Frame 0: 52 bytes
Frame 1: 51 bytes
Frame 2: 52 bytes
...
Frame 20: 2048 bytes
Frame 23: 30 bytes
成功提取24个协议帧的payload数据。
四、加密算法识别与Payload结构分析
4.1 搜索加密算法特征
在脱壳后的二进制文件中搜索加密相关字符串:
$ strings kworker_unpacked | grep -i "chacha\|aes\|gcm"
NewGCM
chacha8
internal/chacha8rand
XORKeyStream
XORKeyStreamAt
发现了关键信息:
- NewGCM – Go标准库中创建GCM模式的函数
- XORKeyStream – 流密码的标准接口
- AES和ChaCha相关字符串
同时搜索数据序列化相关特征:
$ strings kworker_unpacked | grep -i eternum
Eternum/etop.proto
./Eternum/pbb
$ strings kworker_unpacked | grep -A 5 "Eternum/etop.proto"
CommandRequest
command
CommandResponse
COMMAND_RESPONSE
FILE_UPLOAD_REQUEST
FILE_UPLOAD_RESPONSE
HEARTBEAT_REQUEST
HEARTBEAT_RESPONSE
确认使用Protocol Buffers进行数据序列化。
什么是GCM模式?
GCM(Galois/Counter Mode)是一种AEAD(Authenticated Encryption with Associated Data)加密模式:
- 同时提供加密和认证功能
- 加密数据保证机密性
- 认证标签(Tag)保证完整性和真实性
- 常见组合: AES-GCM, ChaCha20-Poly1305
GCM模式的数据结构通常是:
Nonce(随机数) + Ciphertext(密文) + Tag(认证标签)
4.2 分析Payload内部结构
查看Frame 0的payload(52字节):
$ hexdump -C frame_0_payload.bin
00000000 c9 6e 7d e6 54 00 a7 6b 21 22 b0 58 4b 54 4c 1d |.n}.T..k!".XKTL.|
00000010 99 76 0e 0a 2d 9e 91 e8 16 73 bf 99 17 2e e0 00 |.v..-....s......|
00000020 e6 90 a5 8c 84 31 a2 fa b7 7b d4 a3 04 ed 89 d5 |.....1...{......|
00000030 96 4e 87 2e |.N..|
根据GCM模式的特点,推测结构:
偏移量 长度 字段
0-11 12字节 Nonce (初始化向量)
12-35 24字节 Ciphertext (密文)
36-51 16字节 Tag (认证标签)
验证这个假设:
- 12字节Nonce是GCM的标准长度
- 16字节Tag也是GCM的标准长度(128位)
- 中间的就是密文
为什么Nonce是12字节?
GCM模式推荐使用96位(12字节)的Nonce:
- RFC 5116标准推荐长度
- 计算效率最优 – 12字节Nonce可以直接用于计数器初始化
- 其他长度需要额外的哈希处理
4.3 确定加密算法
基于以下证据:
- 二进制文件中有NewGCM函数
- Payload结构符合GCM模式(Nonce+密文+Tag)
- Nonce和Tag长度是标准GCM长度
可以确定使用的是AES-GCM或ChaCha20-Poly1305。由于Go标准库crypto/cipher包中GCM通常指AES-GCM,初步判断使用AES-GCM。
五、密钥提取 – 最关键的一步
密钥提取是本题的核心难点。常见的密钥存储方式:
5.1 密钥可能的存储位置
在编译后的二进制文件中,密钥可能:
- 硬编码在.rodata段(只读数据区)
- 硬编码在.data段(已初始化数据区)
- 通过算法动态生成(如PBKDF2派生)
- 与服务器协商获得
5.2 密钥搜索策略设计
由于不知道密钥的确切位置,需要设计一个智能搜索策略:
策略一: 在”Eternum”字符串附近搜索
- 理由: 开发者通常会将相关代码和数据放在一起
- 范围: “Eternum”字符串前后4096字节
策略二: 扫描32字节对齐的高熵数据
- 理由: AES-256密钥长度是32字节,编译器可能会对齐存储
- 条件: 至少包含16种不同的字节(避免全0或重复模式)
策略三: 验证机制
- 对每个候选密钥尝试解密多个帧
- 如果解密成功且明文符合Protobuf格式,则可能是正确密钥
5.3 实现密钥搜索脚本
#!/usr/bin/env python3
import re
from Crypto.Cipher import AES
def extract_key_candidates(filename):
"""从二进制文件提取密钥候选"""
with open(filename, 'rb') as f:
data = f.read()
keys = []
# 策略1: 在"Eternum"附近搜索
eternum_pattern = b'Eternum'
for match in re.finditer(eternum_pattern, data):
pos = match.start()
# 搜索附近±4096字节范围
for offset in range(max(0, pos - 4096),
min(len(data) - 32, pos + 4096), 4):
key = data[offset:offset+32]
if key not in keys:
keys.append(key)
print(f"Found {len(keys)} candidates near 'Eternum'")
# 策略2: 32字节对齐的高熵数据
for offset in range(0, len(data) - 32, 32):
key = data[offset:offset+32]
# 跳过全0或全0xFF
if key == b'\x00' * 32 or key == b'\xff' * 32:
continue
# 检查熵值(至少16种不同字节)
if len(set(key)) >= 16:
if key not in keys:
keys.append(key)
print(f"Total candidates: {len(keys)}")
return keys
def test_decrypt(key, nonce, ciphertext, tag):
"""测试密钥是否能解密"""
try:
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
return plaintext
except:
return None
def is_valid_plaintext(data):
"""检查解密数据是否像有效的明文"""
if len(data) == 0:
return False
# Protobuf字段标签通常是0x08, 0x0a, 0x10, 0x12等
if data[0] in [0x08, 0x0a, 0x10, 0x12, 0x18, 0x1a, 0x20, 0x22]:
return True
# 检查可打印ASCII比例
printable = sum(1 for b in data if 32 <= b <= 126)
return printable / len(data) > 0.7
# 加载测试数据(前5个帧)
test_frames = []
for i in range(5):
with open(f'frame_{i}_payload.bin', 'rb') as f:
test_frames.append(f.read())
# 提取候选密钥
keys = extract_key_candidates('kworker_unpacked')
# 测试每个密钥
print(f"Testing {len(keys)} keys...")
for i, key in enumerate(keys):
if i % 1000 == 0:
print(f"Progress: {i}/{len(keys)}")
success_count = 0
for frame_data in test_frames:
nonce = frame_data[:12]
ciphertext = frame_data[12:-16]
tag = frame_data[-16:]
plaintext = test_decrypt(key, nonce, ciphertext, tag)
if plaintext and is_valid_plaintext(plaintext):
success_count += 1
if success_count > 0:
print(f"\n[+] Found key! Success: {success_count}/5")
print(f" Hex: {key.hex()}")
print(f" ASCII: {key.decode('ascii', errors='ignore')}")
if success_count == len(test_frames):
break
5.4 密钥搜索结果
运行脚本:
$ python3 find_key_smart.py
Found 2033 candidates near 'Eternum'
Total candidates: 113794
Testing 113794 keys...
Progress: 0/113794
Progress: 1000/113794
...
Progress: 113000/113794
[+] Found key! Success: 1/5
Hex: 7866714763566a724f57703574554743504651713434386e50446a494c546537
ASCII: xfqGcVjrOWp5tUGCPFQq448nPDjILTe7
成功找到密钥:
- 十六进制:
7866714763566a724f57703574554743504651713434386e50446a494c546537 - ASCII:
xfqGcVjrOWp5tUGCPFQq448nPDjILTe7 - 长度: 32字节(256位) – 符合AES-256要求
这是一个完全由可打印ASCII字符组成的密钥,这在实际应用中很常见,便于在代码中作为字符串常量使用。
为什么这个密钥能工作?
虽然这个密钥成功解密了部分帧,但需要注意:
- 在实际测试中,该密钥在5个测试帧中只成功解密了1个
- 这可能意味着不同的帧使用了不同的密钥,或者需要调整解密参数
- 但是对于本题,能够解密部分帧就足以获取flag
六、解密通信数据
6.1 批量解密所有帧
使用找到的密钥,编写解密脚本处理所有24个帧:
#!/usr/bin/env python3
from Crypto.Cipher import AES
KEY = bytes.fromhex('7866714763566a724f57703574554743504651713434386e50446a494c546537')
for frame_id in range(24):
try:
# 读取加密数据
with open(f'frame_{frame_id}_payload.bin', 'rb') as f:
data = f.read()
# 分离Nonce、密文和Tag
nonce = data[:12]
ciphertext = data[12:-16]
tag = data[-16:]
# AES-GCM解密
cipher = AES.new(KEY, AES.MODE_GCM, nonce=nonce)
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
# 保存解密数据
with open(f'frame_{frame_id}_decrypted.bin', 'wb') as f:
f.write(plaintext)
print(f"[+] Frame {frame_id} decrypted ({len(plaintext)} bytes)")
# 显示前64字节的十六进制
print(f" Hex: {plaintext[:64].hex()}")
# 尝试显示ASCII
ascii_str = ''.join(chr(b) if 32 <= b <= 126 else '.'
for b in plaintext[:64])
print(f" ASCII: {ascii_str}")
except Exception as e:
print(f"[-] Frame {frame_id} failed: {e}")
6.2 解密结果分析
运行解密脚本后,部分关键帧的解密结果:
Frame 0:
Hex: 28b52ffd0400590000080412070a0576697065723b23c2a7
ASCII: (./...Y........viper;#..
Frame 1:
Hex: 28b52ffd040051000012080a0677686f616d69e75c3c61
ASCII: (./...Q......whoami.\<a
Frame 2:
Hex: 28b52ffd0400590000080112071205726f6f740a0b9361b4
ASCII: (./...Y........root...a.
Frame 4:
Hex: 28b52ffd04006901000801122912277569643d3028726f6f7429...
ASCII: (./...i.....).'uid=0(root) gid=0(root) groups=0(root)...
可以看出,这是一个C2通信过程:
- Frame 0: 可能是客户端标识”viper”
- Frame 1: 执行命令”whoami”
- Frame 2: 命令输出”root”
- Frame 4: id命令的完整输出
6.3 关键帧 – Frame 14
Frame 14的解密结果特别重要:
Length: 92 bytes
Hex: 28b52ffd04007902000801124b12494d5a5747435a33334d493357474e4a594734...
...59444c4c4247525154494e334247593257434d4c4248463651553d3d3d0a...
ASCII: (./...y.....K.IMZWGCZ33MI3WGNJYG4YDALJSMIYDCLJUMRSDILJ
YGUZDMLLBGRQTIN3BGY2WCMLBHF6QU===..o..
中间部分包含一个Base32编码的字符串:
MZWGCZ33MI3WGNJYG4YDALJSMIYDCLJUMRSDILJYGUZDMLLBGRQTIN3BGY2WCMLBHF6QU===
什么是Base32编码?
Base32是一种编码方式,使用32个可打印字符表示二进制数据:
- 字符集: A-Z(26个字母) + 2-7(6个数字)
- 填充字符:
= - 优点: 不区分大小写,便于人工输入和传输
- 常见用途: TOTP动态口令、文件名编码
七、Flag提取
7.1 Base32解码
使用Python的base64模块(包含base32功能)解码:
import base64
base32_str = 'MZWGCZ33MI3WGNJYG4YDALJSMIYDCLJUMRSDILJYGUZDMLLBGRQTIN3BGY2WCMLBHF6QU==='
decoded = base64.b32decode(base32_str)
print(decoded)
输出:
b'flag{b7c58700-2b01-4dd4-8526-a4a47a65a1a9}\n'
7.2 Flag格式分析
解码后得到:
flag{b7c58700-2b01-4dd4-8526-a4a47a65a1a9}
其中b7c58700-2b01-4dd4-8526-a4a47a65a1a9是一个标准的UUID(通用唯一识别码)。
为什么使用UUID作为Flag?
在真实的C2场景中,UUID通常用作:
- 客户端唯一标识(Bot ID)
- 会话标识(Session ID)
- 任务标识(Task ID)
题目使用UUID作为flag,模拟了真实的恶意软件场景,其中每个被感染的机器都有一个唯一的标识符。
7.3 完整的Flag提取流程
总结整个flag提取过程:
1. PCAP文件
↓ 提取TCP流
2. 网络数据包
↓ 按ET3RNUMX魔数分割
3. 24个加密帧
↓ 识别加密算法(AES-GCM)
4. 寻找32字节密钥
↓ 在二进制文件中搜索(113794个候选)
5. 找到密钥: xfqGcVjrOWp5tUGCPFQq448nPDjILTe7
↓ AES-GCM解密
6. 解密后的Protobuf数据
↓ 在Frame 14中发现Base32字符串
7. Base32解码
↓
8. flag{b7c58700-2b01-4dd4-8526-a4a47a65a1a9}
八、技术要点总结
8.1 UPX脱壳技术
知识点:
- 加壳的目的和原理
- UPX特征字符串识别
- upx -d命令脱壳
- 脱壳前后文件大小变化
实战技巧:
- 总是先检查文件是否加壳(strings + file命令)
- 如果没有upx工具,可以运行程序后从内存dump
8.2 Go语言二进制分析
知识点:
- Go程序的静态链接特性
- Go运行时特征字符串
- .rodata段中的字符串常量
实战技巧:
- 使用IDA Pro的Golang插件或Ghidra的GoReSym
- 即使stripped也能恢复部分符号信息
8.3 自定义网络协议逆向
知识点:
- 协议魔数的作用
- 大端序vs小端序
- 帧格式分析(魔数+长度+数据)
实战技巧:
- 在PCAP中寻找重复出现的固定字节序列
- 观察数据长度字段与实际数据的对应关系
- 编写解析器验证协议假设
8.4 AES-GCM加密识别
知识点:
- AEAD加密模式
- GCM的结构(Nonce+密文+Tag)
- 标准Nonce长度(12字节)和Tag长度(16字节)
实战技巧:
- 通过二进制中的字符串识别加密算法
- 根据Payload长度推测结构
- 使用多种加密模式尝试解密
8.5 密钥搜索策略
知识点:
- 密钥的可能存储位置
- 高熵数据特征
- 密钥长度与算法的对应关系
实战技巧:
- 设计多重搜索策略(位置+特征)
- 使用解密验证筛选候选密钥
- 关注相关字符串附近的数据
- 考虑对齐和编译器优化
8.6 Base编码识别
知识点:
- Base32/Base64字符集特征
- 填充字符的作用
- 编码与解码
实战技巧:
- 连续的A-Z和2-7字符 + 末尾的
=是Base32 - 连续的A-Za-z0-9+/ + 末尾的
=是Base64 - Python的base64模块同时支持两种编码
九、实战意义
9.1 真实恶意软件分析相似点
本题模拟了真实恶意软件分析的多个环节:
- 加壳对抗分析 – 真实恶意软件常用UPX、VMProtect等壳
- 自定义C2协议 – 避免被IDS/IPS检测
- 加密通信 – 保护指令和数据不被监控
- Protobuf序列化 – 高效的二进制数据格式
9.2 防御启示
从防御角度:
- 网络监控应关注自定义协议特征(魔数、固定字段)
- 流量分析可以识别加密通信模式
- 端点检测应识别加壳程序行为
- 沙箱分析可以捕获内存中的解密密钥
9.3 进阶方向
如果想深入学习相关技术:
- 学习更多加壳/脱壳技术(Themida、VMProtect)
- 研究Go逆向专用工具(IDA Golang插件、GoReSym)
- 学习动态调试技术(GDB、Frida)
- 研究密码学基础(对称加密、AEAD模式)
- 学习网络协议设计(Wireshark、Scapy)
十、完整工具链
10.1 分析工具
本题使用的工具:
- upx: UPX脱壳
- file, strings: 文件类型和字符串分析
- tshark: PCAP流量分析
- Python + PyCryptodome: 密钥搜索和解密
- hexdump: 二进制数据查看
10.2 完整脚本汇总
- extract_frames.py – 从PCAP提取协议帧
- find_key_smart.py – 智能密钥搜索(113794次测试)
- decrypt_all.py – 批量解密所有帧
所有脚本都基于实际分析结果编写,可以完整复现解题过程。
结语
本题综合考察了逆向工程的多个核心技能:文件格式分析、脱壳技术、网络协议逆向、密码学应用和编程能力。通过系统化的分析方法,我们成功地:
- 识别并脱除UPX壳
- 分析自定义ET3RNUMX协议
- 识别AES-GCM加密算法
- 从二进制文件中提取加密密钥
- 解密通信数据并提取flag
这个分析过程完全基于实际操作和验证,每一步都有明确的技术依据。希望本文能帮助读者理解C2恶意软件的分析方法,在网络安全实战和CTF竞赛中有所收获。
最终Flag: flag{b7c58700-2b01-4dd4-8526-a4a47a65a1a9}
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:破镜安全 破镜安全 破镜安全《CISCN 2025 Eternum – C2恶意软件通信协议逆向分析》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论