【Windows系统编程】06.HotFixHook与进程通信(详解HotFixHook)

上一讲讲到的InlineHook,每次Hook的时候,都要读写两次内存(先Hook,再还原)这种Hook方式,性能比较低,今天我们讲的这种Hook方式,可以说是InlineHook的升级版本

HotFix(热补丁)

我们先来讲讲原理:

我们继续来看看目标程序反汇编:

770A8E19 | CC                       | int3                                    |
770A8E1A | CC                       | int3                                    |
770A8E1B | CC                       | int3                                    |
770A8E1C | CC                       | int3                                    |
770A8E1D | CC                       | int3                                    |
770A8E1E | CC                       | int3                                    |
770A8E1F | CC                       | int3                                    |
770A8E20 | 8BFF                     | mov edi,edi                             |
770A8E22 | 55                       | push ebp                                |
770A8E23 | 8BEC                     | mov ebp,esp                             |
770A8E25 | 833D 8C8C0D77 00         | cmp dword ptr ds:[770D8C8C],0           |
770A8E2C | 74 22                    | je user32.770A8E50                      |
770A8E2E | 64:A1 18000000           | mov eax,dword ptr fs:[18]               |
770A8E34 | BA 18930D77              | mov edx,user32.770D9318                 | edx:"榍\f"
770A8E39 | 8B48 24                  | mov ecx,dword ptr ds:[eax+24]           | ecx:"榍\f"
770A8E3C | 33C0                     | xor eax,eax                             |
770A8E3E | F0:0FB10A                | lock cmpxchg dword ptr ds:[edx],ecx     | edx:"榍\f", ecx:"榍\f"
770A8E42 | 85C0                     | test eax,eax                            |
770A8E44 | 75 0A                    | jne user32.770A8E50                     |
770A8E46 | C705 288D0D77 01000000   | mov dword ptr ds:[770D8D28],1           |
770A8E50 | 6A FF                    | push FFFFFFFF                           |
770A8E52 | 6A 00                    | push 0                                  |
770A8E54 | FF75 14                  | push dword ptr ss:[ebp+14]              |
770A8E57 | FF75 10                  | push dword ptr ss:[ebp+10]              |
770A8E5A | FF75 0C                  | push dword ptr ss:[ebp+C]               |
770A8E5D | FF75 08                  | push dword ptr ss:[ebp+8]               |
770A8E60 | E8 0BFEFFFF              | call <user32.MessageBoxTimeoutW>        |
770A8E65 | 5D                       | pop ebp                                 |
770A8E66 | C2 1000                  | ret 10                                  |

我们发现呢,在MessageBox API之前,还有一堆int3,而这些int3是没用的,我们是否能用这点空余的内存,来构造一个jmp指令,来跳转到我们HOOK的函数?

