浅谈进程隐藏技术

前言

在之前几篇文章已经学习了解了几种钩取的方法

  • 浅谈调试模式钩取
  • 浅谈热补丁
  • 浅谈内联钩取原理与实现
  • 导入地址表钩取技术

这篇文章就利用钩取方式完成进程隐藏的效果。

进程遍历方法

在实现进程隐藏时,首先需要明确遍历进程的方法。

CreateToolhelp32Snapshot

CreateToolhelp32Snapshot函数用于创建进程的镜像,当第二个参数为0时则是创建所有进程的镜像,那么就可以达到遍历所有进程的效果。

#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>

int main()
{
    //设置编码,便于后面能够输出中文
    setlocale(LC_ALL, "zh_CN.UTF-8");
    //创建进程镜像,参数0代表创建所有进程的镜像
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnapshot == INVALID_HANDLE_VALUE)
    {
        std::cout << "Create Error" << std::endl;
        exit(-1);
    }

    /*
    * typedef struct tagPROCESSENTRY32 { 
    * DWORD dwSize;               进程信息结构体大小,首次调用之前必须初始化
    * DWORD cntUsage;              引用进程的次数,引用次数为0时,则进程结束
    * DWORD th32ProcessID;           进程的ID
    * ULONG_PTR th32DefaultHeapID;       进程默认堆的标识符,除工具使用对我们没用
    * DWORD th32ModuleID;                  进程模块的标识符
    * DWORD cntThreads;             进程启动的执行线程数
    * DWORD th32ParentProcessID;           父进程ID
    * LONG  pcPriClassBase;          进程线程的基本优先级
    * DWORD dwFlags;              保留
    * TCHAR szExeFile[MAX_PATH];          进程的路径
    * } PROCESSENTRY32; 
    * typedef PROCESSENTRY32 *PPROCESSENTRY32; 
    */
    PROCESSENTRY32 pi;
    pi.dwSize = sizeof(PROCESSENTRY32);
    //取出第一个进程
    BOOL bRet = Process32First(hSnapshot, &pi);
    while (bRet)
    {
        wprintf(L"进程路径:%s\t进程号:%d\n", pi.szExeFile, pi.th32ProcessID);
        //取出下一个进程
        bRet = Process32Next(hSnapshot, &pi);
    }
}

EnumProcesses

EnumProcesses用于将所有进程号的收集。

#include <iostream>
#include <Windows.h>
#include <Psapi.h>

int main()
{
    setlocale(LC_ALL, "zh_CN.UTF-8");

    DWORD processes[1024], dwResult, size;
    unsigned int i;
	//收集所有进程的进程号
    if (!EnumProcesses(processes, sizeof(processes), &dwResult))
    {
        std::cout << "Enum Error" << std::endl;
    }
	
    //进程数量
    size = dwResult / sizeof(DWORD);

    for (i = 0; i < size; i++)
    {
        //判断进程号是否为0
        if (processes[i] != 0)
        {
            //用于存储进程路径
            TCHAR szProcessName[MAX_PATH] = { 0 };
            //使用查询权限打开进程
            HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
                PROCESS_VM_READ,
                FALSE,
                processes[i]);

            if (hProcess != NULL)
            {
                HMODULE hMod;
                DWORD dwNeeded;
				//收集该进程的所有模块句柄,第一个句柄则为文件路径
                if (EnumProcessModules(hProcess, &hMod, sizeof(hMod),
                    &dwNeeded))
                {
                    //根据句柄获取文件路径
                    GetModuleBaseName(hProcess, hMod, szProcessName,
                        sizeof(szProcessName) / sizeof(TCHAR));
                }
                wprintf(L"进程路径:%s\t进程号:%d\n", szProcessName, processes[i]);
            }
        }   
    }
}

ZwQuerySystemInfomation

ZwQuerySystemInfomation函数是CreateToolhelp32Snapshot函数与EnumProcesses函数底层调用的函数,也用于遍历进程信息。代码参考https://cloud.tencent.com/developer/article/1454933

#include <iostream>
#include <Windows.h>
#include <ntstatus.h>
#include <winternl.h> 
#pragma comment(lib, "ntdll.lib") 

//定义函数指针
typedef NTSTATUS(WINAPI* NTQUERYSYSTEMINFORMATION)(
	IN      SYSTEM_INFORMATION_CLASS SystemInformationClass,
	IN OUT   PVOID                    SystemInformation,
	IN      ULONG                    SystemInformationLength,
	OUT PULONG                   ReturnLength
	);

