Windows内核实验005 Inline Hook
文章目录
- 准备工作
- 寻找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相关推荐
- Windows内核实验001 中断提权
文章目录 实验环境 内核提权 IDT的基本知识 什么是中断 什么是IDT表 在PC Hunter中查看IDT表 中断提权的基本原理 写一个三环的小程序 修改IDT表 提权测试 本篇文章基于周壑老师的讲 ...
- Windows内核实验004 API调用
文章目录 完善代码 内核API调用 修复一个潜在问题 复现问题 完整代码 前面几次实验我们已经完成了一个三环的程序调用零环API的必要条件. 提升到零环权限 使fs指向KPCR 完善代码 这次我们去掉 ...
- Windows内核实验003 再次回到中断
文章目录 两个实验 死循环 开启中断后的死循环 KiFastCallEntry 调用零环API的两个条件 分析KiFastCallEntry 什么是KPCR 完善代码 完整代码 之前的实验我们已经实现 ...
- Windows内核实验002 中断现场
文章目录 如何获取中断现场环境 中段现场环境 观察中断现场堆栈环境 观察中断现场的寄存器环境 段选择子 段寄存器结构 变化的段寄存器的具体含义 遗留问题:SS段寄存器和栈顶指针来自于哪? 什么是TSS ...
- Windows 内核之双机调试与windbg命令大全
在今后会有相当的实验环节,对于windows内核实验,调试环境是必不可少的,本章讲解双机调试的环境搭建与常见的WINDBG指令. 准备材料: VMware workstation : [https:/ ...
- Windows内核API HOOK 之 Inline Hook
来源:CSDN 作者:daiwen 名字起得好,Inline hook,乍一听,似乎很高深.此处的Inline,我以为,意指将汇编代码直接写入内核API的内存区域.Inline Hook不像用户态 ...
- Windows驱动开发学习笔记(六)—— Inline HOOK
Windows驱动开发学习笔记(六)-- Inline HOOK SSDT HOOK Inline Hook 挂钩 执行流程 脱钩 实验一:3环 Inline Hook 实验二:0环 Inline H ...
- 学习windows 应用层 inline hook 原理总结
inline hook 实际上就是指 通过改变目标函数头部的代码来使改变后的代码跳转到我们自己设置的一个函数里,产生hook. 今天就拿MessageBoxA这个api函数来做实验.功能就是当程序调用 ...
- C/C++:Windows编程—Inline Hook内联钩子(上)
前言 先介绍下Windows中的Hook技术.Hook是Windows中提供的一种用以替换DOS下"中断"的系统机制,中文译为"挂钩"或"钩子&quo ...
最新文章
- 人工智能 | 自动驾驶与人工智能前沿研究报告(概念篇)
- boost::type_erasure相关的测试程序
- 信息学奥赛一本通C++语言——1016: 整型数据类型存储空间大小
- 铁甲雄心机器人建造成本_铁甲雄心最强机器人
- windows版本redis搭建集群步骤
- GAN与自动编码器:深度生成模型的比较
- 数理统计复习笔记二——充分统计量
- 今日恐慌与贪婪指数为22 恐慌程度有所缓解
- 寓教于乐:12个学习编程的游戏化平台
- React18 tearing tree issue 撕裂状态问题
- python文字语音互转
- 外服游戏服务器如何显示中文,避免国外服务器出现乱码的办法
- Angular动态绑定HTML文本
- java对word文档的操作
- DC(Design Compiler)使用说明
- win10右键打不开显示设置和个性化的解决教程
- 定制Xposed框架(干货)
- UE4中的GameMode、GameState、GameInstance
- 完全数,丰沛数,不足数
- 函数中的形式参数和实际参数
热门文章
- Personal preference
- 成功解决graphviz\backend.py, line 162, in pipe raise ExecutableNotFound(args) graphviz.backend.Executab
- 风控业务中的信用与欺诈的定义区别
- Python基础教程【读书笔记】 - 2016/7/5
- iOS开发UI篇—UITableview控件使用小结
- SRM596 DIV2 1000
- 一步一步深入spring(6)--使用基于XML配置的spring实现的AOP
- 手机PIN锁死让输入PUK解决方案
- 《研磨设计模式》chap21 解释器模式Interpreter(1)模式介绍
- 【django】路由传递参数