从hook开始聊聊那些windows内核数据结构

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从hook开始聊聊那些windows内核数据结构相关的知识,希望对你有一定的参考价值。

总览:

IAT HOOK Object Hook Ssdt Hook
源码 内核知识及源码 内核知识级源码

一、IAT HOOK:
因为上一篇博客对已经对IAT Hook基本流程及作用进行了介绍,希望能先学懂PE再来看IATHook.下面贴上Iathook的源码,源码中有详细的注释,还记着为什么不能结束360的进程吗?参考思路如下图(因为写代码的时候解决方案写到了源码中,不粘贴复制过来了):
技术分享图片

以下代码是DLL注入+iathook,通过测试procexp中的kill功能并没有使用OpenProcess函数,所以需要逆向看一看他是如何结束的进程,下回与大家一起讨论,64的注入是成功,但是记得要改DWORD等32位整型变量,整体思路是不变的。
代码中有ZwCreateThreadEx注入,是更底层的函数,大家如果注入系统进程失败,可以把注释打开使用ZwCreateThreadEx进行注入,测试没问题
声明:

// 因为ZwCreateThreadEx没有定义,所以自己定义一个伪函数
typedef DWORD(WINAPI* FnZwCreateThreadEx)(PHANDLE ThreadHandle,
    ACCESS_MASK DesiredAccess,
    LPVOID ObjectAttributes,
    HANDLE ProcessHandle,
    LPTHREAD_START_ROUTINE lpStartAddress,
    LPVOID lpParameter,
    ULONG CreateThreadFlags,
    SIZE_T ZeroBits,
    SIZE_T StackSize,
    SIZE_T MaximunStackSize,
    LPVOID pUnkown);
FnZwCreateThreadEx MyZwCreateThreadEx;

// DLL的路径
    const char DllPath[MAX_PATH] = { "C:\Users\Administrator\documents\visual studio 2013\Projects\Text\Debug\TerminateProcessHook.dll" };

    const char DllPath1[MAX_PATH] = { "C:\Users\Administrator\documents\visual studio 2013\Projects\Text\Debug\HookDll.dll" };

    // 需要声明的变量
    HANDLE hProc = NULL;

    HANDLE RemoteHandle = NULL;

注入源码:
这是写在一个按钮响应消息里面的代码,远程线程挂起没有测试,如果不行可以删除。


    HMODULE hNtdHandle = LoadLibrary(L"ntdll.dll");

    // 获取地址给伪函数(因为ZwCreateThreadEx没有声明)
    MyZwCreateThreadEx = (FnZwCreateThreadEx)GetProcAddress(hNtdHandle, "ZwCreateThreadEx");

    // 其实没必要这样写,不过更为规范一些
    auto pFinAddress = GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "LoadLibraryA");

        // 第一次点击按钮,开启保护(只执行一次), 第二次点击按钮会挂起远程线程(暂停保护),第三次会在恢复......
    if ((IntHookFlag == FALSE) && (IntHookFlag == TRUE))
    {
        IntHookFlag = TRUE;
        // 挂起远程线程
        SuspendThread(RemoteHandle);

        SetDlgItemText(IDC_STATIC3, L"×");
    }
    else
    {
        IntHookFlag = FALSE;
        // 恢复远程线程
        ResumeThread(RemoteHandle);

        SetDlgItemText(IDC_STATIC3, L"√");
    }

    // IAT Hook 自我保护未开启
    if (OneIntHookFlag == FALSE)
    {
        // 标记为真
        OneIntHookFlag = TRUE;

        // 1. 获取被注入句柄
        // HANDLE hProc = FindWindow(L"CalcFrame",NULL);
        hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 5000);

        if (!hProc)
        {
            AfxMessageBox(L"FindWindow() failuer");

            return;
        }

        // DLL名称大小
        SIZE_T dwSize = strlen(DllPath1) + 1;

        // 2. 被注入进程申请内存空间
        auto pDlladdress = VirtualAllocEx(hProc, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);

        if (!pDlladdress)
        {
            CloseHandle(hProc);

            AfxMessageBox(L"VirtualAllocEx() failuer");

            return;
        }

        // 3. 写入内存数据
        if (!WriteProcessMemory(hProc, pDlladdress, DllPath1, dwSize, &dwSize))
        {
            VirtualFree(pDlladdress, dwSize, MEM_RELEASE);

            CloseHandle(hProc);

            AfxMessageBox(L"WriteProcessMemory() failuer");

            return;
        }

        // 补:这个地方创建信号量来传递Pid;
        DWORD m_Pid = GetCurrentProcessId();

        // HANDLE pProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, m_Pid);

        HANDLE hMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 0x10, L"Pid");

        LPVOID hMapFile = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);

        // DLL里面接收PID
        memcpy(hMapFile, &m_Pid, sizeof(HANDLE));

        // 4. 远程注入 LoadLibraryA获取函数地址可以直接函数名(编译器会帮助你获取VA),当然也可以GetProcess自己来获取VA

        DWORD dwTid = 0;

        // LoadLibrary(L"");
        RemoteHandle = CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, (LPVOID)pDlladdress, 0, NULL);

        // HANDLE RemoteHandle = NULL;

        // DWORD dwStatu = MyZwCreateThreadEx(&RemoteHandle, PROCESS_ALL_ACCESS, NULL, hProc, (LPTHREAD_START_ROUTINE)pFinAddress, (LPVOID)pDlladdress, 0, 0, 0, 0, NULL);

        SetDlgItemText(IDC_STATIC3, L"√");

        DWORD error = GetLastError();

        // 5. 这个地方就不等待执行后在返回了WaitForSingleObjectEx();
        // 6. 关闭远程句柄(只是关闭了本进程获取到的句柄,引用计数-1)
        CloseHandle(hProc);