int main()
{
    //设置编码
	setlocale(LC_ALL, "zh_CN.UTF-8");
    //获取模块地址
	HINSTANCE ntdll_dll = GetModuleHandle(L"ntdll.dll");
	if (ntdll_dll == NULL) {
		std::cout << "Get Module Error" << std::endl;
		exit(-1);
	}

	NTQUERYSYSTEMINFORMATION ZwQuerySystemInformation = NULL;
	//获取函数地址
	ZwQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(ntdll_dll, "ZwQuerySystemInformation");
	if (ZwQuerySystemInformation != NULL)
	{
		SYSTEM_BASIC_INFORMATION sbi = { 0 };
        //查询系统基本信息
		NTSTATUS status = ZwQuerySystemInformation(SystemBasicInformation, (PVOID)&sbi, sizeof(sbi), NULL);
		if (status == STATUS_SUCCESS)
		{
			wprintf(L"处理器个数:%d\r\n", sbi.NumberOfProcessors);
		}
		else
		{
			wprintf(L"ZwQuerySystemInfomation Error\n");
		}

		DWORD dwNeedSize = 0;
		BYTE* pBuffer = NULL;

		wprintf(L"\t----所有进程信息----\t\n");
		PSYSTEM_PROCESS_INFORMATION psp = NULL;
        //查询进程数量
		status = ZwQuerySystemInformation(SystemProcessInformation, NULL, 0, &dwNeedSize);
		if (status == STATUS_INFO_LENGTH_MISMATCH)
		{
			pBuffer = new BYTE[dwNeedSize];
            //查询进程信息
			status = ZwQuerySystemInformation(SystemProcessInformation, (PVOID)pBuffer, dwNeedSize, NULL);
			if (status == STATUS_SUCCESS)
			{
				psp = (PSYSTEM_PROCESS_INFORMATION)pBuffer;
				wprintf(L"\tPID\t线程数\t工作集大小\t进程名\n");
				do {
                    //获取进程号
					wprintf(L"\t%d", psp->UniqueProcessId);
                    //获取线程数量
					wprintf(L"\t%d", psp->NumberOfThreads);
                    //获取工作集大小
					wprintf(L"\t%d", psp->WorkingSetSize / 1024);
                    //获取路径
					wprintf(L"\t%s\n", psp->ImageName.Buffer);
                    //移动
					psp = (PSYSTEM_PROCESS_INFORMATION)((PBYTE)psp + psp->NextEntryOffset);
				} while (psp->NextEntryOffset != 0);
				delete[]pBuffer;
				pBuffer = NULL;
			}
			else if (status == STATUS_UNSUCCESSFUL) {
				wprintf(L"\n STATUS_UNSUCCESSFUL");
			}
			else if (status == STATUS_NOT_IMPLEMENTED) {
				wprintf(L"\n STATUS_NOT_IMPLEMENTED");
			}
			else if (status == STATUS_INVALID_INFO_CLASS) {
				wprintf(L"\n STATUS_INVALID_INFO_CLASS");
			}
			else if (status == STATUS_INFO_LENGTH_MISMATCH) {
				wprintf(L"\n STATUS_INFO_LENGTH_MISMATCH");
			}
		}
	}
}

进程隐藏

通过上述分析可以知道遍历进程的方式有三种,分别是利用CreateToolhelp32SnapshotEnumProcesses以及ZwQuerySystemInfomation函数

但是CreateToolhelp32SnapshotEnumProcesses函数底层都是调用了ZwQuerySystemInfomation函数,因此我们只需要钩取该函数即可。

由于测试环境是Win11,因此需要判断在Win11情况下底层是否还是调用了ZwQuerySystemInfomation函数。

可以看到在Win11下还是会调用ZwQuerySystemInfomation函数,在用户态下该函数的名称为NtQuerySystemInformation函数。

这里采用内联钩取的方式对ZwQuerySystemInfomation进行钩取处理,具体怎么钩取在浅谈内联钩取原理与实现已经介绍过了,这里就不详细说明了。这里对自定义的ZwQuerySystemInfomation函数进行说明。

首先第一步需要进行脱钩处理,因为后续需要用到初始的ZwQuerySystemInfomation函数,紧接着获取待钩取函数的地址即可。

...
    //脱钩
    UnHook("ntdll.dll", "ZwQuerySystemInformation", g_pOrgBytes);
    HMODULE hModule = GetModuleHandleA("ntdll.dll");
	//获取待钩取函数的地址
    PROC    pfnOld = GetProcAddress(hModule, "ZwQuerySystemInformation");
	//调用原始的ZwQuerySystemInfomation函数
    NTSTATUS status = ((NTQUERYSYSTEMINFORMATION)pfnOld)(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);
