十念 发表于 2024-7-7 14:38:39

浅谈历程隐藏技能

媒介

在之前几篇文章已经学习了解了几种钩取的方法
● 浅谈调试模式钩取
● 浅谈热补丁
● 浅谈内联钩取原理与实现
● 导入地点表钩取技能
这篇文章就利用钩取方式完成历程隐藏的结果。
历程遍历方法

在实现历程隐藏时,首先必要明确遍历历程的方法。
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
   * LONGpcPriClassBase;          进程线程的基本优先级
   * DWORD dwFlags;              保留
   * TCHAR szExeFile;        进程的路径
   * } 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);
 }
}
​历程隐藏

通过上述分析可以知道遍历历程的方式有三种,分别是利用CreateToolhelp32Snapshot、EnumProcesses以及ZwQuerySystemInfomation函数
但是CreateToolhelp32Snapshot与EnumProcesses函数底层都是调用了ZwQuerySystemInfomation函数,因此我们只必要钩取该函数即可。
由于测试环境是Win11,因此必要判断在Win11情况下底层是否还是调用了ZwQuerySystemInfomation函数。
可以看到在Win11下还是会调用ZwQuerySystemInfomation函数,在用户态下该函数的名称为NtQuerySystemInformation函数。
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202407050943695.png
这里接纳内联钩取的方式对ZwQuerySystemInfomation进行钩取处理,详细怎么钩取在浅谈内联钩取原理与实现已经先容过了,这里就不详细说明了。这里对自界说的ZwQuerySystemInfomation函数进行说明。
首先第一步必要进行脱钩处理,因为后续必要用到初始的ZwQuerySystemInfomation函数,紧接着获取待钩取函数的地点即可。
#include <iostream>
#include <Windows.h>
#include <Psapi.h>

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

   DWORD processes, 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 != 0)
     {
           //用于存储进程路径
           TCHAR szProcessName = { 0 };
           //使用查询权限打开进程
           HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
               PROCESS_VM_READ,
               FALSE,
               processes);

           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);
         }
     }  
 }
}为了隐藏指定历程,我们必要遍历历程信息,找到目标历程并且删除该历程信息实现隐藏的结果。这里必要知道的是历程信息都存储在SYSTEM_PROCESS_INFORMATION布局体中,该布局体是通过单链表对历程信息进行链接。因此我们通过匹配历程名称找到对应的SYSTEM_PROCESS_INFORMATION布局体,然后进行删除即可,结果如下图。
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202407050943696.png
通过单链表中删除节点的操作,取出目标历程的布局体。代码如下
#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;
           //查询进程信息
            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");
            }
      }
    }
}完备代码:https://github.com/h0pe-ay/HookTechnology/blob/main/ProcessHidden/inlineHook.c
但是接纳内联钩取的方法去钩取任务管理器就会出现一个题目,这里将断点取消,利用内联钩取的方式去隐藏历程。
首先利用bl下令查看断点
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202407050943697.png
紧着利用 bc 删除断点
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202407050943698.png
在注入之后任务管理器会在拷贝的时间发生异常
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202407050943699.png
在经过一番调试后发现,由于多线程共同执行导致原本必要可写权限的段被修改为只读权限
在windbg可以用使用!vprot + address查看指定地点的权限,可以看到由于程序往只读权限的地点进行拷贝处理,以是导致了异常。
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202407050943700.png
但是在执行拷贝阶段是先修改了该地点为可写权限,那么导致该缘故起因的情况就是其他线程执行了权限恢复后切换到该线程中进行写,以是导致了这个题目。
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202407050943702.png
因此内联钩取是存在多线程安全的题目,此时可以使用微软自己构建的钩取库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下载
...
   //脱钩
   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);
...实例

挂钩
利用Detours挂钩非常简单,只必要根据下列次序,并且将自界说函数的地点与被挂钩的地点即可完成挂钩处理。
...
      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);
     }
...脱钩
然后根据次序完成脱钩即可。
vcpkg.exe install detours:x86-windows
vcpkg.exe install detours:x64-windows
vcpkg.exe integrate install挂钩的原理

从上述可以看到,Detours是通过事务确保了在DLL加载与卸载时后的原子性,但是如何确保多线程安全呢?后续通过调试去发现。
可以利用x ntdl!ZwQuerySystemInformation查看函数地点,可以看到函数的未被挂钩前的情况如下图。
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202407050943703.png
挂钩之后原始的指令被修改为一个跳转指令把前八个字节覆盖掉,剩余的3字节用垃圾指令填充。
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202407050943704.png
该地点里面又是一个jmp指令,并且完成间接寻址的跳转。
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202407050943705.png
该地点是自界说函数ZwQuerySystemInformationEx,因此该间接跳转是跳转到的自界说函数内部。
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202407050943706.png
跳转到TrueZwQuerySystemInformation内部发现ZwQuerySystemInformation函数内部的八字节指令被移动到该函数内部。紧接着又完成一个跳转。
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202407050943707.png
该跳转到ZwQuerySystemInformation函数内部紧接着完成ZwQuerySystemInformation函数的调用。
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202407050943708.png
综上所述,整体流程如下图。实际上Detours实际上使用的是热补丁的思路,但是Detours并不是直接在原始的函数空间中进行补丁,而是开发了一段临时空间,将指令存储在里面。因此在挂钩后不必要进行脱钩处理就可以调用原始函数。因此就不存在多线程中挂钩与脱钩的辩论。
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202407050943709.png
完备代码:https://github.com/h0pe-ay/HookTechnology/blob/main/ProcessHidden/detoursHook.c
更多网安技能的在线实操练习,请点击这里>>
  

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 浅谈历程隐藏技能