首页
社区
课程
招聘
[原创]代码Hook之指令级实现
发表于: 2014-1-16 22:09 8776

[原创]代码Hook之指令级实现

2014-1-16 22:09
8776
在工作中经常会使用到 Hook 技术,一般函数 Hook 会较为广泛的应用。
函数 Hook 分为很多种,修改 PE 的导入表是较为简单且常用的方法,但修改 PE 存在着实时性不高,时机不好掌握等特点,因为在 PE 文件被装载之后才能去动态地修改其导入表内容,进而达到函数跳转被截取的目的,但 PE 文件的加载可能很随意,任意一 PE 文件有导入表如果没有被 Hook 到,则就出现了漏洞,所以现在介绍一个古老但实用的技术,通过从根本上解决掉函数跳转来实现函数的 Hook。这就是今天的主角,指令级 Hook。
  函数Hook,本质就是在代码调用某个函数的时候,会先进入到自己预设的一个其它函数执行,好让我们得到一个控制的机会,那么指令级Hook,以 x86 平台为例,只要能完成跳转,即可跳转到我们自己的代码,有很多的指令可以完成如 call, jmp 系列的指令都可以做到,且 jmp 这种无条件跳转最能满足我们的需要。
  实现原理,那就很简单了,就是把目标函数的前五个字节替换成一个无条件跳转,然后保存这五个字节的内容,接着跳转到我们预设的函数里,然后我们就得到了控制权,此时你就可以无所欲为了,等您忙以后,再执行我们保存的指令,最后再跳转到目标函数的第六个字节的指令继续执行。
  下面以项目中会实际用到的代码为例,实地完成以上所说的技术。在一个大的项目中有时为了查找其它模块是否调用了 ExitProcess 导致程序退出,会对 ExitProcess 函数进行 Hook,或者查找内存申请和释放是否有泄漏,会对 malloc 和 free 函数进行 Hook,下面以实际项目中的代码为例来讲解一下。

BYTE g_btOldCode[5] = {0};
LPVOID g_pSrc;

void * _cdecl MyMallocBeforeImpl( void *pRetAddr, void *pRetAddr1, int nSize )
{
  if ( !g_bMallocWatchOn )
  {
    return 0;
  }
  if ( g_dwthreadID == 0 )
  {
    printf("Malloc size: %08d", nSize );
  }
  else if ( GetCurrentThreadId() == g_dwthreadID )
  {
    printf("Malloc size: %08d", nSize );
  }

  return 0;
}

void MyMallocAfterImpl( void *pBuff )
{
  if ( !g_bMallocWatchOn )
  {
    return;
  }

  if ( g_dwthreadID == 0 )
  {
    OspPrintf( TRUE, FALSE, TEXT("\t\taddr: 0x%08x\t\t%d\n") , pBuff, GetCurrentThreadId() );
  }
  else if ( GetCurrentThreadId() == g_dwthreadID )
  {
    OspPrintf( TRUE, FALSE, TEXT("\t\taddr: 0x%08x\t\t%d\n") , pBuff, GetCurrentThreadId() );
  }

}

__declspec(naked) void _cdecl MyMallocBefore()
{
  _asm {
    call  MyMallocBeforeImpl
    ret
  }
}

__declspec(naked) void _cdecl MyMallocAfter()
{
  _asm {
    push  eax
    push  ebx
    push  ecx
    mov    ebx, dword ptr[esp + 12]
    mov    ecx, dword ptr[esp + 16]
    mov    dword ptr[esp + 16], ebx
    mov    dword ptr[esp + 12], ecx
    pop    ecx
    pop    ebx
    call  MyMallocAfterImpl
    pop    eax
    ret
  }
}

/*==============================================================================*/
void MyFreeBeforeImpl( void *pRetAddr, void *pRetAddr1, void *pBuff )
{
  //printf( "Free: 0x%x\n", pBuff );
}

void MyFreeAfterImpl( void *pBuff )
{
}

__declspec(naked) void _cdecl MyFreeBefore()
{
  _asm {
    call MyFreeBeforeImpl
    ret
  }
}

