这篇博客的背景是:如果很多进程都恶意程序通过远进程注入了线程,那么应该怎么清除呢?

下面给出两种方法

PLAN A

来自 加号

通过遍历线程后,根据获取到的线程信息,对线程地址和入口代码进行检查,这种方式适合对注入代码偏移位置固定或则入口代码固定,能准确查杀,推荐使用

void Killing::KillMalRemoteThread()
{
    (FARPROC&)ZwQueryInformationThread = GetProcAddress(m_hNtdll, "ZwQueryInformationThread");

    HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    THREADENTRY32 te32;

    te32.dwSize = sizeof(THREADENTRY32);
    Thread32First(hThreadSnap, &te32);
    HANDLE hThread;
    do {
        hThread = OpenThread(THREAD_ALL_ACCESS, false, te32.th32ThreadID);

        setlocale(LC_ALL, ".ACP");

        DWORD startAddr;
        ZwQueryInformationThread(hThread, ThreadQuerySetWin32StartAddress, &startAddr, sizeof(PVOID), NULL);

        printf("进程 %d, 线程 %d, 入口地址 0x%x\n", te32.th32OwnerProcessID, te32.th32ThreadID, startAddr);

        if (startAddr > 0x400000 && startAddr < 0x80000000)
        {
            if ((startAddr & 0xFFFF) == 0x3A72 || (startAddr & 0xFFFF) == 0x36F4)//判断进程入口地址2字节
            {
                PBYTE pbMapBase = (PBYTE)((startAddr & 0xFFFF) == 0x3A72 ? startAddr - 0x3A72 : startAddr - 0x36F4);
                HANDLE hOwnerProc = OpenProcess(PROCESS_VM_READ, false, te32.th32OwnerProcessID);

                BYTE sectionOff_0[4] = { 0 };
                BYTE sectionOff_2F8[5] = { 0 };
                ReadProcessMemory(hOwnerProc, pbMapBase, sectionOff_0, 4, NULL);
                ReadProcessMemory(hOwnerProc, pbMapBase + 0x2F8, sectionOff_2F8, 5, NULL);

                if (*(DWORD*)& sectionOff_0 == 0xFF243C83 &&
                    *(DWORD*)& sectionOff_2F8 == 0x00012DE9 &&
                    sectionOff_2F8[4] == 0x00)//进一步检查入口代码是否为特定值
                {
                    printf("########\n恶意线程,在进程 %d, 线程 %d, 入口地址 0x%x\n", te32.th32OwnerProcessID, te32.th32ThreadID, startAddr);
                    if (TerminateThread(hThread, -1))
                    {
                        printf("已终止该线程\n");
                    }
                    printf("########\n");
                }
            }
        }
        CloseHandle(hThread);

    } while (Thread32Next(hThreadSnap, &te32));

    CloseHandle(hThreadSnap);
}

PLAN B

而这个方案就比较猛一些,可以直接干掉所有不在模块中的线程,要小心一点

因为普通线程创建都会在进程的已知模块中,而恶意代码创建的就是不属于任何模块,也可以用过指定模块名来关闭线程

void WINAPI ClearThread()
{
    int Flag = 3;

    while (Flag)
    {
        Sleep(1000);
        HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);    // 进程快照句柄
        PROCESSENTRY32 process = { sizeof(PROCESSENTRY32) };                        // 进程快照信息

        // 遍历进程
        while (Process32Next(hProcessSnap, &process))
        th32ProcessID{
            HANDLE hThreadSnap = INVALID_HANDLE_VALUE;            // 线程快照句柄 
            THREADENTRY32 te32;                                    // 线程快照信息
            // 创建线程快照
            hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
            if (hThreadSnap == INVALID_HANDLE_VALUE) { printf("创建线程快照失败"); }

            // 为快照分派内存空间
            te32.dwSize = sizeof(THREADENTRY32);

            // 获取第一个线程的信息
            if (!Thread32First(hThreadSnap, &te32)) { printf("线程信息获取失败"); }

            // 遍历线程
            while (Thread32Next(hThreadSnap, &te32))
            {
                if (te32.th32OwnerProcessID == process.th32ProcessID)
                {
                    // 打开线程
                    //printf("PID=%d, TID=%d\n",te32.th32OwnerProcessID,te32.th32ThreadID);

                    HANDLE hThread = ::OpenThread(
                        THREAD_ALL_ACCESS,        // 访问权限,THREAD_ALL_ACCESS :所有权限
                        FALSE,                    // 由此线程创建的进程不继承线程的句柄
                        te32.th32ThreadID        // 线程 ID
                        );
                    if (hThread == NULL) 
                    { 
                        //printf("线程打开失败%x\n", GetLastError()); 
                        continue; 
                    }

                    // 将区域设置设置为从操作系统获取的ANSI代码页
                    setlocale(LC_ALL, ".ACP");

                    // 获取 ntdll.dll 的模块句柄
                    HINSTANCE hNTDLL = ::GetModuleHandleA("ntdll");

                    // 从 ntdll.dll 中取出 ZwQueryInformationThread
                    (FARPROC&)ZwQueryInformationThread = ::GetProcAddress(hNTDLL, "ZwQueryInformationThread");

                    // 获取线程入口地址
                    PVOID startaddr;                        // 用来接收线程入口地址
                    ZwQueryInformationThread(
                        hThread,                            // 线程句柄
                        ThreadQuerySetWin32StartAddress,    // 线程信息类型,ThreadQuerySetWin32StartAddress :线程入口地址
                        &startaddr,                            // 指向缓冲区的指针
                        sizeof(startaddr),                    // 缓冲区的大小
                        NULL
                        );

                    // 获取线程所在模块
                    THREAD_BASIC_INFORMATION tbi;            // _THREAD_BASIC_INFORMATION 结构体对象
                    TCHAR modname[MAX_PATH];                // 用来接收模块全路径
                    ZwQueryInformationThread(
                        hThread,                            // 线程句柄
                        ThreadBasicInformation,                // 线程信息类型,ThreadBasicInformation :线程基本信息
                        &tbi,                                // 指向缓冲区的指针
                        sizeof(tbi),                        // 缓冲区的大小
                        NULL
                        );

                    // 检查入口地址是否位于某模块中
                    GetMappedFileName(
                        ::OpenProcess(                        // 进程句柄
                        PROCESS_ALL_ACCESS,                                    // 访问权限,THREAD_ALL_ACCESS :所有权限
                        FALSE,                                                // 由此线程创建的进程不继承线程的句柄
                        (DWORD)tbi.ClientId.UniqueProcess                    // 唯一进程 ID
                        ),
                        startaddr,                            // 要检查的地址
                        modname,                            // 用来接收模块名的指针
                        MAX_PATH                            // 缓冲区大小
                        );

                    // 判断线程是否在模块中

                    if (modname[0] == NULL)
                    {            
                        //modname是模块名的指针,可以比较是否是恶意模块名
                        printf("线程不在模块中: th32ProcessID=%d, TID=%d  \n", process.th32ProcessID, te32.th32ThreadID);
                        if (TerminateThread(hThread, -1))
                        {
                            printf("线程已被清理\n");
                        }
                    }
                    modname[0] = NULL;
                }
            }
        }
        Flag -= 1;
    }
}

如果病毒还有提权操作的话,我们也需要提高权限去对抗

常见的提权方式为调整令牌 详见



逆向

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!