總覽:
IAT HOOK | Object Hook | Ssdt Hook |
---|---|---|
源碼 | 核心知識及源碼 | 核心知識級源碼 |
一、IAT HOOK:
因為上一篇部落格對已經對IAT Hook基本流程及作用進行了介紹,希望能先學懂PE再來看IATHook.下面貼上Iathook的源碼,源碼中有詳細的注釋,還記着為什麼不能結束360的程序嗎?參考思路如下圖(因為寫代碼的時候解決方案寫到了源碼中,不粘貼複制過來了):
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5SM1YjMmV2MjdjMzMTZ5cDOyUDO1QGOmNTOmFDNhRzMh9CX2EzLcFDM5EDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLxM3Lc9CX6MHc0RHaiojIsJye.png)
以下代碼是DLL注入+iathook,通過測試procexp中的kill功能并沒有使用OpenProcess函數,是以需要逆向看一看他是如何結束的程序,下回與大家一起讨論,64的注入是成功,但是記得要改DWORD等32位整型變量,整體思路是不變的。
代碼中有ZwCreateThreadEx注入,是更底層的函數,大家如果注入系統程序失敗,可以把注釋打開使用ZwCreateThreadEx進行注入,測試沒問題
聲明:
// 因為ZwCreateThreadEx沒有定義,是以自己定義一個僞函數
typedef DWORD(WINAPI* FnZwCreateThreadEx)(PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
ULONG CreateThreadFlags,
SIZE_T ZeroBits,
SIZE_T StackSize,
SIZE_T MaximunStackSize,
LPVOID pUnkown);
FnZwCreateThreadEx MyZwCreateThreadEx;
// DLL的路徑
const char DllPath[MAX_PATH] = { "C:\\Users\\Administrator\\documents\\visual studio 2013\\Projects\\Text\\Debug\\TerminateProcessHook.dll" };
const char DllPath1[MAX_PATH] = { "C:\\Users\\Administrator\\documents\\visual studio 2013\\Projects\\Text\\Debug\\HookDll.dll" };
// 需要聲明的變量
HANDLE hProc = NULL;
HANDLE RemoteHandle = NULL;
注入源碼:
這是寫在一個按鈕響應消息裡面的代碼,遠端線程挂起沒有測試,如果不行可以删除。
HMODULE hNtdHandle = LoadLibrary(L"ntdll.dll");
// 擷取位址給僞函數(因為ZwCreateThreadEx沒有聲明)
MyZwCreateThreadEx = (FnZwCreateThreadEx)GetProcAddress(hNtdHandle, "ZwCreateThreadEx");
// 其實沒必要這樣寫,不過更為規範一些
auto pFinAddress = GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "LoadLibraryA");
// 第一次點選按鈕,開啟保護(隻執行一次), 第二次點選按鈕會挂起遠端線程(暫停保護),第三次會在恢複......
if ((IntHookFlag == FALSE) && (IntHookFlag == TRUE))
{
IntHookFlag = TRUE;
// 挂起遠端線程
SuspendThread(RemoteHandle);
SetDlgItemText(IDC_STATIC3, L"×");
}
else
{
IntHookFlag = FALSE;
// 恢複遠端線程
ResumeThread(RemoteHandle);
SetDlgItemText(IDC_STATIC3, L"√");
}
// IAT Hook 自我保護未開啟
if (OneIntHookFlag == FALSE)
{
// 标記為真
OneIntHookFlag = TRUE;
// 1. 擷取被注入句柄
// HANDLE hProc = FindWindow(L"CalcFrame",NULL);
hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 5000);
if (!hProc)
{
AfxMessageBox(L"FindWindow() failuer");
return;
}
// DLL名稱大小
SIZE_T dwSize = strlen(DllPath1) + 1;
// 2. 被注入程序申請記憶體空間
auto pDlladdress = VirtualAllocEx(hProc, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
if (!pDlladdress)
{
CloseHandle(hProc);
AfxMessageBox(L"VirtualAllocEx() failuer");
return;
}
// 3. 寫入記憶體資料
if (!WriteProcessMemory(hProc, pDlladdress, DllPath1, dwSize, &dwSize))
{
VirtualFree(pDlladdress, dwSize, MEM_RELEASE);
CloseHandle(hProc);
AfxMessageBox(L"WriteProcessMemory() failuer");
return;
}
// 補:這個地方建立信号量來傳遞Pid;
DWORD m_Pid = GetCurrentProcessId();
// HANDLE pProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, m_Pid);
HANDLE hMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 0x10, L"Pid");
LPVOID hMapFile = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
// DLL裡面接收PID
memcpy(hMapFile, &m_Pid, sizeof(HANDLE));
// 4. 遠端注入 LoadLibraryA擷取函數位址可以直接函數名(編譯器會幫助你擷取VA),當然也可以GetProcess自己來擷取VA
DWORD dwTid = 0;
// LoadLibrary(L"");
RemoteHandle = CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, (LPVOID)pDlladdress, 0, NULL);
// HANDLE RemoteHandle = NULL;
// DWORD dwStatu = MyZwCreateThreadEx(&RemoteHandle, PROCESS_ALL_ACCESS, NULL, hProc, (LPTHREAD_START_ROUTINE)pFinAddress, (LPVOID)pDlladdress, 0, 0, 0, 0, NULL);
SetDlgItemText(IDC_STATIC3, L"√");
DWORD error = GetLastError();
// 5. 這個地方就不等待執行後在傳回了WaitForSingleObjectEx();
// 6. 關閉遠端句柄(隻是關閉了本程序擷取到的句柄,引用計數-1)
CloseHandle(hProc);
DLL源碼:
// dllmain.cpp : 定義 DLL 應用程式的入口點。
#include "stdafx.h"
// Save New Function Address
BYTE g_NewAddress[5] = { 0xE9 };
// Save Old Function Address
BYTE g_OldAddress[5] = {};
// Save WriteAttrib
DWORD g_OldAttrib = 0;
// Save ProtectProcessPid
DWORD g_Pid = 0;
// Statement : Camouflage Function
HANDLE WINAPI MyOpenProcess(_In_ DWORD dwDesiredAccess, _In_ BOOL bInheritHandle, _In_ DWORD dwProcessId);
// Statement : Instanll Hook
void InstallHook();
// Statement : UnInstall Hook
void UnInstallHook();
// True OpenProcess
typedef
HANDLE
(WINAPI*
FnOpenProcess)(
_In_ DWORD dwDesiredAccess,
_In_ BOOL bInheritHandle,
_In_ DWORD dwProcessId
);
FnOpenProcess FOpenProcess;
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
// 被遠端線程建立時候會被調用
case DLL_PROCESS_ATTACH:
{
::MessageBox(NULL, L"X64任務管理器", L"注入", NULL);
// 安裝HOOK
InstallHook();
}
break;
case DLL_PROCESS_DETACH:
{
// 解除安裝HOOK
UnInstallHook();
}
break;
}
return TRUE;
}
// Implementation : Camouflage Function
HANDLE WINAPI MyOpenProcess(_In_ DWORD dwDesiredAccess, _In_ BOOL bInheritHandle, _In_ DWORD dwProcessId)
{
/*
做個過濾,因為我們需要保護程序隻有一個,被保護的PID是多少?
我們可以做用映射,信号量等把Pid傳到被注入程序中。
*/
HANDLE hProce = NULL;
if (dwProcessId == g_Pid)
{
// 先回複
UnInstallHook();
// 打開權限預設為NULL 拒絕通路
hProce = OpenProcess(NULL, bInheritHandle, dwProcessId);
// 在安裝
InstallHook();
return hProce;
}
else
{
// 先回複
UnInstallHook();
// 調用正确的OPenProcess
hProce = OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);
// 在安裝
InstallHook();
// 傳回正确的句柄
return hProce;//FOpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);
}
}
// Implementation : Instanll Hook
void InstallHook()
{
// 前奏工作
HANDLE hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"Pid");
LPVOID hAddr = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
g_Pid = *(DWORD*)hAddr;
// 1. 儲存原來的位址(是指令長度)
memcpy(g_OldAddress, OpenProcess, 5);
FOpenProcess = (FnOpenProcess)OpenProcess;
// 2. 計算偏移
DWORD dwOffset = (DWORD)MyOpenProcess - (DWORD)OpenProcess - 5;
// 3. 數組填充新偏移
// memcpy(&g_NewAddress[1], &dwOffset, 4); 切記不可用這種方式 當年這個BUG卡了好久 記憶體大小端字元排序 記憶體拷貝是正向的 偏移錯誤
*(DWORD *)(g_NewAddress + 1) = dwOffset;
VirtualProtect(OpenProcess, 5, PAGE_EXECUTE_READWRITE, &g_OldAttrib);
// 4. 寫入位址
memcpy(OpenProcess, g_NewAddress, 5);
VirtualProtect(OpenProcess, 5, g_OldAttrib, &g_OldAttrib);
}
// Implementation : UnInstall Hook
void UnInstallHook()
{
VirtualProtect(OpenProcess, 5, PAGE_EXECUTE_READWRITE, &g_OldAttrib);
// 寫回去就行了
memcpy(OpenProcess, g_OldAddress, 5);
VirtualProtect(OpenProcess, 5, g_OldAttrib, &g_OldAttrib);
}
二、ObjectHook:
ObjectHook相關核心資料結構知識分享,如果你寫過windows核心程式設計,那麼更容易了解一些。
![]()
從hook開始聊聊那些windows核心資料結構
如上圖所示:這就是Windows核心對象資料結構,表示系統資料庫、程序、線程等等。對象資料結構當然是對象管理器管理,所有的對象内部都會有OBJECT_HEADER的結構體,用來維護生命周期。
對象頭上面是對象頭引導,如OBJECT_HEADER_QUOTA_INFO、OBJECT_HEADER_HANDLE_INFO還有後面兩個,描述了相關對象額外的屬性,這些結構體對應着OBJECT_HEADER中的成員變量,如下介紹:
1、OBJECT_HEADER_NAME_INFO --> NameInfoOffset
2、OBJECT_HEADER_HANDLE_INFO --> HandleInfoOffset
3、OBJECT_HEADER_QUOTA_INFO --> QuotaInfoOffset
圖中OBJECT_HEADER結構體中前兩個PointerCount與HandleCount是引用計數。
1、PointerCount:核心模式對象引用數量
2、HandleCount:句柄數量
補充一些:
1. 核心中的指針引用. 一旦核心中新增了一個對象的引用, 則對象的引用計數自增一,如果一個對象的引用不再有用,則引用計數自減一. 這兩種引用的增減是使用ObReferenceObjectByPointer和ObDereferenceObject導緻的.
2. 一個程序打開一個對象并成功獲得一個句柄, 會使得對象頭中的句柄計數自增一. 當一個句柄不再被使用時, 句柄計數自減一. 這兩種引用的增減來自ObpIncrementHandleCount和ObpDecrementHandleCount函數.
+0x10指向的是_OBJECT_CREATE_INFORMATION
在建立CreateProcess時候會給該結構體申請空間,後面會填充該結構體資訊。
我們重點看一下偏移為+0x008 OBJECT_TYPE,表示對于通用屬性(對象的通用屬性)存儲,如下圖所示:
OBJECT_TYPE.TypeInfo指向了OBJECT_TYPE_INITIALIZER結構體,這個結構體包含特定對象類型的函數,對象管理器用于各類的執行操作,如下圖所示:
你會發現我用紅色框框标記了上圖的一些成員,所謂的ObjectHook就是他們(替換位址),這些函數過程會在特定時機被調用。
如何找到對象頭?
我先們在windbg下來看一看,你要知道的一點是:核心變量ObTypeIndexTable是一個指針數組,它的每個成員都指向一種對象類型的OBJECT_TYPE結構體,也就是說它是由一個指針數組維護的。
那麼我們就好辦了,找到這個指針數組看一看
到底對不對?我們來測試一下?
OBJECT_TYPE了+0x28就是_OBJECT_TYPE_INITIALIZER。windbg下面能找到,編寫代碼的時候如何擷取OBJECT_HEADER呢?
先來看一下,OBJECT_HEADER + 0x18,是成員變量Boby,這是什麼?這是對象主體,我們可以看到對象主體中OBJECT_DIRECTORY,DRIVER_OBJECT,DEVICE_OBJECT等對象結構,我們在編寫windows核心程式設計的時候會建立驅動對象,裝置對象,這樣就好說了。
假設驅動對象位址是OBJECT_HEADER + 0x18偏移的地方,那麼驅動對象位址-0x18則是OBJECT_HEADER的位址,如下圖所示:
上面代碼就是用彙編進行了的ObjectHook,如果了解了以上結構體概念,這些彙編代碼應該沒有難度。
為什麼不用結構體去程式設計?因為我不想定義那麼多結構體,部分結構體windows是沒有公開的,需要自己在頭檔案中定義,但是代碼中仍然給出了完整的結構體,可以用結構體實作,源碼如下:
頭檔案定義:
#include <ntddk.h>
/*定義的結構體資訊*/
typedef struct _OBJECT_TYPE_INITIALIZER
{
USHORT Length;
USHORT type;
PVOID ObjectTypeCode;
PVOID InvalidAttributes;
GENERIC_MAPPING GenericMapping;
PVOID ValidAccessMask;
PVOID RetainAccess;
POOL_TYPE PoolType;
PVOID DefaultPagedPoolCharge;
PVOID DefaultNonPagedPoolCharge;
PVOID DumpProcedure;
PVOID OpenProcedure;
PVOID CloseProcedure;
PVOID DeleteProcedure;
PVOID ParseProcedure;
PVOID SecurityProcedure;
PVOID QueryNameProcedure;
USHORT OkayToCloseProcedure;
} OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER;
typedef struct _OBJECT_TYPE
{
LIST_ENTRY TypeList; // : _LIST_ENTRY
UNICODE_STRING Name; // : _UNICODE_STRING
PVOID DefaultObject; // : Ptr32 Void
ULONG Index; // : UChar
ULONG TotalNumberOfObjects; // : Uint4B
ULONG TotalNumberOfHandles; // : Uint4B
ULONG HighWaterNumberOfObjects; // : Uint4B
ULONG HighWaterNumberOfHandles; // : Uint4B
OBJECT_TYPE_INITIALIZER TypeInfo; // : _OBJECT_TYPE_INITIALIZER
PVOID TypeLock; // : _EX_PUSH_LOCK
ULONG Key; // : Uint4B
LIST_ENTRY CallbackList; // : _LIST_ENTRY
} OBJECT_TYPE, *POBJECT_TYPE;
typedef struct _OBJECT_CREATE_INFORMATION
{
ULONG Attributes;
HANDLE RootDirectory;
KPROCESSOR_MODE ProbeMode;
ULONG PagedPoolCharge;
ULONG NonPagedPoolCharge;
ULONG SecurityDescriptorCharge;
PVOID SecurityDescriptor;
PSECURITY_QUALITY_OF_SERVICE SecurityQos;
SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService;
} OBJECT_CREATE_INFORMATION, *POBJECT_CREATE_INFORMATION;
typedef struct _OBJECT_HEADER
{
//對象頭部的指針計數,對對象頭指針引用的計數
LONG_PTR PointerCount;
union
{
//句柄引用計數
LONG_PTR HandleCount;
PVOID NextToFree;
};
POBJECT_TYPE Type;
//OBJECT_HEADER_NAME_INFO相對于此結構的偏移
UCHAR NameInfoOffset;
//OBJECT_HEADER_HANDLE_INFO相對于此結構的偏移
UCHAR HandleInfoOffset;
//OBJECT_HEADER_QUOTA_INFO相對于此結構的偏移
UCHAR QuotaInfoOffset;
UCHAR Flags;
union
{
//建立對象是用于建立對象附加頭的結構
//裡面儲存了和附加對象頭類似的資訊
PVOID ObjectCreateInfo;
PVOID QuotaBlockCharged;
};
PSECURITY_DESCRIPTOR SecurityDescriptor;
QUAD Body;
} OBJECT_HEADER, *POBJECT_HEADER;
// 擷取頭資訊
#define OBJECT_TO_OBJECT_HEADER(o)\
CONTAINING_RECORD((o),OBJECT_HEADER,Body)
#define CONTAINING_RECORD(address,type,field)\
((type*)(((ULONG_PTR)address)-(ULONG_PTR)(&(((type*)0)->field))))
代碼實作:
#include "HookHead.h"
VOID UnLoadDriver()
{
}
NTSTATUS MyDeleteProcedure();
NTSTATUS MaDefaultFunction(DEVICE_OBJECT* pDeviceObj, IRP* Irp)
{
Irp->IoStatus.Information = 0;
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(DRIVER_OBJECT* pDeviceObj, UNICODE_STRING* RegistryPath)
{
pDeviceObj->DriverUnload = UnLoadDriver;
for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i)
{
pDeviceObj->MajorFunction[i] = MaDefaultFunction;
}
// 1. 擷取OBJECT_HEAD
__asm
{
// 儲存環境
pushad;
pushfd;
// 1 裝置對象就是 OBJECT_BODY 也就是 -0x18是OBJECT_HREAD -0x10是OBJECT_TYPE(OBJECT_HANDLE + 0x8)
lea eax, pDeviceObj;
sub eax, 0x10;
// 2 擷取到OBJECT_TYPE之後 位址加上0x3c則是DeleteProcdure(其實已經在OBJECT_TYPE_INITIALZER結構體中)
lea eax, [eax + 0x3c];
// 3 DeleteProcdure位址替換成的MyDeleteProcedure位址
lea esi, MyDeleteProcedure;
mov eax, esi;
// 恢複環境
popad;
popfd;
}
// 2. 擷取OBJECT_TYPE
// 3. 替換相對應的函數
}
NTSTATUS MyDeleteProcedure()
{
}
三、SsdtHook:
SSDT:System Service Descriptor Table,系統服務描述符表。這個表儲存啥的?其實就是把三環零環的API聯起來,不單單是索引表,而且還包含一些索引基址、服務函數個數等。
先有個基本的概念,下面來看一張圖:
![]()
從hook開始聊聊那些windows核心資料結構
我們發現不論是System support process,還是Service processes他們都會進入核心模式之前都會經過Ntdll.dll子產品。然後通過系統服務排程程式到接口,到微核心,到HAL(驅動硬體相關聯的地方)。
是以核心層下也有微核心與HAL層,前面部落格中介紹過一些相關的結構體如_EPROCESS裡面内嵌_KPROCESS,_EPROCESS被執行體層執行,而_KPROCESS是微核心層排程。
核心層分為:
1、執行體層
2、微核心層
3、HAL層
用OD随便跟蹤函數:
上面調用的函數有點奇怪,并不是想象中的CALL [EDX],而是wow64cpu函數。這是使用者模式下實作的,作為 ntdll.dll 和核心之間的層。如果你的應用程式是32位,為了能在64位的系統上運作起來,就會調用這個函數相容,Windows下的一個子系統。
該函數有三種傳回值:
1、64位運作在64位系統下,不是WOW64模式,return 0;
2、32位運作在64位系統下,WOW64模式,return 1;
3、32位運作在32位系統下,return 0;
有點像傀儡程序一樣,函數過程大概是這樣,原程序會被建立(包括系統資料庫),然後判斷該程式資訊,如果不是64位在C槽下(具體位置記不清楚),Temp的檔案夾下建立一個64位的線程,修改系統資料庫資訊等(不太準确,隻是以前逆向的時候觀察過整個的過程)。
MOV EAX, 0x22(0x23) --> 儲存調用号 ssdt表中的序号
EAX寄存器儲存儲存函數的調用号,其實到核心以後就靠調用号來确認調用的是哪個函數。
對windows核心比較熟悉的應該知道,使用者的堆棧與核心的堆棧不是同一個堆棧空間,而且使用者層沒有權限去通路核心層的資料,通過什麼進入核心層?一條彙編指令 SYSENTER,如下圖所示:
cs:ip執行這一條彙編指令之後,你将進入到核心。如何做到的呢?其實在SYSENTRY指令之前,先會用edx儲存esp的值,執行SYSENTER時候會讀取特殊寄存器MSR模組寄存器,如下圖所示:
沒有名字,隻有編号,通過以下兩條彙編指令對MSR進行操作:
操作碼 | 指令 | 說明 |
---|---|---|
0F 32 | RDMSR | 将 ECX 指定的 MSR 加載到 EDX:EAX |
0F 30 | WRMSR | 将 EDX:EAX 中的值寫入 ECX 指定的 MSR |
詳細:
1、RDMSR:将 ECX 寄存器指定的 64 位型号專用寄存器 (MSR) 的内容加載到寄存器 EDX:EAX。EDX 寄存器中加載 MSR 的高 32 位,EAX 寄存器中加載低 32 位。在讀取的 MSR 中,如果實作的位數小于 64,則傳回 EDX:EAX 中未實作的位的值未定義。
2、WRMSR :将寄存器 EDX:EAX 的内容寫入 ECX 寄存器指定的 64 位型号專用寄存器 (MSR)。高 32 位從 EDX 複制,低 32 位從 EAX 複制。MSR 中未定義或保留的位總是設定為上次讀取時的值。
補充一下:其實有些KiFastCallEntry Hook大家看到這裡應該明白,其實就是改變編号0x176儲存的位址。
當SYSENTER執行時候,就把寄存器的值初始化成真正的寄存器CS,ESP,EIP寄存器的資料。這時候就跑到KiFastCallEntry。
KiFastCallEntry函數大家有興趣可以分析下,那麼對上面的流程更為清晰,怎樣去分析呢,windbg下就可以,如下圖所示:
竟然進入到了核心層,把使用者棧的内容拷貝到核心棧,但是拷貝多少個位元組?參數個數?
通過eax在使用者層儲存的序号,就能找到函數位址。通過調用号作為序号,就能找到參數個數,個數*4就是總位元組,其實這張表就是SSDT,還有一張表叫ShadowSSDT,專門用于儲存和使用者界面相關服務,核心中還有兩張沒有使用的表,如下圖所示(兩個結構體)。
#pragma once
#include <ntddk.h>
#define CTL_SSDT_ENABLE \
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
#define CTL_SSDT_DISABLE \
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
KSYSTEM_SERVICE_TABLE ntoskrnl; // ntoskrnl.exe的服務函數,即SSDT
KSYSTEM_SERVICE_TABLE win32k; // win32k.sys的服務函數(GDI32.dll/User32.dll 的核心支援),即ShadowSSDT
KSYSTEM_SERVICE_TABLE notUsed1; // 不使用
KSYSTEM_SERVICE_TABLE notUsed2; // 不使用
}KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
typedef struct _KSYSTEM_SERVICE_TABLE
{
PULONG ServiceTableBase; // 函數位址表的首位址
PULONG ServiceCounterTableBase; // 函數表中每個函數被調用的次數
ULONG NumberOfService; // 服務函數的個數, NumberOfService * 4 就是整個位址表的大小
UCHAR* ParamTableBase; // 參數個數表首位址
} KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;
// 僞函數(Hook的函數)
typedef NTSTATUS(NTAPI*FnNtOpenProcess)(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId);
// 儲存舊的位址
FnNtOpenProcess g_OldNtOpenProcess;
// 定義KeServieDescriptorTableShadow
KSERVICE_TABLE_DESCRIPTOR* g_ServiceTab = NULL;
// 儲存被保護程序PID
HANDLE g_Pid = 0;
#include "SSDTHookHead.h"
// 聲明:驅動解除安裝
VOID DriverUnLoad(DRIVER_OBJECT* pDeviceobj);
// 聲明:預設初始化
NTSTATUS DefaultFunction(DEVICE_OBJECT* pDeviceObj, IRP* Irp);
// 聲明:控制碼
NTSTATUS ControlCode(DEVICE_OBJECT* pDeviceObj, IRP* Irp);
// 聲明:安裝HOOK
VOID InstallHook();
// 聲明:解除安裝HOOK
VOID UnInstallHook();
// 聲明:關閉分頁保護
NTSTATUS ShudowMemoryPageProtect();
// 聲明:開啟分頁保護
NTSTATUS StartMemoryPageProtect();
// Hook實作函數
NTSTATUS MyOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId);
// 聲明:入口點
NTSTATUS DriverEntry(DRIVER_OBJECT* pDriverObj, UNICODE_STRING* RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
DEVICE_OBJECT* pDeviceObj = NULL;
UNICODE_STRING DevName;
UNICODE_STRING SymbolicLinkName;
NTSTATUS Status = STATUS_SUCCESS;
RtlInitUnicodeString(&DevName, L"\\Device\\SsdtHook");
RtlInitUnicodeString(&SymbolicLinkName, L"\\DosDevices\\SymbolicLinkName");
// DbgBreakPoint();
pDriverObj->DriverUnload = DriverUnLoad;
for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i)
{
pDriverObj->MajorFunction[i] = DefaultFunction;
}
// 裝置對象
Status = IoCreateDevice(pDriverObj, 0, &DevName, FILE_DEVICE_UNKNOWN, 0, 0, &pDeviceObj);
if (!NT_SUCCESS(Status))
return Status;
// 使用緩沖區的方式進行3環與0環通訊
pDriverObj->Flags = DO_BUFFERED_IO;
// 符号對象暴露給三環使用
Status = IoCreateSymbolicLink(&SymbolicLinkName, &DevName);
if (!NT_SUCCESS(Status))
return Status;
pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ControlCode;
return STATUS_SUCCESS;
}
VOID DriverUnLoad(DRIVER_OBJECT* pDeviceobj)
{
UNICODE_STRING DeleteSymblolicLinkName;
RtlInitUnicodeString(&DeleteSymblolicLinkName, L"\\DosDevices\\SymbolicLinkName");
IoDeleteSymbolicLink(&DeleteSymblolicLinkName);
IoDeleteDevice(pDeviceobj->DeviceObject);
}
// 實作:預設初始化
NTSTATUS DefaultFunction(DEVICE_OBJECT* pDeviceObj, IRP* Irp)
{
UNREFERENCED_PARAMETER(pDeviceObj);
Irp->IoStatus.Information = 0;
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
// 實作:控制碼
NTSTATUS ControlCode(DEVICE_OBJECT* pDeviceObj, IRP* Irp)
{
// DbgBreakPoint();
UNREFERENCED_PARAMETER(pDeviceObj);
// 通過Irp棧資料擷取控制碼
// NTSTATUS nStatus = STATUS_SUCCESS;
PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(Irp);
ULONG uControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
// 通過MDL擷取需要保護的Pid
// g_Pid = (HANDLE)MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
PVOID Pbuf = Irp->AssociatedIrp.SystemBuffer;
RtlCopyMemory((PVOID)&g_Pid, Pbuf, sizeof(ULONG));
switch (uControlCode)
{
case CTL_SSDT_ENABLE:
{
// DbgBreakPoint();
InstallHook();
}
break;
case CTL_SSDT_DISABLE:
{
UnInstallHook();
}
break;
default:
break;
}
Irp->IoStatus.Information = 0;
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
// 實作:安裝HOOK
VOID InstallHook()
{
// DbgBreakPoint();
// 1.1 擷取目前線程
PETHREAD pThread = PsGetCurrentThread();
// 1.2 線程結構體 +0xbc 擷取的是 ServiceTable
g_ServiceTab = (KSERVICE_TABLE_DESCRIPTOR*)(*(ULONG*)((ULONG_PTR)pThread + 0xbc));
// 1.3 擷取SSDT位址基址且儲存原始的函數VA
g_OldNtOpenProcess = (FnNtOpenProcess)g_ServiceTab->ntoskrnl.ServiceTableBase[0xBE];
// 1.4 替換修改位址(這個地方先要關閉頁保護)
/*
隻介紹其中的三種:
PE - 是否啟用保護模式,置1則啟用
PG - 是否使用分頁模式, 置1則開啟分頁模式, 此标志置1時,PE标志也必須置1,否則CPU報異常.
WP - WP==1時, 不能修改隻讀的記憶體頁 , WP==0 時, 可以修改隻讀的記憶體頁.
*/
ShudowMemoryPageProtect();
g_ServiceTab->ntoskrnl.ServiceTableBase[0xBE] = (ULONG)MyOpenProcess;
StartMemoryPageProtect();
}
// 實作:解除安裝HOOK
VOID UnInstallHook()
{
ShudowMemoryPageProtect();
g_ServiceTab->ntoskrnl.ServiceTableBase[0xBE] = (ULONG)g_OldNtOpenProcess;
// DbgBreakPoint();
StartMemoryPageProtect();
}
// 實作:關閉分頁保護
NTSTATUS ShudowMemoryPageProtect()
{
__asm
{
pushad;
pushfd;
mov eax, cr0;
// 前提記憶體保護一定是開啟的 WP = 1 否則..就給開啟了
and eax, ~0x10000;
mov cr0, eax;
popfd;
popad;
}
}
// 實作:開啟分頁保護
NTSTATUS StartMemoryPageProtect()
{
__asm
{
pushad;
pushfd;
mov eax, cr0;
or eax, 0x10000;
mov cr0, eax;
popfd;
popad;
}
}
// 實作:HOOK函數
NTSTATUS MyOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId)
{
// 通路權限PROCESS_ALL_ACCESS改為NULL
if (ClientId->UniqueProcess == g_Pid)
{
DbgBreakPoint();
DesiredAccess = 0;
}
return g_OldNtOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
}