文章目录

  • 准备工作
    • 寻找Inline Hook的返回地址
    • 编写代码
    • 动态变化的返回地址
    • JmpTargetAddr
  • Inline Hook基本框架
    • 示例代码
  • 实战HOOK KiTrap01
    • 无需计算偏移的Inline Hook方法
    • 示例代码

准备工作

寻找Inline Hook的返回地址

假设我们现在要HOOK KiFastCallEntry这个内核函数,让所有的程序在进入零环之前先跳到我们自己的代码。

但是会出现一个问题,我们可以从三环的地址跳到零环的地址空间,因为内核层的2GB内存是系统共用的。但是在Inline Hook返回的时候,是无法从零环返回到三环的。用户层的2GB空间每个进程都有一份,而这个函数会被所有进程频繁调用。

如果返回的时候,别的进程在调用这个函数,那么Inline Hook就会跳到那个进程的地址空间。

既然不能用三环的地址,那就只能找一块比较稳定的零环的内存区域让它返回。为了让代码减少不确定性,我们可以用GDTR中的未使用的内存区域。

接着用windbg查看一下gdt表

我们就使用80b95120这块未使用的GDT表的位置来存放hook的返回地址。在使用之前,需要先确认一下内存属性是否是可读可写可执行。

kd> !pte 80b95120VA 80b95120
PDE at C0602028            PTE at C0405CA8
contains 0000000000193063  contains 0000000000B95163
pfn 193       ---DA--KWEV  pfn b95       -G-DA--KWEV

可以看到这块内存目前是可读可写可执行的。

那么我们就可以将自己的代码复制到这块内存空间中,然后让KiFastCallEntry跳转到80b95120的位置执行自己的代码,执行完成之后再返回。

编写代码

首先查看一下_KiFastCallEntry的函数地址(我的pdb符号文件下载失败,只能用windbg看要挂钩的函数地址了)

kd> x nt!_KiFastCallEntry
83e7c0c0          nt!KiFastCallEntry (<no parameter info>)

接着查看一下反汇编

kd> u 83e7c0c0
nt!KiFastCallEntry:
83e7c0c0 b923000000      mov     ecx,23h
83e7c0c5 6a30            push    30h
83e7c0c7 0fa1            pop     fs
83e7c0c9 8ed9            mov     ds,cx
83e7c0cb 8ec1            mov     es,cx
83e7c0cd 648b0d40000000  mov     ecx,dword ptr fs:[40h]
83e7c0d4 8b6104          mov     esp,dword ptr [ecx+4]
83e7c0d7 6a23            push    23h

可以发现第一行刚好是五个字节,那么我们就可以在这里下钩子跳转到之前准备好的地址,然后再跳回到下一行地址83e7c0c5处

接着我们来计算偏移,用要跳转的目标地址83e7c0c5减去起始地址80b95120再减5=0x32E6FA0

接着准备一个数组,将准备好的数据放进数组里,然后拷贝到GDT表的跳转地址中

char code[64] =
{0xb9,0x23,0x00,0x00,0x00,          //mov     ecx,23h0xE9,0xA0,0x6F,0x2E,0x03           //E9 0x32E6FA0
};

然后我们还需要知道code数组的地址,直接在VS中下断点查看即可。我这里的code数组的地址是0x403018。接着将数组循环拷贝到要HOOK的目标地址中。

p = (char*)0x80b95120;for (i=0;i<10;i++){*p = code[i];p++;}

动态变化的返回地址

接着我们运行写好的程序,然后查看一下被修改地址处的反汇编

kd> u 80b95120
80b95120 b923000000      mov     ecx,23h
80b95125 e9a06f2e03      jmp     nt!KiFastCallEntry+0xa (83e7c0ca)
80b9512a b980000000      mov     ecx,80h
80b9512f 0038            add     byte ptr [eax],bh
80b95131 51              push    ecx
80b95132 b980000000      mov     ecx,80h
80b95137 004051          add     byte ptr [eax+51h],al
80b9513a b980000000      mov     ecx,80h
//起始地址(GDT表空闲地址):80b95120
//HOOK的函数地址(KiFastCallEntry):0x83e7c0c0
//返回地址:83e7c0c5

