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

}


通信测试:

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

通信成功:

通信成功

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/79012.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[JavaWeb]【七】web后端开发-MYSQL

前言&#xff1a;MySQL是一种流行的关系型数据库管理系统,它的作用是存储和管理数据。在Web开发中,MySQL是必备的数据库技能之一,因为它可以帮助Web开发人员处理大量的数据,并且提供了强大的数据查询和管理功能。 一 数据库介绍 1.1 什么是数据库 1.2 数据库产品 二 MySQL概述…

NLPR、SenseTime 和 NTU 加速自动视频纵向编辑

视频人像编辑技术已经在电视、视频和电影制作中得到了应用&#xff0c;并有望在不断发展的网真场景中发挥关键作用。最先进的方法已经可以逼真地将同源音频合成为视频。现在&#xff0c;来自北京模式识别国家实验室&#xff08;NLPR&#xff09;、商汤科技研究和南洋理工大学的…

深度分析纳斯达克上市公司慧择的竞争优势和投资价值

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 一、保险行业的现状、竞争与机遇 在疫情期间&#xff0c;很多行业的经营理念与经营方式&#xff0c;甚至客户行为、客户需求都发生了变化&#xff0c;进而催生出新的机遇。保险行业亦是如此&#xff0c;受疫情影响&#xf…

借助 AI 工具,真的能成为 10x 工程师?

或许你听说过 10x 工程师吗&#xff1f; 如果你问猎头公司 10x 工程师是什么意思&#xff0c;他们可能会说 “生产力”&#xff01;10x 是指完成任务比别人快 10 倍的工程师。 2019 年&#xff0c;Twitter 上就曾经对 10 x 工程师这一议题有过一次空前热烈的讨论&#xff0c;引…

解决电脑声音正常但就是某些游戏没声音问题

电脑声音正常&#xff0c;玩普遍游戏也正常&#xff0c;就有游戏不出声音 详细介绍经过&#xff0c;不喜欢的请直接跳 第三部分。 一、先说下起因现象。 1 大富翁11 没声音。 前段时间无聊怀旧就买了个大富翁11玩玩&#xff0c;近二十年前的老台式机正常无问题。后来想在性能…

【网络编程(二)】NIO快速入门

NIO Java NIO 三大核心组件 Buffer&#xff08;缓冲区&#xff09;&#xff1a;每个客户端连接都会对应一个Buffer&#xff0c;读写数据通过缓冲区读写。Channel&#xff08;通道&#xff09;&#xff1a;每个channel用于连接Buffer和Selector&#xff0c;通道可以进行双向读…

Linux下Docker安装及卸载

文章目录 Linux下Docker安装及卸载1 Docker安装及卸载1.1 安装前准备1.2 安装docker软件2.4.3 启动docker2.2.4 测试2.2.5 卸载 Linux下Docker安装及卸载 1 Docker安装及卸载 官方网址&#xff1a;https://docs.docker.com/engine/install/centos/ 1.1 安装前准备 确定你是C…

zookeeper安装配置采坑流程

安装 wget https://dlcdn.apache.org/zookeeper/zookeeper-3.8.2/apache-zookeeper-3.8.2-bin.tar.gz解压&#xff1a; tar -zxvf apache-zookeeper-3.8.2-bin.tar.gz如下 bin目录下文件是可执行文件 conf目录文件是配置文件 修改zoo.cfg&#xff08;复制zoo_sample&#x…

【学习FreeRTOS】第12章——FreeRTOS时间管理

1.FreeRTOS系统时钟节拍 FreeRTOS的系统时钟节拍计数器是全局变量xTickCount&#xff0c;一般来源于系统的SysTick。在STM32F1中&#xff0c;SysTick的时钟源是72MHz/89MHz&#xff0c;如下代码&#xff0c;RELOAD 9MHz/1000-1 8999&#xff0c;所以时钟节拍是1ms。 portNV…

jvm类文件结构

