一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

API HOOK完全手冊(cè)

 你喜歡那個(gè) 2011-08-19
【轉(zhuǎn)】 API HOOK完全手冊(cè)
2011-05-21 09:20
轉(zhuǎn)載自 ok100fen
最終編輯 hyyly520

API Hook是什么我就不多說了,直接進(jìn)入正題。API Hook技術(shù)主要有下面的技術(shù)難點(diǎn):

1. 如何將自己的的代碼Inject到其他進(jìn)程

2. 如何Hook到API
1.1 代碼的Injection

常用的方法有:

1. 使用注冊(cè)表HKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs

 

這種方法可以指定多個(gè)DLL,用空格隔開。這些DLL會(huì)被任何用到User32.dll的所有程序自動(dòng)加載。當(dāng)User32.dll加載的時(shí)候,User32.dll的DllMain會(huì)收到一個(gè)DLL_PROCESS_ATTACH通知,User32在這個(gè)時(shí)候讀取注冊(cè)表項(xiàng)中的值,調(diào)用LoadLibrary加載各個(gè)DLL。

顯然使用這種方法要求設(shè)置注冊(cè)表之后立刻重起系統(tǒng),不過一般情況下這不是大問題。這種方法的主要問題在于,只有用到User32.dll的應(yīng)用程序才會(huì)被Inject。所有的GUI和少部分CUI程序會(huì)用到User32.dll,所以如果你的API Hook程序不打算監(jiān)視CUI程序的話,那么可能問題并不太大。但是如果你的API Hook程序需要監(jiān)視系統(tǒng)中所有進(jìn)程的話,這種方法的限制將是非常致命的。

 

2. 調(diào)用SetWindowsHookEx(WH_GETMESSAGE, …, 0)

 

可以使用SetWindowsHookEx(WH_GETMESSAGE, …, 0) 設(shè)置全局的消息鉤子,雖然可能你的程序并不用到消息鉤子,但是鉤子的一個(gè)副作用是會(huì)將對(duì)應(yīng)的DLL加載到所有的GUI線程之中。類似的,只有用到GUI的進(jìn)程才會(huì)被掛接。雖然有這種限制,這種方法仍然是最常用的掛接進(jìn)程的方法。

 

3. 使用CreateRemoteThread函數(shù)在目標(biāo)進(jìn)程中創(chuàng)建遠(yuǎn)程線程

 

這種方法可以在任意的目標(biāo)進(jìn)程中創(chuàng)建一個(gè)遠(yuǎn)程線程,遠(yuǎn)程線程中可以執(zhí)行任意代碼,這樣便可以做到把我們的代碼Inject到目標(biāo)進(jìn)程中。這種方法具有最大的靈活性,但是難度也最高:

a) 遠(yuǎn)程線程代碼必須可以自重定位

b) 要能夠監(jiān)視進(jìn)程的啟動(dòng)和結(jié)束,這樣才可以掛接到所有進(jìn)程

這兩個(gè)問題都是可以解決的,在本文中我將重點(diǎn)講述如何創(chuàng)建遠(yuǎn)程線程和解決這兩個(gè)問題。

 

4. 如果你只是要掛接某個(gè)特定進(jìn)程的并且情況允許你自己來創(chuàng)建此進(jìn)程,你可以調(diào)用CreateProcess(…, CREATE_SUSPENDED)創(chuàng)建子進(jìn)程并暫停運(yùn)行,然后修改入口代碼使之調(diào)用LoadLibrary加載自己的DLL。該方法在不同CPU之間顯然是無法移植的。
1.2 Hook API

常用的方法有:

1. 找到API函數(shù)在內(nèi)存中的地址,改寫函數(shù)頭幾個(gè)字節(jié)為JMP指令跳轉(zhuǎn)到自己的代碼,執(zhí)行完畢再執(zhí)行API開頭幾個(gè)字節(jié)的內(nèi)容再跳回原地址。這種方法對(duì)CPU有較大的依賴性,而且在多線程環(huán)境下可能出問題,當(dāng)改寫函數(shù)代碼的時(shí)候有可能此函數(shù)正在被執(zhí)行,這樣做可能導(dǎo)致程序出錯(cuò)。

2. 修改PE文件的IAT (Import Address Table),使之指向自己的代碼,這樣EXE/DLL在調(diào)用系統(tǒng)API的時(shí)候便會(huì)調(diào)用你自己的函數(shù)


2 PE文件結(jié)構(gòu)和輸入函數(shù)

