系統調用(R3API調用過程詳解)
WindowsAPI
- API(Application Programming Interface),我們調用時只需提供正確的參數以及接收返回值就可以判斷API執行是否成功或者通過GetLastError獲得錯誤原因.
- 大部分API在R3都是處理各種校驗,真正執行功能都是在R0(并不是所有的API都是在R0處理).
- 系統中幾個核心DLL(Kernel32.dll,User32.dll,GDI32.dll,Ntdll.dll(大多數API通過此DLL進入內核)).
- 通過API ReadProcessMemory / OpenProcess 分析函數從R3進入R0過程,進入R0如何處理原有寄存器數據,傳遞參數,找到對應內核函數并調用,以及從R0返回R3過程.
- 前置知識點(匯編,C,Win32,段頁機制,段描述符,中斷門,).
- 涉及知識點(_KUSER_SHARED_DATA,_KTRAP_FRAME,_KPCR,_KPRCB,_KTHREAD,KiFastSystemCall→KiFastCallEntry,KiIntSystemCall→KiSystemService,SSDT)下文詳解.
- 代碼示例(重寫R3API,SSDTHOOK,內核重載).
R3API調用分析
代碼示例:
復制代碼 隱藏代碼
#include
#include
int main()
{
//隨便選擇一個進程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 2252);
//0x400000大部分情況下為ImageBase
DWORD dwData = 0;
ReadProcessMemory(hProcess, (PVOID)0x400000, &dwData, 4, NULL);
return 0;
}
1).將編譯好的文件拖入DBG / OD 分析(定位MAIN函數找到API調用位置)

2).OpenProcess執行流程分析

OpenProcess執行流程:進程模塊內CALLAPI(OpenProcess) -> kernel32.dll(OpenProcess) -> kernelBase.dll(OpenProcess) -> ntdll.dll(ZwOpenProcess) -> ntdll.dll中執行會進入R0后文詳解.
3).ReadProcessMemory執行流程分析

ReadProcessMemory執行流程:進程模塊內CALLAPI(ReadProcessMemory) -> kernel32.dll(ReadProcessMemory) -> kernelBase.dll(ReadProcessMemory) -> ntdll.dll(ZwReadVirtualMemory) -> ntdll.dll中執行會進入R0后文詳解.
R3API功能實現分析
1).ReadProcessMemory分析(R3功能實現分析)
1).通過IDA導入KernelBase.dll,查詢ReadProcessMemory函數,如下圖:

分析得出ReadProcessMemory函數并未做任何處理而是調用ntdll.dll中NtReadVirtualMemory
2).通過IDA導入Ntdll.dll,查詢NtReadVirtualMemory函數,如下圖:

后文詳解此處...
2).OpenProcess分析(R3功能實現分析)
1).通過IDA導入KernelBase.dll,查詢OpenProcess函數,如下圖:


分析得出OpenProcess函數并未做任何功能實現,而是在原有參數基礎上填充內核需要結構體信息后調用NtOpenProcess
2).通過IDA導入Ntdll.dll,查詢NtOpenProcess函數,如下圖:

這兩個函數最終都執行到ntdll.dll中并且除了eax值不相同其余都一樣.
edx = 7FFE0300h
call [edx]
這里只需要分析edx指向地址7FFE0300h中的值即可.
這里我們需要了解一個結構體_KUSER_SHARED_DATA
_KUSER_SHARED_DATA
1)._KUSER_SHARED_DATA
- 用戶層和內核層分別定義了一個_KUSER_SHARED_DATA結構體,用于在用戶層和內核層共享數據,其大小為4KB(測試環境Win7 x86 這塊結構系統默認用了0x5ff,意味著結構體 + 0x600 ~ 0xFFF可以構建自己的共享數據).
- 頁的知識可以知道共享數據是用戶層和內核層_KUSER_SHARED_DATA結構體對應線性地址指向同一個物理頁,但在用戶層中這塊內存是只讀的,內核層中是可讀可寫的.
- 用戶層和內核層使用固定的地址映射_KUSER_SHARED_DATA結構體,地址如下表所示:
內核起始地址內核結束地址用戶起始地址用戶結束地址
x860xFFDF00000xFFDF0FFF0x7FFE00000x7FFE0FFFx640xFFFFF780 |
- _KUSER_SHARED_DATA共享論證.
測試環境Win7 x86
1).Windbg輸入指令 !process 0 0 找一個進程附加

