彙編Ring 3下實現 HOOK API

【文章標題】彙編ring3下實現HOOK API
【文章做者】nohacks(非安全,hacker0058)
【做者主頁】hacker0058.ys168.com
【文章出處】看雪論壇(bbs.pediy.com)

==================[ 彙編ring3下實現HOOK API ]=====================

                                                 Author: nohacks
                                                 Emil: kker.cn@163.com
                                                 Version: 1.1
                                                 Date: 7.18.2006
                                                 

=====[ 1. 內容 ]=============================================

1. 內容
2. 介紹
  2.1 什麼叫Hook API?
  2.2 API Hook的應用介紹
  2.3 API Hook的原則
3. 掛鉤方法
  3.1 改寫IAT導入表法
  3.2 改寫內存地址JMP法
4. 彙編實現
  4.1. 代碼 
  4.2. 分析
5. 結束語


=====[ 2. 介紹 ]================================================

   這篇文章是有關在OS Windows下掛鉤API函數的方法。全部例子都在基於NT技術的Windows版本NT4.0

及以上有效(Windows NT 4.0, Windows 2000, Windows XP)。可能在其它Windows系統也會有效。

   你應該比較熟悉Windows下的進程、彙編器、和一些API函數,才能明白這篇文章裏的內容。


=====[2.1 什麼叫Hook API?]=================================
   
   所謂Hook就是鉤子的意思,而API是指Windows開放給程序員的編程接口,使得在用戶級別下可

以對操做系統進行控制,也就是通常的應用程序都須要調用API來完成某些功能,Hook API的意思

就是在這些應用程序調用真正的系統API前能夠先被截獲,從而進行一些處理再調用真正的API來完

成功能。

 ====[2.2 API Hook的應用介紹]=================================
    
   API Hook技術應用普遍,經常使用於屏幕取詞,網絡防火牆,病毒木馬,加殼軟件,串口紅外通信,遊戲外

掛,internet通訊等領域API HOOK的中文意思就是鉤住API,對API進行預處理,先執行咱們的函數,例

如咱們用API Hook技術掛接ExitWindowsEx API函數,使關機失效,掛接ZwOpenProcess函數(如:老王的

EncryptPE),隱藏進程等等......

====[2.3 API Hook的原則]=====================================
   
   HOOK API有一個原則,這個原則就是:被HOOK的API的原有功能不能受到任何影響。就象醫生救人,

若是把病人身體裏的病毒殺死了,病人也死了,那麼這個「救人」就沒有任何意義了。若是你HOOK API

以後,你的目的達到了,但API的原有功能失效了,這樣不是HOOK,而是REPLACE,操做系統的正常功能

就會受到影響,甚至會崩潰。

====[ 3. 掛鉤方法 ]==============================================

總的來講,經常使用的掛鉤API方法有如下兩種:

3.1 改寫IAT導入表法

   修改可執行文件的IAT表(即輸入表)由於在該表中記錄了全部調用API的函數地址,則只需將這些

地址改成本身函數的地址便可,可是這樣有一個侷限,由於有的程序會加殼,這樣會隱藏真實的IAT表

,從而使該方法失效。

3.2 改寫內存地址JMP法

    直接跳轉,改變API函數的入口或出口的幾個字節,使程序跳轉到本身的函數,該方法不受程序加殼

的限制。這種技術,提及來也不復雜,就是改變程序流程的技術。在CPU的指令裏,有幾條指令能夠改變

程序的流程:JMP,CALL,INT,RET,RETF,IRET等指令。理論上只要改變API入口和出口的任何機器碼

,均可以HOOK,下面我就說說經常使用的改寫API入口點的方法:
    
    由於工做在Ring3模式下,咱們不能直接修改物理內存,只能一個一個打開修改,但具體的方法又分紅

好幾種,我給你們介紹幾種操做思路:

  <1>首先改寫API首字節,要實現原API的功能須要調用API時先還原被修改的字節,而後再調用原API,調

用完後再改回來,這樣實現有點麻煩,但最簡單,從理論上說有漏HOOK的可能,由於咱們先還原了API,若是