DLL源码:

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"

// Save New Function Address
BYTE g_NewAddress[5] = { 0xE9 };

// Save Old Function Address
BYTE g_OldAddress[5] = {};

// Save WriteAttrib
DWORD g_OldAttrib = 0;

// Save ProtectProcessPid
DWORD g_Pid = 0;

// Statement : Camouflage Function
HANDLE WINAPI MyOpenProcess(_In_ DWORD dwDesiredAccess, _In_ BOOL bInheritHandle, _In_ DWORD dwProcessId);

// Statement : Instanll Hook
void InstallHook();

// Statement : UnInstall Hook
void UnInstallHook();

// True OpenProcess
typedef 
HANDLE
(WINAPI*
FnOpenProcess)(
_In_ DWORD dwDesiredAccess,
_In_ BOOL bInheritHandle,
_In_ DWORD dwProcessId
);
FnOpenProcess FOpenProcess;

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
    )
{
    switch (ul_reason_for_call)
    {
        // 被远程线程创建时候会被调用
    case DLL_PROCESS_ATTACH:
    {
        ::MessageBox(NULL, L"X64任务管理器", L"注入", NULL);
        // 安装HOOK
        InstallHook();
    }
    break;
    case DLL_PROCESS_DETACH:
    {
        // 卸载HOOK
        UnInstallHook();
    }
    break;
    }
    return TRUE;
}

// Implementation : Camouflage Function
HANDLE WINAPI MyOpenProcess(_In_ DWORD dwDesiredAccess, _In_ BOOL bInheritHandle, _In_ DWORD dwProcessId)
{
    /*
    做个过滤,因为我们需要保护进程只有一个,被保护的PID是多少?
    我们可以做用映射,信号量等把Pid传到被注入进程中。
    */
    HANDLE hProce = NULL;

    if (dwProcessId == g_Pid)
    {
        // 先回复
        UnInstallHook();

        // 打开权限默认为NULL 拒绝访问
        hProce = OpenProcess(NULL, bInheritHandle, dwProcessId);

        // 在安装
        InstallHook();

        return hProce;
    }
    else
    {
        // 先回复
        UnInstallHook();

        // 调用正确的OPenProcess
        hProce = OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);

        // 在安装
        InstallHook();

        // 返回正确的句柄
        return hProce;//FOpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);
    }

}

