日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > windows >内容正文

windows

Windows内核实验005 Inline Hook

發(fā)布時(shí)間:2025/3/21 windows 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Windows内核实验005 Inline Hook 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

    • 準(zhǔn)備工作
      • 尋找Inline Hook的返回地址
      • 編寫代碼
      • 動(dòng)態(tài)變化的返回地址
      • JmpTargetAddr
    • Inline Hook基本框架
      • 示例代碼
    • 實(shí)戰(zhàn)HOOK KiTrap01
      • 無需計(jì)算偏移的Inline Hook方法
      • 示例代碼

準(zhǔn)備工作

尋找Inline Hook的返回地址

假設(shè)我們現(xiàn)在要HOOK KiFastCallEntry這個(gè)內(nèi)核函數(shù),讓所有的程序在進(jìn)入零環(huán)之前先跳到我們自己的代碼。

但是會(huì)出現(xiàn)一個(gè)問題,我們可以從三環(huán)的地址跳到零環(huán)的地址空間,因?yàn)閮?nèi)核層的2GB內(nèi)存是系統(tǒng)共用的。但是在Inline Hook返回的時(shí)候,是無法從零環(huán)返回到三環(huán)的。用戶層的2GB空間每個(gè)進(jìn)程都有一份,而這個(gè)函數(shù)會(huì)被所有進(jìn)程頻繁調(diào)用。

如果返回的時(shí)候,別的進(jìn)程在調(diào)用這個(gè)函數(shù),那么Inline Hook就會(huì)跳到那個(gè)進(jìn)程的地址空間。

既然不能用三環(huán)的地址,那就只能找一塊比較穩(wěn)定的零環(huán)的內(nèi)存區(qū)域讓它返回。為了讓代碼減少不確定性,我們可以用GDTR中的未使用的內(nèi)存區(qū)域。

接著用windbg查看一下gdt表

我們就使用80b95120這塊未使用的GDT表的位置來存放hook的返回地址。在使用之前,需要先確認(rèn)一下內(nèi)存屬性是否是可讀可寫可執(zhí)行。

kd> !pte 80b95120VA 80b95120 PDE at C0602028 PTE at C0405CA8 contains 0000000000193063 contains 0000000000B95163 pfn 193 ---DA--KWEV pfn b95 -G-DA--KWEV

可以看到這塊內(nèi)存目前是可讀可寫可執(zhí)行的。

那么我們就可以將自己的代碼復(fù)制到這塊內(nèi)存空間中,然后讓KiFastCallEntry跳轉(zhuǎn)到80b95120的位置執(zhí)行自己的代碼,執(zhí)行完成之后再返回。

編寫代碼

首先查看一下_KiFastCallEntry的函數(shù)地址(我的pdb符號(hào)文件下載失敗,只能用windbg看要掛鉤的函數(shù)地址了)

kd> x nt!_KiFastCallEntry 83e7c0c0 nt!KiFastCallEntry (<no parameter info>)

接著查看一下反匯編

kd> u 83e7c0c0 nt!KiFastCallEntry: 83e7c0c0 b923000000 mov ecx,23h 83e7c0c5 6a30 push 30h 83e7c0c7 0fa1 pop fs 83e7c0c9 8ed9 mov ds,cx 83e7c0cb 8ec1 mov es,cx 83e7c0cd 648b0d40000000 mov ecx,dword ptr fs:[40h] 83e7c0d4 8b6104 mov esp,dword ptr [ecx+4] 83e7c0d7 6a23 push 23h

可以發(fā)現(xiàn)第一行剛好是五個(gè)字節(jié),那么我們就可以在這里下鉤子跳轉(zhuǎn)到之前準(zhǔn)備好的地址,然后再跳回到下一行地址83e7c0c5處

接著我們來計(jì)算偏移,用要跳轉(zhuǎn)的目標(biāo)地址83e7c0c5減去起始地址80b95120再減5=0x32E6FA0

接著準(zhǔn)備一個(gè)數(shù)組,將準(zhǔn)備好的數(shù)據(jù)放進(jìn)數(shù)組里,然后拷貝到GDT表的跳轉(zhuǎn)地址中

