Hook API

1 什么是Hook API

简单的说,一个应用程序要调用一个API函数,例如CreateFileW,那么应用程序必须要知道函数的地址,才能调用它,我对Hook API的理解是,把这个函数地址替换为另一个函数MyCreateFileW的地址,那么每当程序调用CreateFileW时,就会调用MyCreateFileW


2 Hook API有什么用

在《Rootkits——Windows内核的安全防护》中,作者笑言,该技术能够实现的能力很大程度上取决于想像力

举一个简单的例子,360有一个产品叫360安全沙箱,在沙箱中打开一个软件,如果在沙箱中使用这个软件新建或者存储了一个文件,那么你会发现系统中的文件并没有改变。如果你仔细查找,会发现在根目录下有一个隐藏的文件夹,里面存储着这个软件新建或者修改过的那个文件。虽然我不知道360沙箱到底怎么实现这一功能,但是它完全可以用Hook API技术来实现。我们只需要Hook掉CreateFileW函数,检查函数的参数,当发现参数表明软件有“写”行为时,就更改文件路径,让它存储到我们指定的位置。


3.Hook API原理解析

1) 目标:替换掉目标程序中CreateFileW函数的地址

2) 过程

a.DLL注入

我们要替换掉目标程序的一个地址,并且要让目标程序执行我们编写的MyCreateFileW函数,由于进程的地址空间是隔离的,所以我们需要将这些代码(包括替换地址,MyCreateFileW函数代码)写在一个DLL文件中,然后运用DLL注入技术(在我们的应用程序中调用SetWindowsHookEx或者CreateRemoteThread都可以实现),将DLL文件注入目标程序,然后执行我们的代码,由于这不是本文的重点,不再详述。

b.替换CreateFileW函数地址

b1. 地址在哪儿

讲这个问题之前,需要介绍一些基础知识,原则是够用就好,不需要的东西不讲。

DLL基础

目标程序要调用API函数CreateFileW,这个函数的代码在Kernel32.dll中,DLL的好处之一就是DLL文件只在内存中存在一次,当有程序需要使用这个DLL文件时,系统只需要将这个DLL文件映射到程序进程地址空间就可以了,所以我们的目标程序要调用CreateFileW,就需要将Kernel32.dll映射到自己的地址空间中去

PE格式基础

在我们启动目标程序时,操作系统负责为目标程序创建虚拟地址空间,并将这个可执行模块(就是目标程序)加载到地址空间中去,接下来,系统会将目标程序所需要的DLL文件映射到地址空间。我们将需要映射到地址空间的目标程序及所需DLL统称为模块(Module)。使用IceSword查看进程,右击选择模块信息,就可以看到一个进程到底加载了哪些模块。下图是记事本程序的模块信息:



b2.查找线索

这里的问题是,操作系统怎么知道这个目标程序需要映射什么DLL文件?这就涉及到PE文件格式。我们平时使用的EXE文件,DLL文件基本都是PE格式的,PE格式的文件存储了与这个文件相关的大量信息,系统可以使用这些信息去加载必要的文件,并运行这个可执行文件。PE格式中就包含了这个可执行文件所需要映射的DLL文件。

下面就讲PE文件,原则还是,只讲与主题相关的~(绿色字段记录了我们层层寻找的线索)

可以通过下载这个文件边看图边看讲解:

看雪论坛技术文档:http://www.pediy.com/Document.htm ( 这里有一个PE文件结构图,对理解PE文件非常有帮助)

这是PE文件整体布局


关于PE文件格式的定义在WinNT.h中,可自行查阅

我们看PE文件头部分的定义:

[cpp]  view plain  copy
  1. typedef struct _IMAGE_NT_HEADERS {  
  2.     DWORD Signature;  
  3.     IMAGE_FILE_HEADER FileHeader;  
  4.     IMAGE_OPTIONAL_HEADER32 OptionalHeader;  
  5. } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;  

其中PE文件的Signature段被设置为IMAGE_NT_SIGNATURE ,ASCLL码为"PE00",下面是WinNT.h中的定义

[cpp]  view plain  copy
  1. #define IMAGE_NT_SIGNATURE                  0x00004550  // PE00  

FileHeader我们不关心

下面是IMAGE_OPTIONAL_HEADER32 结构的定义

