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);

你可能感兴趣的:(windows)