Windows上的Hook学习

一、概述

Hook的作用:对目标函数的执行内容进行拦截、复制、修改和记录。

Hook的本质:插入特定的代码,然后干扰程序原本的执行流程。

Hook的实现方式:

  1. Address Hook:修改数据(如函数地址表)实现hook;
  2. inline Hook:直接修改函数内部的指令实现hook。

PS:Address Hook是原子操作不用担心多线程执行目标函数因修改数据引发的错误,但是inline Hook存在该问题,所以修改前最好挂起所有其他的线程。

二、Windows中常指的Hook--系统钩子

       全局钩子,大多需要借助一个dll(钩子的安装和卸载以及钩子处理函数),因为不同的进程间不能随意相互访问内存空间,所以通常借助dll。如果只是hook本进程的消息,可以不用dll。通过函数SetWindowsHookEx来向操作系统申请安装钩子,函数UnhookWindowsHookEx来卸载。关于二次hook的函数地址问题,所有Hook依次调用它Hook前的Address,形成了一个调用链。

 代码如下:

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
#include <Windows.h>
#include <fstream>
#include <TlHelp32.h>
using namespace std;




extern "C" _declspec(dllexport) void InstallHook(); 
extern "C" _declspec(dllexport) void UnInstallHook(); 

HHOOK hHook = NULL;
HMODULE g_hModule = NULL;

bool KillProcessEx(char* processName)
{
    HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
 
    PROCESSENTRY32 pe;
    pe.dwSize = sizeof(PROCESSENTRY32);
 
    if (!Process32First(hSnapShot, &pe))
    {
        return false;
    }
 
    while (Process32Next(hSnapShot, &pe))
    {
		char strTemp[25]={0};
		int Length = WideCharToMultiByte(CP_ACP, 0, pe.szExeFile, -1, NULL, 0, NULL, NULL);  
		WideCharToMultiByte(CP_ACP, 0, pe.szExeFile, -1, strTemp, Length, NULL, NULL);
        bool bIn = false;
        if (strcmp(processName, strTemp)==0)
        {
			bIn = true;
        }
        if (bIn)
        {
            DWORD dwProcessID = pe.th32ProcessID;
            HANDLE hProcess = ::OpenProcess(PROCESS_TERMINATE, FALSE, dwProcessID);
            ::TerminateProcess(hProcess, 0);
            CloseHandle(hProcess);
        }
    }
 
    return true;
}

LRESULT CALLBACK KeyboardProc(
  _In_  int code,
  _In_  WPARAM wParam,
  _In_  LPARAM lParam
)
{
	if(code == HC_ACTION && lParam & 0x80000000)
	{
		std::ofstream ofile;               //定义输出文件
		ofile.open("D:\\keybroad.txt",std::ios::app);
		if(wParam>=0x30 && wParam<=0x5A)
			ofile << char(wParam)<<' ';//可显示的键值
		else
			ofile <<std::hex<<"0x"<<wParam<<' ';
		ofile.close();
	}
	return CallNextHookEx(hHook, code, wParam, lParam);
}

void InstallHook()
{
	hHook = SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,g_hModule,0);
	if(hHook)
		MessageBox(NULL, TEXT("成功安装hook!"), TEXT("提示"), MB_OK);
}
	
VOID UnInstallHook()
{
	MessageBox(NULL, TEXT("即将卸载hook!"), TEXT("提示"), MB_OK);
	if(hHook!=NULL)
	{
		if(UnhookWindowsHookEx(hHook))
			MessageBox(NULL, TEXT("成功卸载hook!"), TEXT("提示"), MB_OK);
	}

	CloseHandle(hHook);
	//KillProcessEx("Test.exe");
	TerminateProcess(GetCurrentProcess(),-1);
}
								
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		g_hModule = hModule;
        break;
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}	

 main.cpp的代码如下:

//Test.cpp
#include <Windows.h>

int main()  
{  
	HMODULE hModule = LoadLibraryA("TestDll.dll");
	FARPROC pfInstallHook = GetProcAddress(hModule,"InstallHook");
	FARPROC pfUninstallHook = GetProcAddress(hModule,"UnInstallHook");

	pfInstallHook();
	pfUninstallHook();

    return 0;  
}

 PS:内核地址空间是唯一的,所以内核中的所有hook操作对所有的进程都有效。

三、Windows中的函数Hook

1.Address Hook