我们发现mov ecx,23这句是对的,但是jmp跳转的目标地址居然是错误的,相差了五个字节。

原因在于起始地址(GDT表空闲地址):80b95120被Code数组的前四个字节被恢复寄存器环境的mov ecx,0x23填充了,导致代码的位置也相对的往后移了五个字节。这就产生了一个问题,我们每次去HOOK新函数的时候,都要手动将偏移值重新计算一遍,这显然是不可取的。

JmpTargetAddr

既然如此我们就来手动实现一个跳转到目标地址的函数。借用一个寄存器来保存需要跳转的地址,然后直接用jmp指令跳转过去。这样就可以省去每次手工计算偏移的步骤了。

我们利用83e840cd这个地址作为跳转的目标地址,然后用ecx来保存跳转地址。当跳转回83e840cd时,ecx会被重新赋值,不会出现任何问题。

代码如下:

void __declspec(naked) JmpTargetAddr()
{__asm{mov ecx, 0x23;        //保存现场环境push 0x30;          //保存现场环境pop fs;                 //保存现场环境mov ds, ecx;        //保存现场环境mov es, ecx;        //保存现场环境mov ecx, 0x83e840cd; //返回地址jmp ecx;             //跳转到返回地址}
}

运行程序,接着查看一下GDT表起始位置的反汇编

kd> u 80b95120
80b95120 b923000000      mov     ecx,23h
80b95125 6a30            push    30h
80b95127 0fa1            pop     fs
80b95129 668ed9          mov     ds,cx
80b9512c 668ec1          mov     es,cx
80b9512f b9cd40e883      mov     ecx,offset nt!KiFastCallEntry+0xd (83e840cd)
80b95134 ffe1            jmp     ecx
80b95136 cc              int     3
//起始地址(GDT表空闲地址):80b95120
//HOOK的函数地址(KiFastCallEntry):0x83e840c0
//返回地址:83e840cd

可以看到现在我们的跳转的目标地址就和预期写的是一致的了,没有出现之前的地址动态变化的问题。

Inline Hook基本框架

接下来我们要修改KiFastCallEntry函数的前五个字节,让它跳转到我们自己的地址。首先来查看一下前五个字节的页面属性

kd> x nt!_KiFastCallEntry
83e840c0          nt!KiFastCallEntry (<no parameter info>)
kd> !pte 83e840c0          VA 83e840c0
PDE at C06020F8            PTE at C041F420
contains 00000000001D9063  contains 0000000003E84121
pfn 1d9       ---DA--KWEV  pfn 3e84      -G--A--KREV

可以看到这一块内存是可读不可写的,这样的话我们需要先关掉CPU的写保护。代码如下:

 //关闭写保护__asm{mov eax, cr0;and eax, not 10000h;mov cr0, eax;}//开启写保护__asm{mov eax, cr0;              or eax, 10000h;             mov cr0, eax;                iretd;}

接着再修改目标函数的前五个字节,让它跳到我们之前准备好的地址

//偏移0xfcd1105b
char code[] = { 0xE9,0x5B,0x10,0xD1,0xFC };
//修改要HOOK的函数前五个字节
memcpy((void*)0x83e840c0, code, sizeof(code));

最后运行程序,并且在windbg中查看一下KiFastCallEntry处的代码

kd> x nt!_KiFastCallEntry
83e840c0          nt!KiFastCallEntry (<no parameter info>)
kd> u 83e840c0
nt!KiFastCallEntry:
83e840c0 e95b10d1fc      jmp     80b95120
83e840c5 6a30            push    30h
83e840c7 0fa1            pop     fs
83e840c9 8ed9            mov     ds,cx
83e840cb 8ec1            mov     es,cx
83e840cd 648b0d40000000  mov     ecx,dword ptr fs:[40h]
83e840d4 8b6104          mov     esp,dword ptr [ecx+4]
83e840d7 6a23            push    23h

可以看到现在这个函数已经成功跳到我们指定的GDT表的位置0x80b95120。接着再来看一下GDT表的位置0x80b95120处的代码。

kd> u 0x80b95120
80b95120 b923000000      mov     ecx,23h
80b95125 6a30            push    30h
80b95127 0fa1            pop     fs
80b95129 668ed9          mov     ds,cx
80b9512c 668ec1          mov     es,cx
80b9512f b9cd40e883      mov     ecx,offset nt!KiFastCallEntry+0xd (83e840cd)
80b95134 ffe1            jmp     ecx
80b95136 cc              int     3

现在我们已经完成了一个Inline Hook的基本框架,当有进程调用KiFastCallEntry时,会跳转到我们指定的函数地址,然后再返回。框架完成以后,在HOOK过程中想做什么事,就完全由自己决定了。

示例代码

示例代码如下:

#include "pch.h"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>void JmpTargetAddr();//偏移0xfcd1105b
char code[] = { 0xE9,0x5B,0x10,0xD1,0xFC };
int i;
char* p;//起始地址(GDT表空闲地址):80b95120
//HOOK的函数地址(KiFastCallEntry):0x83e840c0
//返回地址:83e7c0c5
void __declspec(naked) IdtEntry()
{//将自己的函数拷贝的GDT表空闲地址p = (char*)0x80b95120;for (i = 0; i < 64; i++){*p = ((char*)JmpTargetAddr)[i];p++;}//关闭写保护__asm{mov eax, cr0;and eax, not 10000h;mov cr0, eax;}//修改要HOOK的函数前五个字节memcpy((void*)0x83e840c0, code, sizeof(code));//开启写保护__asm{mov eax, cr0;              or eax, 10000h;             mov cr0, eax;                iretd;}
} void __declspec(naked) JmpTargetAddr()
{__asm{mov ecx, 0x23;        //恢复现场环境push 0x30;          //恢复现场环境pop fs;                 //恢复现场环境mov ds, cx;             //恢复现场环境mov es, cx;             //恢复现场环境mov ecx, 0x83e840cd; //返回地址jmp ecx;             //跳转到返回地址}
}void go()
{__asm int 0x20;
}//eq 80b95500 0040ee00`00081040
int main()
{if ((DWORD)IdtEntry != 0x401040){printf("wrong addr:%p", IdtEntry);exit(-1);}go();//printf("%p\n", g_num);system("pause");
}

实战HOOK KiTrap01

接着我们找另外一个函数进行HOOK。

查看PC Hunter中IDT表的1号中断函数,1号中断是单步调试,当CPU的eflags的TF标志位被置1时,就会产生一个单步中断,然后调用Debug函数。调试器的单步断点原理也在于此。

无需计算偏移的Inline Hook方法

之前我们在修改目标函数的前五个字节的时候需要计算跳转的偏移,这个非常不方便。这一次我们用另外的不需要计算偏移的方法来完成Inline Hook。利用下面两条汇编指令:

push 0x12345678;
ret;

这种方法的缺点就是需要HOOK的位置有6个字节的空间。

首先,查看一下要HOOK的函数地址反汇编

kd> x nt!_KiTrap01
83e85150          nt!KiTrap01 (<no parameter info>)
kd> u 83e85150
nt!KiTrap01:
83e85150 6a00            push    0
83e85152 66c74424020000  mov     word ptr [esp+2],0
83e85159 55              push    ebp
83e8515a 53              push    ebx
83e8515b 56              push    esi
83e8515c 57              push    edi
83e8515d 0fa0            push    fs
83e8515f bb30000000      mov     ebx,30h

我们将要HOOK的地址定为0x83e85152,返回地址则为它的下一句

首先修改JmpTargetAddr中的代码为跳回返回地址

void __declspec(naked) JmpTargetAddr()
{__asm{mov word ptr[esp + 2], 0;        //恢复现场环境push 0x83e85159;                //跳转到返回地址ret;                           //跳转到返回地址}
}

接着在返回之前打印出当前的ESP,查看一下产生单步异常的堆栈环境,同样需要借助一个内核的内存地址

push eax;
mov eax, ss:[esp];
mov ds : [0x80b953f0],eax;              //GDT表的空闲位置 用于保存内核变量
pop eax;

最后修改要HOOK的函数地址,跳转到我们自己的地址

char code[] = { 0x68,0x20,0x51,0xB9,0x80,0xC3,0x90 };
memcpy((void*)0x83e85152, code, sizeof(code));

运行程序,检查一下起始地址和被HOOK的函数地址的汇编代码,都是正常的

kd> x nt!_KiTrap01
83e85150          nt!KiTrap01 (<no parameter info>)
kd> u 83e85150
nt!KiTrap01:
83e85150 6a00            push    0
83e85152 682051b980      push    80B95120h
83e85157 c3              ret
83e85158 90              nop
83e85159 55              push    ebp
83e8515a 53              push    ebx
83e8515b 56              push    esi
83e8515c 57              push    edi
kd> u 80B95120
80b95120 50              push    eax
80b95121 368b0424        mov     eax,dword ptr ss:[esp]
80b95125 3ea3f053b980    mov     dword ptr ds:[80B953F0h],eax
80b9512b 58              pop     eax
80b9512c 66c74424020000  mov     word ptr [esp+2],0
80b95133 685951e883      push    offset nt!KiTrap01+0x9 (83e85159)
80b95138 c3              ret
80b95139 cc              int     3

另外在PC Hunter中也检测到我我们挂的钩子

示例代码

最后附上代码

#include "pch.h"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>void JmpTargetAddr();//偏移0xfcd1105b
char code[] = { 0x68,0x20,0x51,0xB9,0x80,0xC3,0x90 };
int i;
char* p;//起始地址(GDT表空闲地址):80b95120
//HOOK的函数地址(KiTrap01):0x83e85152
//返回地址:83e85159
void __declspec(naked) IdtEntry()
{//将自己的函数拷贝的GDT表空闲地址p = (char*)0x80b95120;for (i = 0; i < 64; i++){*p = ((char*)JmpTargetAddr)[i];p++;}//关闭写保护__asm{mov eax, cr0;and eax, not 10000h;mov cr0, eax;}memcpy((void*)0x83e85152, code, sizeof(code));//开启写保护__asm{mov eax, cr0;               or eax, 10000h;             mov cr0, eax;                iretd;}
} void __declspec(naked) JmpTargetAddr()
{__asm{push eax;mov eax, ss:[esp+4];mov ds : [0x80b953f0],eax;             //GDT表的空闲位置 用于保存内核变量pop eax;mov word ptr[esp + 2], 0;       //恢复现场环境push 0x83e85159;                //跳转到返回地址ret;                           //跳转到返回地址}
}void go()
{__asm int 0x20;
}//eq 80b95500 0040ee00`00081040
int main()
{if ((DWORD)IdtEntry != 0x401040){printf("wrong addr:%p", IdtEntry);exit(-1);}go();//printf("%p\n", g_num);system("pause");
}

Windows内核实验005 Inline Hook相关推荐

  1. Windows内核实验001 中断提权

    文章目录 实验环境 内核提权 IDT的基本知识 什么是中断 什么是IDT表 在PC Hunter中查看IDT表 中断提权的基本原理 写一个三环的小程序 修改IDT表 提权测试 本篇文章基于周壑老师的讲 ...

  2. Windows内核实验004 API调用

    文章目录 完善代码 内核API调用 修复一个潜在问题 复现问题 完整代码 前面几次实验我们已经完成了一个三环的程序调用零环API的必要条件. 提升到零环权限 使fs指向KPCR 完善代码 这次我们去掉 ...

  3. Windows内核实验003 再次回到中断

    文章目录 两个实验 死循环 开启中断后的死循环 KiFastCallEntry 调用零环API的两个条件 分析KiFastCallEntry 什么是KPCR 完善代码 完整代码 之前的实验我们已经实现 ...

  4. Windows内核实验002 中断现场

    文章目录 如何获取中断现场环境 中段现场环境 观察中断现场堆栈环境 观察中断现场的寄存器环境 段选择子 段寄存器结构 变化的段寄存器的具体含义 遗留问题:SS段寄存器和栈顶指针来自于哪? 什么是TSS ...

  5. Windows 内核之双机调试与windbg命令大全

    在今后会有相当的实验环节,对于windows内核实验,调试环境是必不可少的,本章讲解双机调试的环境搭建与常见的WINDBG指令. 准备材料: VMware workstation : [https:/ ...

  6. Windows内核API HOOK 之 Inline Hook

    来源:CSDN   作者:daiwen 名字起得好,Inline hook,乍一听,似乎很高深.此处的Inline,我以为,意指将汇编代码直接写入内核API的内存区域.Inline Hook不像用户态 ...

  7. Windows驱动开发学习笔记(六)—— Inline HOOK

    Windows驱动开发学习笔记(六)-- Inline HOOK SSDT HOOK Inline Hook 挂钩 执行流程 脱钩 实验一:3环 Inline Hook 实验二:0环 Inline H ...

  8. 学习windows 应用层 inline hook 原理总结

    inline hook 实际上就是指 通过改变目标函数头部的代码来使改变后的代码跳转到我们自己设置的一个函数里,产生hook. 今天就拿MessageBoxA这个api函数来做实验.功能就是当程序调用 ...

  9. C/C++:Windows编程—Inline Hook内联钩子(上)

    前言 先介绍下Windows中的Hook技术.Hook是Windows中提供的一种用以替换DOS下"中断"的系统机制,中文译为"挂钩"或"钩子&quo ...

最新文章

  1. 人工智能 | 自动驾驶与人工智能前沿研究报告(概念篇)
  2. boost::type_erasure相关的测试程序
  3. 信息学奥赛一本通C++语言——1016: 整型数据类型存储空间大小
  4. 铁甲雄心机器人建造成本_铁甲雄心最强机器人
  5. windows版本redis搭建集群步骤
  6. GAN与自动编码器:深度生成模型的比较
  7. 数理统计复习笔记二——充分统计量
  8. 今日恐慌与贪婪指数为22 恐慌程度有所缓解
  9. 寓教于乐:12个学习编程的游戏化平台
  10. React18 tearing tree issue 撕裂状态问题
  11. python文字语音互转
  12. 外服游戏服务器如何显示中文,避免国外服务器出现乱码的办法
  13. Angular动态绑定HTML文本
  14. java对word文档的操作
  15. DC(Design Compiler)使用说明
  16. win10右键打不开显示设置和个性化的解决教程
  17. 定制Xposed框架(干货)
  18. UE4中的GameMode、GameState、GameInstance
  19. 完全数,丰沛数,不足数
  20. 函数中的形式参数和实际参数

热门文章

  1. Personal preference
  2. 成功解决graphviz\backend.py, line 162, in pipe raise ExecutableNotFound(args) graphviz.backend.Executab
  3. 风控业务中的信用与欺诈的定义区别
  4. Python基础教程【读书笔记】 - 2016/7/5
  5. iOS开发UI篇—UITableview控件使用小结
  6. SRM596 DIV2 1000
  7. 一步一步深入spring(6)--使用基于XML配置的spring实现的AOP
  8. 手机PIN锁死让输入PUK解决方案
  9. 《研磨设计模式》chap21 解释器模式Interpreter(1)模式介绍
  10. 【django】路由传递参数