浅谈进程隐藏技术

news2024/11/27 21:42:36

前言

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

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

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

进程遍历方法

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ViewController 生命周期

ViewController 生命周期 ViewController 生命周期测试程序&#xff1a;ViewControllerLifeCircle ViewController 生命周期 ViewController 是 iOS 开发中 MVC 框架中的 C&#xff0c;ViewColllecter 是 View&#xff08;视图&#xff09;的 Collecter&#xff08;控制器&…

kafka中

Kafka RocketMQ概述 RabbitMQ概述 ActiveMQ概述 ZeroMQ概述 MQ对比选型 适用场景-从公司基础建设力量角度出发 适用场景-从业务场景出发 Kafka配置介绍 运行Kafka 安装ELAK 配置EFAK EFAK界面 KAFKA常用术语 Kafka常用指令 Kafka中消息读取 单播消息 group.id 相同 多播消息 g…

LabVIEW在图像处理中的应用

abVIEW作为一种图形化编程环境&#xff0c;不仅在数据采集和仪器控制领域表现出色&#xff0c;还在图像处理方面具有强大的功能。借助其Vision Development Module&#xff0c;LabVIEW提供了丰富的图像处理工具&#xff0c;广泛应用于工业检测、医学影像、自动化控制等多个领域…

LabVIEW在自动化测试项目中的推荐架构

在自动化测试项目中&#xff0c;推荐使用LabVIEW的生产者-消费者&#xff08;Producer-Consumer&#xff09;架构。这种架构利用队列实现数据的异步传输和处理&#xff0c;提供了高效、稳定和可扩展的解决方案。其主要优点包括&#xff1a;实现数据采集与处理的解耦、提高系统响…

SKF轴承故障频率查询

1&#xff0c;第一步&#xff1a;搜索轴承型号 skf官网 2&#xff0c;第二步&#xff1a;查询故障频率。 第三步&#xff1a;

《基于 defineProperty 实现前端运行时变量检测》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; 近期刚转战 CSDN&#xff0c;会严格把控文章质量&#xff0c;绝不滥竽充数&#xff0c;欢迎多多交流~ &am…

SpringBoot实现多数据源切换

1. 概述 仓库地址&#xff1a;https://gitee.com/aopmin/multi-datasource-demo 随着项目规模的扩大和业务需求的复杂化&#xff0c;单一数据源已经不能满足实际开发中的需求。在许多情况下&#xff0c;我们需要同时操作多个数据库&#xff0c;或者需要将不同类型的数据存储在不…

MyBatis-Plus-实用的功能自动填充字段

前言: java项目用到了mybatis-plus&#xff0c;在一些类里面需要在更新时候&#xff0c;统一设置&#xff0c;修改人&#xff0c;修改ID&#xff0c;修改时间。新增时候设置 创建人&#xff0c;创建时间等 基础类&#xff1a; Data public abstract class BaseModel implements…

昇思25天学习打卡营第18天 | K近邻算法实现红酒聚类

1、实验目的 了解KNN的基本概念&#xff1b;了解如何使用MindSpore进行KNN实验。 2、K近邻算法原理介绍 K近邻算法&#xff08;K-Nearest-Neighbor, KNN&#xff09;是一种用于分类和回归的非参数统计方法&#xff0c;最初由 Cover和Hart于1968年提出(Cover等人,1967)&#…

身体(body)的觉醒

佛&#xff0c;是一个梵文的汉语音译词&#xff0c;指觉醒者。 何谓觉醒&#xff1f;什么的觉醒&#xff1f;其实很简单&#xff0c;就是身体的觉醒。 佛的另一个名字&#xff0c;叫菩提&#xff0c;佛就是菩提&#xff0c;菩提老祖&#xff0c;就是佛祖。 body&#xff0c;即…

如何优化 PostgreSQL 中对于复杂数学计算的查询?

文章目录 一、理解复杂数学计算的特点二、优化原则&#xff08;一&#xff09;索引优化&#xff08;二&#xff09;查询重写&#xff08;三&#xff09;数据库配置调整&#xff08;四&#xff09;使用数据库内置函数的优势 三、具体的优化方案和示例&#xff08;一&#xff09;…

数据结构算法-排序(一)-冒泡排序

什么是冒泡排序 冒泡排序&#xff1a;在原数组中通过相邻两项元素的比较&#xff0c;交换而完成的排序算法。 算法核心 数组中相邻两项比较、交换。 算法复杂度 时间复杂度 实现一次排序找到最大值需要遍历 n-1次(n为数组长度) 需要这样的排序 n-1次。 需要 (n-1) * (n-1) —…

基于springboot+vue+uniapp的高校宿舍信息管理系统小程序

开发语言&#xff1a;Java框架&#xff1a;springbootuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#…

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

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

LabVIEW透视变换

透视变换概述源程序在www.bjcyck.com下载 透视变换是一种几何变换&#xff0c;用于对图像进行扭曲&#xff0c;使其看起来从不同角度拍摄。这在计算机视觉和图像处理领域非常重要&#xff0c;例如在投影校正和图像配准中。LabVIEW提供了强大的图像处理工具&#xff0c;利用其V…

阿里通义音频生成大模型 FunAudioLLM 开源!

01 导读 人类对自身的研究和模仿由来已久&#xff0c;在我国2000多年前的《列子汤问》里就描述了有能工巧匠制作出会说话会舞动的类人机器人的故事。声音包含丰富的个体特征及情感情绪信息&#xff0c;对话作为人类最常使用亲切自然的交互模式&#xff0c;是连接人与智能世界…

gcc的编译C语言的过程

gcc的简介 GCC&#xff08;GNU Compiler Collection&#xff09;是由GNU项目开发和维护的一套开源编程语言编译器集合。它支持多种编程语言&#xff0c;包括但不限于C、C、Objective-C、Fortran、Ada等。GCC被广泛应用于编译和优化各种程序&#xff0c;是许多开发者和组织的首选…

【Unity数据交互】如何Unity中读取Ecxel中的数据

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 专栏交流&#x1f9e7;&…

picgo+gitee图床配置

node.js安装 刚开始顺着picgo操作,直接跳转到了node.js官网 下载的时候直接 Next,然后可以自定义安装路径,我的安装路径是C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Node.js 然后再在安装路径的根目录下新建两个文件夹,nodecache 和 nodeglobal, 如图所示:…

ida动态调试-cnblog

ida动态调试 传递启动ida服务 android_server在ida\dbgsrv目录中 adb push android_server /data/local/tmp/chmod 755 /data/local/tmp/android_server /data/local/tmp/android_serveradb forward tcp:23946 tcp:23946ida报错:大多是手机端口被占用 报错提示&#xff1a; …