// Implementation : Instanll Hook
void InstallHook()
{
    // 前奏工作
    HANDLE hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"Pid");

    LPVOID hAddr = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);

    g_Pid = *(DWORD*)hAddr;

    // 1. 保存原来的地址(是指令长度)
    memcpy(g_OldAddress, OpenProcess, 5);

    FOpenProcess = (FnOpenProcess)OpenProcess;

    // 2. 计算偏移
    DWORD dwOffset = (DWORD)MyOpenProcess - (DWORD)OpenProcess - 5;

    // 3. 数组填充新偏移 
    // memcpy(&g_NewAddress[1], &dwOffset, 4);  切记不可用这种方式 当年这个BUG卡了好久 内存大小端字符排序 内存拷贝是正向的 偏移错误
    *(DWORD *)(g_NewAddress + 1) = dwOffset;

    VirtualProtect(OpenProcess, 5, PAGE_EXECUTE_READWRITE, &g_OldAttrib);

    // 4. 写入地址
    memcpy(OpenProcess, g_NewAddress, 5);

    VirtualProtect(OpenProcess, 5, g_OldAttrib, &g_OldAttrib);
}

// Implementation : UnInstall Hook
void UnInstallHook()
{
    VirtualProtect(OpenProcess, 5, PAGE_EXECUTE_READWRITE, &g_OldAttrib);

    // 写回去就行了
    memcpy(OpenProcess, g_OldAddress, 5);

    VirtualProtect(OpenProcess, 5, g_OldAttrib, &g_OldAttrib);
}

二、ObjectHook:
ObjectHook相关内核数据结构知识分享,如果你写过windows内核编程,那么更容易理解一些。

技术分享图片

如上图所示:这就是Windows内核对象数据结构,表示注册表、进程、线程等等。对象数据结构当然是对象管理器管理,所有的对象内部都会有OBJECT_HEADER的结构体,用来维护生命周期。

对象头上面是对象头引导,如OBJECT_HEADER_QUOTA_INFO、OBJECT_HEADER_HANDLE_INFO还有后面两个,描述了相关对象额外的属性,这些结构体对应着OBJECT_HEADER中的成员变量,如下介绍:
1、OBJECT_HEADER_NAME_INFO --> NameInfoOffset
2、OBJECT_HEADER_HANDLE_INFO --> HandleInfoOffset
3、OBJECT_HEADER_QUOTA_INFO --> QuotaInfoOffset

图中OBJECT_HEADER结构体中前两个PointerCount与HandleCount是引用计数。
1、PointerCount:内核模式对象引用数量
2、HandleCount:句柄数量
补充一些:
1. 内核中的指针引用. 一旦内核中新增了一个对象的引用, 则对象的引用计数自增一,如果一个对象的引用不再有用,则引用计数自减一. 这两种引用的增减是使用ObReferenceObjectByPointer和ObDereferenceObject导致的.
2. 一个进程打开一个对象并成功获得一个句柄, 会使得对象头中的句柄计数自增一. 当一个句柄不再被使用时, 句柄计数自减一. 这两种引用的增减来自ObpIncrementHandleCount和ObpDecrementHandleCount函数.

+0x10指向的是_OBJECT_CREATE_INFORMATION
技术分享图片
在创建CreateProcess时候会给该结构体申请空间,后面会填充该结构体信息。

我们重点看一下偏移为+0x008 OBJECT_TYPE,表示对于通用属性(对象的通用属性)存储,如下图所示:

技术分享图片

OBJECT_TYPE.TypeInfo指向了OBJECT_TYPE_INITIALIZER结构体,这个结构体包含特定对象类型的函数,对象管理器用于各类的执行操作,如下图所示:
技术分享图片
你会发现我用红色框框标记了上图的一些成员,所谓的ObjectHook就是他们(替换地址),这些函数过程会在特定时机被调用。

