5.9 Windows驱动开发:内核InlineHook挂钩技术
在上一章《内核LDE64引擎计算汇编长度》中,LyShark教大家如何通过LDE64引擎实现计算反汇编指令长度,本章将在此基础之上实现内联函数挂钩,内核中的InlineHook函数挂钩其实与应用层一致,都是使用劫持执行流并跳转到我们自己的函数上来做处理,唯一的不同的是内核Hook只针对内核API函数,但由于其身处在最底层所以一旦被挂钩其整个应用层都将会受到影响,这就直接决定了在内核层挂钩的效果是应用层无法比拟的,对于安全从业者来说学会使用内核挂钩也是很重要。
内核挂钩的原理是一种劫持系统函数调用的技术,用于在运行时对系统函数进行修改或者监控。其基本思想是先获取要被劫持的函数的地址,然后将该函数的前15个字节的指令保存下来,接着将自己的代理函数地址写入到原始函数上,这样当API被调用时,就会默认转向到自己的代理函数上执行,从而实现函数的劫持。
挂钩的具体步骤如下:
- 1.使用
MmGetSystemRoutineAddress函数获取要被劫持的函数地址。 - 2.使用自己的代理函数取代原始函数,代理函数和原始函数具有相同的参数和返回值类型,并且在代理函数中调用原始函数。
- 3.保存原始函数的前
15个字节的指令,因为这些指令通常被认为是函数的前导码或是修饰码。 - 4.在原始函数的前15个字节位置写入
jmp MyPsLookupProcessByProcessId的指令,使得API调用会跳转到我们的代理函数。 - 5.当代理函数被调用时,执行我们自己的逻辑,然后在适当的时候再调用原始函数,最后将其返回值返回给调用者。
- 6.如果需要恢复原始函数的调用,将保存的前15个字节的指令写回原始函数即可。
挂钩的原理可以总结为,通过MmGetSystemRoutineAddress得到原函数地址,然后保存该函数的前15个字节的指令,将自己的MyPsLookupProcessByProcessId代理函数地址写出到原始函数上,此时如果有API被调用则默认会转向到我们自己的函数上面执行,恢复原理则是将提前保存好的前15个原始字节写回则恢复原函数的调用。
而如果需要恢复挂钩状态,则只需要还原提前保存的机器码即可,恢复内核挂钩的原理是将先前保存的原始函数前15个字节的指令写回到原始函数地址上,从而还原原始函数的调用。具体步骤如下:
- 1.获取原函数地址,可以通过
MmGetSystemRoutineAddress函数获取。 - 2.将保存的原始函数前15个字节的指令写回到原始函数地址上,可以使用
memcpy等函数实现。 - 3.将代理函数的地址清除,可以将地址设置为NULL。
原理很简单,基本上InlineHook类的代码都是一个样子,如下是一段完整的挂钩PsLookupProcessByProcessId的驱动程序,当程序被加载时则默认会保护lyshark.exe进程,使其无法被用户使用任务管理器结束掉。
#include "lyshark_lde64.h"
#include <ntifs.h>
#include <windef.h>
#include <intrin.h>
#pragma intrinsic(_disable)
#pragma intrinsic(_enable)
// --------------------------------------------------------------
// 汇编计算方法
// --------------------------------------------------------------
// 计算地址处指令有多少字节
// address = 地址
// bits 32位驱动传入0 64传入64
typedef INT(*LDE_DISASM)(PVOID address, INT bits);
LDE_DISASM lde_disasm;
// 初始化引擎
VOID lde_init()
{
lde_disasm = ExAllocatePool(NonPagedPool, 12800);
memcpy(lde_disasm, szShellCode, 12800);
}
// 得到完整指令长度,避免截断
ULONG GetFullPatchSize(PUCHAR Address)
{
ULONG LenCount = 0, Len = 0;
// 至少需要14字节
while (LenCount <= 14)
{
Len = lde_disasm(Address, 64);
Address = Address + Len;
LenCount = LenCount + Len;
}
return LenCount;
}
// --------------------------------------------------------------
// Hook函数封装
// --------------------------------------------------------------
// 定义指针方便调用
typedef NTSTATUS(__fastcall *PSLOOKUPPROCESSBYPROCESSID)(HANDLE ProcessId, PEPROCESS *Process);
ULONG64 protect_eprocess = 0; // 需要保护进程的eprocess
ULONG patch_size = 0; // 被修改了几个字节
PUCHAR head_n_byte = NULL; // 前几个字节数组
PVOID original_address = NULL; // 原函数地址
KIRQL WPOFFx64()
{
KIRQL irql = KeRaiseIrqlToDpcLevel();
UINT64 cr0 = __readcr0();
cr0 &= 0xfffffffffffeffff;
__writecr0(cr0);
_disable();
return irql;
}
VOID WPONx64(KIRQL irql)
{
UINT64 cr0 = __readcr0();
cr0 |= 0x10000;
_enable();
__writecr0(cr0);
KeLowerIrql(irql);
}
// 动态获取内存地址
PVOID GetProcessAddress(PCWSTR FunctionName)
{
UNICODE_STRING UniCodeFunctionName;
RtlInitUnicodeString(&UniCodeFunctionName, FunctionName);
return MmGetSystemRoutineAddress(&UniCodeFunctionName);
}
/*
InlineHookAPI 挂钩地址
参数1:待HOOK函数地址
参数2:代理函数地址
参数3:接收原始函数地址的指针
参数4:接收补丁长度的指针
返回:原来头N字节的数据
*/
PVOID KernelHook(IN PVOID ApiAddress, IN PVOID Proxy_ApiAddress, OUT PVOID *Original_ApiAddress, OUT ULONG *PatchSize)
{
KIRQL irql;
UINT64 tmpv;
PVOID head_n_byte, ori_func;
// 保存跳转指令 JMP QWORD PTR [本条指令结束后的地址]
UCHAR jmp_code[] = "\xFF\x25\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";
// 保存原始指令
UCHAR jmp_code_orifunc[] = "\xFF\x25\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";
// 获取函数地址处指令长度
*PatchSize = GetFullPatchSize((PUCHAR)ApiAddress);
// 分配空间
head_n_byte = ExAllocatePoolWithTag(NonPagedPool, *PatchSize, "LyShark");
irql = WPOFFx64();
// 跳转地址拷贝到原函数上
RtlCopyMemory(head_n_byte, ApiAddress, *PatchSize);
WPONx64(irql);
// 构建跳转
// 1.原始机器码+跳转机器码
ori_func = ExAllocatePoolWithTag(NonPagedPool, *PatchSize + 14, "LyShark");
RtlFillMemory(ori_func, *PatchSize + 14, 0x90);
// 2.跳转到没被打补丁的那个字节
tmpv = (ULONG64)ApiAddress + *PatchSize;
RtlCopyMemory(jmp_code_orifunc + 6, &tmpv, 8);
RtlCopyMemory((PUCHAR)ori_func, head_n_byte, *PatchSize);
RtlCopyMemory((PUCHAR)ori_func + *PatchSize, jmp_code_orifunc, 14);
*Original_ApiAddress = ori_func;
// 3.得到代理地址
tmpv = (UINT64)Proxy_ApiAddress;
RtlCopyMemory(jmp_code + 6, &tmpv, 8);
//4.打补丁
irql = WPOFFx64();
RtlFillMemory(ApiAddress, *PatchSize, 0x90);
RtlCopyMemory(ApiAddress, jmp_code, 14);
WPONx64(irql);
return head_n_byte;
}
/*
InlineHookAPI 恢复挂钩地址
参数1:被HOOK函数地址
参数2:原始数据
参数3:补丁长度
*/
VOID KernelUnHook(IN PVOID ApiAddress, IN PVOID OriCode, IN ULONG PatchSize)
{
KIRQL irql;
irql = WPOFFx64();
RtlCopyMemory(ApiAddress, OriCode, PatchSize);
WPONx64(irql);
}
// 实现我们自己的代理函数
NTSTATUS MyPsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process)
{
NTSTATUS st;
st = ((PSLOOKUPPROCESSBYPROCESSID)original_address)(ProcessId, Process);
if (NT_SUCCESS(st))
{
// 判断是否是需要保护的进程
if (*Process == (PEPROCESS)protect_eprocess)
{
*Process = 0;
DbgPrint("[lyshark] 拦截结束进程 \n");
st = STATUS_ACCESS_DENIED;
}
}
return st;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint("驱动已卸载 \n");
// 恢复Hook
KernelUnHook(GetProcessAddress(L"PsLookupProcessByProcessId"), head_n_byte, patch_size);
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark \n");
// 初始化反汇编引擎
lde_init();
// 设置需要保护进程EProcess
/*
lyshark: kd> !process 0 0 lyshark.exe
PROCESS ffff9a0a44ec4080
SessionId: 1 Cid: 05b8 Peb: 0034d000 ParentCid: 13f0
DirBase: 12a7d2002 ObjectTable: ffffd60bc036f080 HandleCount: 159.
Image: lyshark.exe
*/
protect_eprocess = 0xffff9a0a44ec4080;
// Hook挂钩函数
head_n_byte = KernelHook(GetProcessAddress(L"PsLookupProcessByProcessId"), (PVOID)MyPsLookupProcessByProcessId, &original_address, &patch_size);
DbgPrint("[lyshark] 挂钩保护完成 --> 修改字节: %d | 原函数地址: 0x%p \n", patch_size, original_address);
for (size_t i = 0; i < patch_size; i++)
{
DbgPrint("[byte] = %x", head_n_byte[i]);
}
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
运行这段驱动程序,会输出挂钩保护的具体地址信息;

使用WinDBG观察,会发现挂钩后原函数已经被替换掉了,而被替换的地址就是我们自己的MyPsLookupProcessByProcessId函数。

当你尝试使用任务管理器结束掉lyshark.exe进程时,则会提示拒绝访问。

5.9 Windows驱动开发:内核InlineHook挂钩技术的更多相关文章
- Windows驱动开发-内核常用内存函数
搞内存常用函数 C语言 内核 malloc ExAllocatePool memset RtlFillMemory memcpy RtlMoveMemory free ExFreePool
- Windows驱动开发(中间层)
Windows驱动开发 一.前言 依据<Windows内核安全与驱动开发>及MSDN等网络质料进行学习开发. 二.初步环境 1.下载安装WDK7.1.0(WinDDK\7600.16385 ...
- [Windows驱动开发](一)序言
笔者学习驱动编程是从两本书入门的.它们分别是<寒江独钓——内核安全编程>和<Windows驱动开发技术详解>.两本书分别从不同的角度介绍了驱动程序的制作方法. 在我理解,驱动程 ...
- windows驱动开发推荐书籍
[作者] 猪头三 个人网站 :http://www.x86asm.com/ [序言] 很多人都对驱动开发有兴趣,但往往找不到正确的学习方式.当然这跟驱动开发的本土化资料少有关系.大多学的驱动开发资料都 ...
- windows 驱动开发入门——驱动中的数据结构
最近在学习驱动编程方面的内容,在这将自己的一些心得分享出来,供大家参考,与大家共同进步,本人学习驱动主要是通过两本书--<独钓寒江 windows安全编程> 和 <windows驱动 ...
- Windows驱动——读书笔记《Windows驱动开发技术详解》
=================================版权声明================================= 版权声明:原创文章 谢绝转载 请通过右侧公告中的“联系邮 ...
- Windows驱动开发-IRP的完成例程
<Windows驱动开发技术详解 >331页, 在将IRP发送给底层驱动或其他驱动之前,可以对IRP设置一个完成例程,一旦底层驱动将IRP完成后,IRP完成例程立刻被处罚,通过设置完成例程 ...
- C++第三十八篇 -- 研究一下Windows驱动开发(二)--WDM式驱动的加载
基于Windows驱动开发技术详解这本书 一.简单的INF文件剖析 INF文件是一个文本文件,由若干个节(Section)组成.每个节的名称用一个方括号指示,紧接着方括号后面的就是节内容.每一行就是一 ...
- C++第三十三篇 -- 研究一下Windows驱动开发(一)内部构造介绍
因为工作原因,需要做一些与网卡有关的测试,其中涉及到了驱动这一块的知识,虽然程序可以运行,但是不搞清楚,心里总是不安,觉得没理解清楚.因此想看一下驱动开发.查了很多资料,看到有人推荐Windows驱动 ...
- Windows 驱动开发 - 5
上篇<Windows 驱动开发 - 4>我们已经完毕了硬件准备. 可是我们还没有详细的数据操作,比如接收读写操作. 在WDF中进行此类操作前须要进行设备的IO控制,已保持数据的完整性. 我 ...
随机推荐
- 🤗 Datasets 使用小贴士: 探索解决数据集无法下载的问题
Hugging Face Hub 是强大的机器学习的数据源.相信大家在国内用 Hugging Face Datasets 都遇到过下载的问题.譬如: import datasets dataset = ...
- 转载--阿里云ECS自建K8S集群
一.概述(官方建议) 集群规划 目前在创建Kubernetes集群时,存在着使用很多小规格ECS的现象,这样做有以下弊端: 小规格Woker ECS的网络资源受限. 如果一个容器基本可以占用一个小规格 ...
- Problem 330A - Cakeminator (思维)
330A. Cakeminator https://codeforces.com/problemset/problem/330/A 题意很容易理解:给定一块蛋糕区域,但蛋糕上有几个不能吃的草莓,大胃王 ...
- 嵌入式数据库H2作为服务端
H2数据库一般情况都是作为嵌入式的数据库服务,不需要多个应用连接同一个h2的服务,但有的情况下需要他像oralce那样提供数据服务让多个应用访问. 一.环境准备(linux) 1.其他环境下生成的H2 ...
- python之logging日志
一.logging介绍: 使用 logging.debug(text)来打印信息,info等的使用方法与debug一致,都只有一个位置参数 默认日志界别为:会输出warning以上的信息,代码示例: ...
- python常见面试题讲解(十一)字符串反转-五种解法
题目描述 写出一个程序,接受一个字符串,然后输出该字符串反转后的字符串.(字符串长度不超过1000) 输入描述: 输入N个字符 输出描述: 输出该字符串反转后的字符串 示例1 输入 abcd 输出 d ...
- LLM面面观之Prefix LM vs Causal LM
1. 背景 关于Prefix LM和Causal LM的区别,本qiang在网上逛了一翻,发现多数客官只给出了结论,但对于懵懵的本qiang,结果仍是懵懵... 因此,消遣了多半天,从原理及出处,交出 ...
- Python追踪内存占用
技术背景 当我们需要对python代码所占用的内存进行管理时,首先就需要有一个工具可以对当前的内存占用情况进行一个追踪.虽然在Top界面或者一些异步的工具中也能够看到实时的内存变化,还有一些工具可以统 ...
- 14-TTL与非门的输入特性和输出特性
TTL与非门的电压传输特性 传输特性 输入电压连续发生变化,输出电压发生什么变化?需要研究输出电压与输入电压之间的关系 输入小的时候,输出大的信号:输入大时候输出小信号 中间有截止和导通,需要过渡过程 ...
- 程序&命名-执行环境
开发程序执行环境 系统级别 -- 编译器或解释器 程序级别 -- 命令行参数.配置文件 执行级别 -- 进程.线程.协程运行时上下文(树(命名空间 -- 函数-局部变量.包或模块-全局变量)) 命令行 ...