恶意样本自动化配置提取初探
字数 1054 2025-08-06 18:07:33
恶意样本自动化配置提取技术详解
0x00 前言
本文详细讲解如何通过自动化方式从Emotet恶意软件样本中提取ECC加密密钥和C2服务器配置信息。通过分析CAPEv2沙箱的提取代码,结合PE文件结构分析技术,使用Python实现自动化配置提取。
0x01 环境准备
开发环境
- 编程语言: Python
- 外部库:
yara-python: 用于特征匹配pycryptodomex: 处理加密密钥pefile: 解析PE文件结构
- 标准库:
struct: 处理二进制数据socket: 处理IP地址转换itertools: 提供循环迭代功能
- 开发工具: VSCode
样本信息
- MD5: 4e22717b48f2f75fcfd47531c780b218
- SHA1: 0b637e95b1f2d14faaa71085b7e26321bfeeb6d
- SHA256: 7f94107c9becbcc6ca42070fca7e1e63f29cdd85cbbd8953bbca32a1b4f91219
0x02 ECC密钥提取技术
特征定位技术
-
Yara规则编写:
- 分析IDA Pro和x64dbg中的解密代码特征区
- 将地址部分模糊查询,指令码部分精确匹配
- 示例特征规则:
rule Emotet { meta: description = "Emotet ECC Extra" strings: $ref_ecc = {FF B4 [3] 00 00 FF B4 [3] 00 00 8B 94 [3] 00 00 E8 [4] 83 C4 0C 89 84 [3] 00 00 8D 84 [3] 00 00 B9 [4] 50 FF B4 [3]00 00 FF B4 [3]00 00 8B 94 [3]00 00 E8} condition: $ref_ecc }
-
Yara扫描实现:
import yara def yara_scan(raw_data): addresses = {} yara_rules = yara.compile(source=rule_source) matches = yara_rules.match(data=raw_data) for match in matches: for item in match.strings: addresses[item[1]] = hex(item[0]) # 转为16进制 return addresses
数据定位技术
-
PE文件结构解析:
- 使用pefile库获取ImageBase
- 计算RVA(相对虚拟地址)和FOA(文件偏移地址)转换
-
关键数据偏移计算:
def positioning_data(filebuf): pe = pefile.PE(data=filebuf, fast_load=False) image_base = pe.OPTIONAL_HEADER.ImageBase yara_matches = yara_scan(filebuf) if yara_matches.get("$ref_ecc"): ref_ecc_offset = int(yara_matches["$ref_ecc"],16) delta1 = -5 delta2 = 44 ref_eck_rva = struct.unpack("I", filebuf[ref_ecc_offset + delta1 : ref_ecc_offset + delta1 + 4])[0] - image_base ref_ecs_rva = struct.unpack("I", filebuf[ref_ecc_offset + delta2 : ref_ecc_offset + delta2 + 4])[0] - image_base eck_offset = pe.get_offset_from_rva(ref_eck_rva) ecs_offset = pe.get_offset_from_rva(ref_ecs_rva) return eck_offset, ecs_offset
ECC密钥解密技术
-
XOR解密函数:
from itertools import cycle def xor_data(data, key): return bytes(c ^ k for c, k in zip(data, cycle(key))) -
ECC密钥提取与格式化:
from Cryptodome.PublicKey import ECC def extract_ecc(filebuf): conf_dict = {} # 获取eck_offset和ecs_offset... # 提取ECK密钥 key = filebuf[eck_offset : eck_offset + 4] size = struct.unpack("I", filebuf[eck_offset + 4 : eck_offset + 8])[0] ^ struct.unpack("I", key)[0] eck_offset += 8 eck_key = xor_data(filebuf[eck_offset : eck_offset + size], key) key_len = struct.unpack("<I", eck_key[4:8])[0] conf_dict.setdefault( "ECC ECK1", ECC.construct( curve="p256", point_x=int.from_bytes(eck_key[8 : 8 + key_len], "big"), point_y=int.from_bytes(eck_key[8 + key_len :], "big"), ).export_key(format="PEM"), ) # 类似方法提取ECS密钥... return conf_dict
0x03 C2配置提取技术
C2特征定位
-
Yara规则:
rule Emotet { meta: description = "Emotet C2 Extra" strings: $ref_c2 = {FF 74 [2] FF 74 [2] 8B 54 [2] E8 [4] 8B 54 [2] 83 C4 0C 89 44 [2] 8B F8 03 44 [2] B9 [4] 89 44 [2] E9} condition: $ref_c2 } -
C2列表定位:
def positioning_c2_data(filebuf): pe = pefile.PE(data=filebuf, fast_load=False) image_base = pe.OPTIONAL_HEADER.ImageBase yara_matches = yara_scan2(filebuf) if yara_matches.get("$ref_c2"): delta = -5 c2list_va_offset = int(yara_matches["$ref_c2"],16) c2_list_va = struct.unpack("I", filebuf[c2list_va_offset + delta : c2list_va_offset + delta + 4])[0] c2_list_rva = c2_list_va - image_base c2_list_offset = pe.get_offset_from_rva(c2_list_rva) return c2_list_offset
C2配置解密
- IP和端口处理:
import socket def extra_c2_data(filebuf): conf_dict = {} # 获取c2_list_offset... key = filebuf[c2_list_offset : c2_list_offset + 4] presize = filebuf[c2_list_offset + 4 : c2_list_offset + 8] size = struct.unpack("I", presize)[0] ^ struct.unpack("I", key)[0] c2_list_offset += 8 c2_list = xor_data(filebuf[c2_list_offset:], key) offset = 0 while offset < size: ip = struct.unpack(">I", c2_list[offset : offset + 4])[0] c2_address = socket.inet_ntoa(struct.pack("!L", ip)) port = str(struct.unpack(">H", c2_list[offset + 4 : offset + 6])[0]) if not c2_address or not port: break conf_dict.setdefault("address", []).append(f"{c2_address}:{port}") offset += 8 return conf_dict
0x04 完整实现代码
import yara
import pefile
import struct
from Cryptodome.PublicKey import ECC
from itertools import cycle
import socket
rule_source = """
rule Emotet {
meta:
description = "Emotet Config Extra"
strings:
$ref_c2 = {FF 74 [2] FF 74 [2] 8B 54 [2] E8 [4] 8B 54 [2] 83 C4 0C 89 44 [2] 8B F8 03 44 [2] B9 [4] 89 44 [2] E9}
$ref_ecc = {FF B4 [3] 00 00 FF B4 [3] 00 00 8B 94 [3] 00 00 E8 [4] 83 C4 0C 89 84 [3] 00 00 8D 84 [3] 00 00 B9 [4] 50 FF B4 [3]00 00 FF B4 [3]00 00 8B 94 [3]00 00 E8}
condition:
$ref_c2 or $ref_ecc
}
"""
def yara_scan(raw_data):
addresses = {}
yara_rules = yara.compile(source=rule_source)
matches = yara_rules.match(data=raw_data)
for match in matches:
for item in match.strings:
addresses[item[1]] = hex(item[0])
return addresses
def xor_data(data, key):
return bytes(c ^ k for c, k in zip(data, cycle(key)))
def emotet_extract(filebuf):
conf_dict = {}
pe = pefile.PE(data=filebuf, fast_load=False)
image_base = pe.OPTIONAL_HEADER.ImageBase
yara_matches = yara_scan(filebuf)
# C2配置提取
if yara_matches.get("$ref_c2"):
delta = -5
c2list_va_offset = int(yara_matches["$ref_c2"],16)
c2_list_va = struct.unpack("I", filebuf[c2list_va_offset + delta : c2list_va_offset + delta + 4])[0]
c2_list_rva = c2_list_va - image_base
c2_list_offset = pe.get_offset_from_rva(c2_list_rva)
key = filebuf[c2_list_offset : c2_list_offset + 4]
presize = filebuf[c2_list_offset + 4 : c2_list_offset + 8]
size = struct.unpack("I", presize)[0] ^ struct.unpack("I", key)[0]
c2_list_offset += 8
c2_list = xor_data(filebuf[c2_list_offset:], key)
offset = 0
while offset < size:
ip = struct.unpack(">I", c2_list[offset : offset + 4])[0]
c2_address = socket.inet_ntoa(struct.pack("!L", ip))
port = str(struct.unpack(">H", c2_list[offset + 4 : offset + 6])[0])
if not c2_address or not port:
break
conf_dict.setdefault("address", []).append(f"{c2_address}:{port}")
offset += 8
# ECC密钥提取
if yara_matches.get("$ref_ecc"):
ref_ecc_offset = int(yara_matches["$ref_ecc"],16)
delta1 = -5
delta2 = 44
ref_eck_rva = struct.unpack("I", filebuf[ref_ecc_offset + delta1 : ref_ecc_offset + delta1 + 4])[0] - image_base
ref_ecs_rva = struct.unpack("I", filebuf[ref_ecc_offset + delta2 : ref_ecc_offset + delta2 + 4])[0] - image_base
eck_offset = pe.get_offset_from_rva(ref_eck_rva)
ecs_offset = pe.get_offset_from_rva(ref_ecs_rva)
# ECK密钥处理
key = filebuf[eck_offset : eck_offset + 4]
size = struct.unpack("I", filebuf[eck_offset + 4 : eck_offset + 8])[0] ^ struct.unpack("I", key)[0]
eck_offset += 8
eck_key = xor_data(filebuf[eck_offset : eck_offset + size], key)
key_len = struct.unpack("<I", eck_key[4:8])[0]
conf_dict.setdefault(
"ECC ECK1",
ECC.construct(
curve="p256",
point_x=int.from_bytes(eck_key[8 : 8 + key_len], "big"),
point_y=int.from_bytes(eck_key[8 + key_len :], "big"),
).export_key(format="PEM"),
)
# ECS密钥处理
key = filebuf[ecs_offset : ecs_offset + 4]
size = struct.unpack("I", filebuf[ecs_offset + 4 : ecs_offset + 8])[0] ^ struct.unpack("I", key)[0]
ecs_offset += 8
ecs_key = xor_data(filebuf[ecs_offset : ecs_offset + size], key)
key_len = struct.unpack("<I", ecs_key[4:8])[0]
conf_dict.setdefault(
"ECC ECS1",
ECC.construct(
curve="p256",
point_x=int.from_bytes(ecs_key[8 : 8 + key_len], "big"),
point_y=int.from_bytes(ecs_key[8 + key_len :], "big"),
).export_key(format="PEM"),
)
return conf_dict
if __name__ == "__main__":
import sys
with open(sys.argv[1], "rb") as f:
file_data = f.read()
print(emotet_extract(file_data))
0x05 技术总结
-
关键学习点:
- Yara规则编写需要结合静态分析和动态调试结果
- PE文件结构理解是二进制分析的基础
- 官方文档是解决库使用问题的最佳资源
-
技术要点:
- 特征码定位技术
- RVA与FOA转换技术
- XOR循环解密技术
- ECC密钥格式化技术
- IP地址处理技术
-
应用场景:
- 恶意软件自动化分析
- 威胁情报提取
- 安全检测规则生成
0x06 参考资源
通过本教程,您已经掌握了从Emotet恶意软件样本中自动化提取配置信息的关键技术,这些技术同样适用于其他恶意软件家族的分析工作。