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

汇编Ring 3下实现 HOOK API的更多相关文章

  1. 汇编 -- Hook API (MessageBoxW)

    说到HOOK.我看了非常多的资料和教程.无奈就是学不会HOOK.不懂是我的理解能力差.还是你们说的 不够明确,直到我看了下面这篇文章,最终学会了HOOK: http://blog.sina.com.c ...

  2. 运用Detours库hook API(原理是改写函数的头5个字节)

    一.Detours库的来历及下载: Detours库类似于WTL的来历,是由Galen Hunt and Doug Brubacher自己开发出来,于99年7月发表在一篇名为<Detours: ...

  3. HOOK API入门之Hook自己程序的MessageBoxW(简单入门)

    说到HOOK,我看了很多的资料和教程,无奈就是学不会HOOK,不懂是我的理解能力差,还是你们说的 不够明白,直到我看了以下这篇文章,终于学会了HOOK: http://blog.sina.com.cn ...

  4. HOOK API 在多线程时应该注意的问题点

    在使用INLINE HOOK API实现对系统API的拦截时,正常情况下并没有太大问题,但一旦涉及到多线程,不管是修改IAT还是JMP,2种方法均会出现不可预料的问题,特别是在HOOK一些复杂的大型系 ...

  5. HOOK API(四)—— 进程防终止

    HOOK API(四) —— 进程防终止 0x00        前言 这算是一个实战吧,做的一个应用需要实现进程的防终止保护,查了相关资料后决定用HOOK API的方式实现.起初学习HOOK API ...

  6. HOOK API(二)—— HOOK自己程序的 MessageBox

    HOOK API(二) —— HOOK自己程序的 MessageBox 0x00 前言 以下将给出一个简单的例子,作为HOOK API的入门.这里是HOOK 自己程序的MessageBox,即将自己程 ...

  7. HOOK API (一)——HOOK基础+一个鼠标钩子实例

    HOOK API (一)——HOOK基础+一个鼠标钩子实例 0x00 起因 最近在做毕业设计,有一个功能是需要实现对剪切板的监控和进程的防终止保护.原本想从内核层实现,但没有头绪.最后决定从调用层入手 ...

  8. HOOK API(二) —— HOOK自己程序的 MessageBox

    转载来源:https://www.cnblogs.com/hookjc/ 0x00 前言 以下将给出一个简单的例子,作为HOOK API的入门.这里是HOOK 自己程序的MessageBox,即将自己 ...

  9. HOOK API(四) —— 进程防终止

    0x00        前言 这算是一个实战吧,做的一个应用需要实现进程的防终止保护,查了相关资料后决定用HOOK API的方式实现.起初学习HOOK API的起因是因为要实现对剪切板的监控,后来面对 ...

随机推荐

  1. 第十篇、微信小程序-view组件

    视图容器 常用的样式的属性: 详情:http://www.jianshu.com/p/f82262002f8a display :显示的模式.可选项有:flex(代表view可以伸缩,弹性布局)- f ...

  2. 【知识分享】UIButton setTitle 设置为空 失效

    今天开发练习超级猜图,但是碰到了一个奇怪的问题 困扰我一个晚上,低效的夜晚 可恨~ 示例说明1 [button setTitle:@"" forState:UIControlSta ...

  3. 《HTML5网页开发实例详解》连载(四)HTML5中的FileSystem接口

    HTML 5除了提供用于获取文件信息的File对象外,还添加了FileSystem相关的应用接口.FileSystem对于不同的处理功能做了细致的分类,如用于文件读取和处理的FileReader和Fi ...

  4. 你早该这么玩Excel 读书笔记

    1. Excel用来分析数据,至少要有一份原始数据和对于的分类汇总数据,这两种数据在一项任务中,应该是存放在同一个Excel文档中的,在书籍中,把他们叫做源数据表和分类汇总表.用户输入源数据表,根据相 ...

  5. Poj 1328 / OpenJudge 1328 Radar Installation

    1.Link: http://poj.org/problem?id=1328 http://bailian.openjudge.cn/practice/1328/ 2.Content: Radar I ...

  6. php parallel

    http://www.phpied.com/simultaneuos-http-requests-in-php-with-curl/ http://stackoverflow.com/question ...

  7. Qt模拟C#的File类对文件进行操作

    其实只是QT菜鸟为了练习而搞出来的 文件头: #include <QFile> #include <QString> #include <iostream> #in ...

  8. 代码动态创建checkbox

    根据数据库的内容动态创建Checkbox控件并显示在Panel上 dataset ds=new dataset(); CheckBox[ ] cb=new CheckBox[ds.tables[0]. ...

  9. php提取淘宝URL中ID的代码

    一段可以提取淘宝URL中ID的PHP代码. 例如: <?php $taobao = 'taobao.com'; $tmall = 'tmall.com'; $guojitmall = 'tmal ...

  10. 网络编程Socket UDP

    图表流程 linux udp测试代码 //server.c #include <stdio.h> #include <stdlib.h> #include <errno. ...