浅谈进程隐藏技术

news/2024/10/5 21:46:29/文章来源:https://www.cnblogs.com/hetianlab/p/18288199

前言

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

● 浅谈调试模式钩取

● 浅谈热补丁

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

● 导入地址表钩取技术

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

进程遍历方法

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

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

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

  

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

什么是新质生产力

什么是新质生产力

好消息!数据库管理神器 Navicat 推出免费精简版:Navicat Premium Lite

前言 好消息,前不久Navicat推出了免费精简版的数据库管理工具Navicat Premium Lite,可用于商业和非商业目的,我们再也不需要付费、找破解版或者找其他免费平替工具了,有需要的同学可以马上下载使用起来。 工具官方介绍 Navicat Premium Lite 是 Navicat 的精简版,它包含了…

读人工智能全传05专家系统

读人工智能全传05专家系统1. 知识就是力量 1.1. 人工智能领域此前存在的问题是过度关注搜索和解决问题这种通用法则 1.2. “弱”方法缺少一个关键的要素,而这一要素才是在所有智能行为中起决定性作用的组成部分:知识 1.3. 一种基于知识的人工智能系统:专家系统开始出现,它能…

副本集要点

oplog 中的每个操作都是幂等的。也就是说,无论对目标数据集应用一次还是多次,oplog 操作都会产生相同的结果。 Oplog大小当您第一次启动副本集成员时,如果您未指定 oplog 大小,MongoDB 将创建默认大小的 oplog。默认 oplog 大小取决于存储引擎: 存储引擎默认 Oplog 大小Wi…

副本集

MongoDB 中的副本集是一组mongod提供冗余和高可用性的进程。副本集的成员包括: 主服务器接收所有写操作。 主节点是副本集中唯一接收写入操作的成员。MongoDB 在主节点上应用写入操作,然后将操作记录在主节点的oplog上。辅助成员复制此日志并将操作应用于其数据集。副本集的所…

06.函数

Go 语言中的函数是代码组织和复用的基础,函数的定义和调用非常简单明了。下面是 Go 语言中函数的基础语法和一些关键概念: 函数的定义 在 Go 语言中,函数的定义包括函数名、参数列表、返回值类型和函数体。语法格式如下:func 函数名(参数列表) 返回值类型 {函数体 } 示例 下…

第一周学习总结

暑假第一周,经过短暂的调整,我进入到了Java语言的学习中。通过这一周的学习,在《疯狂Java讲义》与B站黑马程序员课程的帮助下,我初步了解了Java这门编程语言。以下是我对这周学习的总结: 1.学习使用CMD 学会了使用Win+R输入cmd打开CMD窗口,学习了一些常见CMD指令(如dir,…

Aide插件

aide/docs/configuration/README_CN.md at master nicepkg/aide GitHub VSCode代码阅读神器正式发布_哔哩哔哩_bilibili 结合chatgpt实现多种功能,包含代码一键生成逐行翻译、多种编程语言之间的转换等功能,对于接手大型工程的朋友们比较友善。 在安装、运行的过程中自己遇…

如何返回“无法访问此网站,网页可能暂时无法连接,或者它已经永久性地移动到了新网址”

测试网址:https://apd-vlive.apdcdn.tc.qq.com/vhqts.tc.qq.com/AH06mhzdlI9B9pSNWNcCQ3qMPITV0Kvp7M9y38FV4acs/B_tRCdt2L6hl1ezG-aht1_p5_cq9aDE5woqbhlSZqo2k-426BaS8_TYPecOQIueTdj/svp_50112/KVzxIc_QsMTArbayBuac_YlujjBVGNK2tuYZLHLdMFhy2_PHwGTGHd8PTpwBeJf4V8Ol2Ejdk…

05.循环

for循环for 的条件里不需要括号 for 的条件里可以省略初始条件,结束条件,递增表达式将整数转换为字符串:package mainimport ("fmt""strconv" )func convertToBin(n int) string {//将整数转换为二进制result := ""for ; n > 0; n /= 2 {l…

使用 Kotlin 语言开发 NeoForge 模组

前言 Kotlin 是由 JetBrains 推出的一门基于 JVM 平台的编程语言,引入了许多不同于 Java 的先进概念以及语法糖,极大地提高了开发人员的编程效率,广受各路 Java 开发者推崇。但由于 NeoForge 官方并未就使用 Kotlin 开发模组提供支持,使得精通 Kotlin 的开发者未能使用所擅…