2).Windbg輸入指令 .process /i xxxxxxxx

此時Windbg處于Dbgview進程空間中.
3).Windbg輸入指令 !pte 用戶層以及內核層_KUSER_SHARED_DATA結構體對應線性地址

4).修改用戶層結構數據查看內核層對應數據

2)._KUSER_SHARED_DATA.SystemCall
復制代碼 隱藏代碼 (Windbg輸入指令 dt _KUSER_SHARED_DATA) nt!_KUSER_SHARED_DATA +0x300 SystemCall : Uint4B //系統調用 +0x304 SystemCallReturn : Uint4B //調用返回
R3API如果通過 MOV EDX, 7FFE0300h; CALL DWORD PTR [edx];方式進R0,實際上相當于調用_KUSER_SHARED_DATA.SystemCall中的存儲的值.
_KUSER_SHARED_DATA.SystemCall中存儲的值決定了函數通過什么方式進R0.(操作系統通過檢查當前CPU是否支持快速調用來填充這個值,支持函數地址為KiFastSystemCall快速調用,不支持函數地址為KiIntSystemCall中斷調用).
CPU是否支持快速調用?
當EAX = 1 執行CPUID指令 如果EDX第11位(SEP) = 1 說明支持快速調用,否則為中斷調用,即_KUSER_SHARED_DATA.SystemCall中存儲的值.
至此已經了解到R3進入R0兩種方式,接下來分析中斷調用,快速調用如何進入R0.
代碼示例:
復制代碼 隱藏代碼
#include
#include
int main()
{
DWORD dwEAX = 0;
DWORD dwECX = 0;
DWORD dwEDX = 0;
__asm
{
xor eax, eax
mov eax, 1
CPUID
mov dwEAX, eax
mov dwECX, eax
mov dwEDX, edx
}
printf("EAX 0x%08x ",dwEAX);
printf("ECX 0x%08x ",dwECX);
printf("EDX 0x%08x ",dwEDX);
printf("EDX 11(BIT) [%d] ", (dwEDX & 0x800) >> 11);
system("pause");
return 0;
}
intel白皮書介紹如下:


系統調用
1).中斷調用KiIntSystemCall
固定中斷號為: 2Eh 通過解析如下圖:

2).快速調用KiFastSystemCall
如果CPU支持sysenter(快速調用)指令,操作系統會提前將CS/ESP/EIP的值存儲在MSR寄存器中,sysenter指令執行時,CPU會將MSR寄存器中的值寫入相關寄存器(沒有查詢內存過程速度更快).
MSR地址IA32_SYSENTER_CS174HIA32_SYSENTER_ESP175HIA32_SYSENTER_EIP176H

intel白皮書對SYSENTER介紹如下:

