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