您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
HOOK API(二)—— HOOK自己程序的 MessageBox
 
来自于:工匠若水 发布于 2016-2-16
  2670  次浏览      15
 

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 测试

1.启动程序,单击"调用MessageBoxW"按钮

2.单击"启动HookMessageBoxW"按钮,单击"调用MessageBoxW"按钮

3.单击"终止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);

}

   
2670 次浏览       15
 
相关文章

手机软件测试用例设计实践
手机客户端UI测试分析
iPhone消息推送机制实现与探讨
Android手机开发(一)
 
相关文档

Android_UI官方设计教程
手机开发平台介绍
android拍照及上传功能
Android讲义智能手机开发
相关课程

Android高级移动应用程序
Android系统开发
Android应用开发
手机软件测试
最新课程计划
信息架构建模(基于UML+EA)3-21[北京]
软件架构设计师 3-21[北京]
图数据库与知识图谱 3-25[北京]
业务架构设计 4-11[北京]
SysML和EA系统设计与建模 4-22[北京]
DoDAF规范、模型与实例 5-23[北京]

android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   

Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合

领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...