如何找到对象头?
我先们在windbg下来看一看,你要知道的一点是:内核变量ObTypeIndexTable是一个指针数组,它的每个成员都指向一种对象类型的OBJECT_TYPE结构体,也就是说它是由一个指针数组维护的。
那么我们就好办了,找到这个指针数组看一看
技术分享图片
到底对不对?我们来测试一下?
技术分享图片
OBJECT_TYPE了+0x28就是_OBJECT_TYPE_INITIALIZER。windbg下面能找到,编写代码的时候如何获取OBJECT_HEADER呢?

先来看一下,OBJECT_HEADER + 0x18,是成员变量Boby,这是什么?这是对象主体,我们可以看到对象主体中OBJECT_DIRECTORY,DRIVER_OBJECT,DEVICE_OBJECT等对象结构,我们在编写windows内核编程的时候会创建驱动对象,设备对象,这样就好说了。
假设驱动对象地址是OBJECT_HEADER + 0x18偏移的地方,那么驱动对象地址-0x18则是OBJECT_HEADER的地址,如下图所示:
技术分享图片
上面代码就是用汇编进行了的ObjectHook,如果理解了以上结构体概念,这些汇编代码应该没有难度。
为什么不用结构体去编程?因为我不想定义那么多结构体,部分结构体windows是没有公开的,需要自己在头文件中定义,但是代码中仍然给出了完整的结构体,可以用结构体实现,源码如下:
头文件定义:
#include <ntddk.h>

/*定义的结构体信息*/
typedef struct _OBJECT_TYPE_INITIALIZER
{
    USHORT Length;
    USHORT type;
    PVOID ObjectTypeCode;
    PVOID InvalidAttributes;
    GENERIC_MAPPING GenericMapping;
    PVOID ValidAccessMask;
    PVOID RetainAccess;
    POOL_TYPE PoolType;
    PVOID DefaultPagedPoolCharge;
    PVOID DefaultNonPagedPoolCharge;
    PVOID DumpProcedure;
    PVOID OpenProcedure;
    PVOID CloseProcedure;
    PVOID DeleteProcedure;
    PVOID ParseProcedure;
    PVOID SecurityProcedure;
    PVOID QueryNameProcedure;
    USHORT OkayToCloseProcedure;
} OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER;

typedef struct _OBJECT_TYPE
{
    LIST_ENTRY TypeList;         //         : _LIST_ENTRY
    UNICODE_STRING Name;         //             : _UNICODE_STRING
    PVOID DefaultObject;         //    : Ptr32 Void
    ULONG Index;         //            : UChar
    ULONG TotalNumberOfObjects;         // : Uint4B
    ULONG TotalNumberOfHandles;         // : Uint4B
    ULONG HighWaterNumberOfObjects;         // : Uint4B
    ULONG HighWaterNumberOfHandles;         // : Uint4B
    OBJECT_TYPE_INITIALIZER TypeInfo;         //         : _OBJECT_TYPE_INITIALIZER
    PVOID TypeLock;         //         : _EX_PUSH_LOCK
    ULONG Key;         //              : Uint4B
    LIST_ENTRY CallbackList;         //     : _LIST_ENTRY
} OBJECT_TYPE, *POBJECT_TYPE;

typedef struct _OBJECT_CREATE_INFORMATION
{
    ULONG Attributes;
    HANDLE RootDirectory;
    KPROCESSOR_MODE ProbeMode;
    ULONG PagedPoolCharge;
    ULONG NonPagedPoolCharge;
    ULONG SecurityDescriptorCharge;
    PVOID SecurityDescriptor;
    PSECURITY_QUALITY_OF_SERVICE SecurityQos;
    SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService;
} OBJECT_CREATE_INFORMATION, *POBJECT_CREATE_INFORMATION;

typedef struct _OBJECT_HEADER
{
    //对象头部的指针计数,对对象头指针引用的计数
    LONG_PTR PointerCount;
    union
    {
        //句柄引用计数
        LONG_PTR HandleCount;
        PVOID NextToFree;
    };
    POBJECT_TYPE Type;
    //OBJECT_HEADER_NAME_INFO相对于此结构的偏移
    UCHAR NameInfoOffset;
    //OBJECT_HEADER_HANDLE_INFO相对于此结构的偏移
    UCHAR HandleInfoOffset;
    //OBJECT_HEADER_QUOTA_INFO相对于此结构的偏移
    UCHAR QuotaInfoOffset;
    UCHAR Flags;

    union
    {
        //创建对象是用于创建对象附加头的结构
        //里面保存了和附加对象头类似的信息
        PVOID ObjectCreateInfo;
        PVOID QuotaBlockCharged;
    };
    PSECURITY_DESCRIPTOR SecurityDescriptor;
    QUAD Body;
} OBJECT_HEADER, *POBJECT_HEADER;

