驱动开发:内核层InlineHook挂钩函数

简介: 在上一章`《驱动开发:内核LDE64引擎计算汇编长度》`中,`LyShark`教大家如何通过`LDE64`引擎实现计算反汇编指令长度,本章将在此基础之上实现内联函数挂钩,内核中的`InlineHook`函数挂钩其实与应用层一致,都是使用`劫持执行流`并跳转到我们自己的函数上来做处理,唯一的不同的是内核`Hook`只针对`内核API`函数,但由于其身处在`最底层`所以一旦被挂钩其整个应用层都将会受到影响,这就直接决定了在内核层挂钩的效果是应用层无法比拟的,对于安全从业者来说学会使用内核挂钩也是很重要。

在上一章《驱动开发:内核LDE64引擎计算汇编长度》中,LyShark教大家如何通过LDE64引擎实现计算反汇编指令长度,本章将在此基础之上实现内联函数挂钩,内核中的InlineHook函数挂钩其实与应用层一致,都是使用劫持执行流并跳转到我们自己的函数上来做处理,唯一的不同的是内核Hook只针对内核API函数,但由于其身处在最底层所以一旦被挂钩其整个应用层都将会受到影响,这就直接决定了在内核层挂钩的效果是应用层无法比拟的,对于安全从业者来说学会使用内核挂钩也是很重要。

挂钩的原理可以总结为,通过MmGetSystemRoutineAddress得到原函数地址,然后保存该函数的前15个字节的指令,将自己的MyPsLookupProcessByProcessId代理函数地址写出到原始函数上,此时如果有API被调用则默认会转向到我们自己的函数上面执行,恢复原理则是将提前保存好的前15个原始字节写回则恢复原函数的调用。

