C++实现inline hook的原理及应用实例

本文实例简述了C++实现inline hook的原理及应用,对于大家更好的理解inline hook原理及其应用有很大的帮助。具体内容如下:

一、Inline Hook简介:

1.INLINE HOOK原理:

Inline Hook通过硬编码的方式向内核API的内存空间(通常是开始的一段字节,且一般在第一个call之前,这么做是为了防止堆栈混乱)写入跳转语句,这样,该API只要被调用,程序就会跳转到我们的函数中来,我们在自己写的函数里需要完成3个任务:

1)重新调整当前堆栈。程序流程在刚刚跳转的时候,内核API并没有执行完,而我们的函数需要根据其结果来进行信息过滤,所以我们需要保证内核API能在顺利执行完毕后返回到我们的函数中来,这就要求对当前堆栈做一个调整。

2)执行遗失的指令。我们向内核API地址空间些如跳转指令(jmp xxxxxxxx)时,势必要覆盖原先的一些汇编指令,所以我们一定要保证这些被覆盖的指令能够顺利执行(否则,你的及其就要BSOD了,呵呵,Blue Screen Of Death)。关于这部分指令的执行,一般是将其放在我们的函数中,让我们的函数“帮助”内核API执行完被覆盖的指令,然后再跳回内核API中被覆盖内后后的地址继续执行剩余内容。跳回去的时候,一定要算好是跳回到什么地址,是内核API起始地址后的第几个字节。

3)信息过滤。这个就不用多说了,内核API顺利执行并返回到我们的函数中,我们自然要根据其结果做一些信息过滤,这部分内容因被hook的API以及Hook目的的不同而不同。

2.Inline hook的工作流程:

1)验证内核API的版本(特征码匹配)。

2)撰写自己的函数,要完成以上三项任务。

3)获取自己函数的地址,覆盖内核API内存,供跳转。

简而言之,inlinehook的原理就是,修改函数,使其跳转到我们指定的地方。

常见的有改函数入口,也有改函数尾,函数中间的
比如,通常函数开头的汇编代码都是这样:mov edi,edi;push esp;mov ebp,esp,而我们便可以通过修改这里进行HOOK。

二、示例代码(该示例摘自看雪)

#include <ntifs.h>
#include <windef.h>
ULONG g_KiInsertQueueApc;
ULONG g_uCr0;
BYTE g_HookCode[5] = { 0xe9, 0, 0, 0, 0 }; //JMP NEAR
BYTE g_OrigCode[5] = { 0 }; // 原函数的前字节内容
BYTE jmp_orig_code[7] = { 0xEA, 0, 0, 0, 0, 0x08, 0x00 }; //JMP FAR
BOOL g_bHooked = FALSE;
VOID
fake_KiInsertQueueApc (
            PKAPC Apc,
            KPRIORITY Increment
            );
VOID
Proxy_KiInsertQueueApc (
            PKAPC Apc,
            KPRIORITY Increment
            );
