ImageVerifierCode 换一换
格式:DOCX , 页数:41 ,大小:25.32KB ,
资源ID:2601671      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bingdoc.com/d-2601671.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(挂钩Windows API.docx)为本站会员(b****1)主动上传,冰点文库仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰点文库(发送邮件至service@bingdoc.com或直接QQ联系客服),我们立即给予删除!

挂钩Windows API.docx

1、挂钩Windows API= 挂钩Windows API = SoBeIt Author: Holy_Father Version: 1.1 english Date: 6.10.2002= 1. 内容 =1. 内容2. 介绍3. 挂钩方法 3.1 运行前挂钩 3.2 运行时挂钩 3.2.1 使用IAT挂钩本进程 3.2.2 改写入口点挂钩本进程 3.2.3 保存原始函数 3.2.4 挂钩其它进程 3.2.4.1 DLL注入 3.2.4.2 独立的代码 3.2.4.3 原始修改4. 结束语= 2. 介绍 = 这篇文章是有关在OS Windows下挂钩API函数的方法。所有例子都在基于NT技术的

2、Windows版本NT 4.0及以上有效(Windows NT 4.0, Windows 2000, Windows XP)。可能在其它Windows系统也会有效。 你应该比较熟悉Windows下的进程、汇编器、PE文件结构和一些API函数,才能明白这篇文章里的内容。 这里使用Hooking API这个术语表示对API的完全修改。当调用被挂钩的API时,我们的代码能立刻被执行。我将写下完全的挂钩过程。= 3. 挂钩方法 = 一般来说我们的目的是用我们的代码取代一些函数里的代码。这些问题有时可以在进程运行前解决。这些大多数时候可以用我们运行的用户级进程来完成,目的可以是修改程序的行为。举个例子应

3、用程序的破解,比方说有些程序会在启动时需要原光盘,我们想要不用光盘就启动它。如果我们修改获取驱动类型的函数我们就可以让程序从硬盘启动。 当我们挂钩系统进程时(比如说服务)这些不可能做到或者我们不打算这么做,或者在这个例子里我们不知道哪个进程才是目标。这时我们就要用到动态挂钩(在运行时挂钩)的技术。使用的例子有rootkit或者病毒里的反杀毒软件的技术。= 3.1 运行前挂钩 = 这里修改我们想要修改函数来自的物理模块(大多数时候是.exe或.dll)。在这里我们至少有3种可能的做法。 第一种可能是找到函数的入口点然后重写它的代码。这会因为函数的大小而受限制,但我们能动态加载其它一些模块(API

4、 LoadLibrary),所以应该足够了。 内核函数(kernel32.dll)是通用的因为Windows中每个进程都有这个模块的拷贝。另一个好处是如果我们知道哪些模块在某版本中会修改,我们可以在一些API如LoadLibraryA中使用直接的指针。这是因为kernel模块在内存中地址在相同Windows版本中是固定的。我们同样也能用动态加载的模块的作用。在这里它的初始化部分在加载进内存后立刻就运行。在新模块的初始化部分我们不受限制。 第二种可能是在模块中被代替的函数只是原函数的扩展。然后我们选择要么修改开始的5个字节为跳转指令或者改写IAT。如果改为跳转指令,那么将会改变指令执行流程转为执

5、行我们的代码。如果调用了IAT记录被修改的函数,我们的代码能在调用结束后被执行。但模块的扩展没那么容易,因为我们必须注意DLL首部。 下一个是修改整个模块。这意味着我们创建自己的模块版本,它能够加载原始的模块并调用原始的函数,当然我们对这个不感兴趣,但重要的函数都是被更新的。这种方法对于有的模块过大有几百个导出函数的很不方便。= 3.2 运行时挂钩 = 在运行前挂钩通常都非常特殊,并且是在内部面向具体的应用程序(或模块)。如果我们更换了kernel32.dll或ntdll.dll里的函数(只在NT操作系统里),我们就能完美地做到在所有将要运行的进程中替换这个函数。但说来容易做起来却非常难,因为