原理很简单,基本上InlineHook类的代码都是一个样子,如下是一段完整的挂钩PsLookupProcessByProcessId的驱动程序,当程序被加载时则默认会保护lyshark.exe进程,使其无法被用户使用任务管理器结束掉。

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com
#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.com \n");

    // 初始化反汇编引擎
    lde_init();

    // 设置需要保护进程EProcess
    /*
    lyshark.com: 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;
}

运行这段驱动程序,会输出挂钩保护的具体地址信息;

image.png

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

image.png

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

image.png

参考文献

https://www.docin.com/p-1508418694.html

目录
相关文章
|
5月前
|
监控 安全 API
5.9 Windows驱动开发:内核InlineHook挂钩技术
在上一章`《内核LDE64引擎计算汇编长度》`中,`LyShark`教大家如何通过`LDE64`引擎实现计算反汇编指令长度,本章将在此基础之上实现内联函数挂钩,内核中的`InlineHook`函数挂钩其实与应用层一致,都是使用`劫持执行流`并跳转到我们自己的函数上来做处理,唯一的不同的是内核`Hook`只针对`内核API`函数,但由于其身处在`最底层`所以一旦被挂钩其整个应用层都将会受到影响,这就直接决定了在内核层挂钩的效果是应用层无法比拟的,对于安全从业者来说学会使用内核挂钩也是很重要。
44 1
5.9 Windows驱动开发:内核InlineHook挂钩技术
|
5月前
|
监控 安全 API
7.6 Windows驱动开发:内核监控FileObject文件回调
本篇文章与上一篇文章`《内核注册并监控对象回调》`所使用的方式是一样的都是使用`ObRegisterCallbacks`注册回调事件,只不过上一篇博文中`LyShark`将回调结构体`OB_OPERATION_REGISTRATION`中的`ObjectType`填充为了`PsProcessType`和`PsThreadType`格式从而实现监控进程与线程,本章我们需要将该结构填充为`IoFileObjectType`以此来实现对文件的监控,文件过滤驱动不仅仅可以用来监控文件的打开,还可以用它实现对文件的保护,一旦驱动加载则文件是不可被删除和改动的。
31 1
7.6 Windows驱动开发:内核监控FileObject文件回调
|
11月前
驱动开发:摘除InlineHook内核钩子
在笔者上一篇文章`《驱动开发:内核层InlineHook挂钩函数》`中介绍了通过替换`函数`头部代码的方式实现`Hook`挂钩,对于ARK工具来说实现扫描与摘除`InlineHook`钩子也是最基本的功能,此类功能的实现一般可在应用层进行,而驱动层只需要保留一个`读写字节`的函数即可,将复杂的流程放在应用层实现是一个非常明智的选择,与`《驱动开发:内核实现进程反汇编》`中所使用的读写驱动基本一致,本篇文章中的驱动只保留两个功能,控制信号`IOCTL_GET_CUR_CODE`用于读取函数的前16个字节的内存,信号`IOCTL_SET_ORI_CODE`则用于设置前16个字节的内存。
212 0
|
11月前
|
Windows
驱动开发:内核扫描SSDT挂钩状态
在笔者上一篇文章`《驱动开发:内核实现SSDT挂钩与摘钩》`中介绍了如何对`SSDT`函数进行`Hook`挂钩与摘钩的,本章将继续实现一个新功能,如何`检测SSDT`函数是否挂钩,要实现检测`挂钩状态`有两种方式,第一种方式则是类似于`《驱动开发:摘除InlineHook内核钩子》`文章中所演示的通过读取函数的前16个字节与`原始字节`做对比来判断挂钩状态,另一种方式则是通过对比函数的`当前地址`与`起源地址`进行判断,为了提高检测准确性本章将采用两种方式混合检测。
179 0
|
12月前
|
芯片
LED驱动程序外部框架
LED驱动程序外部框架
57 0
|
API C++ Windows
驱动开发:内核封装WSK网络通信接口
本章`LyShark`将带大家学习如何在内核中使用标准的`Socket`套接字通信接口,我们都知道`Windows`应用层下可直接调用`WinSocket`来实现网络通信,但在内核模式下应用层API接口无法使用,内核模式下有一套专有的`WSK`通信接口,我们对WSK进行封装,让其与应用层调用规范保持一致,并实现内核与内核直接通过`Socket`通信的案例。
299 0
驱动开发:内核封装WSK网络通信接口
|
网络安全
驱动开发:内核封装TDI网络通信接口
在上一篇文章`《驱动开发:内核封装WSK网络通信接口》`中,`LyShark`已经带大家看过了如何通过WSK接口实现套接字通信,但WSK实现的通信是内核与内核模块之间的,而如果需要内核与应用层之间通信则使用TDK会更好一些因为它更接近应用层,本章将使用TDK实现,TDI全称传输驱动接口,其主要负责连接`Socket`和协议驱动,用于实现访问传输层的功能,该接口比`NDIS`更接近于应用层,在早期Win系统中常用于实现过滤防火墙,同样经过封装后也可实现通信功能,本章将运用TDI接口实现驱动与应用层之间传输字符串,结构体,多线程收发等技术。
279 0
驱动开发:内核封装TDI网络通信接口
|
监控
驱动开发:内核监控FileObject文件回调
本篇文章与上一篇文章`《驱动开发:内核注册并监控对象回调》`所使用的方式是一样的都是使用`ObRegisterCallbacks`注册回调事件,只不过上一篇博文中`LyShark`将回调结构体`OB_OPERATION_REGISTRATION`中的`ObjectType`填充为了`PsProcessType`和`PsThreadType`格式从而实现监控进程与线程,本章我们需要将该结构填充为`IoFileObjectType`以此来实现对文件的监控,文件过滤驱动不仅仅可以用来监控文件的打开,还可以用它实现对文件的保护,一旦驱动加载则文件是不可被删除和改动的。
323 0
驱动开发:内核监控FileObject文件回调
驱动开发:通过SystemBuf与内核层通信
内核层与应用层之间的数据交互是必不可少的部分,只有内核中的参数可以传递给用户数据才有意义,一般驱动多数情况下会使用`SystemBuf`缓冲区进行通信,也可以直接使用网络套接字实现通信,如下将简单介绍通过SystemBuf实现的内核层与应用层通信机制。
222 0
驱动开发:通过SystemBuf与内核层通信
驱动开发:内核通过PEB得到进程参数
PEB结构`(Process Envirorment Block Structure)`其中文名是进程环境块信息,进程环境块内部包含了进程运行的详细参数信息,每一个进程在运行后都会存在一个特有的PEB结构,通过附加进程并遍历这段结构即可得到非常多的有用信息。
403 0
驱动开发:内核通过PEB得到进程参数