Windows9x、Windows NT、Windows 2000/XP/2003等操作系統(tǒng)中所使用的可執(zhí)行文件格式是純32位PE(Portable Executable)文件格式,大致如下:

 

 


文件中數(shù)據(jù)被分為不同的節(jié)(Section)。代碼(.code)、初始化的數(shù)據(jù)(.idata),未初化的數(shù)據(jù)(.bss)等被按照屬性被分類放到不同的節(jié)中,每個(gè)節(jié)的屬性和位置等信息用一個(gè)IMAGE_SECTION_HEADER結(jié)構(gòu)來描述。所有的這些IMAGE_SECTION_HEADER結(jié)構(gòu)組成一個(gè)節(jié)表(Section Table),這個(gè)表被放在所有節(jié)數(shù)據(jù)的前面。由于數(shù)據(jù)按照屬性被放在不同的節(jié)中,那么不同用途但是屬性相同的數(shù)據(jù)可能被放在同一個(gè)節(jié)中,因此PE文件中還使用IMAGE_DATA_DIRECTORY數(shù)據(jù)目錄結(jié)構(gòu)來指明這些數(shù)據(jù)的位置。數(shù)據(jù)目錄和其他描述文件屬性的數(shù)據(jù)和在一起稱為PE文件頭。PE文件頭被放在節(jié)和節(jié)表的前面。PE文件中的數(shù)據(jù)位置使用RVA(Relative Virtual Address)來表示。RVA指的是相對(duì)虛擬地址,也就是一個(gè)偏移量。當(dāng)PE文件被裝入內(nèi)存中的時(shí)候,Windows把PE文件裝入到某個(gè)特定的位置,稱為映像基址(Image Base)。而某個(gè)RVA值表示某個(gè)數(shù)據(jù)在內(nèi)存中相對(duì)于映像基址的偏移量。

輸入表(Import Table)是來放置輸入函數(shù)(Imported functions)的一個(gè)表。輸入函數(shù)就是被程序調(diào)用的位于外部DLL的函數(shù),這些函數(shù)稱為輸入函數(shù)。它們的代碼位于DLL之中,程序通過引用其DLL來訪問這些函數(shù)。輸入表中放置的是這些函數(shù)的名稱(或者序號(hào))以及函數(shù)所在的DLL路徑等有關(guān)信息。程序通過這些信息找到相應(yīng)的DLL,從而調(diào)用這些外部函數(shù)。這個(gè)過程是在運(yùn)行過程中發(fā)生的,因此屬于動(dòng)態(tài)鏈接。由于操作系統(tǒng)的API也是在DLL之中實(shí)現(xiàn)的,因此應(yīng)用程序調(diào)用API也要通過動(dòng)態(tài)連接。在程序的代碼中,當(dāng)需要調(diào)用API的時(shí)候,就執(zhí)行類似下面語句:

 

0040100E CALL 0040101A

 

可以看到這是一個(gè)call語句。Call語句則調(diào)用下面的語句:

 

0040101A JMP DWORD PTR [00402000]


上面的代碼稱為樁代碼(Stub code),jmp語句中的目標(biāo)地址[00402000]才是API函數(shù)的地址。這段Stub code位于.lib輸入庫(kù)中。如果加以優(yōu)化,那么調(diào)用代碼是下面這樣:

 

XXXXXXXX CALL DWORD PTR [XXXXXXXX]

 

其中[XXXXXXXX]指向IAT(Import Address Table)即輸入地址表中的表項(xiàng)。表項(xiàng)中指定了API的目標(biāo)地址。這是經(jīng)過編譯器優(yōu)化過的調(diào)用方法,通常速度要比原來的CALL+JMP快一些。
3 掛接API

從上面的PE文件結(jié)構(gòu)可知,當(dāng)我們知道了IAT中的地址所在位置,便可以把原來的API 的地址修改為新的API的地址。這樣,進(jìn)程在調(diào)用API的時(shí)候就會(huì)調(diào)用我們所提供的新的API的地址。修改輸入表可以通過調(diào)用ImageDirectoryEntryToData API函數(shù)得到內(nèi)存中模塊的輸入表的地址:

ULONG ulSize;

PIMAGE_IMPORT_DESCRIPTOR pid = (PIMAGE_IMPORT_DESCRIPTOR)

ImageDirectoryEntryToData(

hModule,

TRUE,

IMAGE_DIRECTORY_ENTRY_IMPORT,

&ulSize );


這個(gè)函數(shù)返回一個(gè)IMAGE_IMPORT_DESCRIPTOR的指針,指向輸入描述符數(shù)據(jù)。然后,遍歷該描述符表通過比較DLL名稱查找到相應(yīng)的DLL所對(duì)應(yīng)的IMAGE_IMPORT_DESCRIPTOR:

