【Windows】在任务管理器中隐藏进程

在此前的一篇,我们已经介绍过了注入Dll 阻止任务管理器结束进程 -- Win 10/11。本篇利用 hook NtQuerySystemInformation 并进行断链的方法实现进程隐身,实测支持 taskmgr.exe 的任意多进程隐身。
任务管理器

代码:

cpp 复制代码
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <detours/detours.h>
#include <winternl.h>
#include <string>
#include <iostream>
#include <stdio.h>
#include <vector>
#include <shared_mutex>

#pragma comment(lib, "detours.lib")
#pragma comment(lib, "user32.lib")

typedef struct _VM_COUNTERS
{
    SIZE_T        PeakVirtualSize;
    SIZE_T        VirtualSize;
    ULONG         PageFaultCount;
    SIZE_T        PeakWorkingSetSize;
    SIZE_T        WorkingSetSize;
    SIZE_T        QuotaPeakPagedPoolUsage;
    SIZE_T        QuotaPagedPoolUsage;
    SIZE_T        QuotaPeakNonPagedPoolUsage;
    SIZE_T        QuotaNonPagedPoolUsage;
    SIZE_T        PagefileUsage;
    SIZE_T        PeakPagefileUsage;
} VM_COUNTERS;

// 线程信息结构体
typedef struct _MY_SYSTEM_THREAD_INFORMATION
{
    LARGE_INTEGER   KernelTime;
    LARGE_INTEGER   UserTime;
    LARGE_INTEGER   CreateTime;
    ULONG           WaitTime;
    PVOID           StartAddress;
    CLIENT_ID       ClientId;
    KPRIORITY       Priority;
    KPRIORITY       BasePriority;
    ULONG           ContextSwitchCount;
    LONG            State;// 状态,是THREAD_STATE枚举类型中的一个值
    LONG            WaitReason;//等待原因, KWAIT_REASON中的一个值
} MY_SYSTEM_THREAD_INFORMATION, * PMY_SYSTEM_THREAD_INFORMATION;


typedef struct _MY_UNICODE_STRING
{
    USHORT Length;
    USHORT MaximumLength;
    PWSTR  Buffer;
} MY_UNICODE_STRING, * PMY_UNICODE_STRING;


typedef struct _MY_SYSTEM_PROCESS_INFORMATION
{
    ULONG            NextEntryOffset; // 指向下一个结构体的指针
    ULONG            ThreadCount; // 本进程的总线程数
    ULONG            Reserved1[6]; // 保留
    LARGE_INTEGER    CreateTime; // 进程的创建时间
    LARGE_INTEGER    UserTime; // 在用户层的使用时间
    LARGE_INTEGER    KernelTime; // 在内核层的使用时间
    MY_UNICODE_STRING   ImageName; // 进程名
    KPRIORITY        BasePriority; // 
    ULONG            ProcessId; // 进程ID
    ULONG            InheritedFromProcessId;
    ULONG            HandleCount; // 进程的句柄总数
    ULONG            Reserved2[2]; // 保留
    VM_COUNTERS      VmCounters;
    IO_COUNTERS      IoCounters;
    SYSTEM_THREAD_INFORMATION Threads[5]; // 子线程信息数组
}MY_SYSTEM_PROCESS_INFORMATION, * PMY_SYSTEM_PROCESS_INFORMATION;


// 定义一个指针函数类型
typedef NTSTATUS(WINAPI* __NtQuerySystemInformation)(
    SYSTEM_INFORMATION_CLASS SystemInformationClass,
    PVOID SystemInformation,
    ULONG SystemInformationLength,
    PULONG ReturnLength
    );

// 定义一个存放原函数的指针
PVOID fpNtQuerySystemInformation = NULL;
// 读写锁
std::shared_mutex ppNameListMutex;
// 受保护进程名列表
std::vector<std::wstring> ppNameList;

// 声明函数
extern "C" {
    __declspec(dllexport)
        void StartHookingFunction();
    __declspec(dllexport) 
        void UnmappHookedFunction();
    __declspec(dllexport) 
        bool SetProtectedProcessListFromBuffer(const wchar_t* buffer, size_t length);
}