...

为了隐藏指定进程,我们需要遍历进程信息,找到目标进程并且删除该进程信息实现隐藏的效果。这里需要知道的是进程信息都存储在SYSTEM_PROCESS_INFORMATION结构体中,该结构体是通过单链表对进程信息进行链接。因此我们通过匹配进程名称找到对应的SYSTEM_PROCESS_INFORMATION结构体,然后进行删除即可,效果如下图。

通过单链表中删除节点的操作,取出目标进程的结构体。代码如下

...
 		pCur = (PSYSTEM_PROCESS_INFORMATION)(SystemInformation);
        while (true)
        {
            if (!lstrcmpi(pCur->ImageName.Buffer, L"test.exe"))
            {
                //需要隐藏的进程是最后一个节点
                if (pCur->NextEntryOffset == 0)
                    pPrev->NextEntryOffset = 0;
                //不是最后一个节点,则将该节点取出
                else
                    pPrev->NextEntryOffset += pCur->NextEntryOffset;

            }
            //不是需要隐藏的节点,则继续遍历
            else
                pPrev = pCur;
            //链表遍历完毕
            if (pCur->NextEntryOffset == 0)
                break;
            pCur = (PSYSTEM_PROCESS_INFORMATION)((PBYTE)pCur + pCur->NextEntryOffset);
        }
...

完整代码:https://github.com/h0pe-ay/HookTechnology/blob/main/ProcessHidden/inlineHook.c

但是采用内联钩取的方法去钩取任务管理器就会出现一个问题,这里将断点取消,利用内联钩取的方式去隐藏进程。

首先利用bl命令查看断点

紧着利用 bc [ID]删除断点

在注入之后任务管理器会在拷贝的时候发生异常

在经过一番调试后发现,由于多线程共同执行导致原本需要可写权限的段被修改为只读权限

windbg可以用使用!vprot + address查看指定地址的权限,可以看到由于程序往只读权限的地址进行拷贝处理,所以导致了异常。

但是在执行拷贝阶段是先修改了该地址为可写权限,那么导致该原因的情况就是其他线程执行了权限恢复后切换到该线程中进行写,所以导致了这个问题。

因此内联钩取是存在多线程安全的问题,此时可以使用微软自己构建的钩取库Detours,可以在钩取过程中确保线程安全。

帮助网安学习,全套资料S信免费领取:
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)

Detours

项目地址:https://github.com/microsoft/Detours

环境配置

参考:https://www.cnblogs.com/linxmouse/p/14168712.html

使用vcpkg下载

vcpkg.exe install detours:x86-windows
vcpkg.exe install detours:x64-windows
vcpkg.exe integrate install

实例

挂钩

利用Detours挂钩非常简单,只需要根据下列顺序,并且将自定义函数的地址与被挂钩的地址即可完成挂钩处理。

...
    	//用于确保在 DLL 注入或加载时,恢复被 Detours 修改的进程镜像,保持稳定性
		DetourRestoreAfterWith();
        //开始一个新的事务来附加或分离
        DetourTransactionBegin();
		//进行线程上下文的更新
        DetourUpdateThread(GetCurrentThread());
		//挂钩
        DetourAttach(&(PVOID&)TrueZwQuerySystemInformation, ZwQuerySystemInformationEx);
		//提交事务
        error = DetourTransactionCommit();
...

脱钩

然后根据顺序完成脱钩即可。

...
    	//开始一个新的事务来附加或分离
		DetourTransactionBegin();
		//进行线程上下文的更新
        DetourUpdateThread(GetCurrentThread());
		//脱钩
        DetourDetach(&(PVOID&)TrueZwQuerySystemInformation, ZwQuerySystemInformationEx);
		//提交事务
        error = DetourTransactionCommit();
...

挂钩的原理

从上述可以看到,Detours是通过事务确保了在DLL加载与卸载时后的原子性,但是如何确保多线程安全呢?后续通过调试去发现。

可以利用x ntdl!ZwQuerySystemInformation查看函数地址,可以看到函数的未被挂钩前的情况如下图。

挂钩之后原始的指令被修改为一个跳转指令把前八个字节覆盖掉,剩余的3字节用垃圾指令填充。

该地址里面又是一个jmp指令,并且完成间接寻址的跳转。

该地址是自定义函数ZwQuerySystemInformationEx,因此该间接跳转是跳转到的自定义函数内部。