[cpp]  view plain  copy
  1. typedef struct _IMAGE_OPTIONAL_HEADER {  
  2.     //  
  3.     // Standard fields.  
  4.     //  
  5.   
  6.     WORD    Magic;  
  7.     BYTE    MajorLinkerVersion;  
  8.     BYTE    MinorLinkerVersion;  
  9.     DWORD   SizeOfCode;  
  10.     DWORD   SizeOfInitializedData;  
  11.     DWORD   SizeOfUninitializedData;  
  12.     DWORD   AddressOfEntryPoint;  
  13.     DWORD   BaseOfCode;  
  14.     DWORD   BaseOfData;  
  15.   
  16.     //  
  17.     // NT additional fields.  
  18.     //  
  19.   
  20.     DWORD   ImageBase;  
  21.     DWORD   SectionAlignment;  
  22.     DWORD   FileAlignment;  
  23.     WORD    MajorOperatingSystemVersion;  
  24.     WORD    MinorOperatingSystemVersion;  
  25.     WORD    MajorImageVersion;  
  26.     WORD    MinorImageVersion;  
  27.     WORD    MajorSubsystemVersion;  
  28.     WORD    MinorSubsystemVersion;  
  29.     DWORD   Win32VersionValue;  
  30.     DWORD   SizeOfImage;  
  31.     DWORD   SizeOfHeaders;  
  32.     DWORD   CheckSum;  
  33.     WORD    Subsystem;  
  34.     WORD    DllCharacteristics;  
  35.     DWORD   SizeOfStackReserve;  
  36.     DWORD   SizeOfStackCommit;  
  37.     DWORD   SizeOfHeapReserve;  
  38.     DWORD   SizeOfHeapCommit;  
  39.     DWORD   LoaderFlags;  
  40.     DWORD   NumberOfRvaAndSizes;  
  41.     IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];  
  42. } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;  


 
我们关心的是最后一个结构IMAGE_DATA_DIRECTORY  
 

[cpp]  view plain  copy
  1. typedef struct _IMAGE_DATA_DIRECTORY {  
  2. DWORD VirtualAddress;  
  3. DWORD Size;  
  4. } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;  


DataDirectory是一个此结构体类型的数组,通过这个数组以及索引值我们可以找到大量的表,下面列出这些索引:

[cpp]  view plain  copy
  1. // Directory Entries  
  2. #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory  
  3. #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory  
  4. #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory  
  5. #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory  
  6. #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory  
  7. #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table  
  8. #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory  
  9. // IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage)  
  10. #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data  
  11. #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP  
  12. #define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory  
  13. #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory  
  14. #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers  
  15. #define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table  
  16. #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors  
  17. #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor  


通过DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT],我们可以找到一个“导入表”,这个“导入表”同样是一个结构体数组
这个结构体为IMAGE_IMPORT_DESCRIPTOR:
[cpp]  view plain  copy
  1. typedef struct _IMAGE_IMPORT_DESCRIPTOR {  
  2. union {  
  3. DWORD Characteristics; // 0 for terminating null import descriptor  
  4. DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)  
  5. } DUMMYUNIONNAME;  
  6. DWORD TimeDateStamp; // 0 if not bound,  
  7. // -1 if bound, and real date\time stamp  
  8. // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)  
  9. // O.W. date/time stamp of DLL bound to (Old BIND)  
  10. DWORD ForwarderChain; // -1 if no forwarders  
  11. DWORD Name;  
  12. DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)  
  13. } IMAGE_IMPORT_DESCRIPTOR;  
  14. typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;  



这个结构体就是我们这条线索的终点了!
例如IMAGE_IMPORT_DESCRIPTOR类型的数组有N项,那么就说明这个PE文件需要加载的DLL模块有N个,我们可以根据Name成员变量得到需要加载的DLL文件名
我们一开始说了,目标程序调用CreateFileW,CreateFileW在Kernel32.dll中,那么我们可以通过目标程序文件中Name字段找到Kernel32.dll这个名字。那么CreateFileW又在哪里呢?我们发现结构体中还有一个FirstThunk字段,这就是目标程序使用Kernel32.dll中的那一系列函数记录的入口。
通过这个FirstThunk我们又可以找到一个结构体数组,这个结构体名为IMAGE_THUNK_DATA32,其定义为

[cpp]  view plain  copy
  1. typedef struct _IMAGE_THUNK_DATA32 {  
  2. union {  
  3. DWORD ForwarderString; // PBYTE  
  4. DWORD Function; // PDWORD  
  5. DWORD Ordinal;  
  6. DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME  
  7. } u1;  
  8. } IMAGE_THUNK_DATA32;  
  9. typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;  