__declspec(naked) void _cdecl MyFreeAfter()
{
  _asm {
    push  eax
    push  ebx
    push  ecx
    mov    ebx, dword ptr[esp + 12]
    mov    ecx, dword ptr[esp + 16]
    mov    dword ptr[esp + 16], ebx
    mov    dword ptr[esp + 12], ecx
    pop    ecx
    pop    ebx
    call  MyFreeAfterImpl
    pop    eax
    ret
  }
}

void PlaceHolder()
{
}


/*
 * 指令中的 _emit 为占位符,会被填充为目标函数中被替换掉的指令
 */
__declspec(naked) void Adapter1( void )
{
  _asm {
    call  PlaceHolder
    push  PlaceHolder
    push  eax
    push  ebx
    mov    eax, dword ptr[esp + 12]
    mov    ebx, dword ptr[esp + 16]
    mov    dword ptr[esp + 16], eax
    mov    dword ptr[esp + 12], ebx
    pop    ebx
    pop    eax
    _emit  0x00
    _emit  0x00
    _emit  0x00
    _emit  0x00
    _emit  0x00
    jmp    PlaceHolder
  }
}

__declspec(naked) void Adapter2( void )
{
  _asm {
    call  PlaceHolder
    push  PlaceHolder
    push  eax
    push  ebx
    mov    eax, dword ptr[esp + 12]
    mov    ebx, dword ptr[esp + 16]
    mov    dword ptr[esp + 16], eax
    mov    dword ptr[esp + 12], ebx
    pop    ebx
    pop    eax
    _emit  0x00
    _emit  0x00
    _emit  0x00
    _emit  0x00
    _emit  0x00
    jmp    PlaceHolder
  }
}

void HookDebugFun1( LPVOID pSrc, LPVOID pDstBefore, LPVOID pDstAfter, LPVOID pAdapter )
{
  g_pSrc = pSrc;

  /* 在源函数中构造跳转 */
  BYTE btCode[] = { 0xE9, 0x00, 0x00, 0x00, 0x00 };
  DWORD dwOffset = (DWORD)pAdapter - ((DWORD)pSrc + 5);
  memcpy( btCode + 1, &dwOffset, 4 );

  DWORD dwWriteenBytes;
  DWORD dwReadBytes;
//   LPVOID pAdapterRealAddr = (LPBYTE)pAdapter + 5 + *(LPDWORD)((LPBYTE)pAdapter + 1);            // Debug 下本地函数地址有跳转语句
  LPVOID pAdapterRealAddr = (LPBYTE)pAdapter;
  LPVOID pOldCodeAddr = (LPBYTE)pAdapterRealAddr + 5 + 5 + 20;
  LPVOID pHolder1Addr = (LPBYTE)pAdapterRealAddr + 1;
  LPVOID pHolder2Addr = (LPBYTE)pAdapterRealAddr + 5 + 1;
  LPVOID pHolder3Addr = (LPBYTE)pAdapterRealAddr + 5 + 20 + 5 + 5 + 1;

  ReadProcessMemory( GetCurrentProcess(), pSrc, g_btOldCode, 5, &dwReadBytes );
  /* 在源函数中写入跳转 */
  WriteProcessMemory( GetCurrentProcess(), (LPDWORD)((LPBYTE)pSrc), btCode, 5, &dwWriteenBytes );

  /* 将原函数中被替换的代码写入适配程序中 */
  WriteProcessMemory( GetCurrentProcess(), pOldCodeAddr, g_btOldCode, 5, &dwWriteenBytes );

  /* 将过滤函数写入适配函数中 PlaceHolder1*/
  DWORD dwFilterOffset = (DWORD)pDstBefore - ((DWORD)pHolder1Addr + 4 );
  WriteProcessMemory( GetCurrentProcess(), pHolder1Addr, &dwFilterOffset, 4, &dwWriteenBytes );

  /* 将过滤函数写入适配函数中 PlaceHolder2*/
  //DWORD dwFilterOffset = (DWORD)pDstBefore - ((DWORD)pHolder1Addr + 4 );
  WriteProcessMemory( GetCurrentProcess(), pHolder2Addr, &pDstAfter, 4, &dwWriteenBytes );

  /* 将适配函数中的跳转地址写入被 Hook 的函数的跳转指令之后的地址 PlaceHolder3 */
  dwFilterOffset = ((DWORD)pSrc + 5) - ((DWORD)pHolder3Addr + 4);
  WriteProcessMemory( GetCurrentProcess(), pHolder3Addr, &dwFilterOffset, 4, &dwWriteenBytes );
}
// #endif