很明显,jmp(E9)指令占一个字节,32位环境下,地址也占4个字节,而这里int3的数目,足够让我们来构造一个指令了

  • 思路:

    MessageBox函数第一句指令,mov edi,edi 实际上在32位环境下并没有什么意义,我们利用这个指令的两个字节,跳转到他上面的5个字节,构造跳转到我们自己的函数地址

    这样的话,我们就要修改7个字节的数据

  • 实操演示:

    我们还是利用动态库,注入的方式完成Hook:

    dll:

    // dllmain.cpp : 定义 DLL 应用程序的入口点。
    #include "pch.h"
    
    BOOL hotFixHook(const WCHAR* pszModuleName, const char* pszFuncName, PROC pHookFunc) {
    	//构造段跳转指令:
    	BYTE ShortJmp[2] = { 0xEB,0xF9 };
    	//替换5个int3的jmp指令:
    	BYTE JmpCode[5] = { 0xE9,0, };
    
    	HMODULE hModule = GetModuleHandle(pszModuleName);
    	if (hModule == INVALID_HANDLE_VALUE) {
    		MessageBox(NULL, L"打开进程失败", L"错误", NULL);
    		return FALSE;
    	}
    
    	//获取函数地址
    	FARPROC pFuncAddr = GetProcAddress(hModule, pszFuncName);
    
    	//修改内存属性
    	DWORD dwOldProcAddrProtect = 0;
    	VirtualProtectEx(GetCurrentProcess(), (LPVOID)((DWORD)pFuncAddr - 5), 7, PAGE_EXECUTE_READWRITE, &dwOldProcAddrProtect);
    	DWORD JmpAddr = (DWORD)pHookFunc - (DWORD)pFuncAddr;
    	*(DWORD*)(JmpCode + 1) = JmpAddr;
    	//修改内存
    	memcpy((LPVOID)((DWORD)pFuncAddr - 5), JmpCode, 5);
    	memcpy(pFuncAddr, ShortJmp, 2);
    	//改回内存属性
    	VirtualProtectEx(GetCurrentProcess(), pFuncAddr, 7, dwOldProcAddrProtect, &dwOldProcAddrProtect);
    	return TRUE;
    }
    
    BOOL UnHook(const WCHAR* pszModuleName, const char* pszFuncName) {
    	//构造段跳转指令:
    	BYTE ShortJmp[2] = { 0x8B,0xFF };
    	//替换5个int3的jmp指令:
    	BYTE JmpCode[5] = { 0x90,0x90,0x90,0x90,0x90 };
    
    	HMODULE hModule = GetModuleHandle(pszModuleName);
    	if (hModule == INVALID_HANDLE_VALUE) {
    		MessageBox(NULL, L"打开进程失败", L"错误", NULL);
    		return FALSE;
    	}
    
    	//获取函数地址
    	FARPROC pFuncAddr = GetProcAddress(hModule, pszFuncName);
    
    	//修改内存属性
    	DWORD dwOldProcAddrProtect = 0;
    	VirtualProtectEx(GetCurrentProcess(), (LPVOID)((DWORD)pFuncAddr - 5), 7, PAGE_READWRITE, &dwOldProcAddrProtect);
    	//修改内存
    	memcpy((LPVOID)((DWORD)pFuncAddr - 7), JmpCode, 5);
    	memcpy(pFuncAddr, ShortJmp, 2);
    	//改回内存属性
    	VirtualProtectEx(GetCurrentProcess(), pFuncAddr, 7, dwOldProcAddrProtect, &dwOldProcAddrProtect);
    	return TRUE;
    }
    
    
    typedef int
    (WINAPI
    	*fnMyMessageBox)(
    		_In_opt_ HWND hWnd,
    		_In_opt_ LPCWSTR lpText,
    		_In_opt_ LPCWSTR lpCaption,
    		_In_ UINT uType);
    
    int WINAPI
    MyMessageBox(
    	_In_opt_ HWND hWnd,
    	_In_opt_ LPCWSTR lpText,
    	_In_opt_ LPCWSTR lpCaption,
    	_In_ UINT uType) {
    	//如果我们还是通过正常流程调用MessageBOx,就会进入死循环,这里通过函数指针的方式调用,跳过前两字节(我们构造的短跳转指令)
    	fnMyMessageBox Func = (fnMyMessageBox)((DWORD)MessageBox + 2);
    	int nRet = Func(NULL, L"Hook", L"Hook", NULL);
    	return nRet;
    }
    
    BOOL APIENTRY DllMain( HMODULE hModule,
                           DWORD  ul_reason_for_call,
                           LPVOID lpReserved
                         )
    {
        switch (ul_reason_for_call)
        {
        case DLL_PROCESS_ATTACH:
    		hotFixHook(L"User32.dll", "MessageBoxW", (PROC)MyMessageBox);
    		break;
        case DLL_THREAD_ATTACH:
    		break;
        case DLL_THREAD_DETACH:
    		break;
        case DLL_PROCESS_DETACH:
            break;
        }
        return TRUE;
    }
    
    
    

Hook测试:

运行目标程序:

运行目标程序

注入dll:

注入dll

Hook成功:

Hook成功

进程通信

1.文件映射

进程一:

// Process1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

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

#define BUF_SIZE 256
TCHAR szName[] = L"WdigObject";

int main()
{
	//为指定文件创建或打开命名或未命名的文件映射对象
	HANDLE hFileMap = CreateFileMapping(
		INVALID_HANDLE_VALUE,      //要从中创建文件映射对象的句柄
		NULL,                      //安全属性
		PAGE_READWRITE,    //指定文件映射对象的页面保护
		0,                      //文件映射对象的最大大小的高阶 DWORD 
		BUF_SIZE,                  //文件映射对象最大大小的低序 DWORD
		szName                     //文件映射对象的名称
	);

	//将文件映射的驶入映射到调用进程的地址空间
	LPVOID lpMapAddr = MapViewOfFile(
		hFileMap,        //文件映射对象的句柄
		FILE_MAP_ALL_ACCESS,   //对文件映射对象的访问类型,用于确定页面的页面保护
		0,               //视图开始位置的文件偏移量的高顺序 DWORD 
		0,        //要开始视图的文件偏移量低序 DWORD
		BUF_SIZE             //要映射到视图的文件映射的字节数
	);

	CopyMemory(lpMapAddr, L"Hello FileMap", (wcslen(L"Hello FileMap")+1)*2);
	getchar();
	//从调用进程的地址空间中取消映射文件的映射视图。
	UnmapViewOfFile(lpMapAddr);
	CloseHandle(hFileMap);
}