6、我们不但得考虑精确性和需要编写比较完善的新函数或新模块,但主要问题是只有将要运行的进程才能被挂钩(要挂钩所有进程只能重启电脑)。另一个问题是如何进入这些文件,因为NT操作系统保护了它们。比较好的解决方法在进程正在运行时挂钩。这需要更多的有关知识,但最后的结果相当不错。在运行中挂钩只对能够写入它们的内存的进程能成功。为了能写入它自己我们使用API函数WriteProcessMemory。现在我们开始运行中挂钩我们的进程。= 3.2.1 使用IAT挂钩本进程 = 这里有很多种可能性。首先介绍如何用改写IAT挂钩函数的方法。接下来这张图描述了PE文件的结构: +-+ - offset 0 | MS

7、DOS标志(MZ) 和 DOS块 | +-+ | PE 标志 (PE) | +-+ | .text | - 模块代码 | 程序代码 | | | +-+ | .data | - 已初始化的(全局静态)数据 | 已初始化的数据 | | | +-+ | .idata | - 导入函数的信息和数据 | 导入表 | | | +-+ | .edata | - 导出函数的信息和数据 | 导出表 | | | +-+ | 调试符号 | +-+ 这里对我们比较重要的是.idata部分的导入地址表(IAT)。这个部分包含了导入的相关信息和导入函数的地址。有一点很重要的是我们必须知道PE文件是如何创建的。当在编程语言

8、里间接调用任意API(这意味着我们是用函数的名字来调用它,而不是用它的地址),编译器并不直接把调用连接到模块,而是用jmp指令连接调用到IAT,IAT在系统把进程调入内存时时会由进程载入器填满。这就是我们可以在两个不同版本的Windows里使用相同的二进制代码的原因,虽然模块可能会加载到不同的地址。进程载入器会在程序代码里调用所使用的IAT里填入直接跳转的jmp指令。所以我们能在IAT里找到我们想要挂钩的指定函数,我们就能很容易改变那里的jmp指令并重定向代码到我们的地址。完成之后每次调用都会执行我们的代码了。这种方法的缺点是经常有很多函数要被挂钩(比方说如果我们要在搜索文件的API中改变程序

9、的行为我们就得修改函数FindFirstFile和FindNextFile,但我们要知道这些函数都有ANSI和WIDE版本,所以我们不得不修改FindFirstFileA、FindFirstFileW、FindNextFileA和FileNextFileW的IAT地址。但还有其它类似的函数如FindFirstFileExA和它的WIDE版本FindFirstFileExW,也都是由前面提到的函数调用的。我们知道FindFirstFileW调用FindFirstFileExW,但这是直接调用,而不是使用IAT。再比如说ShellAPI的函数SHGetDesktopFolder也会直接调用Find

10、FirstFilwW或FindFirstFileExW)。如果我们能获得它们所有,结果就会很完美。 我们通过使用imagehlp.dll里的ImageDirectoryEntryToData来很容易地找到IAT。 PVOID ImageDirectoryEntryToData( IN LPVOID Base, IN BOOLEAN MappedAsImage, IN USHORT DirectoryEntry, OUT PULONG Size );在这里Base参数可以用我们程序的Instance(Instance通过调用GetModuleHandle获得): hInstance = GetM

11、oduleHandleA(NULL);DirectoryEntry我们可以使用恒量IMAGE_DIRECTORY_ENTRY_IMPORT。 #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 函数的结果是指向第一个IAT记录指针。IAT的所有记录是由IMAGE_IMPORT_DESCRIPTOR定义的结构。所以函数结果是指向IMAGE_IMPORT_DESCRIPTOR的指针。 typedef struct _IMAGE_THUNK_DATA union PBYTE ForwarderString; PDWORD Function; DWORD Ordinal; P

12、IMAGE_IMPORT_BY_NAME AddressOfData; ; IMAGE_THUNK_DATA,*PIMAGE_THUNK_DATA; typedef struct _IMAGE_IMPORT_DESCRIPTOR union DWORD Characteristics; PIMAGE_THUNK_DATA OriginalFirstThunk; ; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; PIMAGE_THUNK_DATA FirstThunk; IMAGE_IMPORT_DESCRIPTOR,*PIMAGE