NTSTATUS WINAPI HookedNtQuerySystemInformation(
    SYSTEM_INFORMATION_CLASS SystemInformationClass,
    PVOID SystemInformation,
    ULONG SystemInformationLength,
    PULONG ReturnLength
);


void OpenDebugConsole()
{
    AllocConsole();
    FILE* fDummy;
    freopen_s(&fDummy, "CONOUT$", "w", stdout);
    freopen_s(&fDummy, "CONOUT$", "w", stderr);
    freopen_s(&fDummy, "CONIN$", "r", stdin);

    std::wcout << L"Debug console opened.\n";
}

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved)
{
    // 禁用 DLL 模块的通知
    DisableThreadLibraryCalls(hModule);

    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {
        OpenDebugConsole();  // 打开控制台进行调试
        std::wcout << L"DLL injected, setting up hooks.\n";

        // 设置受保护进程列表,此函数也可以从远程进程注入线程来调用
        const WCHAR ppName[] = L"cmd.exe;conhost.exe";
        if (SetProtectedProcessListFromBuffer(ppName, wcslen(ppName) + 1))
        {
            std::wcout << L"Protected process list set successfully.\n";
        }
        else
        {
            std::wcout << L"Failed to set protected process list.\n";
        }

        // 启用 HOOK
        StartHookingFunction();
        std::wcout << L"Hooking started.\n";
    }
    break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        UnmappHookedFunction();
        std::wcout << L"Hooking detached.\n";
        break;
    }
    return TRUE;
}

extern "C"
__declspec(dllexport)
void StartHookingFunction()
{
    //开始事务
    DetourTransactionBegin();
    //更新线程信息  
    DetourUpdateThread(GetCurrentThread());

    fpNtQuerySystemInformation =
        DetourFindFunction(
            "ntdll.dll",
            "NtQuerySystemInformation");
    
    //将拦截的函数附加到原函数的地址上,这里可以拦截多个函数。
    DetourAttach(&(PVOID&)fpNtQuerySystemInformation,
        HookedNtQuerySystemInformation);
    
    //结束事务
    DetourTransactionCommit();
}

extern "C"
__declspec(dllexport)
void UnmappHookedFunction()
{
    //开始事务
    DetourTransactionBegin();
    //更新线程信息 
    DetourUpdateThread(GetCurrentThread());

    //将拦截的函数从原函数的地址上解除,这里可以解除多个函数。
    DetourDetach(&(PVOID&)fpNtQuerySystemInformation,
        HookedNtQuerySystemInformation);
    
    //结束事务
    DetourTransactionCommit();
}

// 从缓冲区解析多个进程名的函数
extern "C"
__declspec(dllexport)
bool SetProtectedProcessListFromBuffer(const wchar_t* buffer, size_t length) {
    if (buffer == nullptr || length == 0) {
        return false;  // 返回错误状态
    }

    std::unique_lock lock(ppNameListMutex);  // 写锁
    ppNameList.clear();  // 清空原列表

    std::wstring tempName;
    for (size_t i = 0; i < length; ++i) {
        if (buffer[i] == L';' || i == length - 1) {
            // 遇到分号或者到达缓冲区末尾,表示一个进程名结束
            if (i == length - 1 && buffer[i] != L';' && buffer[i] != L'\0') {
                tempName += buffer[i];  // 处理最后一个字符不是分号的情况
            }

            if (!tempName.empty()) {
                std::wcout << L"Parsed process name: " << tempName << L"\n";  // 输出调试信息
                std::wcout << L"Length: " << tempName.size() << L"\n";
                ppNameList.push_back(tempName);  // 将进程名存入列表
                tempName.clear();  // 清空临时字符串以解析下一个进程名
            }
        }
        else {
            // 继续读取进程名字符
            tempName += buffer[i];
        }
    }

    std::wcout << L"Total protected processes: " << ppNameList.size() << L"\n";  // 输出调试信息

    return !ppNameList.empty();  // 返回成功标志,如果解析后列表为空则返回false
}

