Windows下的钩子

 

Windows下的钩子

2011-9-9

API钩子应用程序将kernel32.dll加载到自己的私有空间0x0001000~0x7FFE0000之间,所以本地进程只要能访问目标进程的地址空间,就可以直接重写kernel32.dll中或应用程序导入表中的任何函数。通过利用机器码重写DLL文件中特定的文件或重写目标应用程序中的导入表,使其指向自己想要的函数。利用这种方式,可以完成隐藏进程,隐藏网络端口,将文件操作重定向到其他文件、防止应用程序打开特定进程的句柄功能。下面简单介绍几种钩子:

 

导入地址表钩子(import Address table hooking)。

当应用程序使用另一个库中的函数时,必须导入该函数的地址。都是通过IAT来实现的。

应用程序文件系统映像的IMAGE_IMPORT_DESCRIPTOR结构,它包含导入函数的DLL名称,和两个IMAGE_IMPROT_BY_NAME数组指针,它包含了导入函数的名称。这种方式对于显示调用DLL无效。

 

内联函数钩子

内联函数钩子远比IAT钩子强大,在实现内联函数钩子时,实际上是重写目标函数的代码字节, 所以无论目标进程如何或何时解析函数地址,都能够勾住函数。

内联函数钩子要重写目标函数的多个起始字节。保存原始字节后,在目标函数前5个字节中放置一个立即跳转指令。内联函数补丁的里程碑论文

 Detours: Binary Interccption of  Win32 function, G. Hunt and D. Brubacker

使用注册表注入DLL

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\Current Verion\Windows\AppInit_Dlls

将该键值设为修改了目标进程的IAT或直接修改了kernel32.Dll和ntdll.dll的DLL。

 

使用windows钩子注入DLL

定义了一个能够钩住另个进程中的窗口消息的函数,使用SetWindowsHookEx()。

 

鼠标hook的一个示例:

LRESULT WINAPI MouseProc(int nCode,WPARAM wparam,LPARAM lparam)

{

         LPMOUSEHOOKSTRUCT pMouseHook=(MOUSEHOOKSTRUCT FAR *)lparam;

         if (nCode>=0)

         {

                  

         /*============================================================================

         如下代码是得到父窗口标题文本的做法

         HWND glhTargetWnd=pMouseHook->hwnd;     //取目标窗口句柄

         HWND ParentWnd=glhTargetWnd;

         while (ParentWnd !=NULL)

         {

         glhTargetWnd=ParentWnd;

         ParentWnd=GetParent(glhTargetWnd);   //取应用程序主窗口句柄

         }

                   ===========================================================================*/

//                 HWND glhTargetWnd=XYZWindowFromPoint(NULL,pMouseHook->pt);    //用上面的一段注释掉的代码替换此行是得到父窗口标题文本的做法

//                 if(glhTargetWnd!=glhPrevTarWnd)

//                 {

                            //                          char szCaption[256];

                            //                          GetWindowText(glhTargetWnd,szCaption,100);

                            //取目标窗口标题

                            //                          if(IsWindow(glhDisplayWnd))

                            glhDisplayWnd=FindWindow(NULL,"DEMO");

                            SendMessage(glhDisplayWnd,WM_SETTEXT,0,0);

//                          glhPrevTarWnd=glhTargetWnd;

                            //保存目标窗口

//                 }

         }

         return CallNextHookEx(glhHook,nCode,wparam,lparam);

         //继续传递消息

}

1.       使用远程线程注入DLL

Remote thread

HANDLE WINAPI CreateRemoteThread(

  __in          HANDLE hProcess,

  __in          LPSECURITY_ATTRIBUTES lpThreadAttributes,

  __in          SIZE_T dwStackSize,

  __in          LPTHREAD_START_ROUTINE lpStartAddress,

  __in          LPVOID lpParameter,

  __in          DWORD dwCreationFlags,

  __out         LPDWORD lpThreadId

);

其中hProcess为远程进程的句柄,lpThreadAttributes为线程安全描述,指向SECURITY_ATTRIBUTES结构指针。

dwStackSize 表示线程栈的大小,并以字节为单位

lpStartAddress 表示一个LPTHREAD_START_ROUTINE类型的指针,指向在远程进程中执行的函数地址。

lpParameter表示传入的参数。

DwCreationFlags 创建线程的标志

LpThreadID 输出线程的ID,

 

返回值为新线程的句柄,失败的话就返回NULL。

下面使用两种方式使用CreateRemoteThread。

首先创建一个函数FuncA,我们的目标是将它放入计算器中运行。

Static DWORD WINAPI FuncA(LPVOID pData)

{

//

Return *(DWORD*)pData;

}

这里定义了另外一个函数FuncB,用于确定我们代码的大小

 

下面来定位目标进程,这里是一个计算器