跳转到TrueZwQuerySystemInformation内部发现ZwQuerySystemInformation函数内部的八字节指令被移动到该函数内部。紧接着又完成一个跳转。

该跳转到ZwQuerySystemInformation函数内部紧接着完成ZwQuerySystemInformation函数的调用。

综上所述,整体流程如下图。实际上Detours实际上使用的是热补丁的思路,但是Detours并不是直接在原始的函数空间中进行补丁,而是开辟了一段临时空间,将指令存储在里面。因此在挂钩后不需要进行脱钩处理就可以调用原始函数。因此就不存在多线程中挂钩与脱钩的冲突。

完整代码:https://github.com/h0pe-ay/HookTechnology/blob/main/ProcessHidden/detoursHook.c

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/781829.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

区块链技术如何改变供应链管理?

引言 供应链管理在现代商业中扮演着至关重要的角色&#xff0c;确保产品和服务从原材料到最终消费者的顺利流转。然而&#xff0c;当前的供应链管理面临诸多挑战&#xff0c;如信息不透明、数据篡改和效率低下等问题&#xff0c;这些问题严重制约了供应链的整体效能和可信度&am…

Go:hello world

开启转职->Go开发工程师 下面是我的第一个go的程序 在上面的程序介绍&#xff1a; 1、package main 第一行代码package main定义了包名。必须在源文件中非注释的第一行指明这个文件属于哪个包&#xff0c;如&#xff1a;package main。package main表示一个可独立执行的程…

acwing 291.蒙德里安的梦想

解法&#xff1a; 核心&#xff1a;先放横着的&#xff0c;再放竖着的。 总方案数&#xff0c;等于只放横着的小方块的合法方案数。 如何判断当前方案是否合法&#xff1f;所有剩余位置&#xff0c;能否填充满竖着的小方块。 即按列来看&#xff0c;每一列内部所有连续的空着的…

DoIP-1 简介

1. 概述 DoIP-Diagnostic Over Internet Protocol &#xff0c;基于TCPIP协议族的诊断传输协议 DoIP国际标准定义为ISO 13400&#xff0c;总共由五部分组成&#xff1a;  ISO13400-1DoIP的综述  ISO13400-2DoIP的传输层和网络层服务&#xff08;主体部分&#xff09;  I…

JavaSe系列二十七: Java正则表达式

正则表达式 为什么要学习正则表达式再提几个问题解决之道-正则表达式正则表达式基本介绍介绍 正则表达式底层实现实例分析 正则表达式语法基本介绍元字符-转义号 \\\\元字符-字符匹配符元字符-选择匹配符元字符-限定符元字符-定位符分组非贪婪匹配 应用实例对字符串进行如下验证…

【c++刷题笔记-数组】day29:452. 用最少数量的箭引爆气球、 435. 无重叠区间 、 763.划分字母区间

452. 用最少数量的箭引爆气球 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;先按照左边界排序&#xff0c;当前的左边界大于前一个的右边界的时候&#xff0c;表示没有覆盖所以需要一根箭&#xff0c;反之则要更新为最小的右边界 重点&#xff1a;是区间覆盖问题…

webrtc gcc详解

webrtc的gcc算法(Google Congestion Control)&#xff0c;貌似国内很多文章都没有细讲&#xff0c;原理是怎么样的&#xff0c;具体怎么进行计算的。这里详解一下gcc。 gcc算法&#xff0c;主要涉及到&#xff1a; 拥塞控制的关键信息和公式 卡曼滤波算法 gcc如何使用卡曼滤…

JavaScript 原型链那些事

在讲原型之前我们先来了解一下函数。 在JS中&#xff0c;函数的本质就是对象&#xff0c;它与其他对象不同的是&#xff0c;创建它的构造函数与创建其他对象的构造函数不一样。那产生函数对象的构造函数是什么呢&#xff1f;是一个叫做Function的特殊函数&#xff0c;通过newFu…

Python从入门到放弃——深入研究Print函数

深入浅出Print函数 第一个代码“Hello World” 在正常配置了PyCharm或者Thonny等编辑器之后&#xff0c;我们开始写第一个代码。正常的情况下学习一门编程语言&#xff0c;一般第一个代码都是输出Hello World。那么如何打印Hello World呢&#xff1f; print("Hello Wor…

第六十八回 东平府误陷九纹龙 宋公明义释双枪将-文心大模型ernie-speed免费使用方法