// 获取头信息
#define OBJECT_TO_OBJECT_HEADER(o)            CONTAINING_RECORD((o),OBJECT_HEADER,Body)
#define CONTAINING_RECORD(address,type,field)            ((type*)(((ULONG_PTR)address)-(ULONG_PTR)(&(((type*)0)->field))))

代码实现:


#include "HookHead.h"

VOID UnLoadDriver()
{

}

NTSTATUS MyDeleteProcedure();

NTSTATUS MaDefaultFunction(DEVICE_OBJECT* pDeviceObj, IRP* Irp)
{
    Irp->iostatus.Information = 0;

    Irp->IoStatus.Status = STATUS_SUCCESS;

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return STATUS_SUCCESS;
}

NTSTATUS DriverEntry(DRIVER_OBJECT* pDeviceObj, UNICODE_STRING* RegistryPath)
{
    pDeviceObj->DriverUnload = UnLoadDriver;

    for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i)
    {
        pDeviceObj->MajorFunction[i] = MaDefaultFunction;
    }

    // 1. 获取OBJECT_HEAD
    __asm
    {
        // 保存环境
        pushad;
        pushfd;
        // 1 设备对象就是 OBJECT_BODY 也就是 -0x18是OBJECT_HREAD -0x10是OBJECT_TYPE(OBJECT_HANDLE + 0x8)
        lea eax, pDeviceObj;
        sub eax, 0x10;
        // 2 获取到OBJECT_TYPE之后 地址加上0x3c则是DeleteProcdure(其实已经在OBJECT_TYPE_INITIALZER结构体中)
        lea eax, [eax + 0x3c];
        // 3 DeleteProcdure地址替换成的MyDeleteProcedure地址
        lea esi, MyDeleteProcedure;
        mov eax, esi;
        // 恢复环境
        popad;
        popfd;
    }

    // 2. 获取OBJECT_TYPE

    // 3. 替换相对应的函数

}

NTSTATUS MyDeleteProcedure()
{

}

三、SsdtHook:
SSDT:System Service Descriptor Table,系统服务描述符表。这个表保存啥的?其实就是把三环零环的API联起来,不单单是索引表,而且还包含一些索引基址、服务函数个数等。

先有个基本的概念,下面来看一张图:

技术分享图片

我们发现不论是System support process,还是Service processes他们都会进入内核模式之前都会经过Ntdll.dll模块。然后通过系统服务调度程序到接口,到微内核,到HAL(驱动硬件相关联的地方)。
所以内核层下也有微内核与HVL层,前面博客中介绍过一些相关的结构体如_EPROCESS里面内嵌_KPROCESS,_EPROCESS被内核层执行,而_KPROCESS是微内核层调度。

用OD随便跟踪函数:
技术分享图片
下面函数有点奇怪并不是想象中的CALL [EDX],而是wow64cpu函数是在用户模式下实现的,作为 ntdll.dll 和内核之间的层。如果你的应用程序是32位,为了能在64位的系统上运行起来,就会调用这个函数兼容,Windows下的一个子系统。
该函数有三种返回值:
1、64位运行在64位系统下,不是WOW64模式,return 0;
2、32位运行在64位系统下,WOW64模式,return 1;
3、32位运行在32位系统下,return 0;
有点像傀儡进程一样,函数过程大概是这样,原进程会被创建(包括注册表),然后判断该程序信息,如果不是64位在C盘下(具体位置记不清楚),Temp的文件夹下创建一个64位的线程,修改注册表信息等(不太准确,只是以前逆向的时候观察过整个的过程)。
MOV EAX, 0x22(0x23) --> 保存调用号 ssdt表中的序号
EAX寄存器保存保存函数的调用号,其实到内核以后就靠调用号来确认调用的是哪个函数。