// 检查进程是否在受保护列表中的函数(带读锁)
static bool IsProcessProtected(const std::wstring& processName) {
    std::vector<std::wstring> localPpNameList;
    {
        std::shared_lock lock(ppNameListMutex);
        localPpNameList = ppNameList;  // 将受保护列表复制到局部变量
    }

    // 在局部变量中进行比较
    return std::find(localPpNameList.begin(), localPpNameList.end(), processName) != localPpNameList.end();
}

static bool IsHandleValidate(const LPVOID lpAddress)
{
    MEMORY_BASIC_INFORMATION Buffer{};

    VirtualQuery(lpAddress, &Buffer, 0x30u);

    //std::wcout << L"HandleValidate Buffer.Protect: " << Buffer.Protect << L"\n";
    return Buffer.State == MEM_COMMIT && Buffer.Protect != PAGE_NOACCESS;
}

static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) {
    DWORD processId;
    GetWindowThreadProcessId(hwnd, &processId);

    // 检查窗口是否属于当前进程
    if (processId == GetCurrentProcessId()) {
        // 检索指向窗口句柄向量的指针
        std::vector<HWND>* pWindowHandles = reinterpret_cast<std::vector<HWND>*>(lParam);

        // 检查指针是否有效
        if (pWindowHandles && IsHandleValidate(pWindowHandles)) {
            pWindowHandles->push_back(hwnd);
        }
        else {
            // (可选)记录错误或处理无效指针情况
            std::cerr << "Invalid pointer passed to EnumWindowsProc." << std::endl;
        }
    }
    return TRUE; // 继续枚举
}

static void FlushProcessWindows() {
    std::vector<HWND> windowHandles;

    // 枚举所有顶级窗口,通过lParam将指针传递给EnumWindowsProc
    EnumWindows(EnumWindowsProc, reinterpret_cast<LPARAM>(&windowHandles));

    // 向每一个窗口发送 F5 来刷新窗口
    for (HWND hwnd : windowHandles) {
        // 模拟 F5
        PostMessage(hwnd, WM_KEYDOWN, VK_F5, 0);
        Sleep(10);
        PostMessage(hwnd, WM_KEYUP, VK_F5, 0);
    }
}