// if this image has no import section, just simply return and do nothing

if( pid == NULL )

return;

 

// find the corresponding item

while( pid->Name )

{

// pid->Name contains the RVA addr of the module name string

PSTR pszModName = (PSTR) ( (PBYTE)hModule + pid->Name );

if( lstrcmpiA( pszModuleName, pszModName ) == 0 )

{

// found

break;

}

 

pid++;

}

 

if( pid->Name == 0 )

{

// not found, just return

return;

}

 

找到相應(yīng)的DLL之后,遍歷其IAT表,根據(jù)地址pfnCurrentFuncAddr找到相應(yīng)的表項(xiàng),修改之

// get caller's import address table(IAT) for the callee's functions

PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA) ( (PBYTE)hModule + pid->FirstThunk );

 

while( pThunk->u1.Function )

{

PROC *ppfnEntry = (PROC*) &(pThunk->u1.Function);

 

if( *ppfnEntry == pfnCurrentFuncAddr )

{

// …

// Modify IAT

// …

}

 

pThunk++;

}


修改的時(shí)候,需要改變?cè)搲K內(nèi)存的保護(hù)為可讀寫,需要通過VirtualQuery獲得內(nèi)存的信息,然后通過VirtualProtectEx修改為可讀寫。之后可以通過WriteProcessMemory修改內(nèi)存,修改完畢之后還要通過VirtualProtectEx再改回來。

SIZE_T sBytesWritten;

BOOL bProtectResult = FALSE;

DWORD dwOldProtect = 0;

  

MEMORY_BASIC_INFORMATION memInfo;

  

if( ::VirtualQuery( ppfnEntry, &memInfo, sizeof( memInfo ) ) > 0 )

{

  

// change the pages to read/write

bProtectResult =

::VirtualProtect(

memInfo.BaseAddress,

memInfo.RegionSize,

PAGE_READWRITE,

&dwOldProtect );

  

 

// then write it

::WriteProcessMemory( ::GetCurrentProcess(),

ppfnEntry, &pfnReplacementFuncAddr, sizeof( PROC * ), &sBytesWritten

);

  

// restore the page to its old protect status

bProtectResult =

::VirtualProtect(

memInfo.BaseAddress,

memInfo.RegionSize,

PAGE_READONLY,

&dwOldProtect );

}

 


3 遠(yuǎn)程線程

遠(yuǎn)程線程是Win2000以上才支持的技術(shù)。簡(jiǎn)單來講,CreateRemoteThread函數(shù)會(huì)在其他進(jìn)程中創(chuàng)建一個(gè)線程,執(zhí)行指定的代碼。因?yàn)檫@個(gè)線程并非在調(diào)用進(jìn)程之中,而是在其他進(jìn)程,因此稱之為遠(yuǎn)程線程(Remote Thread)。CreateRemoteThread的原型如下:

HANDLE WINAPI CreateRemoteThread(

HANDLE hProcess,

LPSECURITY_ATTRIBUTES lpThreadAttributes,

SIZE_T dwStackSize,

LPTHREAD_START_ROUTINE lpStartAddress,

LPVOID lpParameter,

DWORD dwCreationFlags,

LPDWORD lpThreadId

);

 

雖然概念上非常簡(jiǎn)單,但是使用CreateRemoteThread還會(huì)有一些問題:

1. lpStartAddress必須是其他進(jìn)程的地址,但是我們又如何把代碼放到另外一個(gè)進(jìn)程中呢?幸運(yùn)的是,有兩個(gè)函數(shù)可以做到這一點(diǎn):VirtualAllocEx和WriteProcessMemory,前者可以在指定進(jìn)程中分配一塊內(nèi)存,WriteProcessMemory可以修改指定進(jìn)程的代碼。因此,先調(diào)用VirtualAllocEx在指定進(jìn)程中分配內(nèi)存,再調(diào)用WriteProcessMemory將代碼寫入到分配好的內(nèi)存中,再調(diào)用CreateRemoteThread創(chuàng)建遠(yuǎn)程線程執(zhí)行在事先準(zhǔn)備好的代碼。

2. 此外,這些代碼必須得是自重定位的代碼。在解釋自重定位之前,先解釋一下什么是重定位。在程序訪問數(shù)據(jù)的時(shí)候,必須得訪問某個(gè)絕對(duì)地址,如:

MOV EAX, DWORD PTR [00400120H]