其中Function记录了Kernel32.dll中CreateFileW函数的地址。我们只需要改写这一字段,就可以Hook API成功了!


我也是初学,了解不多,就不多说了,关于PE格式的参考资料:

看雪论坛安全文库:http://www.pediy.com/kssd/index.html (在系统篇中详细讲述了PE文件)

看雪论坛技术文档:http://www.pediy.com/Document.htm ( 这里有一个PE文件结构图,对理解PE文件非常有帮助)

《加密与解密》第10章:PE文件格式

《windows环境下32位汇编语言程序设计》第17章:PE文件

3)代码

根据上述过程我们看下面的代码(来源:http://www.yulv.net/archives/16/ 

[cpp]  view plain  copy
  1. void WINAPI HookOneAPI(LPCSTR pszCalleeModuleName,PROC pfnOriginApiAddress,  
  2.     PROC pfnDummyFuncAddress,HMODULE hModCallerModule)  
  3. {  
  4.     ULONG size;  
  5.     //获取指向PE文件中的Import中IMAGE_DIRECTORY_DESCRIPTOR数组的指针  
  6.     PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)  
  7.         ImageDirectoryEntryToData(hModCallerModule,TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,&size);  
  8.     if (pImportDesc == NULL)  
  9.         return;  
  10.     //查找记录,看看有没有我们想要的DLL  
  11.     for (;pImportDesc->Name;pImportDesc++)  
  12.     {  
  13.         LPSTR pszDllName = (LPSTR)((PBYTE)hModCallerModule+pImportDesc->Name);  
  14.         if (lstrcmpiA(pszDllName,pszCalleeModuleName) == 0)  
  15.             break;  
  16.     }  
  17.     if (pImportDesc->Name == NULL)  
  18.     {  
  19.         return;  
  20.     }  
  21.     //寻找我们想要的函数  
  22.     PIMAGE_THUNK_DATA pThunk =  
  23.         (PIMAGE_THUNK_DATA)((PBYTE)hModCallerModule+pImportDesc->FirstThunk);//IAT  
  24.     for (;pThunk->u1.Function;pThunk++)  
  25.     {  
  26.         //ppfn记录了与IAT表项相应的函数的地址  
  27.         PROC * ppfn= (PROC *)&pThunk->u1.Function;  
  28.         if (*ppfn == pfnOriginApiAddress)  
  29.         {  
  30.             //如果地址相同,也就是找到了我们想要的函数,进行改写,将其指向我们所定义的函数  
  31.             WriteProcessMemory(GetCurrentProcess(),ppfn,&(pfnDummyFuncAddress),  
  32.                 sizeof(pfnDummyFuncAddress),NULL);  
  33.             return;  
  34.         }  
  35.     }  
  36. }  

这段代码可以这样调用 :

[cpp]  view plain  copy
  1. HMODULE hMoudle = GetModuleHandle(glFileOpenShell);  
  2. HookOneAPI("Kernel32.dll",GetProcAddress(GetModuleHandle(_T("Kernel32.dll")),"CreateFileW"),(PROC)&H_CreateFileW,hMoudle);  
其中glFileOpenShell是模块文件的路径,即上面我们用IceSword看到的那些模块的路径。

GetProcAddress函数用于得到Kernel32.dll中CreateFileW的地址,我们需要这个值与前面提到的Function成员变量进行比较,以确定这个Function是不是我们想找的Function。

上面的代码结合之前的讲述应该不难看懂。

需要解释的地方是这两行代码:

[cpp]  view plain  copy
  1. LPSTR pszDllName = (LPSTR)((PBYTE)hModCallerModule+pImportDesc->Name);  
  2. //寻找我们想要的函数  
  3. PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)((PBYTE)hModCallerModule+pImportDesc->FirstThunk);//IAT  

这里得到DLL文件名的时候,以及得到IMAGE_THUNK_DATA地址的时候用了一个加法。为什么要用加法呢?

因为PE文件中Name和FirstThunk记录的都是相对地址,即RVA,这表示一个偏移量,而我们通过GetModuleHandle可以得到模块文件加载到内存后的起始地址,这个起始地址加上偏移量,就可以得到一个值在内存中的地址了。


转载请注明出处:http://blog.csdn.net/on_1y/article/details/7595184

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值