| 查看: 468 | 回复: 1 | |||
| 当前主题已经存档。 | |||
sdlj8051金虫 (著名写手)
|
[交流]
汇编ring3下实现HOOK API[转贴]
|
||
|
汇编ring3下实现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,下面我?S玫母男碅PI入口点的方法: 因为工作在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 WORD ,dwReserved WORD ;和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字节改回来 调用之后在挂勾,如此反复. [ Last edited by sdlj8051 on 2007-1-13 at 20:22 ] |
» 猜你喜欢
基金申报
已经有5人回复
基金委咋了?2026年的指南还没有出来?
已经有7人回复
国自然申请面上模板最新2026版出了吗?
已经有17人回复
纳米粒子粒径的测量
已经有8人回复
疑惑?
已经有5人回复
计算机、0854电子信息(085401-058412)调剂
已经有5人回复
Materials Today Chemistry审稿周期
已经有5人回复
溴的反应液脱色
已经有7人回复
推荐一本书
已经有12人回复
常年博士招收(双一流,工科)
已经有4人回复
sdlj8051
金虫 (著名写手)
- 应助: 0 (幼儿园)
- 贵宾: 0.1
- 金币: 1149.8
- 红花: 3
- 帖子: 2254
- 在线: 18.1小时
- 虫号: 71297
- 注册: 2005-05-30
- 专业: 电路与系统
|
=====[ 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 ]======================== 例子源程序(MASM+RadASM和Windows XP2系统下编译通过): |
2楼2006-08-23 13:12:54











WORD ,dwReserved
回复此楼