代碼示例(特權指令需要在R0下運行)
復制代碼 隱藏代碼
#include <ntifs.h>
NTSTATUS DriverUnload(PDRIVER_OBJECT pDriver)
{
DbgPrint("Driver Exit \r");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
DbgPrint("Driver Load \r");
pDriver->DriverUnload = DriverUnload;
DbgBreakPoint();
ULONGLONG uData = 0;
__asm
{
mov ecx, 0x174 //相當于msr寄存器index
rdmsr
mov dword ptr [uData], eax //eax存儲數據低32位
mov dword ptr [uData + 4],edx //edx存儲數據高32位
}
DbgPrint("MSR[174] -> [0x%llx] \r", uData);
return STATUS_SUCCESS;
}
3).中斷調用快速調用區別如下
快速調用中斷調用
R3執行APIKiFastSystemCallKiIntSystemCall 8bd4 mov edx,esp //三環棧頂 系統調用號在EAX 0f34 sysenter8d542408 lea edx,[esp+8] //參數指針 系統調用號在EAX cd2e int 2Eh |
c3 ret提權方式(段的機制R3進入R0相當于CPL發生改變 )如果CPU支持sysenter指令,操作系統會提前將CS/ESP/EIP的值存儲在MSR寄存器中,sysenter指令執行時,CPU會將MSR寄存器中的值寫入相關寄存器(沒有查詢內存過程速度更快).將特權級切換到R0,如果EFLAG.VM被置位,則清除該標志位.int 2Eh對應段描述符為83e4ee00-00083fee中斷門描述符,其中加載代碼段選擇子為0x0008 對應段描述符為00cf9(1001)b00-0000ffff DPL = 0執行成功后CPL = 0.且因權限切換會向堆棧壓入SS,ESP,EFLAG,CS,EIP.提權方式(段的機制R3進入R0相當于CPL發生改變 )查詢MSR寄存器.CS = rdmsr 174.SS = CS + 8(數值上).ESP = rdmsr 175.EIP = rdmsr 176.ESP,SS由TSS提供.CS由中斷門描述符中低4字節高16位提供.EIP由中斷門描述符中高4字節高16位與低4字節低16位組成.進入R0執行APIKiFastCallEntry(Windbg輸入 rdmsr 176獲取)KiSystemService(Windbg輸入!IDT 2E獲取)
至此已經知道R3API在ntdll.dll中進入R0的兩種方法.
在分析對應內核函數前需要了解兩個結構體_KTRAP_FRAME,_KPCR.
_KTRAP_FRAME
Windbg輸入dt _KTRAP_FRAME指令:
_KTRAP_FRAME結構如下:
復制代碼 隱藏代碼 nt!_KTRAP_FRAME //類似于R3 -> CONTEXT +0x000 DbgEbp : Uint4B +0x004 DbgEip : Uint4B +0x008 DbgArgMark : Uint4B +0x00c DbgArgPointer : Uint4B +0x010 TempSegCs : Uint2B +0x012 Logging : UChar +0x013 Reserved : UChar +0x014 TempEsp : Uint4B +0x018 Dr0 : Uint4B +0x01c Dr1 : Uint4B +0x020 Dr2 : Uint4B +0x024 Dr3 : Uint4B +0x028 Dr6 : Uint4B +0x02c Dr7 : Uint4B +0x030 SegGs : Uint4B +0x034 SegEs : Uint4B +0x038 SegDs : Uint4B +0x03c Edx : Uint4B +0x040 Ecx : Uint4B +0x044 Eax : Uint4B +0x048 PreviousPreviousMode : Uint4B +0x04c ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD +0x050 SegFs : Uint4B +0x054 Edi : Uint4B +0x058 Esi : Uint4B +0x05c Ebx : Uint4B +0x060 Ebp : Uint4B +0x064 ErrCode : Uint4B //如果是發生錯誤導致其他中斷觸發時,這里會有ErrCode,中斷調用進內核函數KiSystemService這里push 0. +0x068 Eip : Uint4B +0x06c SegCs : Uint4B +0x070 EFlags : Uint4B +0x074 HardwareEsp : Uint4B +0x078 HardwareSegSs : Uint4B +0x07c V86Es : Uint4B //0x07c ~ 0x088位置為虛擬8086模式下使用,函數進入R0時棧頂默認指向_KTRAP_FRAME.V86Es +0x080 V86Ds : Uint4B +0x084 V86Fs : Uint4B +0x088 V86Gs : Uint4B
0x07c ~ 0x088位置為虛擬8086模式下使用.
中斷調用進入R0時棧頂(ESP由TSS提供(每個線程進入R0時ESP都由TSS.ESP0提供,以及TSS里存儲的ESP0一直是當前線程進入R0時對應ESP0,線程切換時會更新TSS里存儲的ESP0))默認指向_KTRAP_FRAME.V86Es,中斷門執行權限發生切換時會向堆棧壓入SS,ESP,EFLAG,CS,RETADDR(EIP),由此得知當執行函數KiIntSystemCall進入R0函數KiSystemService時此時ESP指向_KTRAP_FRAME.Eip.(下文分析).
快速調用進入R0時堆棧是由MSR[175]提供的,KiFastCallEntry函數執行時首先會修改FS指向_KPCR結構,通過_KPCR -> _TSS定位到當前線程ESP0,并切換新的堆棧.此時ESP指向_KTRAP_FRAME.V86Ds.(下文分析).
_KPCR
一個核一個_KPCR(Processor Control Region CPU控制塊)記錄當前CPU核對應各種狀態以及上下文環境.
復制代碼 隱藏代碼 查看CPU數量 kd> dd KeNumberProcessors 83fb796c 00000001 //一個核心 查看KPCR kd> dd KiProcessorBlock //幾個核對應幾個KPCR 83fb78c0 83f78d20 00000000 //減去120(kpcr的大小) kd> dt _KPCR 83f78d20-120 //就是kpcr的地址 復制代碼 隱藏代碼 nt!_KPCR +0x000 NtTib : _NT_TIB nt!_NT_TIB +0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD +0x004 StackBase : Ptr32 Void +0x008 StackLimit : Ptr32 Void +0x00c SubSystemTib : Ptr32 Void +0x010 FiberData : Ptr32 Void +0x010 Version : Uint4B +0x014 ArbitraryUserPointer : Ptr32 Void +0x018 Self : Ptr32 _NT_TIB //結構體指針 指向自己 +0x000 Used_ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD +0x004 Used_StackBase : Ptr32 Void +0x008 Spare2 : Ptr32 Void +0x00c TssCopy : Ptr32 Void +0x010 ContextSwitches : Uint4B +0x014 SetMemberCopy : Uint4B +0x018 Used_Self : Ptr32 Void +0x01c SelfPcr : Ptr32 _KPCR +0x020 Prcb : Ptr32 _KPRCB +0x024 Irql : UChar +0x028 IRR : Uint4B +0x02c IrrActive : Uint4B +0x030 IDR : Uint4B +0x034 KdVersionBlock : Ptr32 Void +0x038 IDT : Ptr32 _KIDTENTRY +0x03c GDT : Ptr32 _KGDTENTRY +0x040 TSS : Ptr32 _KTSS +0x044 MajorVersion : Uint2B +0x046 MinorVersion : Uint2B +0x048 SetMember : Uint4B +0x04c StallScaleFactor : Uint4B +0x050 SpareUnused : UChar +0x051 Number : UChar +0x052 Spare0 : UChar +0x053 SecondLevelCacheAssociativity : UChar +0x054 VdmAlert : Uint4B +0x058 KernelReserved : [14] Uint4B +0x090 SecondLevelCacheSize : Uint4B +0x094 HalReserved : [16] Uint4B +0x0d4 InterruptMode : Uint4B +0x0d8 Spare1 : UChar +0x0dc KernelReserved2 : [17] Uint4B +0x120 PrcbData : _KPRCB
KiSystemService(函數分析)
KiIntSystemCall(R3) -> KiSystemService(R0)
設置環境


