Windows_HOOK总结_待完善

DLL创建:

DLL使用:

消息钩取注入DLL:

安装钩子:

HHOOK WINAPI SetWindowsHookEx(
	 	\\1,钩子类型
		 __in int idHook, 	
		 \\2,函数地址,即挂钩类型事件发生时,系统应该调用的函数;
		__in HOOKPROC lpfn, 	
		 \\3,标识一个dll,这个dll中包含第二个参数表示的函数;实例句柄
		__in HINSTANCE hMod, 	
		 \\4,线程ID,0表示系统钩子,0表示给所有GUI线程安装
		__in DWORD dwThreadId);  	
//如果函数成功,则返回挂钩线程的句柄.如果函数失败,返回值为NULL.
//要获取扩展错误信息,请调用GetLastError

钩子类型:

第一个参数为钩子的类型,钩子的类型一共还有14种,表示在什么时机调用钩子。

常用钩子类型有以下几种:

(1)键盘钩子和低级键盘钩子可以监视各种键盘消息。
(2)鼠标钩子和低级鼠标钩子可以监视各种鼠标消息。
(3)外壳钩子可以监视各种Shell事件消息。比如启动和关闭应用程序。
(4)日志钩子可以记录从系统消息队列中取出的各种事件消息。
(5)窗口过程钩子监视所有从系统消息队列发往目标窗口的消息。

例子:

#include "stdafx.h"
#include 
 
int _tmain(int argc, _TCHAR* argv[])
{
	/*
	* Load library in which we'll be hooking our functions.
	*/
	HMODULE dll = LoadLibrary(L"test_dll.dll");
	if(dll == NULL) {
		DWORD err = GetLastError();
		printf("The DLL could not be found.\n");
		getchar();
		return -1;
	}
		
	//__asm int 3;
	/*
	* Get the address of the function inside the DLL.
	*/
	HOOKPROC addr = (HOOKPROC)GetProcAddress(dll, "meconnect");
	if(addr == NULL) {
		printf("The function was not found.\n");
		getchar();
		return -1;
	}
 
	/*
	* Hook the function.
	*/
	HHOOK handle = SetWindowsHookEx(WH_KEYBOARD, addr, dll, 0);
	if(handle == NULL) {
		printf("The KEYBOARD could not be hooked.\n");
	}
 
	/*
	* Unhook the function.
	*/
	printf("Program successfully hooked,
			\nPress enter to unhook the function and stop the program.\n");
	getchar();
	UnhookWindowsHookEx(handle);
	return 0;
}

卸载钩子:

BOOL WINAPI UnhookWindowsHookEx(
  _In_ HHOOK hhk
);

HOOK过程(原理):

几点说明:

1、如果对于同一事件(如鼠标消息)既安装了线程钩子又安装了系统钩子,那么系统会自动先调用线程钩子,然后调用系统钩子。
2、对同一事件消息可安装多个钩子处理过程,这些钩子处理过程形成了钩子链。当前钩子处理结束后应把钩子信息传递给下一个钩子函数。
3、特别是系统钩子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装钩子,在使用完毕后要及时卸载。
4、32位DLL不能被注入到64位进程中,64位DLL不能被注入到32位进程中。
5、调用CallNextHookEx函数链接到下一个钩子过程是可选的,但强烈建议; 否则,已安装钩子的其他应用程序将不会收到钩子通知,因此可能会出现错误的行为。应该调用CallNextHookEx。
6、所有全局钩子函数都必须在库中。
7、目标进程B必须先运行,安装钩子必须在目标进程运行之后,否则钩子会失效。安装全局钩子时,会将钩子和当前系统的所有线程关联起来。后面建立的线程当然就不在其列了。
8、A用LoadLibrary加载val.dll时,两者必须是同一种版本,即同为Debug版或Release版,否则会出现找不到目标文件错误。



远程线程注入DLL:

实际中使用最为广泛的一种注入方式,它即可以精确指定需要注入的进程,又可以注入到非GUI程序中。

DLL注入指的是向运行中的其他进程强制插入特定的DLL文件,从而使之运行特定代码。

DLL注入基本过程:运行程序使其他进程调用LoadLibrary()API,调用用户指定的DLL文件,从而在LoadLibrary()完成后,调用DLL文件中的DllMain()函数。

DLL加载到进程后会自动运行DllMain()函数,用户可以把想要执行的代码放到DllMain()函数里,每当该DLL被加载时,添加的代码就会被执行。利用该过程可以修复程序bug,编写恶意DLL等

  • CreateRemoteThread,原型如下:
WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
CreateRemoteThread(
    //线程所属进程的进程句柄.
    _In_ HANDLE hProcess,
    
    //一个指向 SECURITY_ATTRIBUTES 结构的指针, 该结指定了线程的安全属性.
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
    
    //线程初始大小,以字节为单位,如果该值设为0,那么使用系统默认大小.
    _In_ SIZE_T dwStackSize,
    
    //在远程进程的地址空间中,该线程的线程函数的起始地址.
    _In_ LPTHREAD_START_ROUTINE lpStartAddress,
    
    //传给线程函数的参数.
    _In_opt_ LPVOID lpParameter,
    
    线程的创建标志.
    _In_ DWORD dwCreationFlags,
    
    //指向所创建线程ID的指针,如果创建失败,该参数为NULL.
    _Out_opt_ LPDWORD lpThreadId
    );

	//如果调用成功,返回新线程句柄
	//如果失败,返回NULL

第一个参数指定新创建的线程归哪个进程所有,其他参数和CreateThread相同。

  • VirtualAllocEx,原型如下:
WINBASEAPI
_Ret_maybenull_ _Post_writable_byte_size_(dwSize)
LPVOID
WINAPI
VirtualAllocEx(
    //申请内存所在的进程句柄。
    _In_ HANDLE hProcess,
    
    //保留页面的内存地址;一般用NULL自动分配。
    _In_opt_ LPVOID lpAddress, 
    
    //欲分配的内存大小,字节单位;
    //注意实际分配的内存大小是页内存大小的整数倍
    _In_ SIZE_T dwSize,
    
    //指定内存分配的方式,预定还是要提交
    _In_ DWORD flAllocationType,
    
    //指定应用程序读写的权限,内存的保护属性
    _In_ DWORD flProtect
    );

函数执行成功就返回分配内存的首地址,不成功就是NULL。
远程注入DLL路径:

hTargeProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID); // dwProcessID为被注入目标进程的进程ID
if (!hTargeProcess) {
    return;
}