[00400120] 便是一個(gè)絕對(duì)地址。但是,由于程序?qū)嶋H上可以任意地址加載(這句話其實(shí)是不準(zhǔn)確的,后面會(huì)解釋),因此這個(gè)地址不可能是固定的,而是會(huì)在加載的時(shí)候改變的。假如程序在0x00400000地址加載,訪問地址是0x00400120,那么如果程序在0x00800000加載的話,那么地址應(yīng)該會(huì)變成0x00800120,否則便會(huì)訪問到錯(cuò)誤的地址。因此,有必要在程序加載的時(shí)候修正這些地址,這個(gè)工作是由Windows的PE Loader,也就是程序的加載器負(fù)責(zé)的。當(dāng)編譯連接的時(shí)候,在EXE/DLL中會(huì)保存那些地方的數(shù)據(jù)需要重定位,并把這些位置的RVA和數(shù)據(jù)本身的RVA保存在.reloc重定位節(jié)中,從而在加載的時(shí)候,PE Loader會(huì)自動(dòng)檢查重定位節(jié)的內(nèi)容并在程序執(zhí)行之前對(duì)這些數(shù)據(jù)進(jìn)行修正。

實(shí)際上,并非所有EXE/DLL都需要重定位。由于在單個(gè)地址空間中只有一個(gè)EXE,而這個(gè)EXE必然最先加載,因此這個(gè)EXE的加載地址總是不變的。因此,一般情況下EXE并不需要重定位信息,編譯器一般在編譯鏈接的時(shí)候會(huì)將EXE中的重定位信息去掉,以減少程序大小加快加載速度和運(yùn)行速度。EXE一般在0x40000000的地址加載,一般沒有特別原因無需修改。而DLL因?yàn)橐话銦o法保證預(yù)先設(shè)置好的加載地址總能夠滿足。比如DLL可能指定在0x10000000地址加載,但是有可能此地址已經(jīng)有其他DLL占據(jù)或者被EXE占據(jù),DLL必須得在另外的地址加載,因此一般在DLL中總是保存重定位信息。

一段代碼,一般情況下無法在任意地址執(zhí)行。假設(shè)我們有下面的代碼:

00400120 12h, 34h, 56h, 78h

00400124 MOV EAX, DWORD PTR [00400120H]

 

如果我們手動(dòng)把這段代碼copy到另外一個(gè)地方,如00500000,那么顯然00400120H這個(gè)地址需要被修改,我們當(dāng)然可以仿照自重定位的方法來手動(dòng)修改這個(gè)地址值,但是通常較簡(jiǎn)單的方法是寫自重定位代碼,這樣的代碼可以在任意地址執(zhí)行,具體做法如下:

call @F

@@:

pop ebx

sub ebx,offset @B

DATA db 12h, 34h, 56h, 78h

MOV EAX, [EBX + DATA]


可以看到,該段代碼通過使用call指令壓入當(dāng)前地址eip并彈出從而得到當(dāng)前地址。然后,用當(dāng)前地址減去其標(biāo)號(hào)的偏移量就得到重定位修正值,存入ebx之中。之后,就可以使用ebx作為一個(gè)基準(zhǔn)來訪問數(shù)據(jù),以后訪問數(shù)據(jù)可以用EBX + ???來訪問,這樣由于EBX會(huì)根據(jù)當(dāng)前的地址值而變化,所以這段代碼是自重定位的。

下面給出一段代碼,這段代碼中的InjectRemoteCode函數(shù)負(fù)責(zé)將RemoteThread這個(gè)函數(shù)的自重定位代碼Copy到其他進(jìn)程中執(zhí)行:

;=============================================================================

; RemoteThread.ASM

; Author : ATField

; Description :

; This assembly file contains a InjectRemoteCode function

; which injects remote code into a process

; History :

; 2004-3-8 Start

; 2004-3-9 Completed and tested.

; 2004-3-26 bug fix:

; not all clients connected

; Wait for completion of the remote thread

;=============================================================================

 

.386

.MODEL FLAT, STDCALL ; must be stdcall here,

; or link error will occur

OPTION CASEMAP:NONE

  

  

INCLUDE WINDOWS.INC

INCLUDE USER32.INC

INCLUDELIB USER32.LIB

INCLUDE KERNEL32.INC

INCLUDELIB KERNEL32.LIB

;INCLUDE MACRO.INC

 

 

.DATA

hRemoteThread dd 0

szKernel32 db 'Kernel32.dll',0

hmodKernel32 dd 0

szGetProcAddress db 'GetProcAddress',0