在這以前程序調用了API,就有可能逃過HOOK的可能!

  (2)把被覆蓋的彙編代碼保存起來,在替代函數裏模擬被被覆蓋的功能,而後調用原函數(原地址+被覆

蓋長度).但這樣會產生一個問題,不一樣的彙編指令長度是不同的(好比說咱們寫入的JMP指令佔用5個字

節,而咱們寫入的這5個字節佔用的位置不必定正好是一個或多個完整的指令,有可能須要保存7個字節,

纔不能打亂程序原有的功能,須要編寫一個龐大的判斷體系來判斷指令長度,網上已經有這樣的彙編程序

(Z0MBiE寫的LDE32),很是的複雜!

  (3)把被HOOK的函數備份一下,調用時在替代函數裏調用備份函數.爲了不麻煩,能夠直接備份整個

DLL缺點就是太犧牲內存,通常不推薦使用這種方法!

 
=====[ 4. 彙編實現 ]==============================================

本文就是創建在第2種方法之上的!本着先易後難的原則,今天咱們先來講說它的第1種操做思路.
  
  咱們拿API函數ExitWindowsEx來講明,下面是我在OD裏攔下的ExitWindowsEx原入口部分

     77D59E2D            $  8BFF          mov edi,edi  
     77D59E2F            .  55            push ebp
     77D59E30            .  8BEC          mov ebp,esp
     77D59E32            .  83EC 18       sub esp,18
      ......

  若是咱們把ExitWindowsEx的入口點改成下面的,會出現什麼狀況?

    77D59E2D               B8 00400000   mov eax,4000
    77D59E32               FFE0          jmp eax
    ......


  咱們可想而知,程序執行到77D59E32處就會改變流程跳到00400000的地方


  若是咱們的00400000處是這樣的子程:

=======================
MyAPI proc  bs:DWORD  ,dwReserved:DWORD  ;和ExitWindowsEx同樣帶2個參數                 

;作你想作的事

......

;這裏放API入口點改回原機器碼的代碼

;若是你是備份的整個DLL,就直接調用備份API,不用改來改去了,不會有漏勾API的可能!

invoke ExitWindowsEx,bs,dwReserved 
                          
;這裏放HOOK API的代碼
  
.endif

mov eax,TRUE

ret
=======================

   這裏的MyAPI是和ExitWindowsEx參數同樣的的子程,由於程序是在API的入口部分跳轉的,根據

stdcall約定(參數數據從右向左依次壓棧,恢復堆棧的工做交由被調用者),此時堆棧尚未恢復,咱們

在子程裏取出的參數數據依然有效,咱們能夠在這裏執行本身的代碼,你能夠決定是否繼續按原參數或改

變參數後再調用原API,也能夠什麼都不作,固然在調用以前,咱們要先還原咱們修改過的API(能夠事先用

API函數ReadProcessMemory讀出原API的前幾個字節備份之),調用完後再改回來繼續HOOK API,不過這種

方法有漏API的可能(緣由前面已經說了),你若是以爲這個方法不妥,由於通常系統DLL都不大,你能夠備

份整個DLL.

下面我就列出ring3下HOOK API的幾個步驟:

1.獲得要掛勾API的入口點

2.修改API的入口點所在頁的頁面保護爲可讀寫模式

3.用ReadProcessMemory讀出API的入口點開始的幾字節備份

4.用WriteProcessMemory修改API的入口點象這樣的形式:
  
  mov eax,4000
  
  jmp eax

 其中的4000要用和原API參數同樣的子程序地址代替


  在這個子程序裏咱們決定用什麼參數再調用原API,不過調用以前要用備份的前8字節改回來

調用以後在掛勾,如此反覆.



=====[ 4.1. 代碼 ]==============================================

  前面所講的是本進程掛勾,咱們要掛勾全部進程,能夠用全局勾子,須要單獨的一個DLL,咱們可

以在DLL的DLL_PROCESS_ATTACH事件裏來HOOK API

=================================hookdll.dll==========================
.486 
.model flat,stdcall   ;參數的傳遞約定是stdcall(從右到左,恢復堆棧的工做交由被調用者)
option casemap:none 
include \masm32\include\windows.inc 
include \masm32\include\kernel32.inc 
includelib \masm32\lib\kernel32.lib 
include \masm32\include\user32.inc 
includelib \masm32\lib\user32.lib 


HOOKAPI struct 
a  byte ? 
PMyapi DWORD ?   
d BYTE ?  
e BYTE ?
HOOKAPI ends


;子程序聲明
WriteApi proto :DWORD ,:DWORD,:DWORD,:DWORD
MyAPI proto  :DWORD  ,:DWORD
GetApi proto  :DWORD,:DWORD


;已初始化數據
.data 
hInstance dd 0
WProcess dd 0
hacker HOOKAPI <> 
CommandLine LPSTR ? 

Papi1 DWORD ? 
Myapi1 DWORD ?
ApiBak1 db 10 dup(?) 
DllName1  db "user32.dll",0      
ApiName1  db "ExitWindowsEx",0 
mdb db "下面的程序想關閉計算機,要保持阻止嗎?",0


;未初始化數據

.data? 
hHook dd ? 
hWnd dd ? 

;程序代碼段

.code 

DllEntry proc hInst:HINSTANCE, reason:DWORD, reserved1:DWORD 
   
  
 .if reason==DLL_PROCESS_ATTACH     ;當DLL加載時產生此事件
        push hInst 
        pop hInstance 

invoke GetCommandLine   
mov CommandLine,eax                                         ;取程序命令行

;初始化

mov hacker.a,0B8h     ;mov eax,
;mov hacker.d PMyapi  ;0x000000
mov hacker.d,0FFh     ;jmp 
mov hacker.e, 0E0h    ;eax
 

invoke   GetCurrentProcess                                   ;取進程僞句柄

 mov WProcess ,eax
    
invoke GetApi,addr DllName1,addr ApiName1                    ;取API地址
  
 mov Papi1,eax                                               ;保存API地址

invoke ReadProcessMemory,WProcess,Papi1,addr ApiBak1,8,NULL  ;備份原API的前8字節

 mov hacker.PMyapi,offset MyAPI   ;0x0000,這裏設置替代API的函數地址

invoke WriteApi,WProcess,Papi1, addr hacker ,size HOOKAPI    ;HOOK API

.endif 

.if  reason==DLL_PROCESS_DETACH 

invoke WriteApi,WProcess,Papi1, addr ApiBak1 ,8               ;還原API

.endif 

 mov  eax,TRUE 
    ret 
DllEntry Endp 

GetMsgProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD 
    invoke CallNextHookEx,hHook,nCode,wParam,lParam 
     mov eax,TRUE
     
      ret 
GetMsgProc endp 

InstallHook proc
   
    invoke SetWindowsHookEx,WH_GETMESSAGE,addr GetMsgProc,hInstance,NULL 
    mov hHook,eax 
    ret 
InstallHook endp 

UninstallHook proc 
    invoke UnhookWindowsHookEx,hHook 
   invoke WriteApi,WProcess,Papi1, addr ApiBak1 ,8
  ret 
UninstallHook endp 

GetApi proc DllNameAddress:DWORD,ApiNameAddress:DWORD

invoke  GetModuleHandle,DllNameAddress     ;取DLL模塊句柄
   
  .if eax==NULL
  
  invoke LoadLibrary ,DllNameAddress    ;加載DLL
  
   .endif
  
 invoke GetProcAddress,eax,ApiNameAddress  ;取API地址
   

mov eax,eax
  
ret

GetApi endp


;============================下面是核心部分=========================


WriteApi proc Process:DWORD ,Papi:DWORD,Ptype:DWORD,Psize:DWORD

LOCAL mbi:MEMORY_BASIC_INFORMATION
LOCAL msize:DWORD


;返回頁面虛擬信息
invoke VirtualQueryEx,Process, Papi,addr mbi,SIZEOF MEMORY_BASIC_INFORMATION

;修改成可讀寫模式

invoke VirtualProtectEx,Process, mbi.BaseAddress,8h,PAGE_EXECUTE_READWRITE,addr 

mbi.Protect

;開始寫內存

invoke  WriteProcessMemory,Process, Papi, Ptype,Psize ,NULL

PUSH eax

;改回只讀模式

invoke VirtualProtectEx,Process,mbi.BaseAddress,8h,PAGE_EXECUTE_READ,addr mbi.Protect

pop eax

ret

WriteApi endp



;替代的API,參數要和原來同樣

MyAPI proc  bs:DWORD  ,dwReserved:DWORD                      

invoke MessageBox, NULL,  CommandLine, addr mdb, MB_YESNO      ;彈出信息框選擇是否阻止

.if eax==7                                                   ;若是選擇否

 invoke WriteApi,WProcess,Papi1, addr ApiBak1 ,8              ;先還原API
 
 invoke ExitWindowsEx,bs,dwReserved                           ;再調用API
 
 invoke WriteApi,WProcess,Papi1, addr hacker ,sizeof HOOKAPI  ;調用完後再改回來
  
.endif

mov eax,TRUE 
ret

MyAPI endp

End DllEntry


===============================hookdll.def=============================

LIBRARY hookdll
EXPORTS InstallHook
EXPORTS UninstallHook


=====[ 4.2. 分析 ]==============================================

HOOKAPI struct 
a  byte ? 
PMyapi DWORD ?   
d BYTE ?  
e BYTE ?
HOOKAPI ends


   爲了便於理解和使用,我定義了一個結構:這個結構有4個成員,第一個成員a,是個字節型,我用來放

0B8h(mov eax),PMyapi一個整數型,用來放咱們的替代API函數的地址(0X000),第3個和第4個成員我分別

用來放JMP和EAX(jmp eax)那麼連起來就是 mov,0X0000 ; jmp eax  


 .if reason==DLL_PROCESS_ATTACH     
        push hInst 
        pop hInstance 

invoke GetCommandLine   
mov CommandLine,eax                                         

;初始化

mov hacker.a,0B8h     ;mov eax,
;mov hacker.d PMyapi  ;0x0000
mov hacker.d,0FFh     ;jmp 
mov hacker.e, 0E0h    ;eax


invoke   GetCurrentProcess                                   

 mov WProcess ,eax


  當DLL加載時,咱們先保存模塊句柄,讀取程序命令行,而後初始化HOOKAPI結構,寫入咱們要寫到內存的

指令(PMyapi之後寫入)並調用GetCurrentProcess取出進程僞句柄方便之後寫內存.

invoke GetApi,addr DllName1,addr ApiName1                    
  
 mov Papi1,eax                                               

invoke ReadProcessMemory,WProcess,Papi1,addr ApiBak1,8,NULL  
 
mov hacker.PMyapi,offset MyAPI   ;0x0000   

invoke WriteApi,WProcess,Papi1, addr hacker ,size HOOKAPI    ;HOOK API


  接下來用子程GetApi取出要掛勾API的入口點,並用ReadProcessMemory讀出入口點8字節備份之,寫入
PMyapi調用子程WriteApi改寫API的入口點,這個子程我不許備詳細說了,它很是的簡單,無非就是幾個

API的調用.它的核心就是經過WriteProcessMemory改寫內存.

.if  reason==DLL_PROCESS_DETACH 

invoke WriteApi,WProcess,Papi1, addr ApiBak1 ,8               

.endif 

 mov  eax,TRUE 
    ret 

   若是這個DLL被卸載了,那麼那個在DLL裏的替代函數(MyAPI)將是無效的,若是這個時候程序再調用這

個API,將出現非法操做,所以在DLL卸載前,咱們必須還原API.

   總結一下,如今只要程序加載這個DLL,這個程序的ExitWindowsEx就會被咱們勾住,接下來要怎樣才能

讓全部的程序都加載這個DLL呢?這就須要安裝全局勾子:

  InstallHook proc
   
      invoke SetWindowsHookEx,WH_GETMESSAGE,addr GetMsgProc,hInstance,NULL 
    
      invoke WriteApi,WProcess,Papi1, addr hacker ,sizeof HOOKAPI

      mov hHook,eax 
     ret 
  InstallHook endp 

   經過SetWindowsHookEx安裝勾子,最後一個參數能夠決定該鉤子是局部的仍是系統範圍的。若是該值

爲NULL,那麼該鉤子將被解釋成系統範圍內的,那它就能夠監控全部的進程及它們的線程。

若是該函數調用成功的話,將在eax中返回鉤子的句柄,不然返回NULL。咱們必須保存該句柄,由於後

面咱們還要它來卸載鉤子,能夠看出,咱們建立的Hook類型是WH_CALLWNDPROC類型,該類型的Hook在進程

與系統一通訊時就會被加載到進程空間,從而調用dll的初始化函數完成真正的Hook,值得一提的是:因

爲要調用SetWindowsHookEx來安裝鉤子,咱們GUI程序的這個DLL不會被

UnhookWidowHookEx卸載,也就只有一次DLL_PROCESS_ATTACH事件,所以這裏再要

HOOK API一次!

咱們回頭來看看鉤子回調函數:

  GetMsgProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD 
      invoke CallNextHookEx,hHook,nCode,wParam,lParam 
       mov eax,TRUE
     
       ret 
  GetMsgProc endp 

   能夠看到這裏只是調用CallNextHookEx將消息交給Hook鏈中下一個環節處理,由於這裏API函數
SetWindowsHookEx的惟一做用就是讓進程加載咱們的dll。

  UninstallHook proc 
     invoke UnhookWindowsHookEx,hHook 
     invoke WriteApi,WProcess,Papi1, addr ApiBak1 ,8
   ret 
  UninstallHook endp 

要卸載一個鉤子時調用UnhookWidowHookEx函數,該函數僅有一個參數,就是欲卸載的鉤子的句柄。鉤

子卸載後咱們也要還原咱們GUI程序的API.

  LIBRARY hookdll
  EXPORTS InstallHook
  EXPORTS UninstallHook

   咱們公開DLL裏的InstallHook和UninstallHook函數,方便程序調用,這樣咱們只要在另外的程序中調

用InstallHook即可安裝全局勾子,勾住全部程序中的API:ExitWindowsEx,執行咱們自定的子程!

若是不須要了,能夠調用UninstallHook卸載全局勾子.

   請注意:對於遠程鉤子,鉤子函數必須放到DLL中,它們將從DLL中映射到其它的進程空間中去。當

WINDOWS映射DLL到其它的進程空間中去時,不會把數據段也進行映射。簡言之,全部的進程僅共享DLL

的代碼,至於數據段,每個進程都將有其單獨的拷貝。這是一個很容易被忽視的問題。您可能想固然

的覺得,在DLL中保存的值能夠在全部映射該DLL的進程之間共享。在一般狀況下,因爲每個映射該

DLL的進程都有本身的數據段,因此在大多數的狀況下您的程序運行得都不錯。可是鉤子函數卻不是如

此。對於鉤子函數來講,要求DLL的數據段對全部的進程也必須相同。這樣您就必須把數據段設成共享

的:

 通常來講, 目標文件有三個段, 分別是 text/data/bss 段.

.text 段放置代碼, 是隻讀且可運行段 

.data 段放置靜態數據, 這些數據會被放置入 exe 文件. 這個段是可讀寫, 可是不能運行的. 

.bss 段放置動態數據, 這些數據不被放入 exe 文件, 在exe文件被加載入內存後才分配的空間.

你能夠經過在連接開關中指定段的屬性來實現:

/SECTION:name,[E][R][W][S][D][K][L][P][X]

其中S表示共享,已初期化的段名是.data,未初始化的段名是.bss。假如您想要寫一個包含鉤子函數的

DLL,並且想使它的未初始化的數據段在全部進程間共享,您必須這麼作:
 
link /section:.bss[S]  /DLL  /SUBSYSTEM:WINDOWS ..........

不然,您的全局勾子將不能正常工做!

=====[ 5. 結束語 ]================================================

    我歡迎任何人提出更多的這裏沒有提到的掛鉤方法,我確定那會有不少。一樣歡迎補充我介紹得不

是很詳細的方法。也能夠把我懶得寫的其它方法完成,把源代碼發給我。這篇文檔的目的是演示掛鉤技

術的細節,我但願我作到了。
    
============================[ End ]========================程序員

相關文章
相關標籤/搜索