// 申请内存打印开
API void mallocwatchon( int threadID )
{
  g_dwthreadID = threadID;
  static BOOL32 bStart = FALSE;
  if ( !bStart )
  {
    HookDebugFun1( malloc, MyMallocBefore, MyMallocAfter, Adapter1 );
    HookDebugFun1( free, MyFreeBefore, MyFreeAfter, Adapter2 );
    bStart = TRUE;
  }
  g_bMallocWatchOn = TRUE;
}

// 申请内存打印关
API void mallocwatchoff()
{
  g_bMallocWatchOn = FALSE;
}


代码中有详细的注释,jmp 计算偏移是一个炒剩饭的技术了,如果有疑问可 google 或讨论一下。 在附件代码中,由于是从项目中截取的代码版本,但实现较为完整,只需要加上适当的头文件和 main 函数,经过修改即可运行实验了。

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
免费 5
支持
分享
最新回复 (21)
雪    币: 185
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
嗯。经典的 5 字节了。 不过很容易检测出来。
2014-1-16 22:26
0
雪    币: 31
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
不就是inline hook么,标题起得太深奥
2014-1-16 22:31
0
雪    币: 496
活跃值: (286)
能力值: ( LV13,RANK:400 )
在线值:
发帖
回帖
粉丝
4
我以为你要讲OPCode了……
2014-1-16 22:34
0
雪    币: 28
活跃值: (42)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
5
起名字一直是我的弱项,朝花儿拾,今天又看到微软的 hotfix 实现让我想起了这个古老的技术。
2014-1-16 22:45
0
雪    币: 28
活跃值: (42)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
6
那五个字节确实经典,不过微软经典的不是这5个字节,应该是 mov edi, edi 哈哈。
2014-1-16 22:50
0
雪    币: 95
活跃值: (119)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
是呀,名字起得好玄呀,,,,,,
2014-1-16 22:53
0
雪    币: 257
活跃值: (67)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
8
看标题还以为是指令拦截,没想到是inline  hook
2014-1-16 22:53
0
雪    币: 28
活跃值: (42)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
9
不好意思,让你失望了,古老但实用的技术拿出来 mark 一下而已,
2014-1-16 22:53
0
雪    币: 28
活跃值: (42)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
10
可以改名字吗,我已经后悔了,呵呵
2014-1-16 22:55
0
雪    币: 28
活跃值: (42)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
11
inline hook 这个词没怎么听过,对一些专业名词不是很敏感。
2014-1-16 23:02
0
雪    币: 773
活跃值: (442)
能力值: ( LV9,RANK:200 )
在线值:
发帖
回帖
粉丝
12
多谢楼主,好神奇
2014-1-16 23:03
0
雪    币: 11
活跃值: (40)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
还以为是vt呢……
2014-1-16 23:22
0
雪    币: 185
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
windows 内核 安全防护   这里面就用过这个词。 你out了!
2014-1-17 00:26
0
雪    币: 478
活跃值: (50)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
同4楼,哈哈
2014-1-17 01:08
0
雪    币: 882
活跃值: (350)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
虽然看不明白,好像很厉害那样
2014-1-17 02:02
0
雪    币: 28
活跃值: (42)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
17
我一般关注的重点都在内核的实现原理及算法和性能,对安全防护方面确实不太关注!
2014-1-17 08:41
0
雪    币: 77
活跃值: (48)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
MARK
2014-1-17 09:16
0
雪    币: 85
活跃值: (51)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
19
inline hook ,鉴定完毕,结贴............
2014-1-17 13:40
0
雪    币: 3020
活跃值: (3065)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
20
我以为是 HOOK 一字节的指令呢~
2014-1-17 13:59
0
雪    币: 218
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
跪求楼主修改标题和正文为inline hook吧
2014-1-19 23:26
0
雪    币: 218
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
靠,又被标题骗进来了
能不能改改啊
2014-1-21 01:11
0
游客
登录 | 注册 方可回帖
返回
//