KiSystemService設置環境后跳轉KiFastCallEntry(詳情見下文)
KiFastCallEntry(函數分析)
KiFastSystemCall(R3) -> KiFastCallEntry(R0)
設置環境


尋找內核函數地址,拷貝參數
此時需要了解一個結構SystemServiceTable
結構如下:

復制代碼 隱藏代碼
定位SystemServiceTable
_KTHREAD -> ServiceTable
系統服務表有兩張:
1.ntoskrnl.exe導出的常用系統服務.
2.Win32k.sys導出的與圖形顯示和用戶界面相關的系統服務(只有GDI相關線程訪問對應系統服務表才會有值).
系統服務表結構如下:
typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
KSYSTEM_SERVICE_TABLE ntoskrnl; // 內核函數
KSYSTEM_SERVICE_TABLE win32k; // win32k.sys 函數
KSYSTEM_SERVICE_TABLE unUsed1; // 未使用
KSYSTEM_SERVICE_TABLE unUsed2; // 未使用
} KSERVICE_TABLE_DESCRIPTOR, * PKSERVICE_TABLE_DESCRIPTOR;
typedef struct _KSYSTEM_SERVICE_TABLE
{
PULONG ServiceTableBase; // 函數地址表基址
PULONG ServiceCounterTableBase;// 函數被調用的次數
ULONG NumberOfService; // 函數個數
PULONG ParamTableBase; // 函數參數表基址
} KSYSTEM_SERVICE_TABLE, * PKSYSTEM_SERVICE_TABLE;
ServiceTable指向函數地址表每個成員大小為4字節,存儲函數地址.
ServiceLimit存儲函數地址表的成員個數.
ArgumentTable 函數參數表每個成員大小為1字節,存儲函數參數個數(存儲值 / 4 = 參數個數).
在快速調用和中斷調用R3函數執行時,EAX存儲了系統服務號.
通過第12位確定是哪張表.
通過低12位確定在函數地址表中的索引值以及函數參數表的索引值.
System Services Descriptor Table系統服務描述符表,為導出結構KeServiceDescriptorTable(代碼中只需聲明即可直接使用).