void WPOFF()
{
  ULONG uAttr;
  _asm
  {
    push eax;
    mov eax, cr0;
    mov uAttr, eax;
    and eax, 0FFFEFFFFh; // CR0 16 BIT = 0
    mov cr0, eax;
    pop eax;
    cli
  };
  g_uCr0 = uAttr; //保存原有的 CRO 屬性
}
VOID WPON()
{
  _asm
  {
    sti
      push eax;
    mov eax, g_uCr0; //恢復原有 CR0 屬性
    mov cr0, eax;
    pop eax;
  };
}
//
// 停止inline hook
//
VOID UnHookKiInsertQueueApc ()
{
  KIRQL oldIrql;
  WPOFF();
  oldIrql = KeRaiseIrqlToDpcLevel();
  RtlCopyMemory ( (BYTE*)g_KiInsertQueueApc, g_OrigCode, 5 );
  KeLowerIrql(oldIrql);
  WPON();
  g_bHooked = FALSE;
}
//
// 开始inline hook -- KiInsertQueueApc
//
VOID HookKiInsertQueueApc ()
{
  KIRQL oldIrql;
  if (g_KiInsertQueueApc == 0) {
    DbgPrint("KiInsertQueueApc == NULL\n");
    return;
  }
  //DbgPrint("开始inline hook -- KiInsertQueueApc\n");
  DbgPrint( "KiInsertQueueApc的地址t0x%08x\n", (ULONG)g_KiInsertQueueApc );
  DbgPrint( "fake_KiInsertQueueApc的地址t0x%08x\n", (ULONG)fake_KiInsertQueueApc );

  // 保存原函数的前字节内容
  RtlCopyMemory (g_OrigCode, (BYTE*)g_KiInsertQueueApc, 5);
  //jmp指令,此处为短跳,计算相对偏移,同时,jmp xxxxxx这条指令占了5个字节
  *( (ULONG*)(g_HookCode + 1) ) = (ULONG)fake_KiInsertQueueApc - (ULONG)g_KiInsertQueueApc - 5;
  // 禁止系统写保护,提升IRQL到DPC
  WPOFF();
  oldIrql = KeRaiseIrqlToDpcLevel();
  RtlCopyMemory ( (BYTE*)g_KiInsertQueueApc, g_HookCode, 5 );
  *( (ULONG*)(jmp_orig_code + 1) ) = (ULONG) ( (BYTE*)g_KiInsertQueueApc + 5 );
  RtlCopyMemory ( (BYTE*)Proxy_KiInsertQueueApc, g_OrigCode, 5);
  RtlCopyMemory ( (BYTE*)Proxy_KiInsertQueueApc + 5, jmp_orig_code, 7);
  // 恢复写保护,降低IRQL
  KeLowerIrql(oldIrql);
  WPON();
  g_bHooked = TRUE;
}
//
// 跳转到我们的函数里面进行预处理,裸函数,有调用者进行堆栈的平衡
//
__declspec (naked)
VOID
fake_KiInsertQueueApc (
            PKAPC Apc,
            KPRIORITY Increment
            )
{
  // 去掉DbgPrint,不然这个hook会产生递归
  //DbgPrint("inline hook -- KiInsertQueueApc 成功\n");
  __asm
  {
    jmp Proxy_KiInsertQueueApc
  }
}
//
// 代理函数,负责跳转到原函数中继续执行
//
__declspec (naked)
VOID
Proxy_KiInsertQueueApc (
            PKAPC Apc,
            KPRIORITY Increment
            )
{
  __asm { // 共字节
    _emit 0x90
      _emit 0x90
      _emit 0x90
      _emit 0x90
      _emit 0x90 // 前字节实现原函数的头字节功能
      _emit 0x90 // 这个填充jmp
      _emit 0x90
      _emit 0x90
      _emit 0x90
      _emit 0x90 // 这字节保存原函数+5处的地址
      _emit 0x90
      _emit 0x90 // 因为是长转移,所以必须是0x0080
  }
}
ULONG GetFunctionAddr( IN PCWSTR FunctionName)
{
  UNICODE_STRING UniCodeFunctionName;
  RtlInitUnicodeString( &UniCodeFunctionName, FunctionName );
  return (ULONG)MmGetSystemRoutineAddress( &UniCodeFunctionName );
}
//根据特征值,从KeInsertQueueApc搜索中搜索KiInsertQueueApc
ULONG FindKiInsertQueueApcAddress()
{
  char * Addr_KeInsertQueueApc = 0;
  int i = 0;
  char Findcode[] = { 0xE8, 0xcc, 0x29, 0x00, 0x00 };
  ULONG Addr_KiInsertQueueApc = 0;
  Addr_KeInsertQueueApc = (char *) GetFunctionAddr(L"KeInsertQueueApc");
  for(i = 0; i < 100; i ++)
  {
    if( Addr_KeInsertQueueApc[i] == Findcode[0] &&
      Addr_KeInsertQueueApc[i + 1] == Findcode[1] &&
      Addr_KeInsertQueueApc[i + 2] == Findcode[2] &&
      Addr_KeInsertQueueApc[i + 3] == Findcode[3] &&
      Addr_KeInsertQueueApc[i + 4] == Findcode[4]
    )
    {
      Addr_KiInsertQueueApc = (ULONG)&Addr_KeInsertQueueApc[i] + 0x29cc + 5;
      break;
    }
  }
  return Addr_KiInsertQueueApc;
}
VOID OnUnload( IN PDRIVER_OBJECT DriverObject )
{
  DbgPrint("My Driver Unloaded!");
  UnHookKiInsertQueueApc();
}
NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING theRegistryPath )
{
  DbgPrint("My Driver Loaded!");
  theDriverObject->DriverUnload = OnUnload;
  g_KiInsertQueueApc = FindKiInsertQueueApcAddress();
  HookKiInsertQueueApc();
  return STATUS_SUCCESS;
}
(0)