szLoadLibraryA db 'LoadLibraryA',0

lpRemoteCode dd 0

lpGetProcAddress dd 0

lpLoadLibraryA dd 0

.CODE

 

;=============================================================================

; remote code starts here

;=============================================================================

REMOTE_CODE_START equ this byte

 

;=============================================================================

; data

;=============================================================================

lpRemoteGetProcAddress dd 0

lpRemoteLoadLibraryA dd 0

szRemoteDllPathName db 255 dup(0)

lpRemoteDllHandle dd 0

lpRemoteInitDll dd 0

szRemoteInitDllFuncName db 'InitializeDll',0

;=============================================================================

 

RemoteThread PROC uses ebx lParam

  

;=====================================================================

; relocation

;=====================================================================

; just for debug

;int 3

  

call @F

@@:

pop ebx

sub ebx,offset @B

  

; LoadLibraryA szRemoteDllPathName

lea ecx, [ebx + offset szRemoteDllPathName]

push ecx

call [ebx + offset lpRemoteLoadLibraryA]

  

test eax, eax

jz error

  

mov [ebx + offset lpRemoteDllHandle], eax

; GetProcAddress hModule InitializeDll

lea ecx, [ebx + offset szRemoteInitDllFuncName]

push ecx ; 'InitializeDll'

push [ebx + offset lpRemoteDllHandle] ; hmodule

call [ebx + offset lpRemoteGetProcAddress]

  

test eax, eax

jz error

  

; InitializeDll()

call eax

ret

error:

mov eax, -1

ret

RemoteThread endp

 

REMOTE_CODE_END equ this byte

REMOTE_CODE_LENGTH equ offset REMOTE_CODE_END - offset REMOTE_CODE_START

;=============================================================================

; remote code ends

;=============================================================================

 

; BUG FIX: do not use FAR here!

InjectRemoteCode PROC C, hProcess : HANDLE, szDllPathName : DWORD

  

INVOKE GetModuleHandleA, offset szKernel32

.IF eax

mov hmodKernel32, eax

.ELSE

mov eax, 0

ret

.ENDIF

  

INVOKE GetProcAddress, hmodKernel32, addr szGetProcAddress

mov lpGetProcAddress, eax

  

INVOKE GetProcAddress, hmodKernel32, addr szLoadLibraryA

mov lpLoadLibraryA, eax

  

INVOKE VirtualAllocEx,hProcess,NULL,REMOTE_CODE_LENGTH,MEM_COMMIT,PAGE_EXECUTE_READWRITE

  

.IF eax

; memory allocation success

  

mov lpRemoteCode,eax

  

; copy the code

INVOKE WriteProcessMemory,hProcess,lpRemoteCode,\

offset REMOTE_CODE_START,REMOTE_CODE_LENGTH,NULL

  

; write function start addresses to the remote memory

INVOKE WriteProcessMemory,hProcess,lpRemoteCode,\

offset lpGetProcAddress,sizeof dword * 2,NULL

  

; write dll path name to the remote memory

INVOKE lstrlen, szDllPathName

mov ecx, eax

inc ecx

  

mov ebx, lpRemoteCode

add ebx, 8

INVOKE WriteProcessMemory,hProcess,ebx,szDllPathName,ecx,NULL

  

mov eax,lpRemoteCode

add eax,offset RemoteThread - offset REMOTE_CODE_START

INVOKE CreateRemoteThread,hProcess,NULL,0,eax,0,0,NULL

  

mov hRemoteThread, eax

.IF hRemoteThread

INVOKE WaitForSingleObject, hRemoteThread, INFINITE

INVOKE CloseHandle, hRemoteThread

.ELSE

jmp errorHere

.ENDIF

  

.ELSE

jmp errorHere

.ENDIF

  

mov eax, 0

  

ret

errorHere:

mov eax, -1

ret

InjectRemoteCode ENDP

 

END

 