修改各种表:

  1. IAT(输入表):IAT具体指某个dll模块中的IAT,存放的是函数的地址,必须以静态调用的方式才会被hook,动态调用不受影响。
  2. EAT(输出表):它存放的是函数地址的偏移,使用时需要加上模块的基址。在hook后,所有试图通过EAT获取目标函数地址的操作都会受到影响。同样仅对静态调用有效。
  3. IDT(系统的中断描述符表):是操作系统用于处理中断机制,当中断发生操作系统应该交给谁处理。该表的基址存放在idtr寄存器中,表内项目数存放在idtl寄存器中。
  4. SSDT(系统服务描述符表):程序调用API转入内核处理前首先要用到。
  5. C++虚函数表:父类中定义了虚函数就会有一个虚函数指针指向虚函数表,若子类中也定义了虚函数,则它也有一个自己的虚函数表。(简而言之,每个使用虚函数的类都被赋予自己的虚表。)该表仅是编译器在编译时设置的静态数组。一个虚拟表为该类的对象可以调用的每个虚拟函数包含一个条目。该表中的每个条目都只是一个函数指针,指向该类可访问的最衍生函数。创建类实例时,编译器会自动设置* __ vptr指针,以便它指向该类的虚拟表。

IAT hook原理:通过获取IAT所在内存页的可写权限,修改想要hook的函数在IAT中的函数地址为自己定义的函数地址,从而实现调用自己定义函数的目的。

#include <Windows.h>
#include <stdio.h>
#include <Dbghelp.h>
#pragma comment(lib,"imagehlp.lib") //以MessageBoxA的原型定义一个函数指针类型 typedef int (WINAPI *PFN_MessageBoxA)( HWND hWnd, // handle of owner window LPCTSTR lpText, // address of text in message box LPCTSTR lpCaption, // address of title of message box UINT uType // style of message box ); PFN_MessageBoxA OldMessageBox=NULL; //指向IAT中pThunk的地址 PULONG_PTR g_PointerToIATThunk = NULL; int WINAPI MyMessageBoxA( HWND hWnd, // handle of owner window LPCTSTR lpText, // address of text in message box LPCTSTR lpCaption, // address of title of message box UINT uType // style of message box ) { //在这里,你可以对原始参数进行任意操作 int ret; char newText[1024]={0}; char newCaption[256]="pediy.com"; //为防止原函数提供的缓冲区不够,这里复制到我们自己的一个缓冲区中再进行操作 lstrcpy(newText,lpText); lstrcat(newText,"\n\tMessageBox Hacked by pediy.com!");//篡改消息框内容 uType|=MB_ICONERROR;//增加一个错误图标 ret = OldMessageBox(hWnd,newText,newCaption,uType);//调用原MessageBox,并保存返回值 //调用原函数之后,可以继续对OUT(输出类)参数进行干涉,比如网络函数的recv,可以干涉返回的内容 return ret; } BOOL InstallHook( HMODULE hModToHook, //待Hook的模块基址 char* szModuleName, //目标DLL名字 char* szFuncName, //目标函数名字 PVOID DetourFunc, //Detour函数 PULONG_PTR* pThunkPointer, ULONG_PTR* pOriginalFuncAddr ) { PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor; PIMAGE_THUNK_DATA pThunkData; ULONG ulSize; HMODULE hModule = 0; ULONG_PTR TargetFunAddr; PULONG_PTR lpAddr; char* szModName; BOOL result = FALSE; BOOL bRetn = FALSE; hModule = LoadLibrary(szModuleName); TargetFunAddr = (ULONG_PTR)GetProcAddress(hModule, szFuncName); pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(hModToHook, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize);