对windows内核比较熟悉的应该知道,用户的堆栈与内核的堆栈不是同一个堆栈空间,而且用户层没有权限去访问内核层的数据,通过什么进入内核层?一条汇编指令 SYSENTER,如下图所示:
技术分享图片

cs:ip执行这一条汇编指令之后,你将进入到内核。如何做到的呢?其实在SYSENTRY指令之前,先会用edx保存esp的值,执行SYSENTER时候会读取特殊寄存器MSR模组寄存器,如下图所示:
技术分享图片
没有名字,只有编号通过以下两条汇编指令:

操作码 指令 说明
0F 32 RDMSR 将 ECX 指定的 MSR 加载到 EDX:EAX
操作码 指令 说明
0F 30 WRMSR 将 EDX:EAX 中的值写入 ECX 指定的 MSR

详细:
1、RDMSR:将 ECX 寄存器指定的 64 位型号专用寄存器 (MSR) 的内容加载到寄存器 EDX:EAX。EDX 寄存器中加载 MSR 的高 32 位,EAX 寄存器中加载低 32 位。在读取的 MSR 中,如果实现的位数小于 64,则返回 EDX:EAX 中未实现的位的值未定义。
2、WRMSR :将寄存器 EDX:EAX 的内容写入 ECX 寄存器指定的 64 位型号专用寄存器 (MSR)。高 32 位从 EDX 复制,低 32 位从 EAX 复制。MSR 中未定义或保留的位总是设置为上次读取时的值。
补充一下:其实有些KiFastCallEntry Hook大家看到这里应该明白,其实就是改变编号0x176保存的地址。

当SYSENTER执行时候,就把寄存器的值初始化成真正的寄存器CS,ESP,EIP寄存器的数据。这时候就跑到KiFastCallEntry。

KiFastCallEntry函数大家有兴趣可以分析下,那么对上面的流程更为清晰,怎样去分析呢,windbg下就可以,如下图所示:
技术分享图片

竟然进入到了内核层,把用户栈的内容拷贝到内核栈,但是拷贝多少个字节?参数个数?

通过eax在用户层保存的序号,就能找到函数地址。通过调用号作为序号,就能找到参数个数,个数*4就是总字节,其实这张表就是SSDT,还有一张表叫ShadowSSDT,专门用于保存和用户界面相关服务,内核中还有两张没有使用的表,如下图所示(两个结构体)。

技术分享图片
_KSYSTEM_SERVICE_TABLE便是SSDT的结构体,通过上图我们知道了SSDT的结构,另一个结构体保存了这四张表。

windbg如何找到ssdt?
1、dd KeServiceDescriptorTable(由ntoskrnl.exe导出)。
2、还可以通过_KTHREAD + 0xbc来找到ServiceTableBase结构体基址。
技术分享图片
技术分享图片
使用结构体解析地址看一下,如下图所示:
技术分享图片
图中标红便对应着结构体成员值,函数地址表首地址、每个函数被调用次数、服务函数个数191个,参数表地址。
HOOK的是什么? 其实HOOK的就是函数地址表中的地址,当内核层通过调用号找到ssdt中的索引号,调用的是我们自己的函数地址即可。
到底是不是这样?经过测试确实是这样,下图是源码测试图:
技术分享图片
技术分享图片技术分享图片
我们用ark工具看一下,如下图所示:
技术分享图片
源码如下:
头文件:

#pragma once
#include <ntddk.h>

#define CTL_SSDT_ENABLE     CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
#define CTL_SSDT_DISABLE     CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)

typedef  struct  _KSERVICE_TABLE_DESCRIPTOR
{
    KSYSTEM_SERVICE_TABLE   ntoskrnl;   // ntoskrnl.exe的服务函数,即SSDT
    KSYSTEM_SERVICE_TABLE   win32k;     // win32k.sys的服务函数(GDI32.dll/User32.dll 的内核支持),即ShadowSSDT
    KSYSTEM_SERVICE_TABLE   notUsed1;   // 不使用
    KSYSTEM_SERVICE_TABLE   notUsed2;   // 不使用
}KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;