// NtQuerySystemInformation的Hook函数,用于隐藏受保护的进程
NTSTATUS WINAPI HookedNtQuerySystemInformation(
    SYSTEM_INFORMATION_CLASS SystemInformationClass,
    PVOID SystemInformation,
    ULONG SystemInformationLength,
    PULONG ReturnLength
) {
    //std::wcout << L"NtQuerySystemInformation hook called.\n";
    static bool isNotFirstHook;
    const size_t nodeSize = sizeof(MY_SYSTEM_PROCESS_INFORMATION);

    // 先调用原始的 NtQuerySystemInformation
    NTSTATUS status = ((__NtQuerySystemInformation)fpNtQuerySystemInformation)(
        SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);

    //std::wcout << L"Original NtQuerySystemInformation returned: " << status << L"\n";

    // 只处理 SystemProcessInformation 类型的信息
    if (SystemInformationClass == SystemProcessInformation && NT_SUCCESS(status)) {
        //std::wcout << L"Processing SystemProcessInformation.\n";

        PMY_SYSTEM_PROCESS_INFORMATION pCurrentNode = (PMY_SYSTEM_PROCESS_INFORMATION)SystemInformation;
        PMY_SYSTEM_PROCESS_INFORMATION pPreviousNode = nullptr;
        bool isFirstNode = true;

        while (pCurrentNode != nullptr && IsHandleValidate(pCurrentNode)) {

            if (pCurrentNode->NextEntryOffset == 0) {  // 到达末尾
                break;
            }

            if (pCurrentNode->ImageName.Buffer == nullptr || pCurrentNode->ImageName.Length == 0) {
                // 跳过无效的进程名
                //std::wcout << L"Skipping invalid process name.\n";
                pCurrentNode = (PMY_SYSTEM_PROCESS_INFORMATION)((PUCHAR)pCurrentNode + pCurrentNode->NextEntryOffset);
                continue;
            }

            // 获取当前进程名
            std::wstring processName(pCurrentNode->ImageName.Buffer, pCurrentNode->ImageName.Length / sizeof(WCHAR));
            //std::wcout << L"Processing process: " << processName << L"\n";

            // 检查该进程名是否在受保护列表中
            if (IsProcessProtected(processName)) {
                std::wcout << L"Process is protected: " << processName << L"\n";

                // 如果在受保护列表中,则将该进程从链表中移除
                if (pPreviousNode) {
                    pPreviousNode->NextEntryOffset += pCurrentNode->NextEntryOffset;
                    std::wcout << L"Process removed from list: " << processName << L"\n";
                }
                else if (isFirstNode) {  // 第一个节点是受保护进程,
                                         // 替换为下一个节点的数据
                    if (pCurrentNode->NextEntryOffset == 0) {
                        // 如果没有下一个节点,表示列表中只有一个受保护
                        // 将进程信息列表清空
                        memset(pCurrentNode, 0, SystemInformationLength);
                        std::wcout << L"Only one protected process, clearing list.\n";
                        break;
                    }
                    else {
                        
                        PMY_SYSTEM_PROCESS_INFORMATION pNextNode =
                            (PMY_SYSTEM_PROCESS_INFORMATION)((PUCHAR)pCurrentNode + pCurrentNode->NextEntryOffset);

                        if(!IsHandleValidate(pNextNode)) {
                            std::wcout << L"HandleValidate failed.\n";
                            break;
                        }
                        // 将下一个节点的数据拷贝到当前节点
                        memcpy(pCurrentNode, pNextNode, sizeof(MY_SYSTEM_PROCESS_INFORMATION));
                        pCurrentNode->NextEntryOffset = pNextNode->NextEntryOffset;
                        std::wcout << L"First process was protected, replaced with next process.\n";
                        
                        continue;  // 保持 pPreviousNode 不变,重新检查当前节点
                    }
                }
            }
            else {
                // 如果没有被保护,移动到下一个节点
                pPreviousNode = pCurrentNode;
            }

            // 如果下一个节点超出缓冲区范围,停止处理
            if (((PUCHAR)pCurrentNode + pCurrentNode->NextEntryOffset + nodeSize) >
                (PUCHAR)SystemInformation + SystemInformationLength) {
                std::wcout << L"Reached end of buffer.\n";
                break;
            }

            // 继续下一个进程信息节点
            pCurrentNode = (PMY_SYSTEM_PROCESS_INFORMATION)((PUCHAR)pCurrentNode + pCurrentNode->NextEntryOffset);
        }
    }

    // 只在第一次调用 hook 函数后强制刷新窗口
    if (!isNotFirstHook) {
        isNotFirstHook = true;
        FlushProcessWindows();
    }

    return status;
}

可以删除 dllmain 里面的hook 函数以及所有输出字符串。从外部进程通过注入远程线程的方式来实现动态调整隐身策略。主要利用下面三个函数:
导出函数

执行效果:
隐身效果(调试输出模式)


本文出处链接:[https://blog.csdn.net/qq_59075481/article/details/142676712]。

本文发布于:2024.10.02。

相关推荐
ö Constancy1 小时前
c++ 笔记
开发语言·c++
fengbizhe1 小时前
笔试-笔记2
c++·笔记
徐霞客3201 小时前
Qt入门1——认识Qt的几个常用头文件和常用函数
开发语言·c++·笔记·qt
fpcc1 小时前
redis6.0之后的多线程版本的问题
c++·redis
螺旋天光极锐斩空闪壹式!2 小时前
自制游戏:监狱逃亡
c++·游戏
工业3D_大熊2 小时前
3D可视化引擎HOOPS Luminate场景图详解:形状的创建、销毁与管理
java·c++·3d·docker·c#·制造·数据可视化
暮色_年华3 小时前
Modern Effective C++ Item 11:优先考虑使用deleted函数而非使用未定义的私有声明
c++
流星白龙3 小时前
【C++习题】10.反转字符串中的单词 lll
开发语言·c++
Smile丶凉轩3 小时前
微服务即时通讯系统的实现(服务端)----(1)
c++·git·微服务·github
萝卜兽编程3 小时前
优先级队列
c++·算法