13、_IMPORT_DESCRIPTOR; IMAGE_IMPORT_DESCRIPTOR里的Name成员变量是模块名字的指针。如果我们想要挂钩某个函数比如是来自kernel32.dll我们就在导入表里找属于名字kernel32.dll的描述符号。我们先调用ImageDirectoryEntryToData然后找到名字是kernel32.dll的描述符号(可能不只一个描述符号是这个名字),最后我们在这个模块的记录里所有函数的列表里找到我们想要的函数(函数地址通过GetProcAddress函数获得)。如果我们找到了就必须用VirtualProtect函数来改变内存页面的保护属性,然后就可以在内存中

14、的这些部分写入代码了。在改写了地址之后我们要把保护属性改回来。在调用VirtualProtect之前我们还要先知道有关页面的信息,这通过VirtualQuery来实现。我们可以加入一些测试以防某些函数会失败(比方说如果第一次调用VirtualProctect就失败了,我们就没办法继续)。 PCSTR pszHookModName = kernel32.dll,pszSleepName = Sleep; HMODULE hKernel = GetModuleHandle(pszHookModName); PROC pfnNew = (PROC)0x12345678, /这里存放新地址 pfnHo

15、okAPIAddr = GetProcAddress(hKernel,pszSleepName); ULONG ulSize; PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData( hKernel, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize ); while (pImportDesc-Name) PSTR pszModName = (PSTR)(PBYTE) hKernel + pImportDesc-Name); if

16、 (stricmp(pszModName, pszHookModName) = 0) break; pImportDesc+; PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)(PBYTE) hKernel + pImportDesc-FirstThunk); while (pThunk-u1.Function) PROC* ppfn = (PROC*) &pThunk-u1.Function; BOOL bFound = (*ppfn = pfnHookAPIAddr); if (bFound) MEMORY_BASIC_INFORMATION m

17、bi; VirtualQuery( ppfn, &mbi, sizeof(MEMORY_BASIC_INFORMATION) ); VirtualProtect( mbi.BaseAddress, mbi.RegionSize, PAGE_READWRITE, &mbi.Protect) ) *ppfn = *pfnNew; DWORD dwOldProtect; VirtualProtect( mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwOldProtect ); break; pThunk+; 调用Sleep(1000)的结果如例子所示

18、: 00407BD8: 68E8030000 push 0000003E8h 00407BDD: E812FAFFFF call Sleep Sleep: ;这是跳转到IAT里的地址 004075F4: FF25BCA14000 jmp dword ptr 00040A1BCh 原始表: 0040A1BC: 79 67 E8 77 00 00 00 00 新表: 0040A1BC: 78 56 34 12 00 00 00 00所以最后会跳转到0x12345678。 = 3.2.2 改写入口点挂钩本进程 = 改写函数入口点开始的一些字节这种方法相当简单。就象改变IAT里的地址一样,我们也要先修

19、改页面属性。在这里对我们想要挂钩的函数是一开始的5个字节。为了之后的使用我们用动态分配MEMORY_BASIC_INFORMATION结构。函数的起始地址也是用GetProcAddress来获得。我们在这个地址里插入指向我们代码的跳转指令。接下来程序调用Sleep(5000)(所以它会等待5秒钟),然后Sleep函数被挂钩并重定向到new_sleep,最后它再次调用Sleep(5000)。因为新的函数new_sleep什么都不做并直接返回,所以整个程序只需要5秒钟而不是10秒种。.386p.model flat, stdcallincludelib libkernel32.libSleep P

20、ROTO :DWORDGetModuleHandleA PROTO :DWORDGetProcAddress PROTO :DWORD,:DWORDVirtualQuery PROTO :DWORD,:DWORD,:DWORDVirtualProtect PROTO :DWORD,:DWORD,:DWORD,:DWORDVirtualAlloc PROTO :DWORD,:DWORD,:DWORD,:DWORDVirtualFree PROTO :DWORD,:DWORD,:DWORDFlushInstructionCache PROTO :DWORD,:DWORD,:DWORDGetCurr