typedef  struct  _KSYSTEM_SERVICE_TABLE
{
    PULONG  ServiceTableBase;           // 函数地址表的首地址
    PULONG  ServiceCounterTableBase;    // 函数表中每个函数被调用的次数
    ULONG   NumberOfService;            // 服务函数的个数, NumberOfService * 4 就是整个地址表的大小
    UCHAR*   ParamTableBase;            // 参数个数表首地址
} KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;

// 伪函数(Hook的函数)
typedef NTSTATUS(NTAPI*FnNtOpenProcess)(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId);

// 保存旧的地址
FnNtOpenProcess g_OldNtOpenProcess;

// 定义KeServieDescriptorTableShadow
KSERVICE_TABLE_DESCRIPTOR* g_ServiceTab = NULL;

// 保存被保护进程PID
HANDLE g_Pid = 0;

驱动层:

#include "SSDTHookHead.h"

// 声明:驱动卸载
VOID DriverUnLoad(DRIVER_OBJECT* pDeviceobj);

// 声明:默认初始化
NTSTATUS DefaultFunction(DEVICE_OBJECT* pDeviceObj, IRP* Irp);

// 声明:控制码
NTSTATUS ControlCode(DEVICE_OBJECT* pDeviceObj, IRP* Irp);

// 声明:安装HOOK
VOID InstallHook();

// 声明:卸载HOOK
VOID UnInstallHook();

// 声明:关闭分页保护
NTSTATUS ShudowMemoryPageProtect();

// 声明:开启分页保护
NTSTATUS StartMemoryPageProtect();

// Hook实现函数
NTSTATUS MyOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId);

// 声明:入口点
NTSTATUS DriverEntry(DRIVER_OBJECT* pDriverObj, UNICODE_STRING* RegistryPath)
{
    UNREFERENCED_PARAMETER(RegistryPath);

    DEVICE_OBJECT* pDeviceObj = NULL;
    UNICODE_STRING DevName;
    UNICODE_STRING SymbolicLinkName;
    NTSTATUS Status = STATUS_SUCCESS;

    RtlInitUnicodeString(&DevName, L"\Device\SsdtHook");
    RtlInitUnicodeString(&SymbolicLinkName, L"\DosDevices\SymbolicLinkName");

    // DbgBreakPoint();

    pDriverObj->DriverUnload = DriverUnLoad;

    for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i)
    {
        pDriverObj->MajorFunction[i] = DefaultFunction;
    }

    // 设备对象
    Status = IoCreateDevice(pDriverObj, 0, &DevName, FILE_DEVICE_UNKNOWN, 0, 0, &pDeviceObj);

    if (!NT_SUCCESS(Status))
        return Status;

    // 使用缓冲区的方式进行3环与0环通讯
    pDriverObj->Flags = DO_BUFFERED_IO;

    // 符号对象暴露给三环使用
    Status = IoCreateSymbolicLink(&SymbolicLinkName, &DevName);

    if (!NT_SUCCESS(Status))
        return Status;

    pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ControlCode;

    return STATUS_SUCCESS;
}

VOID DriverUnLoad(DRIVER_OBJECT* pDeviceobj)
{
    UNICODE_STRING DeleteSymblolicLinkName;

    RtlInitUnicodeString(&DeleteSymblolicLinkName, L"\DosDevices\SymbolicLinkName");

    IoDeleteSymbolicLink(&DeleteSymblolicLinkName);

    IoDeleteDevice(pDeviceobj->DeviceObject);
}

// 实现:默认初始化
NTSTATUS DefaultFunction(DEVICE_OBJECT* pDeviceObj, IRP* Irp)
{
    UNREFERENCED_PARAMETER(pDeviceObj);

    Irp->IoStatus.Information = 0;

    Irp->IoStatus.Status = STATUS_SUCCESS;

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return STATUS_SUCCESS;
}

