【文章标题】汇编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 ]========================程序员