查找ReadProcessMemory(測試環境系統服務號為115h)對應內核函數地址以及參數
Windbg查看SSDT
dd KeServiceDescriptorTable

繼續函數分析


至此完成了初始化內核環境以及參數拷貝,函數調用.
函數返回
涉及到APC,此部分會更新到APC處,大致流程為執行完畢后首先判斷當前IRQL等級(不為0跳轉處理藍屏),然后判斷是否為虛擬8086模式,接著判斷有沒有APC需要處理等.最后通過iretd返回.
涉及到的結構體如下圖:

Win7 x86系統調用全過程...
重寫R3API
- WindowsAPI分析中可以判斷出大部分API在R3都未做真正功能實現,只是完成一些內核所需結構數據填充,數據校驗等等.
- 重寫R3API,需對接快速調用/中短調用堆棧要求,以及對應內核函數所需數據就可以實現(可避免惡意掛鉤,R3層的API監控等).
1).快速調用方式重寫
通過WindowsAPI分析,可以得知除了R3API業務實現內還需要注意EDX進入內核前指向棧頂,EAX存儲系統服務號.
復制代碼 隱藏代碼
#include
#include
BOOL MyReadMemory(
HANDLE hProcess,
LPCVOID lpBaseAddress,
LPVOID lpBuffer,
SIZE_T nSize,
SIZE_T* lpNumberOfBytesRead
)
{
BOOL bRet = 0;
__asm
{
//Kernelbase.dll -> ReadProcessMemory
lea eax, nSize
push eax
push nSize
push lpBuffer
push lpBaseAddress
push hProcess
//ntdll.dll -> NtReadVirtualMemory
//模擬call 棧頂-4
sub esp, 4
//系統服務號
mov eax, 0x115
//KiFastSystemCall
//模擬call 堆棧保存返回地址
PUSH RETADDR
//快速調用
mov edx, esp
//sysenter對應硬編碼
_emit 0x0F;
_emit 0x34;
RETADDR:
add esp, 0x18
mov bRet, eax
}
return bRet;
}
int main()
{
DWORD dwData = 0;
HANDLE handle = 0;
handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 2252);
MyReadMemory(handle, (LPVOID)0x400000, &dwData, 4, NULL);
printf("dwData [0x%08x] ", dwData);
system("pause");
return 0;
}
2).中斷調用方式重寫
復制代碼 隱藏代碼
#include
#include
BOOL MyReadMemory(
HANDLE hProcess,
LPCVOID lpBaseAddress,
LPVOID lpBuffer,
SIZE_T nSize,
SIZE_T* lpNumberOfBytesRead
)
{
BOOL bRet = 0;
__asm
{
//系統服務號
mov eax, 0x115
//首參數指針
lea edx, hProcess
//中斷調用固定號
int 0x2E
}
return bRet;
}
int main()
{
DWORD dwData = 0;
HANDLE handle = 0;
handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 2252);
MyReadMemory(handle, (LPVOID)0x400000, &dwData, 4, NULL);
printf("dwData [0x%08x] ", dwData);
system("pause");
return 0;
}
3).動態重寫
- 上述兩種方式中系統服務號都是寫死的不方便項目中使用,下述代碼演示動態獲取系統服務號并調用函數.
復制代碼 隱藏代碼
#include
#include
#include
typedef NTSTATUS(WINAPI* ZwOpenProcessProc)(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, CLIENT_ID* ClientId);
int main()
{
//獲取ZwOpenProcess函數地址
HMODULE hModule = LoadLibraryA("ntdll.dll");
PUCHAR pFunAddr = (PUCHAR)GetProcAddress(hModule, "ZwOpenProcess");
printf("Funaddr -> [0x%08x] \r", pFunAddr);
//獲取ZwOpenProcess函數長度
ULONG uSize = 0;
for (int i = 0; i < 100; i++)
{
//C2 == ret
if (pFunAddr[i] == 0xc2)
{
uSize = i + 2;
break;
}
}
printf("FunLength -> [0x%08x] \r", uSize);
//函數指針申請內存
ZwOpenProcessProc func = (ZwOpenProcessProc)VirtualAlloc(NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
//拷貝默認函數數據
memcpy(func, pFunAddr, uSize);
//分析得知需要打開進程需要填充CLIENT_ID.UniqueProcess
CLIENT_ID Client = { 0 };
Client.UniqueProcess = (HANDLE)2252;
//分析得知需要填充POBJECT_ATTRIBUTES.Length
OBJECT_ATTRIBUTES Obj_Attr = { 0 };
Obj_Attr.Length = sizeof(OBJECT_ATTRIBUTES);
HANDLE hProcess = NULL;
NTSTATUS ntstatus = func(&hProcess, PROCESS_ALL_ACCESS, &Obj_Attr, &Client);
printf("Ret -> [%x] hProcess -> [%x] \r", ntstatus, hProcess);
system("Pause");
return 0;
}
SSDT_HOOK
測試環境Win7 x86
- 重新加載一份按照PE格式拉伸后的內核文件到內存(避免當前內核已經被掛鉤).
- 通過導出表獲取HOOK函數系統服務號.
- 利用導出KeServiceDescriptorTable結構定位系統服務表實現替換函數(類似IAT_HOOK).
復制代碼 隱藏代碼
#include
#include
#include
//獲取系統目錄
PWCHAR GetSystemFullPath();
//內核文件按照PE拉伸后格式映射到內存
PUCHAR FileMaping(PWCHAR SystemPath);
//釋放文件映射
VOID UnFileMaping(PVOID mapBase);
//通過函數名查找導出函數
ULONG64 GetFuntionAddressByExportTableName(PUCHAR ImageBuffer, PUCHAR FunctionName);
//導出未文檔化函數
NTSTATUS MmCreateSection(
__deref_out PVOID* SectionObject,
__in ACCESS_MASK DesiredAccess,
__in_opt POBJECT_ATTRIBUTES ObjectAttributes,
__in PLARGE_INTEGER InputMaximumSize,
__in ULONG SectionPageProtection,
__in ULONG AllocationAttributes,
__in_opt HANDLE FileHandle,
__in_opt PFILE_OBJECT FileObject
);
// 系統服務表
typedef struct _KSYSTEM_SERVICE_TABLE
{
PULONG ServiceTableBase; // 函數地址表(SSDT)
PULONG ServiceCounterTableBase; // SSDT 函數被調用的次數
ULONG NumberOfService; // 函數個數
PULONG ParamTableBase; // 函數參數表(SSPT)
} KSYSTEM_SERVICE_TABLE, * PKSYSTEM_SERVICE_TABLE;
typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
KSYSTEM_SERVICE_TABLE ntoskrnl; // 內核函數
KSYSTEM_SERVICE_TABLE win32k; // win32k.sys 函數
KSYSTEM_SERVICE_TABLE unUsed1;
KSYSTEM_SERVICE_TABLE unUsed2;
} KSERVICE_TABLE_DESCRIPTOR, * PKSERVICE_TABLE_DESCRIPTOR;
extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;
PUCHAR G_MapNtdll = NULL;
//拷貝SSDT表
BOOLEAN SSDT_Init();
//釋放SSDT表
VOID SSDT_Destroy();
//獲取函數系統服務號
ULONG SSDT_GetFunIndex(PUCHAR szFunctionName);
//SSDTHOOK
ULONG_PTR SSDT_Hook(PUCHAR szFunctionName, ULONG_PTR FunctionAddr);
//關閉寫保護以及中斷
ULONG wpOff()
{
ULONG cr0 = __readcr0();
_disable();
__writecr0(cr0 & (~0x10000));
return cr0;
}
//恢復CR0默認數據
VOID wpOn(ULONG value)
{
__writecr0(value);
_enable();
}
//恢復HOOK時用到
ULONG G_OldFunAddr = NULL;
//函數指針
typedef NTSTATUS(NTAPI* OpenProcessProc)(_Out_ PHANDLE ProcessHandle, _In_ ACCESS_MASK DesiredAccess, _In_ POBJECT_ATTRIBUTES ObjectAttributes, _In_opt_ PCLIENT_ID ClientId);
//替換函數
NTSTATUS NTAPI MyOpenProcess(_Out_ PHANDLE ProcessHandle, _In_ ACCESS_MASK DesiredAccess, _In_ POBJECT_ATTRIBUTES ObjectAttributes, _In_opt_ PCLIENT_ID ClientId)
{
PUCHAR pEprocess = (PUCHAR)IoGetCurrentProcess();
DbgPrint("進程ID: [%d] 調用OpenProcess \r", *(PULONG)(pEprocess + 0xb4));
//TODO:
//獲取參數,監控,修改返回值....
return ((OpenProcessProc)G_OldFunAddr)(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
}
NTSTATUS DriverUnload(PDRIVER_OBJECT pDriver)
{
DbgPrint("Driver Exit \r");
//恢復鉤子
if (G_OldFunAddr)
{
SSDT_Hook("ZwOpenProcess", G_OldFunAddr);
}
//釋放后延遲避免有進程還在執行我們函數釋放導致藍屏
SSDT_Destroy();
//延時
LARGE_INTEGER inTime = { 0 };
inTime.QuadPart = -10000 * 3000;
KeDelayExecutionThread(KernelMode, FALSE, &inTime);
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
DbgPrint("Driver Load \r");
pDriver->DriverUnload = DriverUnload;
if (SSDT_Init())
{
G_OldFunAddr = SSDT_Hook("ZwOpenProcess", MyOpenProcess);
}
return STATUS_SUCCESS;
}
PWCHAR GetSystemFullPath()
{
//申請路徑緩沖區
PWCHAR SystemPath = ExAllocatePool(PagedPool, PAGE_SIZE);
if (!SystemPath)
{
return NULL;
}
memset(SystemPath, 0, PAGE_SIZE);
//初始化路徑
RtlStringCbPrintfW(SystemPath, PAGE_SIZE, L"\\??\\%s\\System32\tdll.dll", SharedUserData->NtSystemRoot);
DbgPrint("SystemPath -> [%ws] \r", SystemPath);
return SystemPath;
}
PUCHAR FileMaping(PWCHAR SystemPath)
{
//Initialize UnicodeString
UNICODE_STRING FileName = { 0 };
RtlInitUnicodeString(&FileName, SystemPath);
//Initialize ObjectAttribute
OBJECT_ATTRIBUTES objectFile = { 0 };
InitializeObjectAttributes(&objectFile, &FileName, OBJ_CASE_INSENSITIVE, NULL, NULL);
//CreateFile
HANDLE hFile = NULL;
IO_STACK_LOCATION iostacklocation = { 0 };
NTSTATUS ntstatus = ZwCreateFile(&hFile, GENERIC_READ, &objectFile, &iostacklocation, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, NULL);
if (!NT_SUCCESS(ntstatus))
{
DbgPrint("FileMaping ZwCreateFile Filed \r");
return NULL;
}
//Create Section
OBJECT_ATTRIBUTES objectSection = { 0 };
InitializeObjectAttributes(&objectSection, NULL, OBJ_CASE_INSENSITIVE, NULL, NULL);
PVOID pSection = NULL;
LARGE_INTEGER InputMaximumSize = { 0 };
ntstatus = MmCreateSection(&pSection, SECTION_ALL_ACCESS, &objectSection, &InputMaximumSize, PAGE_EXECUTE_READWRITE, 0x1000000, hFile, NULL);
if (!NT_SUCCESS(ntstatus))
{
DbgPrint("FileMaping MmCreateSection Filed \r");
ZwClose(hFile);
return NULL;
}
PVOID pMapBase = NULL;
SIZE_T ViewSize = 0;
ntstatus = MmMapViewInSystemSpace(pSection, &pMapBase, &ViewSize);
ObDereferenceObject(pSection);
ZwClose(hFile);
if (NT_SUCCESS(ntstatus))
{
return pMapBase;
}
return NULL;
}
VOID UnFileMaping(PVOID pImage)
{
if (pImage)
{
MmUnmapViewInSystemSpace(pImage);
}
}
ULONG64 GetFuntionAddressByExportTableName(PUCHAR ImageBuffer, PUCHAR FunctionName)
{
//Headers
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)ImageBuffer;
if (*(PUSHORT)pDos != IMAGE_DOS_SIGNATURE)
{
DbgPrint("Not PeFile \r");
return NULL;
}
PIMAGE_NT_HEADERS pNts = (PIMAGE_NT_HEADERS)(ImageBuffer + pDos->e_lfanew);
if (*(PULONG)pNts != IMAGE_NT_SIGNATURE)
{
DbgPrint("Not PeFile \r");
return NULL;
}
PIMAGE_FILE_HEADER pFil = (PIMAGE_FILE_HEADER)((ULONG)pNts + 0x4);
PIMAGE_OPTIONAL_HEADER pOpt = (PIMAGE_OPTIONAL_HEADER)((ULONG)pFil + IMAGE_SIZEOF_FILE_HEADER);
PIMAGE_EXPORT_DIRECTORY pExp = (PIMAGE_EXPORT_DIRECTORY)(ImageBuffer + pOpt->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
//遍歷導出表
ULONG64 FunctionAddr = NULL;
for (int i = 0; i < pExp->NumberOfNames; i++)
{
PULONG pAddressOfFuntion = ImageBuffer + pExp->AddressOfFunctions;
PULONG pAddressOfNames = ImageBuffer + pExp->AddressOfNames;
PUSHORT pAddressOfOrd = ImageBuffer + pExp->AddressOfNameOrdinals;
PUCHAR CurrentFunctionName = ImageBuffer + pAddressOfNames[i];
ULONG uIndex = -1;
if (strcmp(CurrentFunctionName, FunctionName) == 0)
{
uIndex = pAddressOfOrd[i];
}
if (uIndex != -1)
{
FunctionAddr = ImageBuffer + pAddressOfFuntion[uIndex];
break;
}
}
if (FunctionAddr)
{
DbgPrint("FindFunctionAddress FunName[%s] Addr[%p] \r", FunctionName, FunctionAddr);
}
else
{
DbgPrint("FindFunctionAddress Error FunName[%s] \r", FunctionName);
}
return FunctionAddr;
}
BOOLEAN SSDT_Init()
{
if (G_MapNtdll)
{
return TRUE;
}
PWCHAR szPath = GetSystemFullPath();
if (szPath == NULL)
{
return FALSE;
}
G_MapNtdll = FileMaping(szPath);
if (G_MapNtdll == NULL)
{
ExFreePool(szPath);
return FALSE;
}
ExFreePool(szPath);
return TRUE;
}
VOID SSDT_Destroy()
{
if (G_MapNtdll)
{
UnFileMaping(G_MapNtdll);
G_MapNtdll = NULL;
}
}
ULONG SSDT_GetFunIndex(PUCHAR szFunctionName)
{
//獲取函數地址
PUCHAR pFunAddr = (PUCHAR)GetFuntionAddressByExportTableName(G_MapNtdll, szFunctionName);
if (pFunAddr == NULL)
{
return -1;
}
//獲取函數系統服務號
return *(PULONG)(pFunAddr + 1);
}
ULONG_PTR SSDT_Hook(PUCHAR szFunctionName, ULONG_PTR FunctionAddr)
{
ULONG uIndex = SSDT_GetFunIndex(szFunctionName);
if (uIndex == -1)
{
return NULL;
}
//備份舊的地址
ULONG OldFuncAddr = KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[uIndex];
//替換SSDT表中函數
ULONG cr0 = wpOff();
KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[uIndex] = FunctionAddr;
wpOn(cr0);
return OldFuncAddr;
}