Hook API

文章标题】汇编ring3下实现HOOK API


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]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值