char code[64] = {0xb9,0x23,0x00,0x00,0x00, //mov ecx,23h0xE9,0xA0,0x6F,0x2E,0x03 //E9 0x32E6FA0 };

然后我們還需要知道code數(shù)組的地址,直接在VS中下斷點(diǎn)查看即可。我這里的code數(shù)組的地址是0x403018。接著將數(shù)組循環(huán)拷貝到要HOOK的目標(biāo)地址中。

p = (char*)0x80b95120;for (i=0;i<10;i++){*p = code[i];p++;}

動(dòng)態(tài)變化的返回地址

接著我們運(yùn)行寫好的程序,然后查看一下被修改地址處的反匯編

kd> u 80b95120 80b95120 b923000000 mov ecx,23h 80b95125 e9a06f2e03 jmp nt!KiFastCallEntry+0xa (83e7c0ca) 80b9512a b980000000 mov ecx,80h 80b9512f 0038 add byte ptr [eax],bh 80b95131 51 push ecx 80b95132 b980000000 mov ecx,80h 80b95137 004051 add byte ptr [eax+51h],al 80b9513a b980000000 mov ecx,80h //起始地址(GDT表空閑地址):80b95120 //HOOK的函數(shù)地址(KiFastCallEntry):0x83e7c0c0 //返回地址:83e7c0c5

我們發(fā)現(xiàn)mov ecx,23這句是對(duì)的,但是jmp跳轉(zhuǎn)的目標(biāo)地址居然是錯(cuò)誤的,相差了五個(gè)字節(jié)。

原因在于起始地址(GDT表空閑地址):80b95120被Code數(shù)組的前四個(gè)字節(jié)被恢復(fù)寄存器環(huán)境的mov ecx,0x23填充了,導(dǎo)致代碼的位置也相對(duì)的往后移了五個(gè)字節(jié)。這就產(chǎn)生了一個(gè)問題,我們每次去HOOK新函數(shù)的時(shí)候,都要手動(dòng)將偏移值重新計(jì)算一遍,這顯然是不可取的。

JmpTargetAddr

既然如此我們就來手動(dòng)實(shí)現(xiàn)一個(gè)跳轉(zhuǎn)到目標(biāo)地址的函數(shù)。借用一個(gè)寄存器來保存需要跳轉(zhuǎn)的地址,然后直接用jmp指令跳轉(zhuǎn)過去。這樣就可以省去每次手工計(jì)算偏移的步驟了。

我們利用83e840cd這個(gè)地址作為跳轉(zhuǎn)的目標(biāo)地址,然后用ecx來保存跳轉(zhuǎn)地址。當(dāng)跳轉(zhuǎn)回83e840cd時(shí),ecx會(huì)被重新賦值,不會(huì)出現(xiàn)任何問題。

代碼如下:

void __declspec(naked) JmpTargetAddr() {__asm{mov ecx, 0x23; //保存現(xiàn)場環(huán)境push 0x30; //保存現(xiàn)場環(huán)境pop fs; //保存現(xiàn)場環(huán)境mov ds, ecx; //保存現(xiàn)場環(huán)境mov es, ecx; //保存現(xiàn)場環(huán)境mov ecx, 0x83e840cd; //返回地址jmp ecx; //跳轉(zhuǎn)到返回地址} }

運(yùn)行程序,接著查看一下GDT表起始位置的反匯編

kd> u 80b95120 80b95120 b923000000 mov ecx,23h 80b95125 6a30 push 30h 80b95127 0fa1 pop fs 80b95129 668ed9 mov ds,cx 80b9512c 668ec1 mov es,cx 80b9512f b9cd40e883 mov ecx,offset nt!KiFastCallEntry+0xd (83e840cd) 80b95134 ffe1 jmp ecx 80b95136 cc int 3 //起始地址(GDT表空閑地址):80b95120 //HOOK的函數(shù)地址(KiFastCallEntry):0x83e840c0 //返回地址:83e840cd

可以看到現(xiàn)在我們的跳轉(zhuǎn)的目標(biāo)地址就和預(yù)期寫的是一致的了,沒有出現(xiàn)之前的地址動(dòng)態(tài)變化的問題。

Inline Hook基本框架

接下來我們要修改KiFastCallEntry函數(shù)的前五個(gè)字節(jié),讓它跳轉(zhuǎn)到我們自己的地址。首先來查看一下前五個(gè)字節(jié)的頁面屬性

kd> x nt!_KiFastCallEntry 83e840c0 nt!KiFastCallEntry (<no parameter info>) kd> !pte 83e840c0 VA 83e840c0 PDE at C06020F8 PTE at C041F420 contains 00000000001D9063 contains 0000000003E84121 pfn 1d9 ---DA--KWEV pfn 3e84 -G--A--KREV

可以看到這一塊內(nèi)存是可讀不可寫的,這樣的話我們需要先關(guān)掉CPU的寫保護(hù)。代碼如下:

//關(guān)閉寫保護(hù)__asm{mov eax, cr0;and eax, not 10000h;mov cr0, eax;}//開啟寫保護(hù)__asm{mov eax, cr0; or eax, 10000h; mov cr0, eax; iretd;}

接著再修改目標(biāo)函數(shù)的前五個(gè)字節(jié),讓它跳到我們之前準(zhǔn)備好的地址

//偏移0xfcd1105b char code[] = { 0xE9,0x5B,0x10,0xD1,0xFC }; //修改要HOOK的函數(shù)前五個(gè)字節(jié) memcpy((void*)0x83e840c0, code, sizeof(code));

最后運(yùn)行程序,并且在windbg中查看一下KiFastCallEntry處的代碼

kd> x nt!_KiFastCallEntry 83e840c0 nt!KiFastCallEntry (<no parameter info>) kd> u 83e840c0 nt!KiFastCallEntry: 83e840c0 e95b10d1fc jmp 80b95120 83e840c5 6a30 push 30h 83e840c7 0fa1 pop fs 83e840c9 8ed9 mov ds,cx 83e840cb 8ec1 mov es,cx 83e840cd 648b0d40000000 mov ecx,dword ptr fs:[40h] 83e840d4 8b6104 mov esp,dword ptr [ecx+4] 83e840d7 6a23 push 23h

可以看到現(xiàn)在這個(gè)函數(shù)已經(jīng)成功跳到我們指定的GDT表的位置0x80b95120。接著再來看一下GDT表的位置0x80b95120處的代碼。

kd> u 0x80b95120 80b95120 b923000000 mov ecx,23h 80b95125 6a30 push 30h 80b95127 0fa1 pop fs 80b95129 668ed9 mov ds,cx 80b9512c 668ec1 mov es,cx 80b9512f b9cd40e883 mov ecx,offset nt!KiFastCallEntry+0xd (83e840cd) 80b95134 ffe1 jmp ecx 80b95136 cc int 3

現(xiàn)在我們已經(jīng)完成了一個(gè)Inline Hook的基本框架,當(dāng)有進(jìn)程調(diào)用KiFastCallEntry時(shí),會(huì)跳轉(zhuǎn)到我們指定的函數(shù)地址,然后再返回。框架完成以后,在HOOK過程中想做什么事,就完全由自己決定了。

示例代碼

示例代碼如下:

#include "pch.h" #include <iostream> #include <stdio.h> #include <stdlib.h> #include <windows.h>void JmpTargetAddr();//偏移0xfcd1105b char code[] = { 0xE9,0x5B,0x10,0xD1,0xFC }; int i; char* p;//起始地址(GDT表空閑地址):80b95120 //HOOK的函數(shù)地址(KiFastCallEntry):0x83e840c0 //返回地址:83e7c0c5 void __declspec(naked) IdtEntry() {//將自己的函數(shù)拷貝的GDT表空閑地址p = (char*)0x80b95120;for (i = 0; i < 64; i++){*p = ((char*)JmpTargetAddr)[i];p++;}//關(guān)閉寫保護(hù)__asm{mov eax, cr0;and eax, not 10000h;mov cr0, eax;}//修改要HOOK的函數(shù)前五個(gè)字節(jié)memcpy((void*)0x83e840c0, code, sizeof(code));//開啟寫保護(hù)__asm{mov eax, cr0; or eax, 10000h; mov cr0, eax; iretd;} } void __declspec(naked) JmpTargetAddr() {__asm{mov ecx, 0x23; //恢復(fù)現(xiàn)場環(huán)境push 0x30; //恢復(fù)現(xiàn)場環(huán)境pop fs; //恢復(fù)現(xiàn)場環(huán)境mov ds, cx; //恢復(fù)現(xiàn)場環(huán)境mov es, cx; //恢復(fù)現(xiàn)場環(huán)境mov ecx, 0x83e840cd; //返回地址jmp ecx; //跳轉(zhuǎn)到返回地址} }void go() {__asm int 0x20; }//eq 80b95500 0040ee00`00081040 int main() {if ((DWORD)IdtEntry != 0x401040){printf("wrong addr:%p", IdtEntry);exit(-1);}go();//printf("%p\n", g_num);system("pause"); }

實(shí)戰(zhàn)HOOK KiTrap01

接著我們找另外一個(gè)函數(shù)進(jìn)行HOOK。

查看PC Hunter中IDT表的1號(hào)中斷函數(shù),1號(hào)中斷是單步調(diào)試,當(dāng)CPU的eflags的TF標(biāo)志位被置1時(shí),就會(huì)產(chǎn)生一個(gè)單步中斷,然后調(diào)用Debug函數(shù)。調(diào)試器的單步斷點(diǎn)原理也在于此。

無需計(jì)算偏移的Inline Hook方法

之前我們?cè)谛薷哪繕?biāo)函數(shù)的前五個(gè)字節(jié)的時(shí)候需要計(jì)算跳轉(zhuǎn)的偏移,這個(gè)非常不方便。這一次我們用另外的不需要計(jì)算偏移的方法來完成Inline Hook。利用下面兩條匯編指令:

push 0x12345678; ret;

這種方法的缺點(diǎn)就是需要HOOK的位置有6個(gè)字節(jié)的空間。

首先,查看一下要HOOK的函數(shù)地址反匯編

kd> x nt!_KiTrap01 83e85150 nt!KiTrap01 (<no parameter info>) kd> u 83e85150 nt!KiTrap01: 83e85150 6a00 push 0 83e85152 66c74424020000 mov word ptr [esp+2],0 83e85159 55 push ebp 83e8515a 53 push ebx 83e8515b 56 push esi 83e8515c 57 push edi 83e8515d 0fa0 push fs 83e8515f bb30000000 mov ebx,30h

我們將要HOOK的地址定為0x83e85152,返回地址則為它的下一句

首先修改JmpTargetAddr中的代碼為跳回返回地址

void __declspec(naked) JmpTargetAddr() {__asm{mov word ptr[esp + 2], 0; //恢復(fù)現(xiàn)場環(huán)境push 0x83e85159; //跳轉(zhuǎn)到返回地址ret; //跳轉(zhuǎn)到返回地址} }

接著在返回之前打印出當(dāng)前的ESP,查看一下產(chǎn)生單步異常的堆棧環(huán)境,同樣需要借助一個(gè)內(nèi)核的內(nèi)存地址

push eax; mov eax, ss:[esp]; mov ds : [0x80b953f0],eax; //GDT表的空閑位置 用于保存內(nèi)核變量 pop eax;

最后修改要HOOK的函數(shù)地址,跳轉(zhuǎn)到我們自己的地址

char code[] = { 0x68,0x20,0x51,0xB9,0x80,0xC3,0x90 }; memcpy((void*)0x83e85152, code, sizeof(code));

運(yùn)行程序,檢查一下起始地址和被HOOK的函數(shù)地址的匯編代碼,都是正常的

kd> x nt!_KiTrap01 83e85150 nt!KiTrap01 (<no parameter info>) kd> u 83e85150 nt!KiTrap01: 83e85150 6a00 push 0 83e85152 682051b980 push 80B95120h 83e85157 c3 ret 83e85158 90 nop 83e85159 55 push ebp 83e8515a 53 push ebx 83e8515b 56 push esi 83e8515c 57 push edi kd> u 80B95120 80b95120 50 push eax 80b95121 368b0424 mov eax,dword ptr ss:[esp] 80b95125 3ea3f053b980 mov dword ptr ds:[80B953F0h],eax 80b9512b 58 pop eax 80b9512c 66c74424020000 mov word ptr [esp+2],0 80b95133 685951e883 push offset nt!KiTrap01+0x9 (83e85159) 80b95138 c3 ret 80b95139 cc int 3

另外在PC Hunter中也檢測到我我們掛的鉤子

示例代碼

最后附上代碼

#include "pch.h" #include <iostream> #include <stdio.h> #include <stdlib.h> #include <windows.h>void JmpTargetAddr();//偏移0xfcd1105b char code[] = { 0x68,0x20,0x51,0xB9,0x80,0xC3,0x90 }; int i; char* p;//起始地址(GDT表空閑地址):80b95120 //HOOK的函數(shù)地址(KiTrap01):0x83e85152 //返回地址:83e85159 void __declspec(naked) IdtEntry() {//將自己的函數(shù)拷貝的GDT表空閑地址p = (char*)0x80b95120;for (i = 0; i < 64; i++){*p = ((char*)JmpTargetAddr)[i];p++;}//關(guān)閉寫保護(hù)__asm{mov eax, cr0;and eax, not 10000h;mov cr0, eax;}memcpy((void*)0x83e85152, code, sizeof(code));//開啟寫保護(hù)__asm{mov eax, cr0; or eax, 10000h; mov cr0, eax; iretd;} } void __declspec(naked) JmpTargetAddr() {__asm{push eax;mov eax, ss:[esp+4];mov ds : [0x80b953f0],eax; //GDT表的空閑位置 用于保存內(nèi)核變量pop eax;mov word ptr[esp + 2], 0; //恢復(fù)現(xiàn)場環(huán)境push 0x83e85159; //跳轉(zhuǎn)到返回地址ret; //跳轉(zhuǎn)到返回地址} }void go() {__asm int 0x20; }//eq 80b95500 0040ee00`00081040 int main() {if ((DWORD)IdtEntry != 0x401040){printf("wrong addr:%p", IdtEntry);exit(-1);}go();//printf("%p\n", g_num);system("pause"); }

總結(jié)

以上是生活随笔為你收集整理的Windows内核实验005 Inline Hook的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。