Posted on 2009-10-17 11:56
S.l.e!ep.¢% 閱讀(927)
評論(0) 編輯 收藏 引用 所屬分類:
Windows WDM
詳談內核三步走Inline?Hook實現
(一)Inline?hook原理
Inline?hook通俗的說就是對函數執行流程進行修改,達到控制函數過濾操作的目的。理論上我們可以在函數任何地方把原來指令替換成我們的跳轉指令,也確實有些人在inline
的時候做的很深,來躲避inline?的檢測,前提是必須對函數的流程和指令非常熟悉,且這種深層次的inlline?不具有通用性,穩定性也是問題。本文討論的是具有通用性的兩類inline的實現。
Inline?hook原理:解析函數開頭的幾條指令,把他們Copy到數組保存起來,然后用一個調用我們的函數的幾條指令來替換,如果要執行原函數,則在我們函數處理完畢,再執行我們保存起來的開頭幾條指令,然后調回我們取指令之后的地址執行。
整個Inline?hook的過程就大體這樣,中間牽扯到對函數的檢查,地址的獲取就直接調用函數即可。
本文所要討論的兩類Inline?hook都是基于上面原理。?
說明三點:
1、堆棧平衡是重中之重,參數壓棧也需要格外注意
2、CR0寄存器中的WP位控制處理器是否允許往只讀內存頁寫入,為0禁用保護機制。
3、提高中斷級別到DISPATCH_LEVEL,禁止線程切換產生的中斷
?????????????????????
(二)inline?hook應用
Inline?hook可分為兩類:
(1)inline?導出函數,選擇ObReferenceObjectByHandle做例子。
(2)inline?未導出函數,選擇KiInsertQueueApc做例子。
導出函數前幾個字節可以利用windbg自己查看是什么內容,而未導出函數就需要自己解析指令確定需要hook幾個字節,其間還有很多問題需要注意。當大家真正的弄懂了我這篇文章,回頭再看inline?hook就會覺得inline也不過如此。
下面通過2個例子來講inline?hook的使用(這部分知識網上也有很多,但都很零散不系統,本文部分思路及代碼的確參考了網上資源,有抄襲之嫌,希望讀者諒解。我一直強調“授人以魚不如授人以漁”,代碼并不重要,關鍵是思想。)
1、inline?hook?ObReferenceObjectByHandle保護進程
ObReferenceObjectByHandle屬于ntoskrnl.exe導出函數,在內核中調用頻繁。
NtCreateProcess創建進程需要調用ObReferenceObjectByHandle,NtTerminateProcess需要調用ObReferenceObjectByHandle,基于這我們就可以利用Hook來保護進程同時屏蔽進程的創建。
效果:已經運行的記事本任務管理器無法結束
流程:
HookObReferenceObjectByHandle------DetourMyObReferenceObjectByHa?ndle----------UnHookObReferenceObjectByHandle
核心代碼分析如下:
//=======================================inline?HOOK?ObReferenceObjectByHandle===========================
//ObReferenceObjectByHandle是ntoskrnl.exe導出函數,采用HOOK前五個字節的方式
//字節型數據??unsigned?char
ULONG??CR0VALUE;
BYTE??OriginalBytes[5]={0};?????????????//保存原始函數前五個字節???????????
BYTE?JmpAddress[5]={0xE9,0,0,0,0};???????//跳轉到HOOK函數的地址
extern?POBJECT_TYPE?*PsProcessType;
NTKERNELAPI?NTSTATUS?ObReferenceObjectByHandle(
?????????????????????????
?????????????????????????IN?HANDLE??Handle,
?????????????????????????IN?ACCESS_MASK??DesiredAccess,
?????????????????????????IN?POBJECT_TYPE??ObjectType??OPTIONAL,
?????????????????????????IN?KPROCESSOR_MODE??AccessMode,
?????????????????????????OUT?PVOID??*Object,
?????????????????????????OUT?POBJECT_HANDLE_INFORMATION??HandleInformation??OPTIONAL
?????????????????????????
?????????????????????????);
//HOOK函數
NTSTATUS?DetourMyObReferenceObjectByHandle(
???????????????????????
???????????????????????IN?HANDLE??Handle,???????????
???????????????????????IN?ACCESS_MASK??DesiredAccess
???????????????????????IN?POBJECT_TYPE??ObjectType??OPTIONAL,?
???????????????????????IN?KPROCESSOR_MODE??AccessMode,
???????????????????????OUT?PVOID??*Object,
???????????????????????OUT?POBJECT_HANDLE_INFORMATION??HandleInformation??OPTIONAL);
//
//hook流程?HookObReferenceObjectByHandle---DetourMyObReferenceObjectByHandle---UnHookObReferenceObjectByHandle
void??HookObReferenceObjectByHandle()
{
??
??//賦值前面定義的數組
??KIRQL?Irql;
??KdPrint(("[ObReferenceObjectByHandle]?:0x%x",ObReferenceObjectByHandle));??//地址驗證
??//保存函數前五個字節內容
??RtlCopyMemory(OriginalBytes,(BYTE?*)ObReferenceObjectByHandle,5);
??//保存新函數五個字節之后偏移
??*(ULONG?*)(JmpAddress+1)=(ULONG)DetourMyObReferenceObjectByHandle-((ULONG)ObReferenceObjectByHandle+5);
??//開始inline?hook
??//關閉內存寫保護
??_asm
????
??{
????push?eax
??????
??????mov?eax,?cr0?
??????mov?CR0VALUE,?eax?
??????and?eax,?0fffeffffh??
??????mov?cr0,?eax
??????pop?eax
??}
??
??//提升IRQL中斷級
??Irql=KeRaiseIrqlToDpcLevel();
??//函數開頭五個字節寫JMP?
??RtlCopyMemory((BYTE?*)ObReferenceObjectByHandle,JmpAddress,5);
??//恢復Irql
??KeLowerIrql(Irql);
??//開啟內存寫保護
??
??__asm
????
??{???????
????
????push?eax
??????
??????mov?eax,?CR0VALUE?
??????
??????mov?cr0,?eax
??????
??????pop?eax
??????
??}
??
}
_declspec?(naked)?NTSTATUS?OriginalObReferenceObjectByHandle(IN?HANDLE??Handle,
???????????????????????????????
???????????????????????????????IN?ACCESS_MASK??DesiredAccess,
???????????????????????????????
???????????????????????????????IN?POBJECT_TYPE??ObjectType??OPTIONAL,
???????????????????????????????
???????????????????????????????IN?KPROCESSOR_MODE??AccessMode,
???????????????????????????????
???????????????????????????????OUT?PVOID??*Object,
???????????????????????????????
???????????????????????????????OUT?POBJECT_HANDLE_INFORMATION??HandleInformation??OPTIONAL)
???????????????????????????????
{
??
??_asm
????
??{???
????
????mov?edi,edi
??????push?ebp
??????mov?ebp,esp
??????mov?eax,ObReferenceObjectByHandle
??????add?eax,5
??????jmp?eax????????????????
??????
??}
??
}
NTSTATUS?DetourMyObReferenceObjectByHandle(
???????????????????????
???????????????????????IN?HANDLE??Handle,
???????????????????????
???????????????????????IN?ACCESS_MASK??DesiredAccess,
???????????????????????
???????????????????????IN?POBJECT_TYPE??ObjectType??OPTIONAL,
??????????????????
???????????????????????IN?KPROCESSOR_MODE??AccessMode,
???????????????????????
???????????????????????OUT?PVOID??*Object,
???????????????????????
???????????????????????OUT?POBJECT_HANDLE_INFORMATION??HandleInformation??OPTIONAL)
???????????????????????
{
??
??NTSTATUS?status;
??
??//調用原函數
??
??status=OriginalObReferenceObjectByHandle(Handle,DesiredAccess,ObjectType,AccessMode,Object,HandleInformation);
??
??if((status==STATUS_SUCCESS)&&(DesiredAccess==1))
????
??{???
????
????if(ObjectType==?*PsProcessType)
??????
????{
??????
??????if(?_stricmp((char?*)((ULONG)(*Object)+0x174),"notepad.exe")==0)
????????
??????{???
????????
????????ObDereferenceObject(*Object);
????????
????????return?STATUS_INVALID_HANDLE;
????????
??????}
??????
????}
????
??}
??
??return?status;
??
}
void?UnHookObReferenceObjectByHandle()
{
??
??//把五個字節再寫回到原函數
??
??KIRQL?Irql;
??
????//關閉寫保護
??
??_asm
????
??{
????
????push?eax
??????
??????mov?eax,?cr0?
??????
??????mov?CR0VALUE,?eax?
??????
??????and?eax,?0fffeffffh??
??????
??????mov?cr0,?eax
??????
??????pop?eax
??????
??}
??
????//提升IRQL到Dpc
??
????Irql=KeRaiseIrqlToDpcLevel();
??
??RtlCopyMemory((BYTE?*)ObReferenceObjectByHandle,OriginalBytes,5);
??
??KeLowerIrql(Irql);
??
????//開啟寫保護
??
??__asm
????
??{???????
????
????????push?eax
??????mov?eax,?CR0VALUE?
??????mov?cr0,?eax
??????
??????pop?eax
??????
??}
}
驅動加載后,結束記事本程序如下:
?
????(圖?一)
詳細分析:
1、ObReferenceObjectByHandle分析
NTSTATUS?
??ObReferenceObjectByHandle(
????IN?HANDLE??Handle,
????IN?ACCESS_MASK??DesiredAccess,
????IN?POBJECT_TYPE??ObjectType??OPTIONAL,
????IN?KPROCESSOR_MODE??AccessMode,
????OUT?PVOID??*Object,
????OUT?POBJECT_HANDLE_INFORMATION??HandleInformation??OPTIONAL
????);
函數原型如上,由句柄獲取對象指針,函數返回值:
STATUS_SUCCESS????????????????????????調用成功
STATUS_OBJECT_TYPE_MISMATCH????????
STATUS_ACCESS_DENIED?????????????????權限不夠
STATUS_INVALID_HANDLE????????????????無效句柄?????????
調用NtTerminateProcess需要調用ObReferenceObjectByHandle,因此我們通過對函數返回值進程修改來達到保護進程。但是NtCreateProcess(最終調用的PspCreateProcess)同樣調用這個函數,如果不加區分的話,創建進程同樣被禁止了,那么如何區分到底是誰在調用呢。參考WRK,我發現可以通過第二個參數DesiredAccess來判別,創建進程和結束進程第二個參數明顯不同,PROCESS_CREATE_PROCESS和PROCESS_TERMINATE,問題就解決了。
PspCreateProcess位于?WRK-v1.2\base\ntos\ps\create.c
調用ObReferenceObjectByHandle代碼:
Status?=?ObReferenceObjectByHandle?(ParentProcess,
????????????????????????????????????????????PROCESS_CREATE_PROCESS,
????????????????????????????????????????????PsProcessType,
????????????????????????????????????????????PreviousMode,
????????????????????????????????????????????&Parent,
????????????????????????????????????????????NULL);
NtTerminateProcess位于?WRK-v1.2\base\ntos\ps\psdelete.c
調用ObReferenceObjectByHandle代碼:
st?=?ObReferenceObjectByHandle?(ProcessHandle,
????????????????????????????????????PROCESS_TERMINATE,
????????????????????????????????????PsProcessType,
????????????????????????????????????KeGetPreviousModeByThread(&Self->Tcb),
????????????????????????????????????&Process,
????????????????????????????????????NULL);
DesiredAccess參數說明:
#define?PROCESS_TERMINATE?????????(0x0001)?//?winnt
#define?PROCESS_CREATE_THREAD?????(0x0002)?//?winnt
#define?PROCESS_SET_SESSIONID?????(0x0004)?//?winnt
#define?PROCESS_VM_OPERATION??????(0x0008)?//?winnt
#define?PROCESS_VM_READ???????????(0x0010)?//?winnt
#define?PROCESS_VM_WRITE??????????(0x0020)?//?winnt
//?begin_ntddk?begin_wdm?begin_ntifs
#define?PROCESS_DUP_HANDLE????????(0x0040)?//?winnt
//?end_ntddk?end_wdm?end_ntifs
#define?PROCESS_CREATE_PROCESS????(0x0080)?//?winnt
#define?PROCESS_SET_QUOTA?????????(0x0100)?//?winnt
#define?PROCESS_SET_INFORMATION???(0x0200)?//?winnt
#define?PROCESS_QUERY_INFORMATION?(0x0400)?//?winnt
#define?PROCESS_SET_PORT??????????(0x0800)
#define?PROCESS_SUSPEND_RESUME????(0x0800)?//?winnt
2、函數調用說明
C語言中我們調用一個函數就直接寫函數名就可以,但是實際是進行了下面的操作:
把函數參數壓入堆棧,壓入函數返回地址,調用函數,為新函數開辟堆棧空間申請局部變量,
恢復堆棧保持堆棧平衡
(_stdcall調用方式)匯編代碼就是:
Push?參數4
Push?參數3
Push?參數2
Push?參數1
Call??函數??;call指令同時完成2個操作,一是把返回地址壓入堆棧,二跳轉到調用函數入口地址
Push??ebp
Mov?ebp,esp
Sub??esp,?XX??;開辟棧幀空間
……
Add??esp?,XX
Pop?ebp
Retn??????????;恢復堆棧平衡
堆棧詳細情況:
ESP
局部變量
EBP
返回地址
參數1
參數2
參數3
參數4
堆棧是由高地址到低地址。
參數就通過EBP來去,四字節對齊的
參數4----------------------EBP+0x14
參數3----------------------EBP+0x10
參數2----------------------EBP+0xc
參數1---------------------?EBP+0x8
局部變量則通過Ebp-XX來獲取
因此inline的時候要時刻考慮堆棧平衡,破壞了堆棧平衡就會導致函數崩潰。
我通常inline?hook的思路就是三步走:
HOOK函數-----DetourMy處理函數----------UnHook函數
處理函數中對返回結果或者中間數據進行修改處理,然后調用原始函數。由于在我們處理的時候原始函數已經被hook了,所以我自己構造了一個原始函數,但是由于參數在我們hook前已經壓人堆棧了,所以這里我們不用重新開辟棧幀,因此聲名函數類型為_declspec?(naked)
。有人就會問那么你調用處理函數的時候,參數不是重復壓棧了,這里請注意,我們是通過JMP方式跳轉到我們處理函數入口地址的,而不是Call的形式,所以并沒有執行上面所說的函數調用過程,參數仍然是原始函數的。也就是說在真個inline?hook過程中我們不能破壞原始棧幀的EBP。
關于函數調用很棧幀的相關聯系可能比較難理解,我也在盡肯能的用通俗的話來解釋清楚,有什么不理解的地方或者個人見解歡迎大家跟我交流。
2、inline?hook?KiInsertQueueApc對抗插APC殺進程
KiInsertQueueAPc為內核未導出函數,我下面提供的代碼可以作為未導出函數inline的通用模板來使用,大家根據自己需要進行修改,基于inline?ObReferenceObjectByHandle已經把原理分析了,這部分我就不詳加分析,仍然采用的但不走,Hook函數---DetourMy函數---UnHook函數
直接看核心代碼:
//===================inline?hook?KiInsertQueueApc====================
//KiInsertQueueApc為內核未導出函數,可以從導出函數KeInsertQueueApc定位
//修改KiInsertQueueApc開頭5字節
//處理函數思路:apc-->kthread---apc_state--eprocess--進程名字
//HookKiInsertQueueApc---DetourMyKiInsertQueueApc---UnHookKiInsertQueueApc
ULONG?CR0VALUE;
ULONG?g_KiInsertQueueApc;
???????????
BYTE?JmpAddress[5]={0xE9,0,0,0,0};???????//跳轉到HOOK函數的地址
BYTE??OriginalBytes[5]={0};?????????????//保存原始函數前五個字
VOID?FASTCALL?DetourMyKiInsertQueueApc(IN?PKAPC?Apc,IN?KPRIORITY?Increment);
VOID?WPOFF()
{
??_asm
????
??{
????
????push?eax
??????
??????mov?eax,?cr0?
??????
??????mov?CR0VALUE,?eax?
??????
??????and?eax,?0fffeffffh??
??????
??????mov?cr0,?eax
??????
??????pop?eax
??????cli
??????
??};
??
}
VOID?WPON()
{
????__asm
????
??{???????
????sti
????push?eax
??????
??????mov?eax,?CR0VALUE?
??????
??????mov?cr0,?eax
??????
??????pop?eax
??????
??};
}
//1、獲取KiInsertQueueApc地址
ULONG?GetFunctionAddr(?IN?PCWSTR?FunctionName)?????//PCWSTR常量指針,指向16位UNICODE
{
??UNICODE_STRING?UniCodeFunctionName;
??RtlInitUnicodeString(?&UniCodeFunctionName,?FunctionName?);
??return?(ULONG)MmGetSystemRoutineAddress(?&UniCodeFunctionName?);???
}
ULONG?GetKiInsertQueueApcAddr()
{
??ULONG?sp_code1=0x28,sp_code2=0xe8,sp_code3=0xd88a;??//特征碼,sp_code3?windbg顯示錯誤,應該為d88a
??ULONG?address=0;
??PUCHAR?addr;
??PUCHAR?p;
??addr=(PUCHAR)GetFunctionAddr(L"KeInsertQueueApc");
??for(p=addr;p<p+PAGE_SIZE;p++)
??{
????if((*(p-1)==sp_code1)&&(*p==sp_code2)&&(*(PUSHORT)(p+5)==sp_code3))
????{
??????address=*(PULONG)(p+1)+(ULONG)(p+5);
??????break;
????}
??}
??KdPrint(("[KeInsertQueueApc]?addr?%x\n",(ULONG)addr));
????KdPrint(("[KiInsertQueueApc]?address?%x\n",address));
????return?address;
}
VOID?HookKiInsertQueueApc()
{???
??KIRQL?Irql;
??g_KiInsertQueueApc=GetKiInsertQueueApcAddr();
??KdPrint(("[KiInsertQueueApc]?KiInsertQueueApc?%x\n",g_KiInsertQueueApc));
????//?保存原函數的前字節內容
????RtlCopyMemory?(OriginalBytes,?(BYTE*)g_KiInsertQueueApc,?5);
??//新函數對原函數的偏移地址
????*(?(ULONG*)(JmpAddress?+?1)?)?=?(ULONG)DetourMyKiInsertQueueApc?-?(ULONG)g_KiInsertQueueApc?-?5;
????//?禁止系統寫保護,提升IRQL到DPC
????WPOFF();
????Irql?=?KeRaiseIrqlToDpcLevel();
????//inline?hook函數
??RtlCopyMemory?(?(BYTE*)g_KiInsertQueueApc,?JmpAddress,?5?);
????//?恢復寫保護,降低IRQL
????KeLowerIrql(Irql);
????WPON();??
}
//原函數
_declspec?(naked)?VOID?FASTCALL?OriginalKiInsertQueueApc(IN?PKAPC?Apc,IN?KPRIORITY?Increment)
{
??_asm
??{
????//前五個字節
????mov?edi,edi
??????push?ebp
??????mov?ebp,esp
??????
??????mov?eax,g_KiInsertQueueApc
??????add?eax,5
??????jmp?eax
??}
}
//處理函數
//apc--kthread--apc_state--eprocess
VOID?FASTCALL?DetourMyKiInsertQueueApc(IN?PKAPC?Apc,IN?KPRIORITY?Increment)
{
??ULONG?thread;
??ULONG?process;
??if(MmIsAddressValid((PULONG)((ULONG)Apc+0x008)))????//地址驗證?KAPC結構+008--->kthread
????thread=*((PULONG)((ULONG)Apc+0x008));
??else
????return?;
??if(MmIsAddressValid((PULONG)((ULONG)thread+0x044)))?//kthread+30-->KAPC_STATE+10-->eprocess
????process=*((PULONG)((ULONG)thread+0x044));
??else
????return?;
????if(MmIsAddressValid((PULONG)((ULONG)process+0x174)))??//eprocess+174---->進程名字
??{
????if((_stricmp((char?*)((ULONG)process+0x174),"notepad.exe")==0)&&(Increment==2))
????{
??????return?;
????}
????else
??????OriginalKiInsertQueueApc(Apc,Increment);
??}
??else
????return;
}
//卸載函數
VOID?UnHookKiInsertQueueApc()
{
??KIRQL?Irql;
????WPOFF();
????Irql?=?KeRaiseIrqlToDpcLevel();
????//inline?hook函數
????RtlCopyMemory?(?(BYTE*)g_KiInsertQueueApc,?OriginalBytes,?5);
????//?恢復寫保護,降低IRQL
????KeLowerIrql(Irql);
????WPON();??
}
?考慮到大家水平不一,對一些問題我詳細如下:
1、特征碼的尋找
利用windbg的kernel?debug來查找:
uf??KeInsertQueueApc
nt!KeInsertQueueApc+0x3b:
804e6d0a?8b450c??????????mov?????eax,dword?ptr?[ebp+0Ch]
804e6d0d?8b5514??????????mov?????edx,dword?ptr?[ebp+14h]
804e6d10?894724??????????mov?????dword?ptr?[edi+24h],eax
804e6d13?8b4510??????????mov?????eax,dword?ptr?[ebp+10h]
804e6d16?8bcf????????????mov?????ecx,edi
804e6d18?894728??????????mov?????dword?ptr?[edi+28h],eax
804e6d1b?e8523fffff????????call????nt!KiInsertQueueApc?(804dac72)
804e6d20?8ad8?(錯誤)??mov?????bl,al
特征碼就是sp_code1=0x28?sp_code2=0xe8?sp_code3=0xd88a(windbg顯示有誤,應該是d88a
)
這種方法就是通過已導出函數定位未導出函數通常使用的方法,具有通用性。詳細見代碼。
2、取EPRocess的過程
Apc-----kthread-----apc_state—eprocess
dt??_KAPC?????????????偏移0x008指向KTHREAD
dt??_KTHREAD?????????偏移0x034指向KAPC_STATE
dt??_KAPC_STATE??????偏移0x10指向EPROCESS
dt??_EPROCESS?????????偏移0x174指向進程名
(三)總結
?很多人覺得inline?hook比較難,處理起來很麻煩。但是我相信看完我這篇文章,你一定不會這么認為了,inline?hook其實只要細心,注意細節跟別的hook沒什么兩樣。本人采用的三步走inline?hook做到了把inline簡單化,同時有保證了堆棧的平衡。
?由于代碼采用的硬編碼,編譯環境是sp3+VMware,請根據自己操作系統自行修改。歡迎讀者跟我交流。