宋江和卢俊义抓阄儿&#xff0c;宋江打东平府&#xff0c;卢俊义打东昌府&#xff0c;谁先打下谁做梁山泊主。宋江带领林冲、花荣、刘唐等二十八人&#xff0c;卢俊义带领吴用、公孙胜、关胜等二十八人。 宋江等人到了东平府外安山镇&#xff0c;郁保四和王定六自告奋勇去下战…

io流 多线程

目录 一、io流 1.什么是io流 2.流的方向 i.输入流 ii.输出流 3.操作文件的类型 i.字节流 1.拷贝 ii.字符流 ​3.字符流输出流出数据 4.字节流和字符流的使用场景 5.练习 6.缓冲流 1.字节缓冲流拷贝文件 2.字符缓冲流特有的方法 1.方法 2.总结 7.转换流基本用法…

掌握MySQL基础命令:数据更新操作详细操作(数据的增删改)

MySQL数据修改是指使用SQL语句&#xff08;如UPDATE、INSERT、DELETE&#xff09;对数据库表中的数据进行更改、添加或删除的操作&#xff0c;常见的操作包括更新表中的记录、插入新记录以及删除现有记录 。 一、数据插入 1插入完整的数据记录 2插入非完整的数据记录 3插入多…

Vulkan 学习(1)---- Vulkan 基本概念和发展历史

目录 Vulkan及其演化史Vulkan 基本概念基本术语 Vulkan 的原理Vulkan应用程序Vulkan的编程模型硬件初始化窗口展示表面资源设置流水线设置描述符和描述符缓冲池基于SPIR-V的着色器流水线管理指令的记录队列的提交 Vulkan及其演化史 目前主流的图形渲染API有OpenGL、OpenGL ES、…

应急响应--网站(web)入侵篡改指南

免责声明:本文... 目录 被入侵常见现象: 首要任务&#xff1a; 分析思路&#xff1a; 演示案例: IIS&.NET-注入-基于时间配合日志分析 Apache&PHP-漏洞-基于漏洞配合日志分析 Tomcat&JSP-弱口令-基于后门配合日志分析 (推荐) Webshell 查杀-常规后门&…

ThinkPHP定时任务是怎样实现的?

接到一个需求&#xff1a;定时检查设备信息&#xff0c;2分钟没有心跳的机器&#xff0c;推送消息给相关人员&#xff0c;用thinkphp5框架&#xff0c;利用框架自带的任务功能与crontab配合来完成定时任务。 第一步&#xff1a;分析需求 先写获取设备信息&#xff0c;2分钟之…

Winform中使用HttpClient实现调用http的post接口并设置传参content-type为application/json示例

场景 Winform中怎样使用HttpClient调用http的get和post接口并将接口返回json数据解析为实体类&#xff1a; Winform中怎样使用HttpClient调用http的get和post接口并将接口返回json数据解析为实体类_winform解析json-CSDN博客 上面使用HttpClient调用post接口时使用的HttpCon…

卷积神经网络基础篇

文章目录 1、卷积层1.1、激活函数1.3、sigmoid1.4、Tanh1.5、ReLU1.6、Leaky ReLU1.7、误差计算 2、池化层3、全连接层4、CNN训练 参考链接1 参考链接2 1、卷积层 卷积层&#xff08;Convolutional layer&#xff09;&#xff0c;这一层就是卷积神经网络最重要的一个层次&…

spRAG框架学习小结

spRAG是什么 spRAG是一个针对非结构化数据的检索引擎。它特别擅长处理对密集文本的复杂查询&#xff0c;比如财务报告、法律文件和学术论文。有两种关键方法用于提高性能&#xff0c;超越了普通的RAG系统&#xff1a; 自动上下文&#xff08;AutoContext&#xff09;&#xff…

几款电脑端能够运行的AI大模型聊天客户端

Ollama Ollama 是一个用于在本地运行和管理大型语言模型的工具。它支持多种流行模型的下载和本地运行&#xff0c;包括 LLaMA-2、CodeLLaMA、Falcon 和 Mistral 。Ollama 提供了一个简单、轻量级和可扩展的解决方案&#xff0c;使得用户可以以最简单快速的方式在本地运行大模型…

中霖教育:二级建造师未注册还需要继续教育吗?

关键词&#xff1a;中霖教育怎么样&#xff0c;中霖教育口碑 如果通过了二级建造师考试但是没有注册&#xff0c;还用继续教育吗? 1. 未注册的二级建造师 二级建造师在其证书获取后三年内没有进行注册时&#xff0c;在申请初始注册之前必须完成规定的本专业继续教育课程。 …