上面講到了CreateremoteThread的做法,可以看到使用CreateRemoteThread是十分復(fù)雜的。不過,實(shí)際上,我們并不用總是這么做,還有更簡(jiǎn)單的方法:利用Kernel32.dll中的LoadLibrary這個(gè)函數(shù)。由于Kernel32.dll在每個(gè)EXE中都會(huì)被加載,而且由于Kernel32.dll總是第一個(gè)被加載的,因此Kernel32.dll的加載地址總是相同的,換句話說,在我們的主程序中Kernel32.dll中的LoadLibrary函數(shù)的地址同時(shí)也是其他程序中LoadLibrary函數(shù)的地址,而LoadLibrary可以加載任意DLL。此外,LoadLibrary只有一個(gè)參數(shù),正好和普通線程的要求相同!所以我們只要調(diào)用CreateRemoteThread(…, LoadLibrary, DLL_PathName)便可以將Dll Inject到任意進(jìn)程中。唯一需要注意的就是,由于LoadLibrary是在其他進(jìn)程中運(yùn)行,而LoadLibrary的參數(shù)必須保存在另外的進(jìn)程中。怎么做到這一點(diǎn)呢?回憶一下前文提到了兩個(gè)函數(shù)VirtualAllocEx和WriteProcessMemory,正好我們可以利用這兩個(gè)函數(shù)分配一塊內(nèi)存然后把Dll的路徑名Copy到該內(nèi)存中去。

此外,由于DLL中的代碼是可以重定位的,因此實(shí)際上我們會(huì)把API Hook的代碼放在DLL中,這樣寫Hook代碼的時(shí)候便不用考慮重定位問題。
4 監(jiān)視進(jìn)程的啟動(dòng)

綜合上面的內(nèi)容,我們已經(jīng)可以掛接單個(gè)進(jìn)程中的指定API了。不過這還不夠,我們還需要掛接系統(tǒng)中的所有進(jìn)程。如果在程序運(yùn)行之后,不允許新進(jìn)程的創(chuàng)建,那么掛接所有進(jìn)程則是非常容易的。Windows操作系統(tǒng)提供了一個(gè)CreateToolhelp32Snapshot的API函數(shù)。這個(gè)API函數(shù)創(chuàng)建當(dāng)前系統(tǒng)的快照(Snapshot),這個(gè)快照可以是所有進(jìn)程的快照(參數(shù)是TH32CS_SNAPPROCESS),或者是指定某個(gè)進(jìn)程的所有模塊(Module)的快照(參數(shù)是TH32CS_SNAPMODULE),等等。通過調(diào)用CreateToolhelp32Snapshot函數(shù)獲得了所有進(jìn)程之后,便可以依次掛接各個(gè)進(jìn)程。但是事情并非如此簡(jiǎn)單。用戶和操作系統(tǒng)都可以啟動(dòng)新的進(jìn)程,這樣單純的調(diào)用CreateToolhelp32Snapshot函數(shù)并不能解決問題。所以需要一種機(jī)制來通知本系統(tǒng)新進(jìn)程的創(chuàng)建和結(jié)束。經(jīng)過查閱相關(guān)資料(其實(shí)也就是Google啦),發(fā)現(xiàn)監(jiān)視系統(tǒng)進(jìn)程開始和結(jié)束的最好方法是通過DDK中的PsSetCreateProcessNotifyRoutine函數(shù),其原型為:

NTSTATUS PsSetCreateProcessNotifyRoutine(

IN PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine,

IN BOOLEAN Remove

);


NotifyRoutine指定了當(dāng)進(jìn)程被創(chuàng)建和結(jié)束的時(shí)候所需要調(diào)用的回調(diào)函數(shù)。則Remove是用來告訴該函數(shù)是設(shè)置該回調(diào)還是移除。NotifyRoutine的類型為PCREATE_PROCESS_NOTIFY_ROUTINE,其定義為:

VOID

(*PCREATE_PROCESS_NOTIFY_ROUTINE) (

IN HANDLE ParentId,

IN HANDLE ProcessId,

IN BOOLEAN Create

);

 

ParentId和ProcessId用來標(biāo)識(shí)進(jìn)程,Create則是用來表示該進(jìn)程是正在被創(chuàng)建還是正在被結(jié)束。這樣,每當(dāng)進(jìn)程被創(chuàng)建或者結(jié)束的時(shí)候,操作系統(tǒng)就會(huì)立刻調(diào)用NotifyRoutine這個(gè)回調(diào)函數(shù)并正確提供參數(shù)。

由于這個(gè)函數(shù)是由ntdll.dll所輸出的,屬于Windows的內(nèi)核空間,因此必須編寫一個(gè)處于內(nèi)核模式的驅(qū)動(dòng)程序才可以。但是,至此問題并沒有完全解決。內(nèi)核模式的驅(qū)動(dòng)程序和用戶模式的主程序如何通訊呢?這里就需要用到IO請(qǐng)求包IRP(IO Request Packet)。這個(gè)IRP的定義為:

typedef struct _CallbackInfo

{

HANDLE hParentId;

HANDLE hProcessId;

BOOLEAN bCreate;

} CALLBACK_INFO, *PCALLBACK_INFO;