一 概述 在 Java 中&#xff0c;JVM 可以理解的代码就叫做字节码&#xff08;即扩展名为 .class 的文件&#xff09;&#xff0c;它不面向任何特定的处理器&#xff0c;只面向虚拟机。Java 语言通过字节码的方式&#xff0c;在一定程度上解决了传统解释型语言执行效率低的问题…

学渣的愤怒!自考本科能不能不考英语和数学?

英语和高数哪个更难&#xff1f; 这是自考生们最头大的两个科目。 自考高数有多难&#xff1f; 高数主要有微积分、线性代数和概率论三个部分。 其中微积分是基础、也是重要的一部分&#xff0c;不仅涉及到很多抽象概念和符号运算&#xff0c;还需要具备良好的计算能力和逻…

简单的洗牌算法

目录 前言 问题 代码展现及分析 poker类 game类 Text类 前言 洗牌算法为ArrayList具体使用的典例&#xff0c;可以很好的让我们快速熟系ArrayList的用法。如果你对ArrayList还不太了解除&#xff0c;推荐先看本博主的ArrayList的详解。 ArrayList的详解_WHabcwu的博客-CSD…

C++笔记之单例模式

C笔记之单例模式 参考笔记&#xff1a;C笔记之call_once和once_flag code review 文章目录 C笔记之单例模式1.返回实例引用2.返回实例指针3.单例和智能指针share_ptr结合4.单例和std::call_once结合5.单例和std::call_once、unique_ptr结合 1.返回实例引用 代码 #include <…

数据结构之队列详解(包含例题)

一、队列的概念 队列是一种特殊的线性表&#xff0c;特殊之处在于它只允许在表的前端&#xff08;front&#xff09;进行删除操作&#xff0c;而在表的后端&#xff08;rear&#xff09;进行插入操作&#xff0c;和栈一样&#xff0c;队列是一种操作受限制的线性表。进行插入操…

亿发创新中医药信息化解决方案,自动化煎煮+调剂,打造智能中药房

传统中医药行业逐步复兴&#xff0c;同时互联网科技和人工智能等信息科技助力中医药行业逐步实现数字化转型。利用互联网、物联网、大数据等科技&#xff0c;实现现代科学与传统中医药的结合&#xff0c;提供智能配方颗粒调配系统、中药自动化调剂系统、中药煎配智能管理系统、…

【Docker报错】docker拉取镜像时报错:no such host

报错信息 [rootSoft soft]# docker pull mysql Using default tag: latest Error response from daemon: Head "https://registry-1.docker.io/v2/library/mysql/manifests/latest": dial tcp: lookup registry-1.docker.io on 192.168.80.2:53: no such host解决方法…

Leetcode-每日一题【剑指 Offer 28. 对称的二叉树】

题目 请实现一个函数&#xff0c;用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样&#xff0c;那么它是对称的。 例如&#xff0c;二叉树 [1,2,2,3,4,4,3] 是对称的。 1 / \ 2 2 / \ / \ 3 4 4 3 但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称…

Android 网络编程-网络请求

Android 网络编程-网络请求 文章目录 Android 网络编程-网络请求一、主要内容二、开发网络请求前的基本准备1、查看需要请求的网址是否有效&#xff08;1&#xff09;通过网页在线验证&#xff08;2&#xff09;使用专用window网咯请求工具&#xff08;3&#xff09;编写app代码…

多维时序 | MATLAB实现WOA-CNN-BiGRU-Attention多变量时间序列预测

多维时序 | MATLAB实现WOA-CNN-BiGRU-Attention多变量时间序列预测 目录 多维时序 | MATLAB实现WOA-CNN-BiGRU-Attention多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 多维时序 | MATLAB实现WOA-CNN-BiGRU-Attention多变量时间序列预测 1.程…

LVS负载均衡集群-NAT模式部署

集群 集群&#xff1a;将多台主机作为一个整体&#xff0c;然后对外提供相同的服务 集群使用场景&#xff1a;高并发的场景 集群的分类 1.负载均衡器集群 减少响应延迟&#xff0c;提高并发处理的能力 2&#xff0c;高可用集群 增强系统的稳定性可靠性&…