SIZE_T dllPathSize = strlen(pszDllPath); // pszDllPath存储了DLL的路径
pVM4DllPath = VirtualAllocEx(hTargeProcess, NULL, dllPathSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!pVM4DllPath) {
    return;
}

if (!WriteProcessMemory(hTargeProcess, pVM4DllPath, pszDllPath, dllPathSize, NULL)) {
    return;
}
  • WriteProcessMemory,原型如下:
  • WaitForSingleObject,原型如下:

远程线程注入步骤:

1、用VirtualAllocEx函数在远程进程申请空间。
2、用WriteProcessMemory函数把DLL的路径复制到申请的内存。
3、用GetProcessAddress函数得到LoadLibraryW(LoadLibraryA)函数(Kernel32.dll)中实际地址。
4、用CreateRenoteThread函数在远程线程创建一个线程,让新的线程调用LoadLibrary函数并在参数中传入第一步分配给的内存地址。这时DLL就被注入到了远程进程地址空间,DLL函数DLLMain会就收触发事件,然后执行我们需要执行的代码,DLLMain返回时远程线程会从LoadLibraryW(LoadLibraryA)调用返回到BaseThreadStart函数。BaseThreadStart然后调用ExitThread,使远程线程终止。
5、需要清理申请的空间,远程线程还在目标进程地址空间。
6、用VirtualFreeEx释放申请的内存。
7、用GetProcessAddress得到FreeLibrary函数在Kernel32.dll中的实际地址。

取消注入:

取消注入就是将DLL从目标进程卸载,我们知道,卸载DLL所用的API是FreeLibrary,但我们不能直接调用这个函数,因为直接调用的话是在我们的进程中卸载DLL,而不是目标进程中卸载,很显然这样达不到卸载的目的。我们需要和加载DLL时一样,将FreeLibrary的地址作为第4个参数传给CreateRemoteThread函数,但同样需要通过GetProcAddress来得到FreeLibrary的确切地址:

PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "FreeLibrary");
if(pfnThreadRtn) {
    hThread = CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, me.modBaseAddr, 0, NULL);
}

几点说明:

1、在代码注入的时候,你所需要的任何变量最好自己以内存拷贝的方式传过去,比如我直接在线程函数里开了一个变量 char[512] = “00000”,当你把这个函数地址拷贝到指定进程里面去的时候,个"00000"所占的内容并没有被拷贝过去,这样你在调用的时候内存就会出错。
2、代码注入限制非常多,被注入的线程函数里面内容非常有讲究,大小有限制,存储方式有限制,不能随便调用API函数,比如直接在里面来了一个Sleep(1000),你觉得这个函数是系统的,在那都能调用,然而并不是,直接就内存错误。
3、请保证要被注入的程序与注入程序和注入DLL是同位的,不要尝试使用32位程序注入64位(或64位注入32位),建议以管理员权限运行。

你可能感兴趣的:(WIndows开发)