其字段的意義就和PCREATE_PROCESS_NOTIFY_ROUTINE一樣,不再贅述。

用戶模式的程序通過DeviceIoControl函數(shù)發(fā)送IO請(qǐng)求包到內(nèi)核模式的驅(qū)動(dòng)。內(nèi)核模式接收到此請(qǐng)求包,并填寫數(shù)據(jù)到用戶程序所提供的CALLBACK_INFO緩沖區(qū)里。這樣通過檢查CALLBACK_INFO的值就可以知道hProcessId所指定的進(jìn)程是正在被創(chuàng)建或者結(jié)束了。

雖然有了數(shù)據(jù)交換的機(jī)制,這還是不夠。這樣只能告訴用戶程序究竟是哪一個(gè)進(jìn)程,是創(chuàng)建還是結(jié)束,但是無法通知用戶程序此事件的發(fā)生。通常,通知某個(gè)程序某個(gè)事件的發(fā)生一般的方法是使用事件(Event)。驅(qū)動(dòng)程序創(chuàng)建一個(gè)內(nèi)核事件(Kernel Event)。用戶程序打開這個(gè)事件用于同步。每當(dāng)事件發(fā)生的時(shí)候驅(qū)動(dòng)程序就首先把該事件設(shè)置為Signaled,然后再Non-signaled。這樣用戶程序就可以接收到通知了。但是為什么需要首先設(shè)置為Signaled,然后再Non-signaled?因?yàn)橛脩舫绦驔]有權(quán)限來設(shè)置其狀態(tài),因此只能由驅(qū)動(dòng)程序來設(shè)置,首先設(shè)置為Signaled,然后再Non-signaled是唯一的辦法。

有了這兩種方法,就可以掛接操作系統(tǒng)中的所有進(jìn)程了。首先,主線程調(diào)用CreateToolhelp32Snapshot函數(shù)創(chuàng)建系統(tǒng)內(nèi)所有進(jìn)程的快照,掛接這些進(jìn)程,然后啟動(dòng)驅(qū)動(dòng)程序,在主程序中啟動(dòng)一個(gè)新線程等待Event來監(jiān)視新的進(jìn)程的創(chuàng)建和舊進(jìn)程的結(jié)束。驅(qū)動(dòng)程序的代碼和監(jiān)聽的代碼可以在http://www./threads/procmon.asp下載到。
5 其他問題
5.1 Unicode

大部分Windows API均有兩個(gè)版本:Ansi和Unicode。如GetWindowText API實(shí)際上只是一個(gè)宏,實(shí)際上在不同編譯選項(xiàng)下對(duì)應(yīng)GetWindowTextA和GetWindowTextW。在NT系統(tǒng)下,GetWindowTextA只是做一個(gè)轉(zhuǎn)換,再調(diào)用GetWindowTextW,實(shí)際的實(shí)現(xiàn)在GetWindowTextW中。因此,掛接API必須要Hook兩個(gè)版本,實(shí)際在Hook的時(shí)候,我們也可以仿照Windows的做法,讓GetWindowTextA做一個(gè)簡(jiǎn)單字符串轉(zhuǎn)換,然后直接調(diào)GetWindowTextW即可。可能有朋友要問了,為何不直接Hook GetWindowTextW呢?反正GetWindowTextA要調(diào)GetWindowTextW就不用Hook GetWindowTextA了嘛。不過實(shí)際上,因?yàn)镚etWindowTextA和GetWindowTextW在同一個(gè)DLL中,他們的調(diào)用很有可能并不是通過IAT來,而是直接調(diào)用的關(guān)系,所以GetWindowTextA會(huì)繞過我們的Hook機(jī)制而直接調(diào)到原始的GetWindowTextW,這不是我們希望看到的,所以兩個(gè)版本保險(xiǎn)起見都應(yīng)該Hook。
5.2 IPC

由于Hook的API代碼位于某個(gè)DLL中,這個(gè)DLL處于不同的進(jìn)程,因此需要用到IPC機(jī)制在主程序和其他被Hook的進(jìn)程進(jìn)行通訊。不同進(jìn)程之間的通訊稱之為IPC(Interprocess Communication),大概的方法有下面幾種:

1. Pipe。管道是比較常用的IPC機(jī)制,可以傳輸大量數(shù)據(jù),代碼寫起來也比較方便。管道也可以用于網(wǎng)絡(luò)間不同計(jì)算機(jī)通訊,但是有一定限制。

