现在的位置: 首页 > 综合 > 正文

hook杂记(一)

2011年03月03日 ⁄ 综合 ⁄ 共 3427字 ⁄ 字号 评论关闭
hook有很多种形式,这里分析inline hook api
这种形式的hook只针对被hook的进程,而不会影响到其他进程。
例如,某个进程用到了MessageBox这个API,程序希望改变原有MessageBox这个函数的行为,可以进行这种hook。

Inline hook有个几种方式
1.先执行hook函数,然后再执行原API。
2.先执行原API,再执行hook函数。

需要了解的几个知识点:
1.如果在Debug版下做实验,需要将Link Increase功能关掉

2.跳转指令 jmp XXXX;跳转到某个地址
对应机器码 e9 relative_add 其中 e9是一个字节 relative_add是四个字节,也就是这个指令共5个字节。跳转后的地址应为add_start+5+relative_add
其中add_start为e9开始的地址,relative_add为相对地址。
相反,relative_add=XXXX-5-add_start。

3.一般先分析被hook的原函数,分析头几个字节。如MessageBox
77D8050B 8B FF                mov         edi,edi
77D8050D 55                   push        ebp
77D8050E 8B EC                mov         ebp,esp
77D80510 83 3D 1C 04 DA 77 00 cmp         dword ptr ds:[77DA041Ch],0
......
可以看出前5个指令可以被替换掉,而替换前6个字节肯定会出错。

原理:
Inline hook时候,需要准备一个PreHook函数和PostHook函数。

1.计算原API函数地址.
2.备份原APi函数的头5个字节。
3.用jmp PreHook 替换OrginApi头5个字节。

//一个是个字节,前面5个字节是备份的原API的头5个字节,后面5个字节是jmp OriginApi
__declspec(naked) int PostHook()
{
  __asm
  {
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
  }
}

PreHook()
{
  Call Hook;
  goto PostHook;
}

测试代码:
#include <windows.h>
#include <stdio.h>

DWORD UpperFunctionReturnAdd;//保存API返回地址
int nRet;
BYTE orig_code[5] = {0x90, 0x90, 0x90, 0x90, 0x90};//存放原始的指令
BYTE hook_code[5] = {0xe9, 0, 0, 0, 0};//存放跳转到MyMessageBoxA的指令
BYTE jmp_org_code[5] = {0xe9, 0, 0, 0, 0};//存放跳转到原起始地址后5字节的指令

int MyMessageBoxA();
int MyMessageBoxAA(
          HWND hWnd,          // handle to owner window
          LPCTSTR lpText,     // text in message box
          LPCTSTR lpCaption,  // message box title
          UINT uType          // message box style
        );
int MyFunc();
void Hook();
int jmp_back();

ULONG OldFuncAddr;
ULONG MyFuncAddr;
ULONG jmp_backAddr;

//在修改前几个字节时,注意:取出的指令为完整的

int main()
{

  Hook();

  int rt = MessageBoxA(NULL, "Hello World", "Title", MB_OK);

  return 0;
}

void Hook()
{
  DWORD dwOldProtect; 
  OldFuncAddr = (ULONG)MessageBoxA;

  // MyFuncAddr = MyMessageBoxA的实际地址
  MyFuncAddr = (ULONG)MyMessageBoxAA;

  // jmp_backAddr = jmp_back的实际地址
  jmp_backAddr = (ULONG)jmp_back;
    //修改内存为PAGE_EXECUTE_READWRITE
  VirtualProtect((LPVOID)jmp_backAddr, 10, PAGE_EXECUTE_READWRITE, &dwOldProtect);

  VirtualProtect((LPVOID)OldFuncAddr, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);

  //计算跳转地址,由OldFuncAddr向MyFuncAddr跳转
  *((ULONG*)(hook_code+1)) = (ULONG)MyFuncAddr - (ULONG)OldFuncAddr - 5;
 
  memcpy(orig_code,(BYTE *)OldFuncAddr, 5);

  memcpy((BYTE*)OldFuncAddr, hook_code, 5);

  //计算返回地址,从jmp_backAddr向OldFuncAddr跳转
  *((ULONG*)(jmp_org_code+1)) = (ULONG)OldFuncAddr - (ULONG)jmp_backAddr - 5;

  memcpy((BYTE *)jmp_backAddr, orig_code, 5);

  memcpy((BYTE *)jmp_backAddr + 5, jmp_org_code, 5);
}

__declspec(naked) int jmp_back()
{
  __asm
  {
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
  }
}

//MyMessageBoxA:在函数执行前进行自己的处理
__declspec(naked) int MyMessageBoxA()
{  
  printf("MyMessageBoxA is called\r\n");

  MyFunc();////可以加入函数过程
  __asm
  {
    //跳回MessageBoxA入口点
    jmp jmp_back;
  }
}

//MyMessageBoxA:在函数执行后进行自己的处理
__declspec(naked) int MyMessageBoxAA(
                  HWND hWnd,          // handle to owner window
                  LPCTSTR lpText,     // text in message box
                  LPCTSTR lpCaption,  // message box title
                  UINT uType          // message box style
                  )
{  
  printf("MyMessageBoxAA is called\r\n");
  __asm
  {
    pop UpperFunctionReturnAdd
    push  offset s1;//返回地址为S1:

    //跳回MessageBoxA入口点
    jmp jmp_back;
s1:    nop 
  }

  MyFunc();
 
  __asm
  {
    ;//将原返回地址压栈
    mov eax, 0;////演示:将返回结果改为0,也可由MyFunc返回
    push UpperFunctionReturnAdd
    ret;
  }
}

int MyFunc()
{
  printf("Hello World\r\n");
  return 1;
}

参考:
1.http://bbs.pediy.com/showthread.php?t=69666

抱歉!评论已关闭.