进程二:

// Process2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

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

#define BUF_SIZE 256
TCHAR szName[] = L"WdigObject";

int main()
{
    //打开文件映射
	HANDLE hFileMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, szName);
	LPVOID lpBuffer = MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE);

	MessageBox(NULL, (LPCWSTR)lpBuffer, L"success", NULL);

	UnmapViewOfFile(lpBuffer);
	CloseHandle(hFileMap);
}

通信测试:

先运行进程一,在运行进程二:

通信成功:

通信成功

我们来看看文件映射完成进程通信的原理:

就是生成一个文件映射,两个进程都能访问这些内存,就完成了进程通信

2.命名管道

进程一:

// Process1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

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

int main()
{
	//创建命名管道的实例,并返回后续管道操作的句柄
	HANDLE hPipe = CreateNamedPipe(
		L"\\\\.\\pipe\\Communication",          //管道的唯一名称
		PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,//打开模式
		PIPE_TYPE_BYTE,                        //管道模式
		1,                                     //可为此管道创建的最大实例数
		1024,                                  //要为输出缓冲区保留的字节数
		1024,                                  //要为输入缓冲区保留的字节数
		0,                                     //
		NULL                                   //安全属性
	);

	//创建一个事件
	HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

	//使命名管道服务进程能够等待客户端进程连接到命名管道的实例
	OVERLAPPED ovlap;
	ZeroMemory(&ovlap, sizeof(ovlap));
	ovlap.hEvent = hEvent;

	if (!ConnectNamedPipe(hPipe, &ovlap)) {
		//这里需要注意,如果成功,也会报错
		if (GetLastError() != ERROR_IO_PENDING) {
			std::cout << "ConnectNamedPipe Failed Code:" << GetLastError() << std::endl;
			CloseHandle(hPipe);
			CloseHandle(hEvent);
			return -1;
		}
	}

	if (WaitForSingleObject(hEvent, INFINITE) == WAIT_FAILED) {
		std::cout << "WaitForSingleObject Error" << GetLastError() << std::endl;
		CloseHandle(hPipe);
		CloseHandle(hEvent);
		return -1;
	}

	CloseHandle(hEvent);
	char szBuffer[0x100] = { 0 };
	DWORD dwReadSize = 0;
	ReadFile(hPipe, szBuffer, 0x100, &dwReadSize, NULL);
	std::cout << szBuffer << std::endl;

	char WriteBuffer[] = "Hello";
	DWORD dwWriteSize = 0;
	WriteFile(hPipe, WriteBuffer, strlen(WriteBuffer) + 1, &dwWriteSize, NULL);

	system("pause");
	return 0;
}

进程二:

// Process2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

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

int main()
{
	//等待命名管道
	if (!WaitNamedPipe(
		L"\\\\.\\pipe\\Communication",    //命名管道的名称
		NMPWAIT_WAIT_FOREVER             //函数等待命名管道实例可用的毫秒数
	)) {
		std::cout << "WaitNamedPipe Failed Code:" << GetLastError() << std::endl;
		return -1;
	}

	//创建或打开文件或 I/O 设备
	HANDLE hFile = CreateFile(
		L"\\\\.\\pipe\\Communication",     //要创建或打开的文件或设备的名称
		GENERIC_READ | GENERIC_WRITE,     //请求对文件或设备的访问权限
		NULL,                             //请求的文件或设备的共享模式
		NULL,                             //安全属性
		OPEN_EXISTING,                    //要对存在或不存在的文件或设备执行的操作
		FILE_ATTRIBUTE_NORMAL,            //文件或设备属性和标志
		NULL
	);

	char WriteBuffer[] = "Hello";
	DWORD dwWrittenByte = 0;
	WriteFile(hFile, WriteBuffer, strlen(WriteBuffer) + 1, &dwWrittenByte, NULL);

	char lpBuffer[0x100] = { 0 };
	DWORD dwReadBytes = 0;
	ReadFile(hFile, lpBuffer, 0x100, &dwReadBytes, NULL);
	std::cout << lpBuffer << std::endl;
	system("pause");
	return 0;

}


通信测试:

先运行进程一,在运行进程二

通信成功:

通信成功

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Shad0w-2023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值