21、entProcess PROTOExitProcess PROTO :DWORD.datakernel_name db kernel32.dll,0sleep_name db Sleep,0old_protect dd ?MEMORY_BASIC_INFORMATION_SIZE equ 28PAGE_READWRITE dd 000000004hPAGE_EXECUTE_READWRITE dd 000000040hMEM_COMMIT dd 000001000hMEM_RELEASE dd 000008000h.codestart: push 5000 call Sleepdo_hook:

22、 push offset kernel_name call GetModuleHandleA push offset sleep_name push eax call GetProcAddress mov edi,eax ;最后获得Sleep地址 push PAGE_READWRITE push MEM_COMMIT push MEMORY_BASIC_INFORMATION_SIZE push 0 call VirtualAlloc test eax,eax jz do_sleep mov esi,eax ;为MBI结构分配内存 push MEMORY_BASIC_INFORMATION_S

23、IZE push esi push edi call VirtualQuery ;内存页的信息 test eax,eax jz free_mem call GetCurrentProcess push 5 push edi push eax call FlushInstructionCache ;只是为了确定一下:) lea eax,esi+014h push eax push PAGE_EXECUTE_READWRITE lea eax,esi+00Ch push eax push esi call VirtualProtect ;我们要修改保护属性,这样才能够写入代码 test eax,e

24、ax jz free_mem mov byte ptr edi,0E9h ;写入跳转指令 mov eax,offset new_sleep sub eax,edi sub eax,5 inc edi stosd ;这里是跳转地址 push offset old_protect lea eax,esi+014h push eax lea eax,esi+00Ch push eax push esi call VirtualProtect ;恢复页保护属性free_mem: push MEM_RELEASE push 0 push esi call VirtualFree ;释放内存do_slee

25、p: push 5000 call Sleep push 0 call ExitProcessnew_sleep: ret 004hend start第二次调用Sleep的结果是这样: 004010A4: 6888130000 push 000001388h 004010A9: E80A000000 call Sleep Sleep: ;这里是跳转到IAT里的地址 004010B8: FF2514204000 jmp dword ptr 000402014h tabulka: 00402014: 79 67 E8 77 6C 7D E8 77 Kernel32.Sleep: 77E86779:

26、 E937A95788 jmp 0004010B5h new_sleep: 004010B5: C20400 ret 004h = 3.2.3 保存原始函数 = 更多时候我们需要的不仅仅是挂钩函数。比方说也许我们并不想取代给定的函数而只是想检查一下它的结果,或者也许我们只是想在函数被使用特定的参数来调用时才取代原函数。比较好的例子有前面提过的通过取代FindXXXFile函数来完成隐藏文件。所以如果我们想要隐藏指定的文件并且不想被注意的话,就得对其它所有文件只调用没有被修改过的原始函数。这对使用修改IAT的方法时是很简单的,为调用原始函数我们可以用GetProcAddress获得它的原始地址,

27、然后直接调用。但修改入口点的方法就会有问题,因为修改了函数入口点的5个字节,使我们破坏了原函数。所以我们必须保存开始的那些指令。这将用到以下的技术。 我们知道我们要修改开始的5个字节但不知道里面包含多少条指令以及指令的长度。我们得为开始那些指令保留足够的内存空间。16个字节应该足够了,因为函数开始时通常没有多长的指令,很可能根本就用不到16个字节。整个被保留的内存用0x90(0x90=nop)来填满。下一个5个字节预留给将在之后填入的跳转指令。old_hook: db 090h,090h,090h,090h,090h,090h,090h,090h db 090h,090h,090h,090h,

28、090h,090h,090h,090h db 0E9h,000h,000h,000h,000h 现在我们已准备好拷贝开始的指令。为获得指令长度的代码相当麻烦,这就是我们得使用已完成的引擎的原因。它是由Z0MBiE写的。传入参数是我们要获得长度的指令的地址。输出参数在eax里。; LDE32, Length-Disassembler Engine, 32-bit, (x) 1999-2000 Z0MBiE; special edition for REVERT tool; version 1.05C_MEM1 equ 0001h ; |C_MEM2 equ 0002h ; |may be used simultaneouslyC_MEM4 equ 0004h ; |C_DATA1 equ 0100h ; |C_DATA2 equ 0200h ; |may be used simultaneouslyC_DATA4 equ 0400h ; |C_67 equ 0010h ; used with C

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2