while(pImportDescriptor->FirstThunk) { szModName = (char*)((PBYTE)hModToHook + pImportDescriptor->Name); if (stricmp(szModName, szModuleName) != 0) { pImportDescriptor++; continue; } pThunkData = (PIMAGE_THUNK_DATA)((BYTE*)hModToHook + pImportDescriptor->FirstThunk); while (pThunkData->u1.Function) { lpAddr = (ULONG_PTR*)pThunkData; if ((*lpAddr) == TargetFunAddr) { DWORD dwOldProtect; MEMORY_BASIC_INFORMATION mbi; VirtualQuery(lpAddr,&mbi,sizeof(mbi)); bRetn = VirtualProtect(mbi.BaseAddress,mbi.RegionSize,PAGE_EXECUTE_READWRITE,&dwOldProtect); if (bRetn) { if (pThunkPointer != NULL) { *pThunkPointer = lpAddr; } if (pOriginalFuncAddr != NULL) { *pOriginalFuncAddr = *lpAddr; } *lpAddr = (ULONG_PTR)DetourFunc; result = TRUE; VirtualProtect(mbi.BaseAddress, mbi.RegionSize, dwOldProtect, 0); } break; } pThunkData++; } pImportDescriptor++; } FreeLibrary(hModule); return result; } void IAT_InstallHook() { BOOL bResult = FALSE; HMODULE currectExe = GetModuleHandle(NULL); PULONG_PTR pt; ULONG_PTR OrginalAddr; OldMessageBox = MessageBoxA; bResult = InstallHook(currectExe,"user32.dll","MessageBoxA",MyMessageBoxA,&pt,&OrginalAddr); } VOID IAT_UnInstallHook() { DWORD dwOLD; MEMORY_BASIC_INFORMATION mbi; if (g_PointerToIATThunk) { //查询并修改内存页的属性 VirtualQuery((LPCVOID)g_PointerToIATThunk,&mbi,sizeof(mbi)); VirtualProtect(mbi.BaseAddress,mbi.RegionSize,PAGE_EXECUTE_READWRITE,&dwOLD); //将原始的MessageBoxA地址填入IAT中 *g_PointerToIATThunk = (ULONG)OldMessageBox; //恢复内存页的属性 VirtualProtect(mbi.BaseAddress,mbi.RegionSize,dwOLD,0); } } int main() { MessageBoxA(NULL,"Before install","IAT",MB_OK); IAT_InstallHook(); MessageBoxA(NULL,"Correct!","IAT",MB_OK); IAT_UnInstallHook(); MessageBoxA(NULL,"After uninstall","IAT",MB_OK); return 0; }

2.Inline Hook

 

 原理:通过获取目标函数的地址,修改开头的指令为jmp指令,跳向我们事先定义好的目标函数Detour的地址,在完成我们想要的操作后调用函数Trampoline(该函数的作用是跳回原函数并执行原本修改掉的指令,完成原函数功能。)。

#include <Windows.h>
#include <iostream>

using namespace std;

ULONG64 g_JmpAddr;

int WINAPI OrignalMessageBoxA(
  _In_opt_  HWND hWnd,
  _In_opt_  LPCTSTR lpText,
  _In_opt_  LPCTSTR lpCaption,
  _In_      UINT uType
)
{
	//这里我nop掉了要修改掉的指令,因为参数都保存在栈,这几条指令是修改ebp的,若执行会导致参数丢失。
	__asm {
		nop
		nop
		nop
		jmp g_JmpAddr
	}
	return 0;
}

int WINAPI My_MessageBoxA(
  _In_opt_  HWND hWnd,
  _In_opt_  LPCTSTR lpText,
  _In_opt_  LPCTSTR lpCaption,
  _In_      UINT uType
)
{
	cout<<"hack success!"<<endl;
	lpText = "Hacker!";
	OrignalMessageBoxA(hWnd,lpText,lpCaption,uType);
	return 0;
}

int main()  
{
	PBYTE AddrMesssageBox =  (PBYTE)GetProcAddress(GetModuleHandle("user32.dll"),"MessageBoxA");
	g_JmpAddr = (ULONG)AddrMesssageBox+5;

	BYTE newEntry[5]={0};
	newEntry[0] = 0xe9;
	//这里的5是指jmp指令占的5个字节,求的是偏移,在当前位置向上或下偏移多少位
	*(ULONG*)(newEntry+1) = (ULONG)My_MessageBoxA - (ULONG)AddrMesssageBox -5;
	DWORD OldProtect;
	MEMORY_BASIC_INFORMATION MBI = {0};
	VirtualQuery((LPCVOID)AddrMesssageBox,&MBI,sizeof(MEMORY_BASIC_INFORMATION));
	VirtualProtect(MBI.BaseAddress,5,PAGE_EXECUTE_READWRITE,&OldProtect);
	memcpy(AddrMesssageBox,newEntry,5);
	VirtualProtect(MBI.BaseAddress,5,OldProtect,&OldProtect);

	MessageBoxA(0,"OK","Tip",MB_OK);

    return 0;  
}

 安装hook的过程:构造好jmp指令后,调用memcpy函数复制到目标函数中,这里需要使用VirrtualProtect函数修改该地址区域的权限,必须具备可写权限。

PS:jmp指令和call指令占5个字节。

posted @ 2020-08-27 23:20  An2i  阅读(943)  评论(0编辑  收藏  举报