首页 > 系统相关 >浅谈进程隐藏技术

浅谈进程隐藏技术

时间:2024-07-07 09:08:30浏览次数:7  
标签:函数 ZwQuerySystemInfomation 钩取 pCur 进程 include 隐藏 浅谈

前言

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

● 浅谈调试模式钩取

● 浅谈热补丁

● 浅谈内联钩取原理与实现

● 导入地址表钩取技术

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

进程遍历方法

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

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,可以在钩取过程中确保线程安全。

【----帮助网安学习,以下所有学习资料免费领!加vx:dctintin,备注 “博客园” 获取!】

 ① 网安学习成长路径思维导图
 ② 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

更多网安技能的在线实操练习,请点击这里>>

  

标签:函数,ZwQuerySystemInfomation,钩取,pCur,进程,include,隐藏,浅谈
From: https://www.cnblogs.com/hetianlab/p/18288199

相关文章

  • 进程的初步认识
    目录一、硬件方面介绍1.冯诺依曼体系结构2.存储分级二、软件 方面1.操作系统是一款进行管理的软件,它可以管理硬件也可以管理软件2.操作系统如何管理?三、进程 1.概念总结四、linux中对进程的管理 1.task_struct内容分类2.查看进程 3.通过系统调用获取进程标......
  • Windows如何查看端口是否占用,并结束端口进程
    需求与问题:前后端配置了跨域操作,但是仍然报错,可以考虑端口被两个程序占用,找不到正确端口或者后端接口书写是否规范,特别是利用PythonFlask书写时要保证缩进是否正确!Windows操作系统中,查看端口是否占用并结束占用端口的程序是一个常见的操作,特别是在进行网络配置或软件安装时。......
  • Linux开发:进程间通过Unix Domain Socket传递数据
    进程间传递数据的方式有很多种,Linux还提供一种特殊的Socket用于在多进程间传递数据,就是UnixDomainSocket(UDS)。虽然通过普通的Socket也能做到在多进程间传递数据,不过这样需要通过协议栈层的打包与拆包,未免有些浪费效率,通过UDS,数据仅仅通过一个特殊的sock文件就可以进行传递。......
  • 进程间通信方式-共享内存
    目录1.特点2.使用步骤3.函数接口3.1创建key值3.2创建或打开共享内存3.3映射共享内存3.4取消映射3.5删除共享内存4.命令5.基本操作1.特点(1)共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝。(2)为了在多个进程间交换信息,内核......
  • 进程、程序、应用程序之间的关系
    文章目录进程和程序进程和应用程序总结参考资料进程和程序程序:程序是存放在硬盘中的可执行文件,主要包括代码指令和数据。程序本身是一个静态的文件,只有在被操作系统加载到内存中并执行时才会变成进程。进程:进程是程序在操作系统中的执行实例。一个进程是一个程序......
  • 【并查集】浅谈思想 & 代码实现 & 实战例题(C/C++)
    思想综述并查集(Union-Find)算法的主要操作包括两种:合并(Union):将两个不相交的集合合并成一个集合。查询(Find):查询两个元素是否属于同一个集合。并查集算法的核心思想是使用树(通常是森林)来表示这些不相交的集合,其中每个集合被表示为一棵树,树的根节点代表这个集合的标识(或称为代表......
  • 线程和进程
    1.什么是线程?什么是进程?正在运行的程序称之为进程。进程它是系统分配资源的基本单位。线程,又称轻量级进程(LightWeightProcess)。线程是进程中的一条执行路径,也是CPU的基本调度单位。若一个程序可同一时间执行多个线程,就是支持多线程的.一个进程由一个或多个线程组成,彼此......
  • GPT-4o不仅能写代码,还能自查Bug,程序员替代进程再进一步!
    目录1 CriticGPT01综合性(Comprehensiveness):02幻觉问题(Hallucinatesaproblem):2其他CriticGPT案例随着人工智能(AI)技术不断进步,AI在编程领域的应用取得了显著的成果。通过使用自然语言处理(NLP)和机器学习(ML)技术,AI可以自动生成代码、检测错误并优化性能。一个例......
  • python多线程与多进程开发实践及填坑记(1)
    1.需求分析1.1.概述基于Flask、Pika、Multiprocessing、Thread搭建一个架构,完成多线程、多进程工作。具体需求如下:并行计算任务:使用multiprocessing模块实现并行计算任务,提高计算效率、计算能力。消息侦听任务:使用threading模块完成RabbitMQ消息队列的侦听任务,将接收到......
  • Linux进程间的通信方式(三)System V 信号量
    文章目录前言一、信号量概念1.1信号跟信号量的区别1.2同步跟互斥的区别1.2.1同步的概念1.2.2互斥的概念1.3原子操作概念二、信号量的相关操作函数2.1ftok函数(获取一个key值)2.2semget函数(创建或者获取信号量)2.3semctl函数(控制信号量)2.4semop函数(操作信号......