HOOK API(二) —— HOOK自己程序的 MessageBox
转载来源:https://www.cnblogs.com/hookjc/
0x00 前言
以下将给出一个简单的例子,作为HOOK API的入门。这里是HOOK 自己程序的MessageBox,即将自己程序对MessageBox API的调用重定向到自己实现的API中,在自己定义的API中实现内容的替换。
需要注意的是,本例子的HOOK仅仅对自己实现的MFC窗口程序,当开始HOOK 后,自己的程序调用MessageBox将被重定向,但其他程序滴啊用MessageBox时是正常的。
在Windows中,每个进程都有自己的进程控制块,有自己的安全运行空间,各函数在初始化时被加载到进程的地址空间中,各进程的地址空间是不相交的。本实例中,HOOK API仅仅在自己程序的地址空间中实现了地址的替换,因此不影响其他进程的工作,若想HOOK其他程序,那么就要想办法将自己实现的API注入到目标进程的地址空间中,并替换原API的地址,才能实现我们想要的功能,这将在后续的学习中进一步介绍。
本事例仅对自己的程序进行HOOK,实用性不是很大,但是对于入门,理解HOOK API的过程还是很有帮助的。
0x01 实现思想
在自己实现的窗体程序(Windows-A)中实现一个与MessageBox API定义一模一样的API(MessBox-New),这个API除了完成原API(MessBox-Old)的工作之外,还将显示内容进行修改。Windows A 加载时,对将自己所使用的API地址都加载到自己的地址空间中,这里包括我们自己写的MessBox-New,因此我们可以很方便的使用MessBox-New的调用地址来替换MessBox-Old的入口地址,进而实现对MessBox-Old的重定向、即替换。地址被替换之后,只要本程序调用MessageBox这个API,就会被重定向到我们实现的MessBox-New中。此过程中,若想要恢复正常,只需要将MessBox-Old的入口地址恢复即可。
0x02 HOOK API实现过程
本小节将介绍程序的实现过程。
1.定义自己的API
定义自己的API,因为我们这里要HOOK 自己程序的MessageBox,因此就要定义一个原型与MessageBox API一模一样的API。查MSDN,可得MessageBox有两种调用形式,分别是MessageBoxA和MessageBoxW,前者处理窄字符串,即每个字符占一个字节;后者处理宽字符串,即一个字符占两个字节。我们这里HOOK MessageBoxW,其原型为:
int WINAPI MessageBoxW(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType
);
由此,可以定义我们自己的API如下:
这里很容易漏掉函数前面的 WINAPI,若是少了将无法正常实现HOOK,一定要注意我们实现的函数的原型要与原API一致。
// // 自己定义的,用于替换相应API的,假的API // int WINAPI MyMessageBoxW(HWND hwnd,LPCWSTR lpText,LPCWSTR lpCation,UINT uType) { TRACE(lpText); /* 调用原函数之前,先停止HOOK,也就是恢复原系统API函数的入口,否则无法调用到原API函数, 而是继续调用自己的API,会造成死循环,进而造成堆栈溢出,崩溃。 */ HookOff(); /* 调用原来的MessageBoxW打印我们的信息。 */ int ret = MessageBoxW(hwnd,_T("哈哈,被HOOK咯!!"),lpCation,uType); /* 调用完原系统API后,记得恢复HOOK,也就是启动HOOK,将原API函数入口换成我们自己定义的函数入口, 否则下一次调用MessageBoxW的时候就无法转到我们自己定义的API函数中,也就无法实现HOOK。 */ HookOff(); return ret; } |
2. 定义API类型
定义原API的类型,下面的TypeMessageBoxW其它普通的数据类型的使用方法是一样的。定义一个TypeMessageBoxW类型的变量,用于存储原API的指针,还定义一个远指针类型,pfOldMsgBoxW,因为系统API是在动态链接库(DLL)中实现的,因此程序实际上是通过远地址指针来DLL中相应的API调用。而本实例中设计的MessageBox是在 User32.dll 中的。关于远地址指针这里不做多介绍,需要了解的可以查阅相关资料。
typedef int (WINAPI *TypeMessageBoxW)(HWND hwnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT uType); TypeMessageBoxW OdlMsgBoxW = NULL; // 指向函数原型指针 FARPROC pfOldMsgBoxW; // 指向函数远指针 |
3.获取原API函数入口
由MSDN可知,原API在User32.dll中实现,因此在此之前要加载User32.dll,并获取到原API的函数入口。实现代码如下:
HMODULE hmod = LoadLibrary(_T("User32.dll")); if ( NULL == hmod) { AfxMessageBox(_T("加载User32.dll失败")); return; } OdlMsgBoxW = (TypeMessageBoxW)::GetProcAddress(hmod,"MessageBoxW"); pfOldMsgBoxW = (FARPROC)OdlMsgBoxW; if ( pfOldMsgBoxW == NULL) { AfxMessageBox(_T("获取原API入口地址出错")); return; } |
4.保存原API入口的前5个字节
为了最后恢复原API的地址,必须要在HOOK之前将原API的入口地址保存起来。而这里为什么是5个字节呢?因为我们使用jmp xxxx 指令实现原API的重定向,该指令的长度为5个字节,jmp占一个字节,而xxxx表示新API的入口地址,占4个字节,我们使用jmp xxxx这条指令来替换掉原API入口的5个字节,这样一来当本程序调用MessageBoxW时,就会跳转到我们实现的API。综上所述,我们这里需要保存原API的前5个字节,实现代码如下:
// // 将原API的入口5个字节代码保存到OdeCode[]中 // _asm { lea edi,OldCode // 取数组OldCode[]地址,存放到edi中 mov esi,pfOldMsgBoxW // 获取原API入口地址,存入esi中 cld // 设置方向 movsd // 移动dword ,4 Byte movsb // 移动 1 Byte } |
5.设置新的(自己的)API入口的前5个字节
保存好原API的入口之后,我们这里需要设置jmp xxxx指令,xxxx为新API的入口地址,以便之后实现地址的替换。
而xxxx如何计算呢,可遵循前人总结的一条计算公式:
int xxxx = MyFunAddr – SystemFunAddr - CodeLength;
jmp xxxx;
MyFunAddr : 我们编写的新的API的地址;
SystemFunAddr : 原API的地址;
CodeLength : 入口指令长度,本实例是 jmp xxxx 的长度,为5个字节。
// // 新的API入口保存到NewCode[]中,即jmp xxxx,xxxx为新API地址,该指令总长度为5个字节 // NewCode[0] = 0xe9; // 0xe9相当于jmp指令 _asm { lea eax,MyMessageBoxW mov ebx,pfOldMsgBoxW sub eax,ebx sub eax,CODE_LENGTH mov dword ptr[NewCode+1],eax } |
6.修改原(真实)API入口的前5个字节为新的(自己的)API入口地址
在保存了原API和新API的入口地址之后,接下来就是要实现地址的替换,即使用新API入口替换原API入口,从而实现HOOK MessageBoxW。这里涉及到两个重要的API,VirtualProtectEx 和 WriteProcessMemory,关于API的详细说明可以查询MSDN。
/* 启动HOOK,将原API的入口地址换成我们自己定义函数的入口地址 */ VOID HookOn() { // // 确保本程序进程句柄hProcess不为NULL // ASSERT(hProcess!=NULL); DWORD dwTemp; DWORD dwOldProtect; SIZE_T writedByte; // // 修改API入口的前5个字节,jmp xxxx // VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,PAGE_READWRITE,&dwOldProtect); WriteProcessMemory(hProcess,pfOldMsgBoxW,NewCode,CODE_LENGTH,&writedByte); if (writedByte == 0) { AfxMessageBox(_T("替换原API地址失败")); } VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,dwOldProtect,&dwTemp); } |
7.恢复原API入口地址
在HOOK后,对MessageBox的调用会被重定向到我们实现的API中,若需要调用原API,则必须回复原API的入口地址,否则会出现死循环。实现代码如下:
/* 停止HOOK,将入口换成原来的API入口地址 */ VOID HookOff() { ASSERT(hProcess != NULL); DWORD dwTemp; DWORD dwOldProtect; SIZE_T wirtedByte; // // 回复原API地址 // VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,PAGE_READWRITE,&dwOldProtect); WriteProcessMemory(hProcess,pfOldMsgBoxW,OldCode,CODE_LENGTH,&wirtedByte); if (wirtedByte == 0) { AfxMessageBox(_T("回复原API地址失败")); } VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,dwOldProtect,&dwTemp); } |
0x03 窗口按钮实现
1.启动HookMessageBoxW
// // 启动 HookMessageBoxW // void CHookMessageboxWindowDlg::OnBnClickedButtonStart() { // TODO: 在此添加控件通知处理程序代码 AdjustPrivileges(); // 提升权限,因为调用 OpenProcess() 需要合适的权限 DWORD dwPid = ::GetCurrentProcessId(); hProcess = OpenProcess(PROCESS_ALL_ACCESS,0,dwPid); if (hProcess == NULL) { CString logInfo; logInfo.Format(_T("获取进程句柄失败!!,进程 id = 0x%x ,错误代码 = 0x%x"),dwPid,GetLastError()); AfxMessageBox(logInfo); return; } GetApiEntrancy(); // 获取新旧API入口,并开始HOOK m_status.SetWindowText(_T("Hook已启动")); } |
2.终止HookMessageBoxW
// // 终止 HookMessageBoxW // void CHookMessageboxWindowDlg::OnBnClickedButtonStop() { // TODO: 在此添加控件通知处理程序代码 HookOff(); m_status.SetWindowText(_T("Hook已停止")); } |
3.调用MessageBoxW
// // 调用 HookMessageBoxW // void CHookMessageboxWindowDlg::OnBnClickedButtonCall() { // TODO: 在此添加控件通知处理程序代码 ::MessageBoxW(m_hWnd,_T("这是正常的MessageBoxW"),_T("Hello"),0); } |
4.提升权限
bool AdjustPrivileges() { HANDLE hToken; TOKEN_PRIVILEGES tp; TOKEN_PRIVILEGES oldtp; DWORD dwSize=sizeof(TOKEN_PRIVILEGES); LUID luid; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) { if (GetLastError()==ERROR_CALL_NOT_IMPLEMENTED) return true; else return false; } if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) { CloseHandle(hToken); return false; } ZeroMemory(&tp, sizeof(tp)); tp.PrivilegeCount=1; tp.Privileges[0].Luid=luid; tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED; /* Adjust Token Privileges */ if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &oldtp, &dwSize)) { CloseHandle(hToken); return false; } // close handles CloseHandle(hToken); return true; } |
0x04 测试
- 启动程序,单击"调用MessageBoxW"按钮
- 单击"启动HookMessageBoxW"按钮,单击"调用MessageBoxW"按钮
- 单击"终止HookMessageBoxW"按钮,单击"调用MessageBoxW"按钮
0x04 附录——全部源码
以下给出主要实现的所有源代码,以便从整体上把握整个实现过程。
// HookMessageboxWindowDlg.cpp : 实现文件 // #include "stdafx.h" #include "HookMessageboxWindow.h" #include "HookMessageboxWindowDlg.h" #include "afxdialogex.h" // 定义API类型 #define CODE_LENGTH 5 // 入口指令长度 typedef int (WINAPI *TypeMessageBoxW)(HWND hwnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT uType); TypeMessageBoxW OdlMsgBoxW = NULL; // 指向函数原型指针 FARPROC pfOldMsgBoxW; // 指向函数远指针 BYTE OldCode[CODE_LENGTH]; // 原系统API入口 BYTE NewCode[CODE_LENGTH]; // 自己实现的API的入口,(jmp xxxx),xxxx为新API入口地址 HANDLE hProcess = NULL; // 本程序进程句柄 HINSTANCE hInst = NULL; // API所在的dll文件句柄 VOID HookOn(); // 开始HOOK VOID HookOff(); // 停止HOOK VOID GetApiEntrancy(); // 获取API入口地址 bool AdjustPrivileges();// 提高权限 // // 自己定义的,用于替换相应API的,假的API // int WINAPI MyMessageBoxW(HWND hwnd,LPCWSTR lpText,LPCWSTR lpCation,UINT uType) { TRACE(lpText); /* 调用原函数之前,先停止HOOK,也就是恢复原系统API函数的入口, 否则无法调用到原API函数,而是继续调用自己的API,会造成死 循环,进而造成堆栈溢出,崩溃。 */ HookOff(); /* 调用原来的MessageBoxW打印我们的信息。 */ int ret = MessageBoxW(hwnd,_T("哈哈,被HOOK咯!!"),lpCation,uType); /* 调用完原系统API后,记得恢复HOOK,也就是启动HOOK,将原API函数入口换成我们自己定义的函数入口, 否则下一次调用MessageBoxW的时候就无法转到我们自己定义的API函数中,也就无法实现HOOK。 */ HookOff(); return ret; } /* 启动HOOK,将原API的入口地址换成我们自己定义函数的入口地址 */ VOID HookOn() { // // 确保本程序进程句柄hProcess不为NULL // ASSERT(hProcess!=NULL); DWORD dwTemp; DWORD dwOldProtect; SIZE_T writedByte; // // 修改API入口的前5个字节,jmp xxxx // VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,PAGE_READWRITE,&dwOldProtect); WriteProcessMemory(hProcess,pfOldMsgBoxW,NewCode,CODE_LENGTH,&writedByte); if (writedByte == 0) { AfxMessageBox(_T("替换原API地址失败")); } VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,dwOldProtect,&dwTemp); } /* 定制HOOK,将入口换成原来的API入口地址 */ VOID HookOff() { ASSERT(hProcess != NULL); DWORD dwTemp; DWORD dwOldProtect; SIZE_T wirtedByte; // 回复原API地址 VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,PAGE_READWRITE,&dwOldProtect); WriteProcessMemory(hProcess,pfOldMsgBoxW,OldCode,CODE_LENGTH,&wirtedByte); if (wirtedByte == 0) { AfxMessageBox(_T("回复原API地址失败")); } VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,dwOldProtect,&dwTemp); } /* 保存原API和新API的地址 */ VOID GetApiEntrancy() { // // 保存原来API地址 // HMODULE hmod = LoadLibrary(_T("User32.dll")); if ( NULL == hmod) { AfxMessageBox(_T("加载User32.dll失败")); return; } OdlMsgBoxW = (TypeMessageBoxW)::GetProcAddress(hmod,"MessageBoxW"); pfOldMsgBoxW = (FARPROC)OdlMsgBoxW; if ( pfOldMsgBoxW == NULL) { AfxMessageBox(_T("获取原API入口地址出错")); return; } // // 将原API的入口5个字节代码保存到OdeCode[]中 // _asm { lea edi,OldCode // 取数组OldCode[]地址,存放到edi中 mov esi,pfOldMsgBoxW // 获取原API入口地址,存入esi中 cld // 设置方向 movsd // 移动dword ,4 Byte movsb // 移动 1 Byte } // // 新的API入口保存到NewCode[]中,即jmp xxxx,xxxx为新API地址,该指令总长度为5个字节 // NewCode[0] = 0xe9; // 0xe9相当于jmp指令 _asm { lea eax,MyMessageBoxW mov ebx,pfOldMsgBoxW sub eax,ebx sub eax,CODE_LENGTH mov dword ptr[NewCode+1],eax } // // 填充完毕,开始HOOK,即使用NewCode[]替换原API入口 // HookOn(); } bool AdjustPrivileges() { HANDLE hToken; TOKEN_PRIVILEGES tp; TOKEN_PRIVILEGES oldtp; DWORD dwSize=sizeof(TOKEN_PRIVILEGES); LUID luid; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) { if (GetLastError()==ERROR_CALL_NOT_IMPLEMENTED) return true; else return false; } if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) { CloseHandle(hToken); return false; } ZeroMemory(&tp, sizeof(tp)); tp.PrivilegeCount=1; tp.Privileges[0].Luid=luid; tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED; /* Adjust Token Privileges */ if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &oldtp, &dwSize)) { CloseHandle(hToken); return false; } // close handles CloseHandle(hToken); return true; } // // 启动 HookMessageBoxW // void CHookMessageboxWindowDlg::OnBnClickedButtonStart() { // TODO: 在此添加控件通知处理程序代码 AdjustPrivileges(); // 提升权限,因为调用 OpenProcess() 需要合适的权限 DWORD dwPid = ::GetCurrentProcessId(); hProcess = OpenProcess(PROCESS_ALL_ACCESS,0,dwPid); if (hProcess == NULL) { CString logInfo; logInfo.Format(_T("获取进程句柄失败!!,进程 id = 0x%x ,错误代码 = 0x%x"),dwPid,GetLastError()); AfxMessageBox(logInfo); return; } GetApiEntrancy(); // 获取新旧API入口,并开始HOOK m_status.SetWindowText(_T("Hook已启动")); } // // 终止 HookMessageBoxW // void CHookMessageboxWindowDlg::OnBnClickedButtonStop() { // TODO: 在此添加控件通知处理程序代码 HookOff(); m_status.SetWindowText(_T("Hook已停止")); } // // 调用 HookMessageBoxW // void CHookMessageboxWindowDlg::OnBnClickedButtonCall() { // TODO: 在此添加控件通知处理程序代码 ::MessageBoxW(m_hWnd,_T("这是正常的MessageBoxW"),_T("Hello"),0); } |
来源:python脚本自动迁移
HOOK API(二) —— HOOK自己程序的 MessageBox的更多相关文章
- HOOK API(二)—— HOOK自己程序的 MessageBox
HOOK API(二) —— HOOK自己程序的 MessageBox 0x00 前言 以下将给出一个简单的例子,作为HOOK API的入门.这里是HOOK 自己程序的MessageBox,即将自己程 ...
- 汇编Ring 3下实现 HOOK API
[文章标题]汇编ring3下实现HOOK API [文章作者]nohacks(非安全,hacker0058) [作者主页]hacker0058.ys168.com [文章出处]看雪论坛(bbs.ped ...
- HOOK API(三)—— HOOK 所有程序的 MessageBox
HOOK API(三) —— HOOK 所有程序的 MessageBox 0x00 前言 本实例要实现HOOK MessageBox,包括MessageBoxA和MessageBoxW,其实现细节与H ...
- HOOK API(三) —— HOOK 所有程序的 MessageBox
转载来源:https://www.cnblogs.com/hookjc/ 0x00 前言 本实例要实现HOOK MessageBox,包括MessageBoxA和MessageBoxW,其实现细节与H ...
- Windows Dll Injection、Process Injection、API Hook、DLL后门/恶意程序入侵技术
catalogue 1. 引言2. 使用注册表注入DLL3. 使用Windows挂钩来注入DLL4. 使用远程线程来注入DLL5. 使用木马DLL来注入DLL6. 把DLL作为调试器来注入7. 使用c ...
- HOOK API入门之Hook自己程序的MessageBoxW(简单入门)
说到HOOK,我看了很多的资料和教程,无奈就是学不会HOOK,不懂是我的理解能力差,还是你们说的 不够明白,直到我看了以下这篇文章,终于学会了HOOK: http://blog.sina.com.cn ...
- HOOK大法实现不修改程序代码给程序添加功能
[文章标题]: HOOK大法实现不修改程序代码给程序添加功能[文章作者]: 0x18c0[软件名称]: Scylla[使用工具]: OD.Stub_PE.ResHacker[版权声明]: 本文原创于0 ...
- 汇编 -- Hook API (MessageBoxW)
说到HOOK.我看了非常多的资料和教程.无奈就是学不会HOOK.不懂是我的理解能力差.还是你们说的 不够明确,直到我看了下面这篇文章,最终学会了HOOK: http://blog.sina.com.c ...
- HOOK API(四)—— 进程防终止
HOOK API(四) —— 进程防终止 0x00 前言 这算是一个实战吧,做的一个应用需要实现进程的防终止保护,查了相关资料后决定用HOOK API的方式实现.起初学习HOOK API ...
随机推荐
- 编写Java程序,方法练习题__构建英雄类,定义一个int类型的变量output,表示英雄的血量
返回本章节 返回作业目录 需求说明: 定义一个int类型的变量output,表示英雄的血量,当battle()方法执行一次,output变量值减少10.在控制台随机输入一个小于100的整数,将该整数值 ...
- 20道JavaScript经典面试题
该篇文章整理了一些前端经典面试题,附带详解,涉及到JavaScript多方面知识点,满满都是干货-建议收藏阅读 前言 如果这篇文章有帮助到你,️关注+点赞️鼓励一下作者,文章公众号首发,关注 前端南玖 ...
- Storm集群开启HA高可用
Storm开启HA高可用,包括Nimbus和UI开启两个及以上的进程. 基于已经安装好的Storm集群,开启关键节点角色的HA高可用. Storm安装请参考Storm集群安装Version1.0.1 ...
- Sentry 企业级数据安全解决方案 - Relay 运行模式
内容整理自官方开发文档 Relay 可以在几种主要模式之一下运行,如果您正在配置 Relay server 而不是使用默认设置,那么事先了解这些模式至关重要. 模式存储在配置文件中,该文件包含 rel ...
- minio文件上传与下载
目录 一.minio简介 二.minio安装 一.java中使用 一.minio简介 MinIO 是在 GNU Affero 通用公共许可证 v3.0 下发布的高性能对象存储. 它是与 Amazon ...
- [ vue ] Quasar封装q-dialog组件,在外层实现弹出框的开启和关闭
场景描述: 见:https://www.cnblogs.com/remly/p/12981582.html 具体实现: <!-- 父组件 --> <template> < ...
- MySQL索引失效的常见场景
当然请记住,explain是一个好习惯! MySQL索引失效的常见场景 在验证下面的场景时,请准备足够多的数据量,因为数据量少时,MySQL的优化器有时会判定全表扫描无伤大雅,就不会命中索引了. 1. ...
- Java中生成一个唯一值的方式
现总结几种生成一个唯一值的方式 第一种:采用nanoTime() // 理论上存在重复的可能,可以在后面再加上一个随机字符串 Random r = new Random(); for (int i = ...
- 【刷题-LeetCode】224. Basic Calculator
Basic Calculator Implement a basic calculator to evaluate a simple expression string. The expression ...
- 『德不孤』Pytest框架 — 2、Pytest的基本使用
目录 1.Pytest安装 2.Pytest常用插件 3.Pytest运行的第一个例子 4.Pytest框架的运行方式 5.在PyCharm中以Pytest的方式运行测试用例 1.Pytest安装 C ...