2. Socket。雖然Socket一般用于網(wǎng)絡(luò),但是顯然也可以用于本機(jī),優(yōu)點(diǎn)是大家可能對(duì)Socket編程比較熟悉,此外可以很容易擴(kuò)展到網(wǎng)絡(luò)之間的通訊,基本沒有限制,因此也是很不錯(cuò)的選擇。

3. Message。消息一般適用于比較簡(jiǎn)單的通訊,如果要傳遞數(shù)據(jù)必須要使用WM_COPYDATA消息。優(yōu)點(diǎn)是比較簡(jiǎn)單,但是性能可能無法保證。

4. Shared Segment。也就是共享段。簡(jiǎn)單來說,就是把EXE/DLL中的某個(gè)段標(biāo)記為共享,這樣多個(gè)EXE/DLL的實(shí)例之間會(huì)共享同一塊內(nèi)存,通過讀寫此塊內(nèi)存便可以互相傳遞數(shù)據(jù),但是同步比較困難。具體做法是:

#pragma bss_seg("shared_bss")

int a;

#pragma bss_seg()

#pragma comment(linker, "/Section:shared_bss,rws")

 

這樣,變量a便放在了共享段之中。

5. Memory Mapped File(內(nèi)存映射文件)。比較簡(jiǎn)單,但是缺點(diǎn)和Shared Segment類似,無法同步。

6. Event/Semaphore/Mutex。這些只能用于同步,無法傳遞數(shù)據(jù)。

7. …還有很多

可以根據(jù)自己的情況靈活選用。
6 總結(jié)

API Hook的通常做法如下:

1. 通過全局消息鉤子或者驅(qū)動(dòng)程序監(jiān)視進(jìn)程啟動(dòng)/結(jié)束來掛接系統(tǒng)中所有進(jìn)程

a. 如果不需要掛接CUI程序則選用全局消息鉤子

b. 否則則選用驅(qū)動(dòng)程序

2. 通過全局消息鉤子或者遠(yuǎn)程線程來注入代碼到目標(biāo)進(jìn)程中

a. 全局消息鉤子無需考慮如何加載DLL的問題,系統(tǒng)會(huì)自動(dòng)加載

b. 遠(yuǎn)程線程一般直接創(chuàng)建線程執(zhí)行LoadLibrary代碼加載DLL,當(dāng)然也可以執(zhí)行自己寫的匯編代碼

3. 通過修改IAT (Import Address Table)中的API地址為自己的函數(shù)地址來Hook API。所使用的API是ImageDirectoryEntryToData.

4. 自己編寫的API的代碼放在DLL中以解決重定位問題(如果用全局消息鉤子的話放在DLL是強(qiáng)制要求)
7 相關(guān)參考文獻(xiàn)

我當(dāng)初在寫程序和寫作本文的時(shí)候,參考了下面這些書籍和文章,有興趣的朋友可以參考一下看看:

Windows核心編程,第22章
Windows環(huán)境下32位匯編語言程序設(shè)計(jì),第13章,17章
API Hooking Revealed, 地址:http://www./system/hooksys.asp
Detecting Windows NT/2K process execution, 地址:http://www./threads/procmon

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    亚洲免费黄色高清在线观看| 欧美国产日韩在线综合| 亚洲欧美日产综合在线网| 伊人久久五月天综合网| 欧美日韩亚洲精品在线观看| 日韩夫妻午夜性生活视频| 精品国产品国语在线不卡| 欧美成人免费一级特黄| 日韩免费成人福利在线| 老司机精品视频免费入口| 色好吊视频这里只有精| 国产精品蜜桃久久一区二区| 国产精品日韩欧美一区二区 | 小草少妇视频免费看视频| 日本精品中文字幕在线视频| 精品丝袜一区二区三区性色| 亚洲少妇人妻一区二区| 黄色美女日本的美女日人| 国产户外勾引精品露出一区 | 中文字幕久热精品视频在线| 久久国产成人精品国产成人亚洲| 中文字幕高清不卡一区| 欧美尤物在线视频91| 微拍一区二区三区福利| 丝袜视频日本成人午夜视频| 久草视频在线视频在线观看| 国产一区二区精品高清免费| 精品女同一区二区三区| 久久热九九这里只有精品| 五月综合激情婷婷丁香| 日本精品中文字幕在线视频| 亚洲国产av在线视频| 成人三级视频在线观看不卡| 日韩成人h视频在线观看| 成人午夜视频在线播放| 婷婷伊人综合中文字幕| 欧美国产极品一区二区| 69老司机精品视频在线观看| 成人日韩在线播放视频| 日本熟女中文字幕一区| 免费久久一级欧美特大黄孕妇|