相关推荐

  • 详解C++中的inline用法

    1. 引入inline关键字的原因 在c/c++中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了inline修饰符,表示为内联函数. 栈空间就是指放置程序的局部数据(也就是函数内数据)的内存空间. 在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足而导致程序出错的问题,如,函数的死循环递归调用的最终结果就是导致栈内存空间枯竭. 下面我们来看一个例子: #include <stdio.h> //函数定义为inline即:内联函数 inline char*

  • C/C++中static,const,inline三种关键字详细总结

    一.关于staticstatic 是C++中很常用的修饰符,它被用来控制变量的存储方式和可见性,下面我将从 static 修饰符的产生原因.作用谈起,全面分析static 修饰符的实质. static 的两大作用: 一.控制存储方式 static被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间. 引出原因:函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,大家知道,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保

  • c++中inline的用法分析

    在函数声明或定义中函数返回类型前加上关键字inline即把min()指定为内联. inline int min(int first, int secend) {/****/};inline函数对编译器而言必须是可见的,以便它能够在调用点内展开该函数.与非inline函数不同的是,inline函数必须在调用该函数的每个文本文件中定义.当然,对于同一程序的不同文件,如果inline函数出现的话,其定义必须相同.对于由两个文件compute.C和draw.C构成的程序来说,程序员不能定义这样的min(

  • c++内联函数(inline)使用详解

    介绍内联函数之前,有必要介绍一下预处理宏.内联函数的功能和预处理宏的功能相似.相信大家都用过预处理宏,我们会经常定义一些宏,如 复制代码 代码如下: #define TABLE_COMP(x) ((x)>0?(x):0) 就定义了一个宏. 为什么要使用宏呢?因为函数的调用必须要将程序执行的顺序转移到函数所存放在内存中的某个地址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方.这种转移操作要求在转去执行前要保存现场并记忆执行的地址,转回后要恢复现场,并按原来保存地址继续执行.因此,函数调

  • C++中inline函数详解

    本文主要记录了C++中的inline函数,也就是内联函数,主要记录了以下几个问题: 一.C++为什么引入inline函数? 主要目的:用它代替C语言中表达式形式的宏定义来解决程序中函数调用的效率问题. C语言中的宏定义:#define ExpressionName(var1,var2) (var1+var2)*(var1-var2)这种宏定义,它使用预处理器实现,没有了参数压栈.代码生成等一系列得到操作,因此效率很高.但缺点如下: 仅仅是做预处理器符号表中的简单替换,因此不能进行参数有效性的检测

  • c++ 尽量不要使用#define 而是用const、enum、inline替换。

    例如:这里程序文件开头有如下#define语句 复制代码 代码如下: #define N 10 #define PI 3.14 #define MAX 10000 #define Heigth 6.65 ... ... 假设这里程序运行出错误,而且就是在我们使用这些常量有错误,此时编辑器应该会抛出错误信息.如果该信息提示6.65这里有错误,Ok如果你运气好你正好记得或者程序简单一眼能找到6.65表示什么,如果程序很复杂,而且报出6.65的文件是引用该文件,不记得,那么你会困惑这是什么?或许会花大

  • C++ 关键字 inline详细介绍

    1.  内联函数 在C++中我们通常定义以下函数来求两个整数的最大值: 复制代码 代码如下: int max(int a, int b){ return a > b ? a : b;} 为这么一个小的操作定义一个函数的好处有: ① 阅读和理解函数 max 的调用,要比读一条等价的条件表达式并解释它的含义要容易得多 ② 如果需要做任何修改,修改函数要比找出并修改每一处等价表达式容易得多 ③ 使用函数可以确保统一的行为,每个测试都保证以相同的方式实现 ④ 函数可以重用,不必为其他应用程序重写代码 虽

  • C++实现inline hook的原理及应用实例

    本文实例简述了C++实现inline hook的原理及应用,对于大家更好的理解inline hook原理及其应用有很大的帮助.具体内容如下: 一.Inline Hook简介: 1.INLINE HOOK原理: Inline Hook通过硬编码的方式向内核API的内存空间(通常是开始的一段字节,且一般在第一个call之前,这么做是为了防止堆栈混乱)写入跳转语句,这样,该API只要被调用,程序就会跳转到我们的函数中来,我们在自己写的函数里需要完成3个任务: 1)重新调整当前堆栈.程序流程在刚刚跳转的

  • Inline Hook(ring3)的简单C++实现方法

    C++的Inline Hook代码,采用了备份dll的方法,因此在自定义的函数中可以直接调用在内存中备份的dll代码,而不需要把函数头部改来改去.用SetWindowsHookEx程序的稳定性应该会增加许多. 需要注意的是,例子中没有把原函数的头部几个字节改回去是因为,程序很简单,仅仅测试了效果后便可以退出,没有其他的功能.实际应用中,还要在你注入的dll模块卸载时,把原函数的头几个字节改回去,以免影响到程序继续运行的稳定性.(因为注入的程序不是自己的,我们当然不可能知道它到底在何时.有多少个我

  • 代码实例分析android中inline hook

    以下内容通过1.实现目标注入程序,2.实现主程序,3.实现注入函数,4.thumb指令集实现等4个方面详细分析了android中inline hook的用法,以下是全部内容: 最近终于沉下心来对着书把hook跟注入方面的代码敲了一遍,打算写几个博客把它们记录下来. 第一次介绍一下我感觉难度最大的inline hook,实现代码参考了腾讯GAD的游戏安全入门. inline hook的大致流程如下: 首先将目标指令替换为跳转指令,跳转地址为一段我们自己编写的汇编代码,这段汇编代码先是执行用户指定的

  • php中钩子(hook)的原理与简单应用demo示例

    本文实例讲述了php中钩子(hook)的原理与简单应用.分享给大家供大家参考,具体如下: 我们先来回顾下原本的开发流程; 产品汪搞出了一堆需求; 当用户注册成功后需要发送短信.发送邮件等等; 然后聪明机智勇敢的程序猿们就一扑而上; 把这些需求转换成代码扔在 用户注册成功 和 跳转到首页 之间; 没有什么能够阻挡;充满创造力的猿们; <?php class Test{ public function index(){ // 用户注册成功 /* 此处是一堆发送短信的代码 */ /* 此处是一堆发送邮

  • Java设计模式之策略模式原理与用法实例详解

    本文实例讲述了Java设计模式之策略模式原理与用法.分享给大家供大家参考,具体如下: 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换.策略模式让算法独立于使用它的客户而独立变化.其中JDK里面的TreeSet类和TreeMap类就用到了策略模式.这两个类是带排序的集合类,其中排序的规则就相当于策略模式里定义的一系列算法,而集合类就相当于是策略模式里的环境类,供用户使用,用只知道TreeSet和TreeMap是带排序的,至于怎么排序的,是由排序的算法决定的. 策略模式

  • Java设计模式之装饰模式原理与用法实例详解

    本文实例讲述了Java设计模式之装饰模式原理与用法.分享给大家供大家参考,具体如下: 装饰模式能在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能.它是通过创建一个包装对象,也就是装饰来包裹真实的对象.JDK中IO的设计就用到了装饰模式,通过过滤流对节点流进行包装来实现功能的扩展. 装饰模式的角色的组成: ① 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加工功能的对象.(InputStream.OutputStream) ② 具体构件(Concrete Co

  • Spring-boot原理及spring-boot-starter实例和代码

    Spring-boot自出现后,到现在火的很,大家貌似都在用,连招聘里面也要求会这个.但是说实话,spring-boot无外乎想实现一种可插拔的编程方式,说是简化配置,其实并没有减少多少,只不过把配置落到代码里面,通过各种annotation来标示了. 作为个人其实不太愿意团队用spring-boot这种层层封装的方式,各种jar铺天盖地,最终大家都被绑架了,自己底层的能力不一定有什么提高. spring-boot的starter又是个什么鬼? 一句话,spring-boot的starter是一

  • 提升python处理速度原理及方法实例

    这篇文章主要介绍了提升python处理速度原理及方法实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 导读:作为日常生产开发中非常实用的一门语言,python广泛应用于网络爬虫.web开发.自动化测试.数据分析和人工智能等领域.但python是单线程的,想要提升python的处理速度,涉及到一个很关键的技术--协程.本篇文章,将讲述python协程的理解与使用. 1.操作系统相关概念 在理解与使用协程之前,先简单的了解几个与操作系统相关的概念

  • Android Handler 原理分析及实例代码

    Android Handler 原理分析 Handler一个让无数android开发者头疼的东西,希望我今天这边文章能为您彻底根治这个问题 今天就为大家详细剖析下Handler的原理 Handler使用的原因 1.多线程更新Ui会导致UI界面错乱 2.如果加锁会导致性能下降 3.只在主线程去更新UI,轮询处理 Handler使用简介 其实关键方法就2个一个sendMessage,用来接收消息 另一个是handleMessage,用来处理接收到的消息 下面是我参考疯狂android讲义,写的一个子

  • Android编程之杀毒的实现原理及具体实例

    本文实例讲述了Android杀毒的实现原理.分享给大家供大家参考,具体如下: 一个杀毒软甲最核心的部分一个是病毒库一个是杀毒引擎,病毒库从服务器中获得,杀毒引擎实际上是判断程序中的包名和签名是否匹配病毒库中的包名和签名,如果匹配则为病毒,界面使用帧动画来显示. 思路: 1.从服务器端把病毒的版本库信息下载下来将解析的数据存放到List集合中 2.获取到手机中所有应用程序的包名以及程序的签名 3.将病毒库匹配手机应用程序包名及签名 4.用ScrollView标签进行自动滚动显示 关键代码如下: 特

随机推荐