zoukankan      html  css  js  c++  java
  • 汇编 -- Hook API (MessageBoxW)

    说到HOOK。我看了非常多的资料和教程。无奈就是学不会HOOK。不懂是我的理解能力差。还是你们说的

    不够明确,直到我看了下面这篇文章,最终学会了HOOK:

    http://blog.sina.com.cn/s/blog_628821950100xmuc.html    //感谢文章作者的分享,让我学会了HOOK

    文章出处,好像是这篇:http://blog.csdn.net/glliuxueke/article/details/2702608     //后来才看到

    ----------------------------------------------------------------------------------------------------------------------------------------------

    既然窝已经入门了HOOK,窝会写几篇关于HOOK的文章,让相同想入门HOOK,却难以入门的童鞋

    有个參考。这篇是第一篇,希望帮助到有此须要的盆友,我測试的环境都是:Win7+VS2008+MFC

    --------------------------------------------------------------------------------------------------------------------------------------------------

    第一篇说的是HOOK自己程序的MessageBoxW。诚然HOOK自己程序用到的API在实际应用中没有什么

    大的用处,只是我觉得对于我们理解HOOK却有莫大的帮助,因此我的HOOK文章就从HOOK自己的程序

    開始。文章后面附有本样例程序的VS2008源代码下载地址。

    ---------------------------------------------------------------------------------------------------------------------------------------------------

    //先看下我写的样例程序及执行效果截图,后面再对其进行分析


    //未钩MessageBoxW前


    //钩MessageBoxW后


    ---------------------------------------------------------------------------------------------------------------------

    依据我的理解,先说一下HOOK API的一般思路和步骤。

    HOOK API的思路就是改动原API的入口。使其跳转到我们的假API入口。

    然后运行我们的假API函数。为什么说是假API函数呢?

    由于我们的假API,除了函数名称和真实API的名称不一样之外,其他都是同样的。即

    它们的函数參数和返回值和调用形式都是一样的。

    -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    HOOK API的一般步骤:

    1.定义假API函数

           注意假API函数,除了函数名称和真实API不一样之外,其他的都要跟真实API的定义同样,如參数类型和返回值、调用形式等。

    如我们能够这样定义假的MessageBoxW:

    1. int WINAPI MyMessageBoxW(HWND hWnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT uType)  
    2. {  
    3.     //定义假API时。详细的函数体代码临时可不写...  
    4.     return 0;  
    5. }  

    至于我们怎样知道真实API的原型定义呢?非常easy,查看MSDN就可以,或者你也能够在VS2008开发环境中,

    写下该真实API,然后选中该API。再点鼠标右键,选择【转到定义】,就能够看到其原型声明了。

    ...........................................................................................................................................................................

    2.定义API函数类型

           由于我们要动态获取原API函数的地址。获取到后,我们要将其保存起来,保存在哪里呢?

    这就是定义API函数类型的原因了,有了API函数类型的定义后。我们就能够用其定义一个变量来 保存获取到的

    真实API函数的地址了。比如。我定义的MessageBoxW函数类型的语句例如以下:

    1. //原函数类型定义  
    2. typedef int (WINAPI* MsgBoxW)(HWND hWnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT uType);  
    注意,原函数类型定义中的函数返回值和參数类型要跟MessageBoxW的定义同样。

    上面就是定义了API函数类型MsgBoxW,这跟我们常见的int、char、float等类型。使用方法是一样的,

    实际上,我们全然能够把MsgBoxW当成int类型来使用,这样一说。API函数类型也只是如此嘛。

    有了API函数类型MsgBoxW,我们就能够用其来定义变量,从而为保存原API地址做准备了。

    如:

    1. MsgBoxW OldMsgBoxW=NULL;//指向原函数的指针  

    有了API函数类型后,我们还须要一个远指针类型的变量,由于系统API是在dll文件里的。很多其它关于

    远指针的信息,我如今也不是非常清楚,有兴趣的盆友,最好还是百度一下。系统已经给我们定义好了

    远指针类型:FARPROC,我们直接拿来用就可以。如:

    1. FARPROC pfOldMsgBoxW;  //指向函数的远指针  

    ..................................................................................................................................................................................................................

    3.获取API函数入口

             有了保存原API函数地址的变量OldMsgBox和指向原API函数的远指针,我们就能够获取真实API的地址了。

    如:

    1. //获取原API入口地址  
    2.   HMODULE hmod=::LoadLibrary(_T("User32.dll"));  
    3.   OldMsgBoxW=(MsgBoxW)::GetProcAddress(hmod,"MessageBoxW");  
    4.   pfOldMsgBoxW=(FARPROC)OldMsgBoxW;  

    注意,上面我们将原API地址OldMsgBoxW强制转换成了远指针pfOldMsgBoxW。至于为什么要强制转换。

    我如今也不是非常清楚,想知道的童鞋。能够百度一下。

    ................................................................................................................................................................................................................

    4.保存原API入口的前5个字节

               保存的目的是为了恢复用的,毕竟我们HOOK了API后,还须要调用真实的API嘛,

    怎样保存一个函数入口的前5个字节呢?这里用到了汇编代码,至于不用汇编能够吗?我想是能够的。

    有空时,我再试一下,不用汇编能否保存一个API入口的前5个字节。这里就先用别人写的汇编代码吧,

    代码例如以下:

    1. // 将原API的入口前5个字节代码保存到OldCode[]  
    2.   BYTE OldCode[5];  
    3.   _asm   
    4.   {   
    5.    lea edi,OldCode      //获取OldCode数组的地址,放到edi  
    6.    mov esi,pfOldMsgBoxW //获取原API入口地址,放到esi  
    7.    cld    //方向标志位,为下面两条指令做准备  
    8.    movsd //复制原API入口前4个字节到OldCode数组  
    9.    movsb //复制原API入口第5个字节到OldCode数组  
    10.   }  

    ....................................................................................................................................................................................................................

    5.获取新入口的前5个字节

              由于我们改动真实API入口的前5个字节。使其跳转到我们假API函数的入口地址。即改成Jmp xxxxxxxx,当中xxxxxxxx就是我们

    假API的入口地址,那么我们怎样得到该地址呢?

    前人已经总结出了一条公式:

    int nAddr= UserFunAddr – SysFunAddr - (我们定制的这条指令的大小);

    Jmp nAddr; //我们要获取的就是nAddr的值

    既然已经有了公式。我们拿来用就可以,UserFunAddr相当于我们的假API,SysFunAddr相当于真实API,这里指令的大小为5,

    实现以上公式的汇编代码例如以下:

    1. //获取MyMessageBoxW的相对地址,为Jmp做准备  
    2.   //int nAddr= UserFunAddr – SysFunAddr - (我们定制的这条指令的大小);  
    3.   //Jmp nAddr;  
    4.   //(我们定制的这条指令的大小), 这里是5。5个字节嘛  
    5.   BYTE NewCode[5];  
    6.   _asm   
    7.   {   
    8.    lea eax,MyMessageBoxW //获取我们的MyMessageBoxW函数地址  
    9.    mov ebx,pfOldMsgBoxW  //原系统API函数地址  
    10.    sub eax,ebx           //int nAddr= UserFunAddr – SysFunAddr  
    11.    sub eax,5             //nAddr=nAddr-5  
    12.    mov dword ptr [NewCode+1],eax //将算出的地址nAddr保存到NewCode后面4个字节  
    13.                                  //注:一个函数地址占4个字节  
    14.   }   

    ......................................................................................................................................................................................................................

    6.改动真实API入口的前5个字节

           前面我们已经保存了真实API入口的前5个字节,也已经计算出了新入口的前5个字节,

    可谓万事俱备,仅仅欠东风,如今能够改动真实API入口的前5个字节了。

    代码例如以下:

    1. //填充完成,如今NewCode[]里的指令相当于Jmp MyMessageBoxW  
    2.   //既然已经获取到了Jmp MyMessageBoxW  
    3.   //如今该是将Jmp MyMessageBoxW写入原API入口前5个字节的时候了  
    4.   //知道为什么是5个字节吗?  
    5.   //Jmp指令相当于0xe9,占一个字节的内存空间  
    6.   //MyMessageBoxW是一个地址,事实上是一个整数,占4个字节的内存空间  
    7.   //int n=0x123;   n占4个字节和MyMessageBoxW占4个字节是一样的  
    8.   //1+4=5,知道为什么是5个字节了吧  
    9.   HookOn();   

    改动API入口前5个字节的HookOn()函数详细代码例如以下:

    1. //开启钩子的函数  
    2. void HookOn()   
    3. {   
    4.  ASSERT(hProcess!=NULL);  
    5.   
    6.  DWORD dwTemp=0;  
    7.  DWORD dwOldProtect;  
    8.    
    9.  //改动API函数入口前5个字节为jmp xxxxxx  
    10.  VirtualProtectEx(hProcess,pfOldMsgBoxW,5,PAGE_READWRITE,&dwOldProtect);   
    11.  WriteProcessMemory(hProcess,pfOldMsgBoxW,NewCode,5,0);  
    12.  VirtualProtectEx(hProcess,pfOldMsgBoxW,5,dwOldProtect,&dwTemp);  
    13.   
    14. }  

    以上改动API函数入口前5个字节,用到了两个系统API函数,百度百科里面对它们有具体解释,在此就不说了。

    注:hProcess是进程句柄,我们能够这样获取它:

    DWORD dwPid=::GetCurrentProcessId();
    HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,0,dwPid);

    ..........................................................................................................................................................................................................


    7.恢复API函数入口前5个字节

              有改动。就有恢复嘛,恢复函数代码例如以下:

    1. //关闭钩子的函数  
    2. void HookOff()  
    3. {   
    4.  ASSERT(hProcess!=NULL);  
    5.   
    6.  DWORD dwTemp=0;  
    7.  DWORD dwOldProtect;  
    8.   
    9.  //恢复API函数入口前5个字节  
    10.  VirtualProtectEx(hProcess,pfOldMsgBoxW,5,PAGE_READWRITE,&dwOldProtect);   
    11.  WriteProcessMemory(hProcess,pfOldMsgBoxW,OldCode,5,0);   
    12.  VirtualProtectEx(hProcess,pfOldMsgBoxW,5,dwOldProtect,&dwTemp);    
    13. }  


    注:hProcess是进程句柄。我们能够这样获取它:

    DWORD dwPid=::GetCurrentProcessId();
    HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,0,dwPid);

    -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    以上就是HOOK API的大致流程了,看不明确的盆友,最好还是再看下我写的源代码。文章结合源代码,

    希望有助于你们的理解。

    源代码下载地址:Hook自己程序的MessageBoxW.zip

    学会了HOOK自己的程序,下篇我们再接再厉。HOOK系统全部程序的MessageBoxA和MessageBoxW

    ------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    以下贴一下以上这个程序的主要代码,算是有个总体印象吧:

    1. //原函数类型定义  
    2. typedef int (WINAPI* MsgBoxW)(HWND hWnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT uType);  
    3. MsgBoxW OldMsgBoxW=NULL;//指向原函数的指针  
    4. FARPROC pfOldMsgBoxW;  //指向函数的远指针  
    5. BYTE OldCode[5]; //原系统API入口代码  
    6. BYTE NewCode[5]; //原系统API新的入口代码 (jmp xxxxxxxx)  
    7.   
    8. HANDLE hProcess=NULL;//本程序进程句柄  
    9. HINSTANCE hInst=NULL;//API所在的dll文件句柄  
    10.   
    11. void HookOn();  
    12. void HookOff();  
    13. int WINAPI MyMessageBoxW(HWND hWnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT uType)  
    14. {  
    15.     TRACE(lpText);  
    16.     HookOff();//调用原函数之前。记得先恢复HOOK呀。不然是调用不到的  
    17.               //假设不恢复HOOK,就调用原函数,会造成死循环  
    18.               //毕竟调用的还是我们的函数。从而造成堆栈溢出。程序崩溃。

        

    19.   
    20.     int nRet=::MessageBoxW(hWnd,_T("哈哈,MessageBoxW被HOOK了"),lpCaption,uType);  
    21.   
    22.     HookOn();//调用完原函数后。记得继续开启HOOK,不然下次会HOOK不到。

         

    23.   
    24.     return nRet;  
    25. }  
    26.   
    27.   
    28.   
    29.   
    30. //开启钩子的函数  
    31. void HookOn()   
    32. {   
    33.  ASSERT(hProcess!=NULL);  
    34.   
    35.  DWORD dwTemp=0;  
    36.  DWORD dwOldProtect;  
    37.    
    38.  //改动API函数入口前5个字节为jmp xxxxxx  
    39.  VirtualProtectEx(hProcess,pfOldMsgBoxW,5,PAGE_READWRITE,&dwOldProtect);   
    40.  WriteProcessMemory(hProcess,pfOldMsgBoxW,NewCode,5,0);  
    41.  VirtualProtectEx(hProcess,pfOldMsgBoxW,5,dwOldProtect,&dwTemp);  
    42.   
    43. }  
    44.   
    45. //关闭钩子的函数  
    46. void HookOff()  
    47. {   
    48.  ASSERT(hProcess!=NULL);  
    49.   
    50.  DWORD dwTemp=0;  
    51.  DWORD dwOldProtect;  
    52.   
    53.  //恢复API函数入口前5个字节  
    54.  VirtualProtectEx(hProcess,pfOldMsgBoxW,5,PAGE_READWRITE,&dwOldProtect);   
    55.  WriteProcessMemory(hProcess,pfOldMsgBoxW,OldCode,5,0);   
    56.  VirtualProtectEx(hProcess,pfOldMsgBoxW,5,dwOldProtect,&dwTemp);    
    57. }  
    58.   
    59. //获取API函数入口前5个字节  
    60. //旧入口前5个字节保存在前面定义的字节数组BYTE OldCode[5]  
    61. //新入口前5个字节保存在前面定义的字节数组BYTE NewCode[5]  
    62. void GetApiEntrance()  
    63. {  
    64.    
    65.   //获取原API入口地址  
    66.   HMODULE hmod=::LoadLibrary(_T("User32.dll"));  
    67.   OldMsgBoxW=(MsgBoxW)::GetProcAddress(hmod,"MessageBoxW");  
    68.   pfOldMsgBoxW=(FARPROC)OldMsgBoxW;  
    69.     
    70.   if (pfOldMsgBoxW==NULL)  
    71.   {  
    72.     MessageBox(NULL,_T("获取原API入口地址出错"),_T("error!"),0);  
    73.     return;  
    74.   }  
    75.   
    76.   // 将原API的入口前5个字节代码保存到OldCode[]  
    77.   _asm   
    78.   {   
    79.    lea edi,OldCode      //获取OldCode数组的地址,放到edi  
    80.    mov esi,pfOldMsgBoxW //获取原API入口地址。放到esi  
    81.    cld    //方向标志位,为下面两条指令做准备  
    82.    movsd //复制原API入口前4个字节到OldCode数组  
    83.    movsb //复制原API入口第5个字节到OldCode数组  
    84.   }  
    85.   
    86.   
    87.   NewCode[0]=0xe9;//实际上0xe9就相当于jmp指令  
    88.   
    89.   //获取MyMessageBoxW的相对地址,为Jmp做准备  
    90.   //int nAddr= UserFunAddr – SysFunAddr - (我们定制的这条指令的大小);  
    91.   //Jmp nAddr;  
    92.   //(我们定制的这条指令的大小), 这里是5,5个字节嘛  
    93.   BYTE NewCode[5];  
    94.   _asm   
    95.   {   
    96.    lea eax,MyMessageBoxW //获取我们的MyMessageBoxW函数地址  
    97.    mov ebx,pfOldMsgBoxW  //原系统API函数地址  
    98.    sub eax,ebx           //int nAddr= UserFunAddr – SysFunAddr  
    99.    sub eax,5             //nAddr=nAddr-5  
    100.    mov dword ptr [NewCode+1],eax //将算出的地址nAddr保存到NewCode后面4个字节  
    101.                                  //注:一个函数地址占4个字节  
    102.   }   
    103.    
    104.   
    105.   //填充完成,如今NewCode[]里的指令相当于Jmp MyMessageBoxW  
    106.   //既然已经获取到了Jmp MyMessageBoxW  
    107.   //如今该是将Jmp MyMessageBoxW写入原API入口前5个字节的时候了  
    108.   //知道为什么是5个字节吗?  
    109.   //Jmp指令相当于0xe9,占一个字节的内存空间  
    110.   //MyMessageBoxW是一个地址,事实上是一个整数。占4个字节的内存空间  
    111.   //int n=0x123;   n占4个字节和MyMessageBoxW占4个字节是一样的  
    112.   //1+4=5。知道为什么是5个字节了吧  
    113.   HookOn();   
    114. }  
    115.   
    116.   
    117.   
    118. //開始Hook MessageBoxW  
    119. void CHookMsgBoxDlg::OnBnClickedBtnStartHook()  
    120. {  
    121.     DWORD dwPid=::GetCurrentProcessId();  
    122.     hProcess=OpenProcess(PROCESS_ALL_ACCESS,0,dwPid);   
    123.   
    124.     GetApiEntrance();  
    125.     SetDlgItemText(IDC_STATIC_INFO,_T("Hook已启动"));  
    126. }  
    127.   
    128. //调用MessageBoxW  
    129. void CHookMsgBoxDlg::OnBnClickedBtnCallMsgBox()  
    130. {  
    131.     ::MessageBoxW(m_hWnd,_T("这是正常的MessageBoxW"),_T("Hello"),0);  
    132. }  
    133.   
    134. //停止Hook MessageBoxW  
    135. void CHookMsgBoxDlg::OnBnClickedBtnStopHook()  
    136. {  
    137.     HookOff();  
    138.     SetDlgItemText(IDC_STATIC_INFO,_T("Hook未启动"));  
    139. }  
  • 相关阅读:
    IoC和AoP
    学习树
    Avalon Framework概念
    java利用WatchService实时监控某个目录下的文件变化并按行解析
    [DBT-08001] 无法检查可用内存。
    C#之http协议与soap协议之间的区别
    C#之ActionResult 详解
    C#实现连接池
    C#MVC之传入字典的模型项为 null,但此字典需要类型“System.Decimal”的非 null 模型项。
    Func的介绍
  • 原文地址:https://www.cnblogs.com/blfbuaa/p/7072469.html
Copyright © 2011-2022 走看看