SSDT 中文名稱為系統服務描述符表,該表的作用是將Ring3應用層與Ring0內核層,兩者的API函數連接起來,起到承上啟下的作用,SSDT並不僅僅只包含一個龐大的地址索引表,它還包含着一些其它有用的信息,諸如地址索引的基址、服務函數個數等,SSDT 通過修改此表的函數地址可以對常用 Windows 函數進行內核級的Hook,從而實現對一些核心的系統動作進行過濾、監控的目的。
通過前面的學習我們已經可以編寫一個驅動程序並掛鈎到指定的內核函數上了,接下來我們將一步步的通過編寫驅動程序,手動的來解除 NtOpenProcess
函數的驅動保護,以此來模擬如何一步步干掉游戲保護。
一般情況下當游戲啟動的時候都會加載保護,而這種保護通常都是通過在SSDT層掛鈎來實現的,而一旦函數被掛鈎那么通過前面的讀取方式就無法讀取到函數的原始地址了,如下圖是一個被Hook過的函數,可以看到函數的當前地址與原始地址已經發生了明顯的變化。
那么如何獲取到原始函數地址呢?很簡單只需要使用系統提供給我們的 MmGetSystemRoutineAddress
函數即可獲取到原始函數的地址,最終測試代碼如下:
#include <ntddk.h>
extern "C" LONG KerServiceDescriptorTable;
ULONG Get_SSDTAddr(){
UNICODE_STRING NtOpen; // 存放函數的Unicode字符串
ULONG SSDT_Addr; // 用於存放原始的SSDT地址
// 將NtOpenProcess字符串以Uncode格式寫入到NtOpen變量中
RtlInitUnicodeString(&NtOpen, L"NtOpenProcess");
// 獲取系統程序地址,取得NtOpenProcess的原始地址
SSDT_Addr = (ULONG)MmGetSystemRoutineAddress(&NtOpen);
DbgPrint("原始函數的地址是: %x\n", SSDT_Addr);
return SSDT_Addr;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("驅動卸載成功 ! \n"));
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
Get_SSDTAddr();
DriverObject->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
編譯這段驅動代碼,然后回到虛擬機並加載這段驅動,手動驗證一下觀察:
上方的驅動代碼也可以改用匯編來實現,其效果是相同的,貼出匯編代碼的實現流程,這里就不演示了。
#include <ntddk.h>
extern "C" LONG KerServiceDescriptorTable;
ULONG Get_SSDTAddr(){
UNICODE_STRING NtOpen; // 存放函數的Unicode字符串
ULONG SSDT_Addr; // 用於存放原始的SSDT地址
// 將NtOpenProcess字符串以Uncode格式寫入到NtProcess變量中
RtlInitUnicodeString(&NtOpen, L"NtOpenProcess");
__asm
{
lea eax, NtOpen // 將初始化的NtOpenProcess地址給EAX
push eax // EAX壓入堆棧 等待調用
call DWORD ptr DS:[MmGetSystemRoutineAddress]
mov SSDT_Addr,eax // 將結果賦值給變量
}
DbgPrint("原始函數的地址是: %x\n", SSDT_Addr);
return SSDT_Addr;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("驅動卸載成功 ! \n"));
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
Get_SSDTAddr();
DriverObject->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
通過偏移二次讀取: 上面的代碼運行后只能獲取到一部分函數的原始地址,有些函數的地址是無法獲取到的,比如我們想要獲取 NtReadVirtualMemory
這個內核函數的地址時,上方的代碼就會顯示獲取失敗,如下獲取結果始終顯示為0。
既然無法獲取到當前函數的地址,那么我們可以嘗試獲取NtReadVirtualMemory
函數的前一個函數的內存地址,並通過相加偏移的方式來獲取該函數的地址,首先我們通過Xuetr 查詢到 NtReadVirtualMemory
函數的當前地址,然后通過 WinDBG
調試器找到其對應的前一個函數的偏移。
lkd> u 83e7f82c
nt!MmCopyVirtualMemory+0x50a:
83e7f82c 6a18 push 18h
83e7f82e 68285ac783 push offset nt!NtBuildGUID+0xc9a4 (83c75a28)
83e7f833 e870e3e1ff call nt!strchr+0x118 (83c9dba8)
83e7f838 648b3d24010000 mov edi,dword ptr fs:[124h]
83e7f83f 8a873a010000 mov al,byte ptr [edi+13Ah]
83e7f845 8845e4 mov byte ptr [ebp-1Ch],al
83e7f848 8b7514 mov esi,dword ptr [ebp+14h]
83e7f84b 84c0 test al,al
查詢結果中可以發現上一個函數的是 MmCopyVirtualMemory
而相對應的偏移地址是 0x50a
,接着直接改進上方的程序,即可實現查詢,代碼如下:
#include <ntddk.h>
extern "C" LONG KerServiceDescriptorTable;
ULONG Get_SSDTAddr(){
UNICODE_STRING NtOpen;
ULONG SSDT_Addr;
RtlInitUnicodeString(&NtOpen, L"MmCopyVirtualMemory");
SSDT_Addr = (ULONG)MmGetSystemRoutineAddress(&NtOpen);
__asm
{
push eax
mov eax,SSDT_Addr
add eax,50ah
mov SSDT_Addr,eax
}
DbgPrint("原始函數的地址是: %x\n", SSDT_Addr);
return SSDT_Addr;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
KdPrint(("Uninstall Driver Is OK \n"));
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
Get_SSDTAddr();
DriverObject->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
**判斷函數是否被Hook:** 上方的代碼中,我們可以通過使用`MmGetSystemRoutineAddress`函數來獲取到函數的原始地址,而在第一部分我們又通過匯編的方式得到了函數的當前地址,通過使用當前地址與原始地址做比較即可判斷出函數是否被Hook。 ```C #include
typedef struct _JMPDATE
{
BYTE E9; // 定義一個字節的e9成員名用來存放一字節數據
ULONG JMPADDR; // 定義JMPADDR成員名用來存放4字節跳轉地址數據
}JMPDATE;
ULONG Get_Origin_SSDTAddr(){ // 獲取到NTOpenProcess的原始地址
UNICODE_STRING NtOpen;
ULONG SSDT_Addr;
RtlInitUnicodeString(&NtOpen, L"NtOpenProcess");
SSDT_Addr = (ULONG)MmGetSystemRoutineAddress(&NtOpen);
return SSDT_Addr;
}
ULONG Get_Now_SSDTAddr(){ // 獲取到NTOpenProcess的當前地址
ULONG SSDT_Addr;
__asm{
push ebx
push eax
mov ebx, KeServiceDescriptorTable // 系統描述符號表的地址
mov ebx, [ebx] // 取服務表基址給EBX
mov eax, 0xBE // NtOpenProcess=轉成十六進制等於BE
imul eax, eax, 4 // eax=eax4 -> 7a4=1e8
add ebx, eax // eax=1e8與服務表基址EBX相加
mov ebx, [ebx] // 取ebx里面的內容給EBX
mov SSDT_Addr, ebx // 將得到的基址給變量
pop eax
pop ebx
}
return SSDT_Addr;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
KdPrint(("驅動卸載成功 !\n"));
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath)
{
ULONG Get_Origin_SSDT, Get_Now_SSDT;
JMPDATE JmpDate;
Get_Now_SSDT = Get_Now_SSDTAddr(); // 獲取NTOpenProcess的當前地址
Get_Origin_SSDT = Get_Origin_SSDTAddr(); // 獲取原始的NTOpenProcess的地址
if (Get_Now_SSDT != Get_Origin_SSDT)
{
DbgPrint("該函數已經被Hook了! \n");
JmpDate.E9 = 0xe9; // 0xe9 機器碼是 jmp指令
JmpDate.JMPADDR = Get_Origin_SSDT - Get_Now_SSDT - 5; // 原始地址-當前地址-5 = 需要跳轉的機器碼數據
DbgPrint("寫入了JMP數據=%x \n", JmpDate.JMPADDR);
}else
{
DbgPrint("該函數沒有被Hook ! \n");
}
DriverObject->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}

**恢復被Hook過的函數:** 接下來我們通過編寫驅動程序的方式恢復 `NtOpenProcess`內核函數所Hook的地址,恢復Hook的原理非常的簡單,只需要在函數頭部添加一條`Jmp xxxx`並將其跳轉到原始函數地址上面去即可恢復掛鈎。
在下方的代碼中需要注意一條計算公式 `JmpDate.JMPADDR = Get_Origin_SSDT - Get_Now_SSDT - 5;` 如下使用 `12345678 - 00401000 - 5` 即可得到 `E9`機器碼后面的跳轉地址 `7346F411` 這也是計算代碼的核心。
```C
00401000 > - E9 7346F411 jmp 12345678
有了計算的公式,我們在前面的代碼的基礎上繼續改進一下即可,最終代碼如下:
#include <ntddk.h>
#include <windef.h> //包含windef.h文件
extern "C" LONG KerServiceDescriptorTable;
extern "C" LONG KeServiceDescriptorTable;
#pragma pack(1) // 使下面的結構以一字節方式對齊,而不是默認的4字節對齊
typedef struct _JMPDATE
{
BYTE E9; // 定義一個字節的e9成員名用來存放一字節數據
ULONG JMPADDR; // 定義JMPADDR成員名用來存放4字節跳轉地址數據
}JMPDATE, *PJMPDATE;
#pragma pack()
JMPDATE Origin_Data; // 存放原始跳轉數據
PJMPDATE pNow_Data; // 存放當前跳轉數據
ULONG Get_Origin_SSDTAddr(){ // 獲取到NTOpenProcess的原始地址
UNICODE_STRING NtOpen;
ULONG SSDT_Addr;
RtlInitUnicodeString(&NtOpen, L"NtOpenProcess");
SSDT_Addr = (ULONG)MmGetSystemRoutineAddress(&NtOpen);
return SSDT_Addr;
}
ULONG Get_Now_SSDTAddr(){ // 獲取到NTOpenProcess的當前地址
ULONG SSDT_Addr;
__asm{
push ebx
push eax
mov ebx, KeServiceDescriptorTable // 系統描述符號表的地址
mov ebx, [ebx] // 取服務表基址給EBX
mov eax, 0xBE // NtOpenProcess=轉成十六進制等於BE
imul eax, eax, 4 // eax=eax*4 -> 7a*4=1e8
add ebx, eax // eax=1e8與服務表基址EBX相加
mov ebx, [ebx] // 取ebx里面的內容給EBX
mov SSDT_Addr, ebx // 將得到的基址給變量
pop eax
pop ebx
}
return SSDT_Addr;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
__asm //去掉內核頁面保護
{
cli
mov eax, cr0
and eax, not 10000h
mov cr0, eax
}
// 恢復原始地址
pNow_Data->E9 = Origin_Data.E9;
pNow_Data->JMPADDR = Origin_Data.JMPADDR;
__asm //恢復內核頁面保護
{
mov eax, cr0
or eax, 10000h
mov cr0, eax
sti
}
KdPrint(("驅動卸載成功 !\n"));
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
ULONG Get_Origin_SSDT, Get_Now_SSDT;
JMPDATE JmpDate;
Get_Now_SSDT = Get_Now_SSDTAddr(); // 獲取NTOpenProcess的當前地址
Get_Origin_SSDT = Get_Origin_SSDTAddr(); // 獲取原始的NTOpenProcess的地址
if (Get_Now_SSDT != Get_Origin_SSDT)
{
DbgPrint("該函數已經被Hook了! \n");
pNow_Data = (PJMPDATE)(Get_Now_SSDT); // 初始化獲取NtOpenProcess當前地址的PJMPDATE結構指針保存在 pNow_Data
Origin_Data.E9 = pNow_Data->E9; // 將pNow_Data中E9里的內容給Origin_Data中的E9,1字節
Origin_Data.JMPADDR = pNow_Data->JMPADDR; // NtOpenProcess當前地址后4字節,保存在Origin_Data.JMPADDR里面
// 取出當前的地址,然后保存到JmpData結構中
JmpDate.E9 = 0xe9; // 0xe9 機器碼是 jmp指令
JmpDate.JMPADDR = Get_Origin_SSDT - Get_Now_SSDT - 5; // 原始地址-當前地址-5 = 需要跳轉的機器碼數據
DbgPrint("寫入JMP的數據 = %x \n", JmpDate.JMPADDR);
__asm //去掉內核頁面保護
{
cli
mov eax, cr0
and eax, not 10000h
mov cr0, eax
}
pNow_Data->E9 = JmpDate.E9; // jmp 1字節數據寫入
pNow_Data->JMPADDR = JmpDate.JMPADDR; // 寫入跳轉到目標地址
__asm //恢復內核頁面保護
{
mov eax, cr0
or eax, 10000h
mov cr0, eax
sti
}
}
DriverObject->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
附上匯編版本的Hook恢復代碼,如下,自行替換此處不做測試了。
// 掛鈎代碼匯編版本,替換到上方完整代碼指定字段即可,此處不做演示。
__asm{........} //去掉內核頁面保護
__asm
{
//保存寫入前的數據,用於驅動卸載恢復。
mov ebx, Get_Now_SSDT // 將當前的地址給EBX
lea ecx, Origin_Data // 將原始地址的地址給ECX
mov al, byte ptr[ebx] // 取EBX的第一個字節給AL
mov byte ptr[ecx], al // AL內容以字節方式寫入ECX=原始地址里
mov eax, [ebx + 1] // EBX=當前的地址+1的4字節內容給EAX
mov[ecx + 1], eax // EAX的內容寫入ECX=原始地址+1偏移處
//寫入數據
mov ebx, Get_Now_SSDT // 將當前的地址給EBX
lea ecx, JmpDate // 將JmpDate結構的地址給ECX
mov al, byte ptr[ecx] // 取出JmpDate結構地址的第一個字節給AL
mov byte ptr[ebx], al // al=JmpDate.E9 ,將數據寫入到EBX=當前的地址。
mov eax, [ecx + 1] // [ECX+1]=JmpDate.JMPADDR ,將數據寫入到EAX里保存
mov[ebx + 1], eax // 將EAX的數據寫入到EBX+1=dangqian的地址+1偏移處。
}
__asm{........} //恢復內核頁面保護
// 恢復代碼匯編版本,替換到上方完整代碼指定字段即可,此處不做演示。
__asm{........} //去掉內核頁面保護
__asm
{
mov ebx, pNow_Data // 將當前結構賦值到ebx中
lea ecx, Origin_Data // 將原始結構的地址讓ECX保存,等候使用
mov al, byte ptr[ecx] // 取出原始結構的地址中第一個字節的數據給AL
mov byte ptr[ebx], al // 將AL的數據以一個字節的方式寫入ebx=當前的地址。
mov eax, [ecx + 1] // 取出原始結構的地址+1偏移處開始的4字節數據給EAX。
mov[ebx + 1], eax // EAX的數據以4字節的方式寫入到當前的地址+1偏移處。
}
__asm{........} //恢復內核頁面保護
將代碼編譯,並拖入虛擬機加載驅動,Hook之前如圖一所示,Hook之后如圖二,發現程序已經跳轉到了原始的代碼上了,Hook被解除啦。
在任意位置寫入恢復代碼: 上方的代碼片段雖然可以恢復淺層的Hook,但如果保護驅動Hook的較深的話需上面的代碼將無法恢復,我們需要使用如下代碼.
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
BYTE Jmp_OEP[5] = { 0xEB,0x28,0xDC,0xC8,0x83}; // jmp 83C8DC28 硬編碼
BYTE *NtOpen = (BYTE*)0x83C8DC28; // 此處為了方便演示直接寫地址
__asm //去掉內核頁面保護。
{
cli
mov eax, cr0
and eax, not 10000h
mov cr0, eax
}
// 拷貝內存,在NtOpen的地址基礎上加3,填充為 jmp 指令填充4字節
RtlCopyMemory(NtOpen+3, Jmp_OEP, 5);
__asm //恢復內核頁面保護
{
mov eax, cr0
or eax, 10000h
mov cr0, eax
sti
}
DriverObject->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
編譯生成好代碼以后,拖入虛擬機並加載驅動,觀察內存變化,發現已經寫入地址成功,我們可以使用該方法寫入任意位置,注意堆棧平衡,否則會直接藍屏。
給系統函數添加額外功能: 通過使用Jmp跳轉指令,我們可以給相應的系統函數添加新功能,以NtOpenProcess為例
核心匯編偽代碼如下,這里並沒有寫全,可以自行完善:
#include <ntddk.h>
#include <windef.h>
VOID UnDriver(PDRIVER_OBJECT driver)
{
KdPrint(("Uninstall Driver Is OK \n"));
}
BYTE *RetAddr = NULL; // 保存函數的返回地址
BYTE *MyHook = NULL; // Hook要修改的地址
// 對於jmp類型hook 如果沒有使用_declspec(naked)修飾,會破破壞我們的堆棧平衡
// 對於call類型的hook,如果使用_declspec(naked)修飾的話,要注意自己恢復堆棧平衡
__declspec(naked) VOID inline_NtOpenProcess()
{
__asm
{
mov ecx, dword ptr[ebp + 14] // 這兩條原始代碼必須寫在這里
mov edx, dword ptr[ebp + 10]
mov eax, RetAddr
jmp eax
}
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
BYTE *NtOpenProcess = (BYTE*)0x83C159DC; // 函數的基址: 為方便演示這里直接填上了
BYTE Jmp_Addr[6] = { 0xE9, 0, 0, 0, 0,0x90}; // JMP跳轉字節碼 0x0=等待添加跳轉地址 0x90 為填充字節(湊夠6字節)
__asm
{
push eax
mov eax, NtOpenProcess // 取出NtOpenProcess函數的基地址,原始地址
add eax, 0x13 // 我們要寫入數據的地方 (寫入數據的位置)-(NtOpenProcess起始位置) = 相對偏移
mov MyHook, eax // 相加后獲取到需要Hook的位置
add eax, 0x06 // 獲取到函數的返回地址,相加后得到,0x6h就是指令的間隔
mov RetAddr, eax // 獲取返回的地址
pop eax
}
// Jmp_Addr+1=指向E9后面的字節 // inline_NtOpenProcess - MyHook + 5 得到需要跳轉到的位置
*(ULONG *)(Jmp_Addr + 1) = (ULONG)inline_NtOpenProcess - ((ULONG)MyHook + 5);
CloseProtect(); //去掉內核頁面保護
RtlCopyMemory(MyHook, Jmp_Addr, 6); //將Jmp_Addr的6字節寫入到 MyHook要HOOK的位置
StartProtect(); //恢復內核頁面保護
DriverObject->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
Hook以后截圖如下,可以看到我們能夠在自己的函數中為NtOpenProcess函數增加額外的功能,也可以利用該方法過掉某些游戲的驅動保護,點到為止不說了。
### 拓展:還原 Shadow SSDT 中被Hook的函數
Shadow SSDT的全稱是 Shadow System Services Descriptor Table 影子系統服務描述符表,該表中存放的是一些與系統圖形回調隊列以及鍵盤鼠標事件相關的信息。
ServiceDescriptor中只有指向KiServiceTable的SST,是ServiceDescriptorTable是被系統所導出的表結構,而ServiceDescriptorTableShadow是未導出的,但我們依然可以通過相加偏移的方式得到其當前地址。
在網絡游戲中通常會Hook掛鈎 NtUserSendInput 這個內核函數,從而實現攔截用戶使用能夠模擬合成鼠標鍵盤事件操作的軟件腳本精靈
,那么該怎末過保護?來直接上車。
通過WinDBG附加內核調試,然后輸入以下命令,記得加載符號鏈接。
lkd> dd KeServiceDescriptorTable // 獲取到SSDT表基址
8055d700 80505570 00000000 0000011c 805059e4
8055d710 00000000 00000000 00000000 00000000
lkd> dd KeServiceDescriptorTableShadow // 獲取到ShadowSSDT的基址
8055d6c0 80505570 00000000 0000011c 805059e4
8055d6d0 bf9a1500 00000000 0000029b bf9a2210
KeServiceDescriptorTable - KeServiceDescriptorTableShadow 相減得到SSDT相對SSSDT的偏移地址此處的便宜地址是0x40,然后直接 dd poi(KeServiceDescriptorTable-0x40)
此處的poi命令為取出后面的內存地址。
lkd> dd KeServiceDescriptorTable - KeServiceDescriptorTableShadow
00000040 ???????? ???????? ???????? ????????
00000050 ???????? ???????? ???????? ????????
lkd> dd poi(KeServiceDescriptorTable-0x40)
80505570 805a5664 805f23ea 805f5c20 805f241c
80505580 805f5c5a 805f2452 805f5c9e 805f5ce2
lkd> u 805a5664 // 得到SSDT表中第一個函數的地址
nt!NtAcceptConnectPort:
805a5664 689c000000 push 9Ch
805a5669 6850ab4d80 push offset nt!_real+0x118 (804dab50)
805a566e e8cd76f9ff call nt!_SEH_prolog (8053cd40)
lkd> Dd poi(KeServiceDescriptorTable-0x40+0x10) // +0x10得到SSSDT表地址中第一個NtGdiAbortDoc
bf9a1500 bf93b025 bf94c876 bf88e421 bf9442da
bf9a1510 bf94df11 bf93b2b9 bf93b35e bf839eba
上方結果顯示 bf93b025
是第一個函數NtGdiAbortDoc
的地址,加上 NtUserSendInput
的序號十進制的529
轉為十六進制是0x211
,然后乘以4字節即可獲取到 NtUserSendInput
函數的基址,這里由於電腦管家Hook了所以顯示的地址是a1ea5e9e 如果管家關閉的話這里就是了。
lkd> dd poi[KeServiceDescriptorTable-0x40+0x10]+0x211*4
bf9a1d44 a1ea5e9e bf86b7d8 bf82938b bf914622
bf9a1d54 bf80e6cb bf8921d4 bf914ae8 bf915076
既然流程都已經清楚了,還原就很簡單了,附上匯編代碼。
extern "C" LONG KeServiceDescriptorTable;
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
ULONG Shadow_Address; // 存儲ShadowSSDT地址
ULONG NtUserSendInput; // 保存NtUserSendInput當前地址
__asm
{
push eax
push ebx
push ecx
mov eax, KeServiceDescriptorTable // 系統服務描述表地址給EAX
sub eax, 0x40 // EAX-0x40
add eax, 0x10 // eax+0x10
mov eax, [eax] // 取[EAX] eax里面的數據給EAX
mov Shadow_Address, eax // 將取出的數據給變量Shadow_Address
mov ecx, eax // 取出的數據給了ECX
mov eax, 0x211 // 0x211給EAX,NtUserSendInput的序號
imul eax, eax, 4 // EAX*4
add ecx, eax // ecx+eax
mov ebx, [ecx] // 取ecx里面的數據給EBX
mov NtUserSendInput, ebx // EBX給局部變量NtUserSendInput
pop ecx
pop ebx
pop eax
}
DbgPrint("KeServiceDescriptorTable地址為:%x", Shadow_Address);
DbgPrint("NtUserSendInput地址為:%x", NtUserSendInput);
driver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
恢復代碼如下,只附上關鍵代碼吧,其他的和上方基本一致。
ULONG NtUserSendInput_Now; // 局部變量存放當前地址
ULONG NtUserSendInput_Ord = 0xFFFFFFFF; // 此處的地址可以用Xuetr直接獲取到起源地址
__asm //去掉內核頁面保護。
{
cli
mov eax, cr0
and eax, not 10000h
mov cr0, eax
}
__asm
{
push eax
push ebx
push ecx
push edx
mov eax, KeServiceDescriptorTable
sub eax, 0x40
add eax, 0x10
mov eax, [eax]
mov ecx, eax
mov eax, 0x211
imul eax, eax, 4
add ecx, eax
mov edx, NtUserSendInput_Ord // 將函數的原始地址給EDX
mov[ecx], edx // EDX寫入到ECX里
mov ebx, [ecx] // 取出NtUserSendInput的地址
mov NtUserSendInput_Now, ebx // 取出NtUserSendInput的起源地址
pop edx
pop ecx
pop ebx
pop eax
}
__asm //恢復內核頁面保護
{
mov eax, cr0
or eax, 10000h
mov cr0, eax
sti
}
}