驱动开发:内核扫描SSDT挂钩状态

本文涉及的产品
实时计算 Flink 版,5000CU*H 3个月
简介: 在笔者上一篇文章`《驱动开发:内核实现SSDT挂钩与摘钩》`中介绍了如何对`SSDT`函数进行`Hook`挂钩与摘钩的,本章将继续实现一个新功能,如何`检测SSDT`函数是否挂钩,要实现检测`挂钩状态`有两种方式,第一种方式则是类似于`《驱动开发:摘除InlineHook内核钩子》`文章中所演示的通过读取函数的前16个字节与`原始字节`做对比来判断挂钩状态,另一种方式则是通过对比函数的`当前地址`与`起源地址`进行判断,为了提高检测准确性本章将采用两种方式混合检测。

在笔者上一篇文章《驱动开发:内核实现SSDT挂钩与摘钩》中介绍了如何对SSDT函数进行Hook挂钩与摘钩的,本章将继续实现一个新功能,如何检测SSDT函数是否挂钩,要实现检测挂钩状态有两种方式,第一种方式则是类似于《驱动开发:摘除InlineHook内核钩子》文章中所演示的通过读取函数的前16个字节与原始字节做对比来判断挂钩状态,另一种方式则是通过对比函数的当前地址起源地址进行判断,为了提高检测准确性本章将采用两种方式混合检测。

具体原理,通过解析内核文件PE结构找到导出表,依次计算出每一个内核函数的RVA相对偏移,通过与内核模块基址相加此相对偏移得到函数的原始地址,然后再动态获取函数当前地址,两者作比较即可得知指定内核函数是否被挂钩。

在实现这个功能之前我们需要解决两个问题,第一个问题是如何得到特定内核模块的内存模块基址此处我们需要封装一个GetOsBaseAddress()用户只需要传入指定的内核模块即可得到该模块基址,如此简单的代码没有任何解释的必要;

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#include <ntifs.h>
#include <ntimage.h>
#include <ntstrsafe.h>