// 实现:控制码
NTSTATUS ControlCode(DEVICE_OBJECT* pDeviceObj, IRP* Irp)
{
    // DbgBreakPoint();

    UNREFERENCED_PARAMETER(pDeviceObj);

    // 通过Irp栈数据获取控制码
    // NTSTATUS nStatus = STATUS_SUCCESS;

    PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(Irp);

    ULONG uControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;

    // 通过MDL获取需要保护的Pid
    // g_Pid = (HANDLE)MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);

    PVOID Pbuf = Irp->AssociatedIrp.SystemBuffer;

    RtlCopyMemory((PVOID)&g_Pid, Pbuf, sizeof(ULONG));

    switch (uControlCode)
    { 
    case CTL_SSDT_ENABLE:
    {
        // DbgBreakPoint();
        InstallHook();
    }
    break;
    case CTL_SSDT_DISABLE:
    {
        UnInstallHook();
    }
    break;
    default:
        break;
    }

    Irp->IoStatus.Information = 0;

    Irp->IoStatus.Status = STATUS_SUCCESS;

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return STATUS_SUCCESS;
}

// 实现:安装HOOK
VOID InstallHook()
{
    // DbgBreakPoint();

    // 1.1 获取当前线程
    PETHREAD pThread = PsGetCurrentThread();

    // 1.2 线程结构体 +0xbc 获取的是 ServiceTable 
    g_ServiceTab = (KSERVICE_TABLE_DESCRIPTOR*)(*(ULONG*)((ULONG_PTR)pThread + 0xbc));

    // 1.3 获取SSDT地址基址且保存原始的函数VA
    g_OldNtOpenProcess = (FnNtOpenProcess)g_ServiceTab->ntoskrnl.ServiceTableBase[0xBE];

    // 1.4 替换修改地址(这个地方先要关闭页保护)  
    /*
        只介绍其中的三种:
            PE - 是否启用保护模式,置1则启用
            PG - 是否使用分页模式, 置1则开启分页模式, 此标志置1时,PE标志也必须置1,否则CPU报异常.
            WP - WP==1时, 不能修改只读的内存页 , WP==0 时, 可以修改只读的内存页.
    */
    ShudowMemoryPageProtect();

    g_ServiceTab->ntoskrnl.ServiceTableBase[0xBE] = (ULONG)MyOpenProcess;

    StartMemoryPageProtect();
}

// 实现:卸载HOOK
VOID UnInstallHook()
{
    ShudowMemoryPageProtect();

    g_ServiceTab->ntoskrnl.ServiceTableBase[0xBE] = (ULONG)g_OldNtOpenProcess;

    // DbgBreakPoint();

    StartMemoryPageProtect();
}

// 实现:关闭分页保护
NTSTATUS ShudowMemoryPageProtect()
{
    __asm
    {
        pushad;
        pushfd;

        mov eax, cr0;
        // 前提内存保护一定是开启的 WP = 1 否则..就给开启了
        and eax, ~0x10000;
        mov cr0, eax;

        popfd;
        popad;

    }
}

// 实现:开启分页保护
NTSTATUS StartMemoryPageProtect()
{
    __asm
    {
        pushad;
        pushfd;

        mov eax, cr0;
        or eax, 0x10000;
        mov cr0, eax;

        popfd;
        popad;

    }
}

// 实现:HOOK函数
NTSTATUS MyOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId)
{
    // 访问权限PROCESS_ALL_ACCESS改为NULL 
    if (ClientId->UniqueProcess == g_Pid)
    {
        DbgBreakPoint();

        DesiredAccess = 0;
    }

    return g_OldNtOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
}

以上是关于从hook开始聊聊那些windows内核数据结构的主要内容,如果未能解决你的问题,请参考以下文章

wwwnw1022com19908836661

聊聊Netty那些事儿之从内核角度看IO模型

聊聊Netty那些事儿之从内核角度看IO模型

聊聊Netty那些事儿之从内核角度看IO模型

Windows内核分析——内核调试机制的实现(NtCreateDebugObjectDbgkpPostFakeProcessCreateMessagesDbgkpPostFakeThreadMes(代

HOOK这是一种思想(附源码)