HWND hStart = ::FindWindow(TEXT(“Calculator”), NULL);

通过下面代码来得到线程的句柄

DWORD PID, TID;

TID = ::GetWindowThreadProcessID( hStart, &PID);

 

HANDLE hProcess;

hProcess = OpenProcess(PROCESS_ALL_ACCESSS, false, PID);

 

目标进程中的内存的属性一般只是只读的,所以通过修改目标进程的属性为PAGE_READWRITE才可以对目标进程进行写操作。

代码实现如下:

char szBuffer[10];

cbParamSize = 10;

*(DWORD*) szBuffer = 1000;

Void *pDataRemote = (char*) VirtualAllocEx ( hProcess, 0, sizeof(szBuffer), MEM_COMMIT, PAGE_READWRITE);

 

通过WriteProcessMemory把需要的内容分配到目标进程中分配的变量

::WriteProcessMemory ( hProcess, pDataRemote,   szBuffer, sizeof(szBuffer), NULL);

 

在目标进程中分配代码地址空间,

首先计算代码的大小,以字节为单位

DWORD cbCodeSize=((LPBYTE) FuncB – (LPBYTE)FuncA);

 

分配代码地址空间

PDWORD pCodeRemote = (PDWROD) VirtualAllocEx( hProcess, 0, cbCodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

 

写内容到目标进程中分配的代码地址空间

WriteProcessMemory(hProcess, pCodeRemote, &FuncA, cbCodeSize, NULL)

 

VirtualAllocEx() 是用来在进程的虚拟地址空间内分配内存用的,如果第二个参数如果设为0时,那么所分配内存的位置就由VirtualAllocEx()函数自己决定。

最后调用CreateRemoteThread()来在目标进程中执行代码

HANDLE hThread = CreateRemoteThread(hProcess,

NULL,

0,

(LPTHREAD_STRAT_ROUTINE)pCodeRemote,// 线程函数

pDataRemote,// 传递的参数

0,

NULL);

DWORD code;

If (hThread)

{

     ::WaitForSingleObject(hThread, INFINITE);

     ::GetExitCodeThread(hThread, &code);

     TRACE(“run and return %d\n”, code);

     ::CloseHandle(hThread);

}

这里需要注意:需要使用WaitForSingleObject等待线程结束;

可以使用GetExitCodeThread获得返回值

对于句柄,需要使用CloseHandle关闭。

最后需要对现场进行清理:

首先释放空间:

::VirtualFreeEx(hProcess,pCodeRemote,cbCodeSize,MEM_RELEASE);

::VirtualFreeEx(hProcess,pDataRemote,cbParamSize,MEM_RELEASE);

 

::CloseHandle ( hProcess);

 

下面我们来怎样将一个动态库植入到远程进程中,大致的步骤和以上相似,两个关键是需要将动态库的路径作为变量传入变量空间。另外一个是在使用函数CreateRemoteThread()时,需要将GetProcAddress作为目标执行函数。

 

第一参数是要注入线程的进程的句柄,可以通过进程标识符(PID),调用OpenProcess,

第4个参数为目标进程中的LoadLibrary()地址。可以使用目标进程中的LoadLibrary()函数。由于该地址必须存在于目标进程中,因此必须将Kernel32.dll加载到目标进程中,这种方法才有效,原因是LoadLibrary是由Kernel32.dll导出的,获得LoadLibrary的地址,可以使用

GetProcAddress(GetModuleHandle(TEXT(“Kernel32”)), “LoadLibraryA”);

 

通过LoadLibrary得到目标进程要注入DLL的地址,是在Kernel32.dll位于目标进程中同一个基地址处。(这种情况是经常出现的,因为DLL加载到内存中时,重新确定DLL的基地址会很耗时,微软也希望避免避免由于重新确定DLL基址所浪费的时间。LoadLibrary的格式与返回类型与结构体THREAD_START_ROUTINE函数相同,可以用作第四个参数。

   第5个参数lpParameter是LoadLibrary函数参数的内存地址。本地进程不能只传递一个字符串,因为这只能指向本地进程的一个地址,与目标进程没有关系。通过VirtualAllocEx函数可以分配目标进程中的内存。然后调用WriteProcesMemory函数来把我们所需的dll的路径与名称传递到目标进程。

简单过程如下:

hThread = ::GreateRemoteThread(hProcess, NULL, 0,

           (LPTHREAD_START_ROUTINE) :: GetProcAddress(GetProcAddress(GetModuleHandle(TEXT(“Kernel32”)), “LoadLibraryA”);

, “LoadLibraryA”), pDataRemote, 0, NULL);

然后可以调用我们的函数进行一些操作

在执行之后需要释放掉这个动态库,类似的

hThread = ::GreateRemoteThread(hProcess, NULL, 0,

           (LPTHREAD_START_ROUTINE) :: GetProcAddress( hModule, “FreeLibrary”), 参数, 0, NULL);

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
二、API Hook的原理 这里的API既包括传统的Win32 APIs,也包括任何Module输出的函数调用。熟悉PE文件格 式的朋友都知道,PE文件将对外部Module输出函数的调用信息保存在输入表中,即.idata段。 下面首先介绍本段的结构。 输入表首先以一个IMAGE_IMPORT_DESCRIPTOR(简称IID)数组开始。每个被PE文件隐式链接 进来的DLL都有一个IID.在这个数组中的最后一个单元是NULL,可以由此计算出该数组的项数。 例如,某个PE文件从两个DLL中引入函数,就存在两个IID结构来描述这些DLL文件,并在两个 IID结构的最后由一个内容全为0的IID结构作为结束。几个结构定义如下: IMAGE_IMPORT_DESCRIPTOR struct union{ DWORD Characteristics; ;00h DWORD OriginalFirstThunk; }; TimeDateStamp DWORD ;04h ForwarderChain DWORD ;08h Name DWORD ;0Ch FirstThunk DWORD ;10h IMAGE_IMPROT_DESCRIPTOR ends typedef struct _IMAGE_THUNK_DATA{ union{ PBYTE ForwarderString; PDWORD Functions; DWORD Ordinal; PIMAGE_IMPORT_BY_NAME AddressOfData; }u1; } IMAGE_IMPORT_BY_NAME结构保存一个输入函数的相关信息: IMAGE_IMPORT_BY_NAME struct Hint WORD ? ;本函数在其所驻留DLL的输出表中的序号 Name BYTE ? ;输入函数的函数名,以NULL结尾的ASCII字符串 IMAGE_IMPORT_BY_NAME ends OriginalFirstThunk(Characteristics):这是一个IMAGE_THUNK_DATA数组的RVA(相对于PE文件 起始处)。其中每个指针都指向IMAGE_IMPORT_BY_NAME结构。 TimeDateStamp:一个32位的时间标志,可以忽略。 ForwarderChain:正向链接索引,一般为0。当程序引用一个DLL中的API,而这个API又引用别的 DLL的API时使用。 NameLL名字的指针。是个以00结尾的ASCII字符的RVA地址,如"KERNEL32.DLL"。 FirstThunk:通常也是一个IMAGE_THUNK_DATA数组的RVA。如果不是一个指针,它就是该功能在 DLL中的序号。 OriginalFirstThunk与FirstThunk指向两个本质相同的数组IMAGE_THUNK_DATA,但名称不同, 分别是输入名称表(Import Name Table,INT)和输入地址表(Import Address Table,IAT)。 IMAGE_THUNK_DATA结构是个双字,在不同时刻有不同的含义,当双字最高位为1时,表示函数以 序号输入,低位就是函数序号。当双字最高位为0时,表示函数以字符串类型的函数名 方式输入,这时它是指向IMAGE_IMPORT_BY_NAME结构的RVA。 三个结构关系如下图: IMAGE_IMPORT_DESCRIPTOR INT IMAGE_IMPORT_BY_NAME IAT -------------------- /-->---------------- ---------- ---------------- |01| 函数1 ||02| 函数2 || n| ... |"USER32.dll" | |--------------------| | | FirstThunk |---------------------------------------------------------------/ -------------------- 在PE文件中对DLL输出函数的调用,主要以这种形式出现: call dword ptr[xxxxxxxx] 或 jmp [xxxxxxxx] 其中地址xxxxxxxx就是IAT中一个IMAGE_THUNK_DATA结构的地址,[xxxxxxxx]取值为IMAGE_THUNK_DATA 的值,即IMAGE_IMPORT_BY_NAME的地址。在操作系统加载PE文件的过程中,通过IID中的Name加载相应 的DLL,然后根据INT或IAT所指向的IMAGE_IMPORT_BY_NAME中的输入函数信息,在DLL中确定函数地址, 然后将函数地址写到IAT中,此时IAT将不再指向IMAGE_IMPORT_BY_NAME数组。这样[xxxxxxxx]取到的 就是真正的API地址。 从以上分析可以看出,要拦截API的调用,可以通过改写IAT来实现,将自己函数的地址写到IAT中, 达到拦截目的。 另外一种方法的原理更简单,也更直接。我们不是要拦截吗,先在内存中定位要拦截的API的地址, 然后改写代码的前几个字节为 jmp xxxxxxxx,其中xxxxxxxx为我们的API的地址。这样对欲拦截API的 调用实际上就跳转到了咱们的API调用去了,完成了拦截。不拦截时,再改写回来就是了。 这都是自己从网上辛辛苦苦找来的,真的很好啊
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值