typedef struct _LDR_DATA_TABLE_ENTRY
{
   
   
    LIST_ENTRY InLoadOrderLinks;
    LIST_ENTRY InMemoryOrderLinks;
    LIST_ENTRY InInitializationOrderLinks;
    PVOID DllBase;
    PVOID EntryPoint;
    ULONG SizeOfImage;
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;
    ULONG Flags;
    USHORT LoadCount;
    USHORT TlsIndex;
    LIST_ENTRY HashLinks;
    ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

// 得到内核模块基址
ULONGLONG GetOsBaseAddress(PDRIVER_OBJECT pDriverObject, WCHAR *wzData)
{
   
   
    UNICODE_STRING osName = {
   
    0 };
    // WCHAR wzData[0x100] = L"ntoskrnl.exe";
    RtlInitUnicodeString(&osName, wzData);

    LDR_DATA_TABLE_ENTRY *pDataTableEntry, *pTempDataTableEntry;
    //双循环链表定义
    PLIST_ENTRY    pList;
    //指向驱动对象的DriverSection
    pDataTableEntry = (LDR_DATA_TABLE_ENTRY*)pDriverObject->DriverSection;
    //判断是否为空
    if (!pDataTableEntry)
    {
   
   
        return 0;
    }

    //得到链表地址
    pList = pDataTableEntry->InLoadOrderLinks.Flink;

    // 判断是否等于头部
    while (pList != &pDataTableEntry->InLoadOrderLinks)
    {
   
   
        pTempDataTableEntry = (LDR_DATA_TABLE_ENTRY *)pList;
        if (RtlEqualUnicodeString(&pTempDataTableEntry->BaseDllName, &osName, TRUE))
        {
   
   
            return (ULONGLONG)pTempDataTableEntry->DllBase;
        }
        pList = pList->Flink;
    }
    return 0;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
   
   
    DbgPrint("驱动卸载 \n");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
   
   
    DbgPrint("Hello LyShark.com \n");

    ULONGLONG kernel_base = GetOsBaseAddress(Driver, L"ntoskrnl.exe");
    DbgPrint("ntoskrnl.exe => 模块基址: %p \n", kernel_base);

    ULONGLONG hal_base = GetOsBaseAddress(Driver, L"hal.dll");
    DbgPrint("hal.dll => 模块基址: %p \n", hal_base);

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

如上直接编译并运行,即可输出ntoskrnl.exe以及hal.dll两个内核模块的基址;

image.png

其次我们还需要实现另一个功能,此时想像一下当我告诉你一个内存地址,我想要查该内存地址属于哪个模块该如何实现,其实很简单只需要拿到这个地址依次去判断其是否大于等于该模块的基地址,并小于等于该模块的结束地址,那么我们就认为该地址落在了此模块上,在这个思路下LyShark实现了以下代码片段。

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#include <ntifs.h>
#include <ntimage.h>
#include <ntstrsafe.h>

typedef struct _LDR_DATA_TABLE_ENTRY
{
   
   
    LIST_ENTRY InLoadOrderLinks;
    LIST_ENTRY InMemoryOrderLinks;
    LIST_ENTRY InInitializationOrderLinks;
    PVOID DllBase;
    PVOID EntryPoint;
    ULONG SizeOfImage;
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;
    ULONG Flags;
    USHORT LoadCount;
    USHORT TlsIndex;
    LIST_ENTRY HashLinks;
    ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

// 扫描指定地址是否在某个模块内
VOID ScanKernelModuleBase(PDRIVER_OBJECT pDriverObject, ULONGLONG address)
{
   
   
    LDR_DATA_TABLE_ENTRY *pDataTableEntry, *pTempDataTableEntry;
    PLIST_ENTRY pList;
    pDataTableEntry = (LDR_DATA_TABLE_ENTRY*)pDriverObject->DriverSection;
    if (!pDataTableEntry)
    {
   
   
        return;
    }

    // 得到链表地址
    pList = pDataTableEntry->InLoadOrderLinks.Flink;

    // 判断是否等于头部
    while (pList != &pDataTableEntry->InLoadOrderLinks)
    {
   
   
        pTempDataTableEntry = (LDR_DATA_TABLE_ENTRY *)pList;

        ULONGLONG start_address = (ULONGLONG)pTempDataTableEntry->DllBase;
        ULONGLONG end_address = start_address + (ULONG)pTempDataTableEntry->SizeOfImage;

        // 判断区间
        // DbgPrint("起始地址 [ %p ] 结束地址 [ %p ] \n",start_address,end_address);
        if (address >= start_address && address <= end_address)
        {
   
   
            DbgPrint("[LyShark] 当前函数所在模块 [ %ws ] \n", (CHAR *)pTempDataTableEntry->FullDllName.Buffer);
        }
        pList = pList->Flink;
    }
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
   
   
    DbgPrint("驱动卸载 \n");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
   
   
    DbgPrint("Hello LyShark.com \n");

    ScanKernelModuleBase(Driver, 0xFFFFF8051AF5D030);

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

我们以0xFFFFF8051AF5D030地址为例对其进行判断可看到输出了如下结果,此地址被落在了hal.dll模块上;

image.png

为了能读入磁盘PE文件到内存此时我们还需要封装一个LoadKernelFile()函数,该函数的作用是读入一个内核文件到内存空间中,此处如果您使用前一篇《驱动开发:内核解析PE结构导出表》文章中的内存映射函数来读写则会蓝屏,原因很简单KernelMapFile()是映射而映射一定无法一次性完整装载其次此方法本质上还在占用原文件,而LoadKernelFile()则是读取磁盘文件并将其完整拷贝一份,这是两者的本质区别,如下代码则是实现完整拷贝的实现;

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#include <ntifs.h>
#include <ntimage.h>
#include <ntstrsafe.h>

// 将内核文件装载入内存(磁盘)
PVOID LoadKernelFile(WCHAR *wzFileName)
{
   
   
    NTSTATUS Status;
    HANDLE FileHandle;
    IO_STATUS_BLOCK ioStatus;
    FILE_STANDARD_INFORMATION FileInformation;

    // 设置路径
    UNICODE_STRING uniFileName;
    RtlInitUnicodeString(&uniFileName, wzFileName);

    // 初始化打开文件的属性
    OBJECT_ATTRIBUTES objectAttributes;
    InitializeObjectAttributes(&objectAttributes, &uniFileName, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, NULL);

    // 打开文件
    Status = IoCreateFile(&FileHandle, FILE_READ_ATTRIBUTES | SYNCHRONIZE, &objectAttributes, &ioStatus, 0, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0, CreateFileTypeNone, NULL, IO_NO_PARAMETER_CHECKING);
    if (!NT_SUCCESS(Status))
    {
   
   
        return 0;
    }

    // 获取文件信息
    Status = ZwQueryInformationFile(FileHandle, &ioStatus, &FileInformation, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation);
    if (!NT_SUCCESS(Status))
    {
   
   
        ZwClose(FileHandle);
        return 0;
    }

    // 判断文件大小是否过大
    if (FileInformation.EndOfFile.HighPart != 0)
    {
   
   
        ZwClose(FileHandle);
        return 0;
    }

    // 取文件大小
    ULONG64 uFileSize = FileInformation.EndOfFile.LowPart;

    // 分配内存
    PVOID pBuffer = ExAllocatePoolWithTag(NonPagedPool, uFileSize + 0x100, (ULONG)"LyShark");
    if (pBuffer == NULL)
    {
   
   
        ZwClose(FileHandle);
        return 0;
    }

    // 从头开始读取文件
    LARGE_INTEGER byteOffset;
    byteOffset.LowPart = 0;
    byteOffset.HighPart = 0;
    Status = ZwReadFile(FileHandle, NULL, NULL, NULL, &ioStatus, pBuffer, uFileSize, &byteOffset, NULL);
    if (!NT_SUCCESS(Status))
    {
   
   
        ZwClose(FileHandle);
        return 0;
    }

    // ExFreePoolWithTag(pBuffer, (ULONG)"LyShark");
    ZwClose(FileHandle);
    return pBuffer;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
   
   
    DbgPrint("驱动卸载 \n");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
   
   
    // 加载内核模块
    PVOID BaseAddress = LoadKernelFile(L"\\SystemRoot\\system32\\ntoskrnl.exe");
    DbgPrint("BaseAddress = %p\n", BaseAddress);

    // 解析PE头
    PIMAGE_DOS_HEADER pDosHeader;
    PIMAGE_NT_HEADERS pNtHeaders;

    // DLL内存数据转成DOS头结构
    pDosHeader = (PIMAGE_DOS_HEADER)BaseAddress;

    // 取出PE头结构
    pNtHeaders = (PIMAGE_NT_HEADERS)((ULONGLONG)BaseAddress + pDosHeader->e_lfanew);

    DbgPrint("[LyShark] => 映像基址: %p \n", pNtHeaders->OptionalHeader.ImageBase);

    // 结束后释放内存
    ExFreePoolWithTag(BaseAddress, (ULONG)"LyShark");

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

运行如上这段程序,则会将ntoskrnl.exe文件载入到内存,并读取出其中的OptionalHeader.ImageBase映像基址,如下图所示;

image.png

有了上述方法,最后一步就是组合并实现判断即可,如下代码通过对导出表的解析,并过滤出所有的Nt开头的系列函数,然后依次对比起源地址与原地址是否一致,得出是否被挂钩,完整代码如下所示;

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

ULONGLONG ntoskrnl_base = 0;

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
   
   
    DbgPrint("Hello LyShark.com \n");

    // 加载内核模块
    PVOID BaseAddress = LoadKernelFile(L"\\SystemRoot\\system32\\ntoskrnl.exe");
    DbgPrint("BaseAddress = %p\n", BaseAddress);

    // 获取内核模块地址
    ntoskrnl_base = GetOsBaseAddress(Driver, L"ntoskrnl.exe");

    // 取出导出表
    PIMAGE_DOS_HEADER pDosHeader;
    PIMAGE_NT_HEADERS pNtHeaders;
    PIMAGE_SECTION_HEADER pSectionHeader;
    ULONGLONG FileOffset;
    PIMAGE_EXPORT_DIRECTORY pExportDirectory;

    // DLL内存数据转成DOS头结构
    pDosHeader = (PIMAGE_DOS_HEADER)BaseAddress;
    // 取出PE头结构
    pNtHeaders = (PIMAGE_NT_HEADERS)((ULONGLONG)BaseAddress + pDosHeader->e_lfanew);
    // 判断PE头导出表表是否为空
    if (pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0)
    {
   
   
        return 0;
    }

    // 取出导出表偏移
    FileOffset = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;

    // 取出节头结构
    pSectionHeader = (PIMAGE_SECTION_HEADER)((ULONGLONG)pNtHeaders + sizeof(IMAGE_NT_HEADERS));
    PIMAGE_SECTION_HEADER pOldSectionHeader = pSectionHeader;

    // 遍历节结构进行地址运算
    for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
    {
   
   
        if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
        {
   
   
            FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
        }
    }

    // 导出表地址
    pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((ULONGLONG)BaseAddress + FileOffset);

    // 取出导出表函数地址
    PULONG AddressOfFunctions;
    FileOffset = pExportDirectory->AddressOfFunctions;

    // 遍历节结构进行地址运算
    pSectionHeader = pOldSectionHeader;
    for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
    {
   
   
        if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
        {
   
   
            FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
        }
    }

    // 这里注意一下foa和rva
    AddressOfFunctions = (PULONG)((ULONGLONG)BaseAddress + FileOffset);

    // 取出导出表函数名字
    PUSHORT AddressOfNameOrdinals;
    FileOffset = pExportDirectory->AddressOfNameOrdinals;

    // 遍历节结构进行地址运算
    pSectionHeader = pOldSectionHeader;
    for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
    {
   
   
        if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
        {
   
   
            FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
        }
    }

    // 注意一下foa和rva
    AddressOfNameOrdinals = (PUSHORT)((ULONGLONG)BaseAddress + FileOffset);

    // 取出导出表函数序号
    PULONG AddressOfNames;
    FileOffset = pExportDirectory->AddressOfNames;

    // 遍历节结构进行地址运算
    pSectionHeader = pOldSectionHeader;
    for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
    {
   
   
        if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
        {
   
   
            FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
        }
    }

    // 注意一下foa和rva
    AddressOfNames = (PULONG)((ULONGLONG)BaseAddress + FileOffset);

    // 分析导出表
    ULONG uOffset;
    LPSTR FunName;
    ULONG uAddressOfNames;
    ULONG TargetOff = 0;

    for (ULONG uIndex = 0; uIndex < pExportDirectory->NumberOfNames; uIndex++, AddressOfNames++, AddressOfNameOrdinals++)
    {
   
   
        uAddressOfNames = *AddressOfNames;
        pSectionHeader = pOldSectionHeader;
        for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
        {
   
   
            if (pSectionHeader->VirtualAddress <= uAddressOfNames && uAddressOfNames <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
            {
   
   
                uOffset = uAddressOfNames - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
            }
        }
        FunName = (LPSTR)((ULONGLONG)BaseAddress + uOffset);

        if (FunName[0] == 'N' && FunName[1] == 't')
        {
   
   
            // 得到相对RVA
            TargetOff = (ULONG)AddressOfFunctions[*AddressOfNameOrdinals];

            // LPSTR -> UNCODE
            // 先转成ANSI 然后在转成 UNCODE
            ANSI_STRING ansi = {
   
    0 };
            UNICODE_STRING uncode = {
   
    0 };

            RtlInitAnsiString(&ansi, FunName);
            RtlAnsiStringToUnicodeString(&uncode, &ansi, TRUE);

            // 得到当前地址
            PULONGLONG local_address = MmGetSystemRoutineAddress(&uncode);

            /*
            // 读入内核函数前6个字节
            unsigned char local_opcode[6] = { 0 };
            unsigned char this_opcode[6] = { 0 };

            RtlCopyMemory(local_opcode, (void *)local_address, 6);
            RtlCopyMemory(this_opcode, (void *)(ntoskrnl_base + TargetOff), 6);

            // 当前机器码
            for (int x = 0; x < 6; x++)
            {
            DbgPrint("当前 [ %d ] 机器码 [ %x ] ", x, local_opcode[x]);
            }

            // 起源机器码
            for (int y = 0; y < 6; y++)
            {
            DbgPrint("起源 [ %d ] 机器码 [ %x ] ", y, this_opcode[y]);
            }

            */

            // 检测是否被挂钩 [不相等则说明被挂钩了]
            if (local_address != (ntoskrnl_base + TargetOff))
            {
   
   
                DbgPrint("索引 [ %d ] RVA [ %p ] \n --> 起源地址 [ %p ] | 当前地址 [ %p ] | 函数名 [ %s ] \n\n",
                    *AddressOfNameOrdinals, TargetOff, ntoskrnl_base + TargetOff, local_address, FunName);
            }

            // 检查当前地址所在模块
            // ScanKernelModuleBase(Driver, (PULONGLONG)local_address);
        }
    }

    // 结束后释放内存
    ExFreePoolWithTag(BaseAddress, (ULONG)"LyShark");

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

使用ARK工具手动改写几个Nt开头的函数,并运行这段代码,观察是否可以输出被挂钩的函数详情;

image.png

相关实践学习
基于Hologres轻松玩转一站式实时仓库
本场景介绍如何利用阿里云MaxCompute、实时计算Flink和交互式分析服务Hologres开发离线、实时数据融合分析的数据大屏应用。
Linux入门到精通
本套课程是从入门开始的Linux学习课程,适合初学者阅读。由浅入深案例丰富,通俗易懂。主要涉及基础的系统操作以及工作中常用的各种服务软件的应用、部署和优化。即使是零基础的学员,只要能够坚持把所有章节都学完,也一定会受益匪浅。
目录
相关文章
|
1月前
|
监控 安全 API
7.3 Windows驱动开发:内核监视LoadImage映像回调
在笔者上一篇文章`《内核注册并监控对象回调》`介绍了如何运用`ObRegisterCallbacks`注册`进程与线程`回调,并通过该回调实现了`拦截`指定进行运行的效果,本章`LyShark`将带大家继续探索一个新的回调注册函数,`PsSetLoadImageNotifyRoutine`常用于注册`LoadImage`映像监视,当有模块被系统加载时则可以第一时间获取到加载模块信息,需要注意的是该回调函数内无法进行拦截,如需要拦截则需写入返回指令这部分内容将在下一章进行讲解,本章将主要实现对模块的监视功能。
38 0
7.3 Windows驱动开发:内核监视LoadImage映像回调
|
5月前
|
存储 API 开发者
6.7 Windows驱动开发:内核枚举LoadImage映像回调
在笔者之前的文章`《内核特征码搜索函数封装》`中我们封装实现了特征码定位功能,本章将继续使用该功能,本次我们需要枚举内核`LoadImage`映像回调,在Win64环境下我们可以设置一个`LoadImage`映像加载通告回调,当有新驱动或者DLL被加载时,回调函数就会被调用从而执行我们自己的回调例程,映像回调也存储在数组里,枚举时从数组中读取值之后,需要进行位运算解密得到地址。
35 1
6.7 Windows驱动开发:内核枚举LoadImage映像回调
|
5月前
|
监控 安全 API
6.9 Windows驱动开发:内核枚举进线程ObCall回调
在笔者上一篇文章`《内核枚举Registry注册表回调》`中我们通过特征码定位实现了对注册表回调的枚举,本篇文章`LyShark`将教大家如何枚举系统中的`ProcessObCall`进程回调以及`ThreadObCall`线程回调,之所以放在一起来讲解是因为这两中回调在枚举是都需要使用通用结构体`_OB_CALLBACK`以及`_OBJECT_TYPE`所以放在一起来讲解最好不过。
50 1
6.9 Windows驱动开发:内核枚举进线程ObCall回调
|
5月前
|
监控 安全 API
7.6 Windows驱动开发:内核监控FileObject文件回调
本篇文章与上一篇文章`《内核注册并监控对象回调》`所使用的方式是一样的都是使用`ObRegisterCallbacks`注册回调事件,只不过上一篇博文中`LyShark`将回调结构体`OB_OPERATION_REGISTRATION`中的`ObjectType`填充为了`PsProcessType`和`PsThreadType`格式从而实现监控进程与线程,本章我们需要将该结构填充为`IoFileObjectType`以此来实现对文件的监控,文件过滤驱动不仅仅可以用来监控文件的打开,还可以用它实现对文件的保护,一旦驱动加载则文件是不可被删除和改动的。
31 1
7.6 Windows驱动开发:内核监控FileObject文件回调
|
5月前
|
存储 算法 数据安全/隐私保护
6.5 Windows驱动开发:内核枚举PspCidTable句柄表
在 Windows 操作系统内核中,PspCidTable 通常是与进程(Process)管理相关的数据结构之一。它与进程的标识和管理有关,每个进程都有一个唯一的标识符,称为进程 ID(PID)。与之相关的是客户端 ID,它是一个结构,其中包含唯一标识进程的信息。这样的标识符在进程管理、线程管理和内核对象的创建等方面都起到关键作用。
33 1
6.5 Windows驱动开发:内核枚举PspCidTable句柄表
|
5月前
|
存储 Windows
4.6 Windows驱动开发:内核遍历进程VAD结构体
在上一篇文章`《内核中实现Dump进程转储》`中我们实现了ARK工具的转存功能,本篇文章继续以内存为出发点介绍`VAD`结构,该结构的全程是`Virtual Address Descriptor`即`虚拟地址描述符`,VAD是一个`AVL`自`平衡二叉树`,树的每一个节点代表一段虚拟地址空间。程序中的代码段,数据段,堆段都会各种占用一个或多个`VAD`节点,由一个`MMVAD`结构完整描述。
45 0
4.6 Windows驱动开发:内核遍历进程VAD结构体
|
5月前
|
监控 安全 API
7.1 Windows驱动开发:内核监控进程与线程回调
在前面的文章中`LyShark`一直在重复的实现对系统底层模块的枚举,今天我们将展开一个新的话题,内核监控,我们以`监控进程线程`创建为例,在`Win10`系统中监控进程与线程可以使用微软提供给我们的两个新函数来实现,此类函数的原理是创建一个回调事件,当有进程或线程被创建或者注销时,系统会通过回调机制将该进程相关信息优先返回给我们自己的函数待处理结束后再转向系统层。
62 0
7.1 Windows驱动开发:内核监控进程与线程回调
|
11月前
驱动开发:内核实现SSDT挂钩与摘钩
在前面的文章`《驱动开发:内核解析PE结构导出表》`中我们封装了两个函数`KernelMapFile()`函数可用来读取内核文件,`GetAddressFromFunction()`函数可用来在导出表中寻找指定函数的导出地址,本章将以此为基础实现对特定`SSDT`函数的`Hook`挂钩操作,与`《驱动开发:内核层InlineHook挂钩函数》`所使用的挂钩技术基本一致,不同点是前者使用了`CR3`的方式改写内存,而今天所讲的是通过`MDL映射`实现,此外前者挂钩中所取到的地址是通过`GetProcessAddress()`取到的动态地址,而今天所使用的方式是通过读取导出表寻找。
11243 0
|
监控 API 索引
驱动开发:恢复SSDT内核钩子
SSDT 中文名称为系统服务描述符表,该表的作用是将Ring3应用层与Ring0内核层,两者的API函数连接起来,起到承上启下的作用,SSDT并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用的信息,诸如地址索引的基址、服务函数个数等,SSDT 通过修改此表的函数地址可以对常用 Windows 函数进行内核级的Hook,从而实现对一些核心的系统动作进行过滤、监控的目的。
372 0
驱动开发:恢复SSDT内核钩子
|
安全 API
驱动开发:内核层InlineHook挂钩函数
在上一章`《驱动开发:内核LDE64引擎计算汇编长度》`中,`LyShark`教大家如何通过`LDE64`引擎实现计算反汇编指令长度,本章将在此基础之上实现内联函数挂钩,内核中的`InlineHook`函数挂钩其实与应用层一致,都是使用`劫持执行流`并跳转到我们自己的函数上来做处理,唯一的不同的是内核`Hook`只针对`内核API`函数,但由于其身处在`最底层`所以一旦被挂钩其整个应用层都将会受到影响,这就直接决定了在内核层挂钩的效果是应用层无法比拟的,对于安全从业者来说学会使用内核挂钩也是很重要。
421 0
驱动开发:内核层InlineHook挂钩函数