概述 每一个进程,都有一个进程空间,进程运行所需要的所有代码和数据,全部存储于进程空间,进程空间被分为两部分,用户空间与内核空间。
不同进程的用户空间,相互之间隔离,互不影响,但是内核空间是大家共享的,操作系统通过内核层代码给应用程序提供支持。
用户空间代码不能直接访问内核空间,所能执行的指令也有限制,在内核空间的代码可以执行特权指令,由用户空间访问内核空间,必须通过一定的接口。
我们编写的驱动程序,并非是平时我们看到的一个能够运行起来的程序,而是加载到内核空间,成为操作系统的一部分,成为应用程序与硬件的桥梁,为应用程序提供支持的
一个模块。
根据功能的不同我们将驱动开发大致分为两类
1仅仅作为 windows操作系统内核的扩展,而并非是使得一个硬件工作起来,我们将其称为内核程序
2针对于某一种硬件,使得其能够很好的工作起来,通常称之为驱动程序根据开发框架的不同我们将驱动开发分为三类
想要在Window10 上运行必须要有签名
方法:使用管理员权限打开cmd窗口 输入bcdedit /set testsigning on (设置为测试模式,然后重启)
初识驱动编程 开发环境搭建 搭建Windows驱动开发环境需要到微软官方下载WDK(WindowsDrover Kit),即微软驱动开发包
注:安装后需要加上对应的缓解库
在官网找到对应的版本
驱动创建 要写一个能够运行的驱动程序,最起码包含一下几点
包含ntddk.h
编写一个DriverEntry入口函数
编写一个驱动卸载函数,否则驱动将无法卸载
创建空的WDM驱动工程
修改项目属性
一个简单的驱动程序 要写一个能够运行的NT式驱动程序,最起码要包含以下几个点
包含ntddk.h文件
编写一个DriverEntry入口函数
编写一个驱动卸载函数(不写驱动卸载函数,驱动将无法卸载)
DriverEntry 1 2 3 4 5 6 NTSTATUS DriverEntry ( PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {}
卸载函数 一个驱动程序必须要有个卸载函数,否在驱动程序无法卸载,在NT式驱动中,卸载函数通常做一些资源回收,清理工作,函数原型如下
1 VOID Unload (_In_ struct _DRIVER_OBJECT *DriverObject) {};
示例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <ntddk.h> VOID Unload (PDRIVER_OBJECT DriverObject) { KdPrint(("driver unloading...\n" )); UNREFERENCED_PARAMETER(DriverObject); } NTSTATUS DriverEntry ( PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(DriverObject); UNREFERENCED_PARAMETER(RegistryPath); DbgBreakPoint(); DriverObject->DriverUnload = Unload; KdPrint(("driver loading...\n" )); return STATUS_SUCCESS; }
注意:驱动程序的编译要求比较高,实际上可以相对的降低编译要求
返回状态
状态代码
描述
STATUS_SUCCESS
正常完成
STATUS_UNSUCCESSFUL
请求失败,没有描述失败原因的代码
STATUS_NOT_IMPLEMENTED
一个没有实现的功能
STATUS_INVALID_HANDLE
提供给该操作数的句柄无效
STATUS_INVALID_PARAMETER
参数错误
STATUS_INVALID_DEVICE_REQUEST
该请求对这个文件设备无效
STATUS_END_OF_FILE
到达文件尾
STATUS_DELETE_PENDING
设备正处于从系统资源被删除的过程中
STATUS_INSUFFICIENT_RESOURCES
没有足够的系统资源(通常是内存)来执行该操作
驱动调试 调试信息 驱动不像应用程序,通常可以输出到控制台,输出到窗口。在驱动程序编程中,无法直接显示提示信息,通常使用输出调试信息,所以需要工具捕获(DebugView、Windbg)
由于Windows7默认对调试信息做了过滤处理,因此需要需要修改注册表设置开启Windows7的调试信息输出功能
1 2 3 4 5 6 Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter] [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter] "DEFAULT"=dword:0000000f
双机环境 驱动不像.exe文件,可以独立运行互不干扰,其代码运行于内核空间,不能随意下断点,故而需要使用双机调试的方式。
通过Windbg + 虚拟机 + VirtualKD配置调试环境,VirtualKD是一个开的源调试辅助软件,能版主Windbg与VMWare建立快速通讯,下载官网 http://virtualkd.sysprogs.org
VirtualKD分为两部分,在target子文件夹的所有文件需要拷贝到虚拟机中,并且在目标机中运行里面的vminstall.exe,点击Install按钮安装VirtualKD的虚拟机部分。按照默认方式安装后,VirtualKD会在目标机上添加一个新的启动项,此时在主机上运行vmmon.exe或vmmon64.exe来启动VirtualKD监视工具,点击“Debugger path…”按钮选择Windbg完成基本配置。
虚拟机启动目标操作系统后,Windbg会自动弹出,即可进行双击调试。
蓝屏错误处理 内核编程通常指在系统内核进行程序编写的一种编程行为,其代码运行在内核层
高等级的内核编程就是一个集大成者,从最基础的反汇编、逆向、PE文件到系统结构、硬件层的x86结构等都需要完全的掌握,任何的差错换来的都是BSoD(Blue Screen of Death “蓝屏死机的英文缩写”)
蓝屏之后,就会生成转储文件
将该文件拖入Windgb中即可开始调试
使用命令
1 !analyze -v //这条命令会显示程序崩溃处的代码
加载驱动 代码加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 SC_HANDLE hServiceMgr = OpenSCManager(NULL , NULL , SC_MANAGER_ALL_ACCESS); SC_HANDLE hServiceDDK = CreateService( hServiceMgr, L"服务名称" , L"显示名称" , SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, L"E:\\Test\\Test.sys" , NULL , NULL , NULL , NULL , NULL ); StartService(hServiceDDK, NULL , NULL );
也可使用其它工具加载
内核编程基础 概述 在Windows系统内核中,依然要秉承着以面向对象的观点来看待整体结构,同Windwos用户层一样,内核编程也充斥的对象的概念,对象之间通过消息相互作用
在内核编程有三个重要的概念
驱动对象
设备对象
IRP
驱动对象 驱动程序就是一个 .sys 模块, 驱动对象则是 .sys 被加载到内核中的实例化出来的对象, 用于表示这个驱动模块.
Windows内核使用 DRIVER_OBJECT 结构体来描述一个驱动对象.
虽然Windows内核源码使用C语言编写, 但也使用了面向对象的思想. 在面向对象思想中, 有父类, 抽象类,纯虚函数的概念, 纯虚函数就是一个类中无须定义的虚函数. 拥有这个函 数的类被称为抽象类, 继承抽象类的子类必须实现纯虚函数.
在 DRIVER_OBJECT 这个结构体中, 它相当于一个类. 类中有几个字段是函数指针, 这些函 数指针能够保存一些系统会调用的特定函数.
驱动对象就类似于一个类对象, 这个类对象直接被操作系统内核框架所操作, 当一个驱动 对象要加载时, 系统会调用 DriverEntry 函数来构造此对象, 函数的第一个参数就是 DRIVER_OBJECT* , 结构体指针, 这个结构体指针就类似于 this 指针(C语言没有this指 针,此处只是类比), 在这个函数内, 需要编写代码来初始化驱动对象的一些必要字段
PDRIVER_INITIALIZE DriverInit - 驱动对象初始化函数指针, 此函数指针被I/O 管理器初始化,指向 DriverEntry 函数
PDRIVER_STARTIO DriverStartIo - 驱动对象用于处理I/O的函数指针, 此函数指 针可以被设置为 NULL
PDRIVER_UNLOAD DriverUnload - 驱动对象的卸载函数指针, 当驱动被卸载时会 被系统调用, 此函数指针必须被赋值,否则当驱动被卸载时,字段为NULL会引发系统蓝屏
PDRIVER_DISPATCHMajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1] - 驱动 对象的派遣函数指针数组. 这个数组中,不同的下标保存着不同的函数, 下表列出了每 个元素的下标(宏)所保存的函数指针 , (这个数组内的函数指针都可以设置为NULL)
设备对象 设备对象一般是由驱动对象出来的(非即插即用驱动) . 驱动对象能够保存各种派遣函数, 但是, 这些派遣函数的一般是由I/O管理器所调用. 在调用派遣函数时, I/O 管理器会将附 加的信息打包到一个结构体,并传递给派遣函数.
一般这个结构体被称为 IRP 结构. 而只有设备对象才能接收到 I/O 管理器的I/O 请求(什么是I/O请求? 比如, 当一个文件的设备对 象被打开( CreateFile )之后, 对此文件的设备对象的读( ReadFile )和写( WriteFile ) 就是 I/O 请求)
但是设备对象是不能独立存在的, 设备对象能够接收 I/O 请求, 但是却没有处理 I/O 请 求的派遣函数, 处理 I/O 请求的派遣函数保存在驱动对象中.
因此, 在一个驱动项目中, 要有两种对象存在:
驱动对象 , 能够保存处理 I/O 请求的派遣函数.
设备对象 , 能够接收到 I/O 请求.
而且, 驱动对象无法被用户层代码所访问到 , 但是设备对象可以
设备对象虽然在内核层, 但是创建了 DOS 下的符号链接之后, 在用户层中就可以通过CreateFile 来打开设备对象. 并能够通过ReadFile , WriteFile , DeviceIOControl , GetFileSize 等函数来间接地调用保存 在驱动对象中的派遣函数
小结 驱动对象:DriverEntry传递进来的参数,他就代表着这个驱动程序,我们需要给DriverEntry填写一些字段。在产生特定事件的时候,会自动调用填写的函数。
设备对象:需要我们主动创建,可以接收消息。
IRP:凡是有信息传递,在这里都视为IO,IRP就是传递IO的一个消息包。
包含一个IRP结构体和一个IRP栈结构体,两个结构体配合,一起提供消息相关的一些信息。
设备对象的创建和销毁
IoCreateDevice - 创建设备对象
IoDeleteDevice - 销毁设备对象
符号链接 符号链接就是一个名字, \DosDevices\D:\\\\这是一个盘符, 但其实也可以视为一个符 号链接名.它的作用是能够让用处层的API发出IO请求, 并能够在发出IO请求时指定一个设备来处理此IO请求: 当用户层的应用程序发出一个IO请求时, 对象管理器通过此符号链接名称来找到对应的设 备, 对象管理器能够解析符号链接的名称, 以确定IO请求的目的地. 符号链接是给设备对象使用的, 设备对象默认没有符号链接, 没有符号链接的设备对象无 法被用户层的代码所使用. 为设备对象创建符号链接之后才可以. 在内核中, 符号链接的种类有两种:
NT 设备名 - 设备名一般格式为 “\Device\自定义设备名” , 此格式的名字一般是用 于传递函数 IoCreateDevice 所要求给出的设备名. 这个设备名可以在内核下使用, 但是用户层程序无法使用
DOS设备名 - 设备名一般格式为: “\DosDevices\自定义设备名” , 此格式的名字一 般用户传递给函数 IoCreateSymbolLinkName 的参数, 后面这个函数的功能是为一 个NT设备名创建一个用户层能够使用的符号链接名.
符号的创建和销毁
IoCreateSymbolicLink - 为一个NT设备名链接到一个DOS设备名 , DOS设备名 可供用户层程序使用. IoDeleteSymbolicLink - 删除一个DOS设备名.
打开设备 在用户层打开设备
当驱动对象创建了设备对象, 并且设备对象也建立了DOS符号链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 / 驱动程序入口函数 NTSTATUS DriverEntry (DRIVER_OBJECT* driver, UNICODE_STRING* path) { driver->DriverUnload = NULL ; UNICODE_STRING ntDeviceName; RtlInitUnicodeString(&ntDeviceName, L"\\Device\\dev_test_1" ); DEVICE_OBJECT* device; NTSTATUS ret; ret = IoCreateDevice(driver, 0 , &ntDeviceName, FILE_DEVICE_UNKNOWN, 0 , 0 , &device); if (!NT_SUCCESS(ret)) { return ret; } UNICODE_STRING dosDeviceName; RtlInitUnicodeString(&dosDeviceName, L"\\DosDevice\\dev_test_1" ); IoCreateSymbolicLink(&dosDeviceName, &ntDeviceName); }
上面的代码创建的DOS设备名为: dev_test_1 ,( \DosDevice\ 只是一个前缀). 那么 在三环中, 可以通过以下方式打开此DOS设备:
1 2 3 4 5 6 7 file = CreateFileW(L"\\\\.\\dev_test_1" , GENERIC READ | GENERIC WRITE, 0 , NULL , OPEN_EXISTING, 0 , NULL );
派遣函数和IRP 上述代码打开了一个设备对象, 然后保存在驱动对象中派遣函数数组 MajorFunction 的 第 IRP_MJ_CREATE 项就会被调用, 如果这个元素被设置为NULL,则不会被调用, 但是, CreateFile 也将调用失败. 调用此函数后, 系统会将 CreateFile 传递的参数传递给派 遣函数. 但派遣函数的原型(参数列表)中却没有这些形参:
1 2 3 4 5 typedef NTSTATUS DRIVER_DISPATCH ( _In_ struct _DEVICE_OBJECT *DeviceObject, _Inout_ struct _IRP *Irp ) ;
上面的代码就是所有派遣函数的原型, 只有一个设备对象, 和IRP结构体指针两个参数. 那 么在用户层中传递过来的参数在哪里传递?
系统其实已经将这些参数保存在了 IRP 和 IO_STACK_LOCALTION 结构中.
因此, IRP实际就是一个用户保存用户层传递进来的参数. 这些参数有多种 , 而且, 对于不 同的IO请求, 会有不同的参数, 而无论什么IO请求 ,多少个参数, 都只能通过此结构体来保 存, 因此这个结构体比较庞大.
在MSDN的文档中, 专门有介绍不同的IO请求
IRP处理过程 在用户层中打开文件时的过程
在用户层通过子系统调用I/O系统服务来打开命名文件
进入到内核层. 由IO管理器调用对象管理器去查找和解析文件对象的符号链接, 它会 调用安全引用监视器来检查子系统是否具有正确的访问权限来打开文件对象.
如果卷尚未挂载,I/O管理器将暂时挂起打开的请求,并调用一个或多个文件系统, 直到其中一个文件系统将文件对象识别为文件系统使用的大容量存储设备之一上存 储的对象。当文件系统已安装卷时,I/O管理器恢复请求。
I/O管理器为打开请求分配内存并初始化IRP, 同时也会分配 IO_STACK_LOCATION 数 组。对于驱动程序,打开等同于“创建”请求。
I/O管理器调用文件系统驱动程序,将IRP传递给它。文件系统驱动程序访问其在IRP 中的I/O堆栈位置,以确定它必须执行什么操作,检查参数,确定所请求的文件是否 在高速缓存中,如果没有,则在IRP中设置下一级驱动程序的I/O堆栈位置。
两个驱动程序都处理IRP并完成所请求的I/O操作,调用由I/O管理器和其他系统组件 提供的内核模式支持例程(在前面的图中未示出)。
驱动程序将IRP返回到I/O管理器,并在IRP中设置I/O状态块,以指示所请求的操作是否成功或为什么失败。
I/O管理器从IRP获取I/O状态,因此它可以通过受保护的子系统向原始调用者返回状态信息。
释放已经处理完成的IRP
如果打开操作成功,I/O管理器将文件对象句柄返回到子系统。如果存在错误,则返 回适当的状态给子系统。
在子系统成功打开表示数据文件、设备或卷的文件对象之后,子系统使用返回的句柄在 设备I/O操作的后续请求(通常是读、写或设备I/O控制请求)中标识文件对象。为了做出 这样的请求,子系统调用I/O系统服务。I/O管理器将这些请求路由到发送给适当驱动程序 的IRP。
IRP处理流程总结
IRP被IO管理器所创建
一个IRP被发出来,可以被多个驱动对象所处理, 每个驱动对象都能做出不同的处理.
IO管理器创建IRP时, 会找出所有能够处理此IRP的设备对象, 并为每一个设备对 象建立一个IRP栈元素,(一个驱动对象所创建的设备可以挂载到另一个驱动的设 备对象链中, IoAttatchDeviceToDeviceStack ) 于是便有了IRP栈, 通过函数 IoGetCurrentIrpStackLocation 能够获取到本驱动的IRP栈,
一个IRP被处理完成之后, 使用 IoCompleteRequest 来设置IRP的完成状态, 设置时, 主要设置以下内容
Irp.IoStatus.Status - 将完成的状态设置到此字段(成功了失败了, 总之得 有一个状态码) , 这个状态码可以使用 STATUS_XXXX 这系列的宏.
Irp.IoStatus.Information - 设置完成的字节数(如读取了多少字节, 写入了 多少字节等等)
IRQR IRQL即 :中断请求级别(Interrupt ReQuest Level,IRQL) .
内核实际就是一个进程( ntoskrnl.exe ) , 里面有非常多的全局变量, 而且有大量的线程 在运行. 当几个线程同时操作一个全局变量时, 就会出现问题, 为了解决这个问题, 微软这才提出了 IRQL 的概念. 这套东西主要是为了保证代码执行的优先级,原子级的, 它分为以 下级别:
Dispatch :所有运行在Dispatch级的代码都是会被进行原子操作的,且不能访问 分页内存,也就是说操作系统中在一个时间内只能运行一段Dispatch级的代码,且 必须将其完全执行完毕后才会发生线程切换
APC :比 Dispatch 低的一个级别,可以访问分页内存
Passive : 的优先级,大多数代码所运行的级别
MDL 1 2 3 4 5 6 7 8 9 10 typedef struct _MDL { struct _MDL *Next ; CSHORT Size; CSHORT MdlFlags; struct _EPROCESS *Process ; PVOID MappedSystemVa; PVOID StartVa; ULONG ByteCount; ULONG ByteOffset; } MDL, *PMDL;
一个连续的虚拟内存地址范围可能是由多个分布(spread over)在不相邻的物理页所组 成的。系统使用MDL(内存描述符表)结构体来表明虚拟内存缓冲区的物理页面布局。 我们应该避免直接访问MDL。我们可以使用MS-Windows提供的宏,他们提供了对这个 结构体基本 的访问。
MmGetMdlVirtualAddress 获取缓冲区的虚拟内存地址 ·
MmGetMdlByteCount 获取缓冲区的大小(字节数)
MmGetMdlByteOffset 获取缓冲区开端的物理页的大小(字节数)
MmGetMdlPfnArray 获取记录物理页码的一个数组指针。
我们可以用 IoAllocateMdl 函数来分配一个MDL。如果要取消分配,可是使用 IoFreeMdl 函数。或者,可以使用 MmInitializeMdl 来把一个之前定义的缓冲区定制 成一个MDL。但是以上两种方式都不能初始化物理页码数组。
对于在非分页池中分配的缓冲区,可以用 MmBuidlMdlForNonpagedPool 函数来初始化 页码数组。对于可分页的内存,虚拟内存和物理内存之间的联系是暂时的,所以MDL的页码数组只在特定的环境和时间段有效,因为很可能其他的程序对它们进行重新分配, 为了使其他的程序无法对他们进行修改和重新分配(在我们释放之前),我们就需要把 这段内存锁定,防止其他程序修改,我们可以用 MmProbeAndLockPages 来实现,这个 函数同时还为当前的布局初始化了页码数组。当我们用 MmUnlockPages 来释放被锁定 的内存时,页码数组也会随之无效。
假如MDL指定的是映射一块内核级别的虚拟地址空间,那么我们要用 MmGetSystemAddressForMdlSafe ,这样我们就能防止映射目标是来自用户模式的空 间,而来自用户模式空间的物理页只能在用户模式上下文环境中使用,并且随时可能被 清空。用函数进行申明后,就可以防止以上情况发生了。
总结 MDL就是描述一块虚拟内存的结构体,里面有个成员记录了多个页码,这些页码 即处于各个不同物理地址的物理块的页号。
所以要对一块受系统保护的区域进行写操作的话,可以这样来修改它的保护属性:
创建一个MDL,显然里面的物理页号数组没有初始化 IoAllocateMdl
初始化页码数组,使之成为实际有效的MDL MmBuildMdlForNonPagedPool
进行锁定,并且重新赋值新的保护属性为可读 MmProbeAndLockPages
获得我们所映射后的实际内存区域的虚拟地址 MmMapLockedPagesSpecifyCache
使用示例
1 2 3 4 5 6 7 8 9 10 11 12 const wchar_t * pStr = L"123456789abcdefg0" ; PMDL mdl = IoAllocateMdl(pStr, 17 , 0 , 0 , 0 ); MmBuildMdlForNonPagedPool(mdl); wchar_t * p = (wchar_t *)MmMapLockedPagesSpecifyCache(mdl, KernelMode, MmWr p[1 ] = 'A' ; MmUnmapLockedPages(p, mdl); IoFreeMdl(mdl);
数据类型 Windows内核开发中, 采用的是和用户层编程所不一样的数据类型:
数据类型
长度
基本型
类型名称
UINT8
8 bit
unsigned char
无符号字符
UCHAR
8 bit
unsigned char
无符号字符
PUCHAR
32 bit
unsigned char*
无符号字符指针
UINT16
16 bit
unsigned short
无符号短整形
USHORT
16 bit
unsigned short
无符号短整形
PUSHORT
32 bit
unsigned short*
无符号短整形指针
UINT32
32 bit
unsigned int
无符号整形
UINT
32 bit
unsigned int
无符号整形
ULONG
32 bit
unsigned long
无符号长整形
PUNIT
32 bit
unsigned int*
无符号整形指针
UINT64
64 bit
unsigned __int64
无符号64位整形
ULONG64
64 bit
unsigned __int64
无符号64位整形
PULONG64
32 bit
unsigned __int64*
无符号64位整形指针
这些数据类型和用户层的数据类型大同小异
字符串操作 字符串的操作是截然不同的.
内核中同一采用UNICODE_STRING结构体来存取字符串. 这样做是为了更安全. Windows 内核默认使用Unicode编码.
下面是结构体的说明:
1 2 3 4 5 6 typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWCH Buffer; #endif } UNICODE_STRING;
在这个结构体中, length保存的是当前字符串的字符长度(字节数), MaximumLength保存的是字符串缓冲区的最大长度(字节数).
使用这个结构体的时候, 将字符串赋值给Buffer字段, 但不能只给这个字段赋值, 其它字段也必须一起初始化, 下面是操作这个结构体的函数:
函数名
功能
RtlInitUnicodeString
初始化字符串 ,注意,此函数不会分配空间.
RtlFreeUnicodeString
销毁字符串
RtlCopyUnicodeString
拷贝字符串
RtlAppendUnicodeStringToString
追加字符串
RtlCmpareUnicodeString
比较字符串
RtlUnicodeStringToInteger
字符串转数字
RtlIntegerToUnicodeString
数字转字符串
RtlAppendUnicodeStringToString
将UNICODE字符串结构转换为ANSI
Kdprint
输出调试信息
内存操作 ExAllocatePool - 内存分配函数
ExFreePool - 内存释放函数
但是这两个已经被弃用了
1 2 3 4 5 6 7 8 9 10 11 PCHAR str = (PCHAR)ExAllocatePoolWithTag(NonPagedPool, 1024 ,0 ); if (str == NULL ) { KdPrint(("非分页内存分配失败\n" )); return STATUS_INSUFFICIENT_RESOURCES; } KdPrint(("非分页内存分配成功\n" )); RtlZeroMemory(str, 1024 ); strcpy (str, "已经分配好的内存空间" ); KdPrint(("%s\n" , str)); ExFreePoolWithTag(str,0 );
链表操作 在内核中, 有很多链表:
进程内核对象链表
线程内核对象链表
驱动对象链表
模块链表
在Windows内核中, 无论是什么链表, 都是使用如下结构的双向链表:
1 2 3 4 typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink ; struct _LIST_ENTRY *Blink ; } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
函数名
描述
InitializeListHead
初始化
lsListEmpty
链表初始化
lnsertHeadList
从首部插入链表
insertTaiList
从尾部插入链表
RemoveHeadList
从首部删除链表
RemoveTaliList
从尾部删除链表
内核安全编程 内核对象分析
查看某一个进程的内核对象结构
他的TypeIndex是0x7。他是ObTypeIndexTable的下标
这个数组中存放的都是_OBJECT_TYPE的地址。
至此如果改掉这个回调的地址呢
OBJECT_HOOK 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 #include <ntifs.h> VOID DriverUnload (PDRIVER_OBJECT objDriver) ;typedef ULONG (*OBGETOBJECTTYPE) (PVOID Object) ;typedef NTSTATUS (*PARSEPRODECEDURE) (IN PVOID ParseObject, IN PVOID ObjectType, IN OUT PACCESS_STATE AccessState, IN KPROCESSOR_MODE AccessMode, IN ULONG Attributes, IN OUT PUNICODE_STRING CompleteName, IN OUT PUNICODE_STRING RemainingName, IN OUT PVOID Context OPTIONAL, IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL, OUT PVOID* Object) ;PARSEPRODECEDURE g_OldFun; NTSTATUS NewParseProcedure (IN PVOID ParseObject, IN PVOID ObjectType, IN OUT PACCESS_STATE AccessState, IN KPROCESSOR_MODE AccessMode, IN ULONG Attributes, IN OUT PUNICODE_STRING CompleteName, IN OUT PUNICODE_STRING RemainingName, IN OUT PVOID Context OPTIONAL, IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL, OUT PVOID* Object) { KdPrint(("Hook Success\n" )); return g_OldFun(ParseObject, ObjectType, AccessState, AccessMode, Attributes, CompleteName, RemainingName, Context, SecurityQos, Object); } OBGETOBJECTTYPE g_OBGetObjectType; VOID GetObjectTypeAddress () { PUCHAR addr; UNICODE_STRING pslookup; RtlInitUnicodeString(&pslookup, L"ObGetObjectType" ); addr = (PUCHAR)MmGetSystemRoutineAddress(&pslookup); g_OBGetObjectType = (OBGETOBJECTTYPE)addr; } typedef struct _OBJECT_TYPE_INITIALIZER { USHORT Length; UCHAR ObjectTypeFlags; UCHAR CaseInsensitive; UCHAR UnnamedObjectsOnly; UCHAR UseDefaultObject; UCHAR SecurityRequired; UCHAR MaintainHandleCount; UCHAR MaintainTypeList; UCHAR SupportsObjectCallbacks; UCHAR CacheAligned; ULONG ObjectTypeCode; BOOLEAN InvalidAttributes; GENERIC_MAPPING GenericMapping; BOOLEAN ValidAccessMask; BOOLEAN RetainAccess; POOL_TYPE PoolType; BOOLEAN DefaultPagedPoolCharge; BOOLEAN DefaultNonPagedPoolCharge; PVOID DumpProcedure; ULONG OpenProcedure; PVOID CloseProcedure; PVOID DeleteProcedure; ULONG ParseProcedure; ULONG SecurityProcedure; ULONG QueryNameProcedure; UCHAR OkayToCloseProcedure; } OBJECT_TYPE_INITIALIZER, * POBJECT_TYPE_INITIALIZER; typedef struct _OBJECT_TYPE { LIST_ENTRY TypeList; UNICODE_STRING Name; PVOID DefaultObject; ULONG Index; ULONG TotalNumberOfObjects; ULONG TotalNumberOfHandles; ULONG HighWaterNumberOfObjects; ULONG HighWaterNumberOfHandles; OBJECT_TYPE_INITIALIZER TypeInfo; ULONG TypeLock; ULONG Key; LIST_ENTRY CallbackList; } OBJECT_TYPE, * POBJECT_TYPE; OBJECT_TYPE* g_fileType = NULL ; HANDLE KernelCreateFile ( IN PUNICODE_STRING pstrFile, IN BOOLEAN bIsDir) { HANDLE hFile = NULL ; NTSTATUS Status = STATUS_UNSUCCESSFUL; IO_STATUS_BLOCK StatusBlock = { 0 }; ULONG ulShareAccess = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; ULONG ulCreateOpt = FILE_SYNCHRONOUS_IO_NONALERT; OBJECT_ATTRIBUTES objAttrib = { 0 }; ULONG ulAttributes = OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE; InitializeObjectAttributes( &objAttrib, pstrFile, ulAttributes, NULL , NULL ); ulCreateOpt |= bIsDir ? FILE_DIRECTORY_FILE : FILE_NON_DIRECTORY_FILE; Status = ZwCreateFile( &hFile, GENERIC_ALL, &objAttrib, &StatusBlock, 0 , FILE_ATTRIBUTE_NORMAL, ulShareAccess, FILE_OPEN_IF, ulCreateOpt, NULL , 0 ); if (!NT_SUCCESS(Status)) return (HANDLE)-1 ; return hFile; } void OnHook () { UNICODE_STRING ustrFilePath; RtlInitUnicodeString(&ustrFilePath, L"\\??\\D:\\123.txt" ); HANDLE hFile = KernelCreateFile(&ustrFilePath, FALSE); PVOID pObject; ObReferenceObjectByHandle(hFile, GENERIC_ALL, NULL , KernelMode, &pObject, NULL ); g_fileType = (OBJECT_TYPE*)g_OBGetObjectType(pObject); g_OldFun = (PARSEPRODECEDURE)(g_fileType->TypeInfo.ParseProcedure); g_fileType->TypeInfo.ParseProcedure = (ULONG)NewParseProcedure; } void OffHook () { g_fileType->TypeInfo.ParseProcedure = (ULONG)g_OldFun; } NTSTATUS DriverEntry ( PDRIVER_OBJECT pDriver, PUNICODE_STRING strRegPath) { UNREFERENCED_PARAMETER(strRegPath); DbgBreakPoint(); __try { GetObjectTypeAddress(); OnHook(); } except(1 ) { KdPrint(("掠过一个异常\n" )); } KdPrint(("My First Dirver!" )); pDriver->DriverUnload = DriverUnload; return STATUS_SUCCESS; } VOID DriverUnload (PDRIVER_OBJECT objDriver) { UNREFERENCED_PARAMETER(objDriver); KdPrint(("My Dirver is unloading..." )); OffHook(); }
驱动操作 遍历驱动 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 #include <ntddk.h> typedef struct _LDR_DATA_TABLE_ENTRY { struct _LIST_ENTRY InLoadOrderLinks ; struct _LIST_ENTRY InMemoryOrderLinks ; struct _LIST_ENTRY InInitializationOrderLinks ; VOID* DllBase; VOID* EntryPoint; ULONG SizeOfImage; struct _UNICODE_STRING FullDllName ; struct _UNICODE_STRING BaseDllName ; } *PLDR_DATA_TABLE_ENTRY; VOID DriverUnload (PDRIVER_OBJECT DriverObject) { KdPrint(("driver unloading...\n" )); UNREFERENCED_PARAMETER(DriverObject); } NTSTATUS DriverEntry ( PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(DriverObject); UNREFERENCED_PARAMETER(RegistryPath); DbgBreakPoint(); DriverObject->DriverUnload = DriverUnload; KdPrint(("driver loading...\n" )); PLDR_DATA_TABLE_ENTRY current = DriverObject->DriverSection; PLDR_DATA_TABLE_ENTRY item = DriverObject->DriverSection; int index = 1 ; do { KdPrint(("%d: %wZ %wZ\n" , index++, &item->BaseDllName, &item->FullDllName)); item = (PLDR_DATA_TABLE_ENTRY)item->InLoadOrderLinks.Flink; } while (current != item); current->InLoadOrderLinks.Flink->Blink = current->InLoadOrderLinks.Blink; current->InLoadOrderLinks.Blink->Flink = current->InLoadOrderLinks.Flink; return STATUS_SUCCESS; }
隐藏驱动 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 WCHAR* DllName = (WCHAR*)Irp->AssociatedIrp.SystemBuffer; UNICODE_STRING SysName = { 0 }; RtlInitUnicodeString(&SysName, (PCWSTR)DllName); PLDR_DATA_TABLE_ENTRY pLdr = (PLDR_DATA_TABLE_ENTRY)DeviceObject->DriverObject->DriverSection; PLDR_DATA_TABLE_ENTRY pTemp = pLdr; while ((PLDR_DATA_TABLE_ENTRY)pTemp->InLoadOrderLinks.Flink != pLdr) { if (pTemp->FullDllName.Buffer != 0 ) { KdPrint(("%wZ\n" , &pTemp->BaseDllName)); if (RtlCompareUnicodeString(&SysName, &pTemp->BaseDllName, TRUE) == 0 ) { pTemp->InLoadOrderLinks.Blink->Flink = pLdr->InLoadOrderLinks.Flink; pTemp->InLoadOrderLinks.Flink->Blink = pLdr->InLoadOrderLinks.Blink; break ; } } pTemp = (PLDR_DATA_TABLE_ENTRY)pTemp->InLoadOrderLinks.Flink; }
文件操作 文件操作函数在内容中使用的 ZwXXXX 系列
函数名
描述
ZwCreateFile
打开文件
ZwReadFile
读取文件
ZwWriteFile
写入文件
ZwQueryInformationFile
查询文件信息
ZwDeleteFile
删除文件
ZwClose
关闭文件
打开目录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 HANDLE KeCreateFile (LPCWSTR filePath, ACCESS_MASK access, ULONG createDisposition, BOOLEAN isFile) { HANDLE fileHandle = NULL ; IO_STATUS_BLOCK ioStatusBlock = { 0 }; UNICODE_STRING myFilePath1 = { 0 }; RtlInitUnicodeString(&myFilePath1, filePath); OBJECT_ATTRIBUTES objectAttributes = { 0 }; InitializeObjectAttributes( &objectAttributes, &myFilePath1, OBJ_CASE_INSENSITIVE, NULL , NULL ); ULONG CreateOptions = isFile ? FILE_NON_DIRECTORY_FILE : FILE_DIRECTORY_FILE; NTSTATUS Status = ZwCreateFile( &fileHandle, access, &objectAttributes, &ioStatusBlock, 0 , FILE_ATTRIBUTE_NORMAL, 0 , createDisposition, CreateOptions, 0 , 0 ); if (NT_SUCCESS(Status)) return fileHandle; else return (HANDLE)-1 ; }
遍历目录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 VOID EnumSystemDir (PVOID Buffer, ULONG BufferSize) { ULONG haveWriteSize = 0 ; WCHAR* NewBuffer = Buffer; WCHAR NotePage[] = L"NotePage" ; HANDLE DirHandle = KeCreateFile(L"\\??\\C:\\" , GENERIC_READ, FILE_OPEN, FALSE); IO_STATUS_BLOCK IoStatusBlock = { 0 }; ULONG InfoSize = sizeof (FILE_FULL_DIR_INFORMATION) + 260 * 2 ; PFILE_FULL_DIR_INFORMATION FileInfo = (PFILE_FULL_DIR_INFORMATION) ExAllocatePoolWithTag(NonPagedPool, InfoSize, 'jbo.'); NTSTATUS Status = ZwQueryDirectoryFile( DirHandle, NULL , NULL , NULL , &IoStatusBlock, FileInfo, InfoSize, FileFullDirectoryInformation, TRUE, NULL , TRUE); if (NT_SUCCESS(Status)) { do { haveWriteSize = haveWriteSize + FileInfo->FileNameLength + 2 ; if (haveWriteSize >= BufferSize) { RtlZeroMemory(Buffer, BufferSize); RtlCopyMemory(Buffer, NotePage, 18 ); break ; } RtlCopyMemory(NewBuffer, FileInfo->FileName, FileInfo->FileNameLength); NewBuffer += FileInfo->FileNameLength / 2 ; RtlCopyMemory(NewBuffer, L"\n" , 1 ); NewBuffer += 1 ; RtlZeroMemory(FileInfo, InfoSize); ZwQueryDirectoryFile( DirHandle, NULL , NULL , NULL , &IoStatusBlock, FileInfo, InfoSize, FileFullDirectoryInformation, TRUE, NULL , FALSE); } while (IoStatusBlock.Status != STATUS_NO_MORE_FILES); } ExFreePoolWithTag(FileInfo, 'jbo.'); ZwClose(DirHandle); }
删除文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 NTSTATUS KernelDeleteFile (IN PUNICODE_STRING pstrFile) { OBJECT_ATTRIBUTES objAttrib = { 0 }; ULONG ulAttributes = OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE; InitializeObjectAttributes( &objAttrib, pstrFile, ulAttributes, NULL , NULL ); return ZwDeleteFile(&objAttrib); } VOID DeleteFile (PVOID Buffer, ULONG BufferSize, PIRP Irp) { WCHAR* Path = (WCHAR*)Irp->AssociatedIrp.SystemBuffer; NTSTATUS status; WCHAR szSymbol[0x512 ] = L"\\??\\" ; wcscat_s(szSymbol, _countof(szSymbol), Path); UNICODE_STRING filePath = { 0 }; RtlInitUnicodeString(&filePath, (PCWSTR)szSymbol); status = KernelDeleteFile(&filePath); if (status == STATUS_SUCCESS) { RtlZeroMemory(Buffer, BufferSize); RtlCopyMemory(Buffer, L"删除成功" , 10 ); } }
获取文件信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 NTSTATUS KernelGetFileInfo (HANDLE fileHandle, WCHAR* buff, ULONG BufferSize) { int Zero = 0x00 ; IO_STATUS_BLOCK ioStatusBlock = { 0 }; FILE_STANDARD_INFORMATION fileInfo1 = { 0 }; NTSTATUS Status = ZwQueryInformationFile( fileHandle, &ioStatusBlock, &fileInfo1, sizeof (fileInfo1), FileStandardInformation); ULONG fileSize = (ULONG)fileInfo1.EndOfFile.QuadPart; UNICODE_STRING strFileSize = { 0 }; strFileSize.Buffer = (PWSTR)ExAllocatePoolWithTag(PagedPool, 0x400 , 0x81 ); strFileSize.MaximumLength = 0x400 ; RtlIntegerToUnicodeString(fileSize, 10 , &strFileSize); RtlCopyMemory(buff, strFileSize.Buffer, strFileSize.Length); buff += strFileSize.Length / 2 ; RtlCopyMemory(buff, &Zero, 1 ); buff += 1 ; FILE_BASIC_INFORMATION fileInfo2 = { 0 }; ZwQueryInformationFile( fileHandle, &ioStatusBlock, &fileInfo2, sizeof (fileInfo2), FileStandardInformation); LARGE_INTEGER fileCreateTime = fileInfo2.CreationTime; UNICODE_STRING strFileCreate = { 0 }; strFileCreate.Buffer = (PWSTR)ExAllocatePoolWithTag(PagedPool, 0x400 , 0x80 ); strFileCreate.MaximumLength = 0x400 ; RtlIntegerToUnicodeString(fileCreateTime.LowPart, 10 , &strFileCreate); RtlCopyMemory(buff, strFileCreate.Buffer, strFileCreate.Length); buff += strFileCreate.Length / 2 ; RtlCopyMemory(buff, &Zero, 1 ); buff += 1 ; RtlFreeUnicodeString(&strFileSize); RtlFreeUnicodeString(&strFileCreate); return Status; } VOID LookFileInfo (PVOID Buffer, ULONG BufferSize, PIRP Irp) { DbgBreakPoint(); WCHAR* Path = (WCHAR*)Irp->AssociatedIrp.SystemBuffer; NTSTATUS status; WCHAR szSymbol[0x512 ] = L"\\??\\" ; wcscat_s(szSymbol, _countof(szSymbol), Path); UNICODE_STRING filePath = { 0 }; RtlInitUnicodeString(&filePath, (PCWSTR)szSymbol); HANDLE hFile = KeCreateFile(szSymbol, GENERIC_READ, FILE_OPEN, TRUE); KernelGetFileInfo(hFile, Buffer, BufferSize); ZwClose(hFile); }
函数封装 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 HANDLE KernelCreateFile ( IN PUNICODE_STRING pstrFile, IN BOOLEAN bIsDir) { HANDLE hFile = NULL ; NTSTATUS Status = STATUS_UNSUCCESSFUL; IO_STATUS_BLOCK StatusBlock = { 0 }; ULONG ulShareAccess = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; ULONG ulCreateOpt = FILE_SYNCHRONOUS_IO_NONALERT; OBJECT_ATTRIBUTES objAttrib = { 0 }; ULONG ulAttributes = OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE; InitializeObjectAttributes( &objAttrib, pstrFile, ulAttributes, NULL , NULL ); ulCreateOpt |= bIsDir ? FILE_DIRECTORY_FILE : FILE_NON_DIRECTORY_FILE; Status = ZwCreateFile( &hFile, GENERIC_ALL, &objAttrib, &StatusBlock, 0 , FILE_ATTRIBUTE_NORMAL, ulShareAccess, FILE_OPEN_IF, ulCreateOpt, NULL , 0 ); if (!NT_SUCCESS(Status)) return (HANDLE)-1 ; return hFile; } ULONG64 KernelGetFileSize (IN HANDLE hfile) { IO_STATUS_BLOCK StatusBlock = { 0 }; FILE_STANDARD_INFORMATION fsi = { 0 }; NTSTATUS Status = STATUS_UNSUCCESSFUL; Status = ZwQueryInformationFile( hfile, &StatusBlock, &fsi, sizeof (FILE_STANDARD_INFORMATION), FileStandardInformation); if (!NT_SUCCESS(Status)) return 0 ; return fsi.EndOfFile.QuadPart; } ULONG64 KernelReadFile ( IN HANDLE hfile, IN PLARGE_INTEGER Offset, IN ULONG ulLength, OUT PVOID pBuffer) { IO_STATUS_BLOCK StatusBlock = { 0 }; NTSTATUS Status = STATUS_UNSUCCESSFUL; Status = ZwReadFile( hfile, NULL , NULL , NULL , &StatusBlock, pBuffer, ulLength, Offset, NULL ); if (!NT_SUCCESS(Status)) return 0 ; return StatusBlock.Information; } ULONG64 KernelWriteFile ( IN HANDLE hfile, IN PLARGE_INTEGER Offset, IN ULONG ulLength, IN PVOID pBuffer) { IO_STATUS_BLOCK StatusBlock = { 0 }; NTSTATUS Status = STATUS_UNSUCCESSFUL; Status = ZwWriteFile( hfile, NULL , NULL , NULL , &StatusBlock, pBuffer, ulLength, Offset, NULL ); if (!NT_SUCCESS(Status)) return 0 ; return StatusBlock.Information; } NTSTATUS KernelDeleteFile (IN PUNICODE_STRING pstrFile) { OBJECT_ATTRIBUTES objAttrib = { 0 }; ULONG ulAttributes = OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE; InitializeObjectAttributes( &objAttrib, pstrFile, ulAttributes, NULL , NULL ); return ZwDeleteFile(&objAttrib); }
进程操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 NTKERNELAPI HANDLE PsGetProcessInheritedFromUniqueProcessId ( IN PEPROCESS pEProcess) ;NTKERNELAPI UCHAR* PsGetProcessImageFileName ( IN PEPROCESS pEProcess) ;NTKERNELAPI PPEB PsGetProcessPeb (PEPROCESS Process) ;PEPROCESS LookupProcess (HANDLE hPid) { PEPROCESS pEProcess = NULL ; if (NT_SUCCESS( PsLookupProcessByProcessId( hPid, &pEProcess))) return pEProcess; return NULL ; }
遍历进程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 VOID EnumCurrentProcess (WCHAR* Buffer, ULONG BufferSize) { PEPROCESS EProcess = PsGetCurrentProcess(); PLIST_ENTRY ProcessList = (PLIST_ENTRY)((PUCHAR)EProcess + 0x00b8 ); PEPROCESS Process = NULL ; PUCHAR ProcessName = NULL ; ULONG ProcessId = 0 ; WCHAR* NewBuffer = Buffer; int Zero = 0x00 ; ULONG CalcBufferSize = 0 ; WCHAR NotePage[] = L"NotePage" ; ULONG NonPage = 1 ; while (Process != EProcess) { if (Process == NULL ) { Process = (PEPROCESS)((PUCHAR)ProcessList - 0x00b8 ); } ProcessName = (PUCHAR)Process + 0x016c ; UNICODE_STRING UName = { 0 }; ANSI_STRING AName = { 0 }; RtlInitAnsiString(&AName, (PCSZ)ProcessName); RtlAnsiStringToUnicodeString(&UName, &AName, TRUE); ProcessId = *(ULONG*)((ULONG_PTR)Process + 0x00b4 ); UNICODE_STRING UId = { 0 }; UId.Buffer = (PWSTR)ExAllocatePoolWithTag(PagedPool, 0x400 , 0x88 ); UId.MaximumLength = 0x400 ; RtlIntegerToUnicodeString(ProcessId, 10 , &UId); ULONG Flag = 1 ; PEPROCESS pEProc = LookupProcess((HANDLE)ProcessId); if (pEProc == 0 ) Flag = 0 ; if (Flag) { ULONG uEproc = (ULONG)pEProc; UNICODE_STRING UProcess = { 0 }; UProcess.Buffer = (PWSTR)ExAllocatePoolWithTag(PagedPool, 0x400 , 0x89 ); UProcess.MaximumLength = 0x400 ; RtlIntegerToUnicodeString(uEproc, 10 , &UProcess); UINT32 PPID = (UINT32)PsGetProcessInheritedFromUniqueProcessId(pEProc); UNICODE_STRING UPPID = { 0 }; UPPID.Buffer = (PWSTR)ExAllocatePoolWithTag(PagedPool, 0x400 , 0x8A ); UPPID.MaximumLength = 0x400 ; RtlIntegerToUnicodeString(PPID, 10 , &UPPID); UNICODE_STRING UImageFileName = { 0 }; ANSI_STRING AImageFileName = { 0 }; RtlInitAnsiString(&AImageFileName, PsGetProcessImageFileName(pEProc)); RtlAnsiStringToUnicodeString(&UImageFileName, &AImageFileName, TRUE); CalcBufferSize = CalcBufferSize + UName.Length + 2 + UId.Length + 2 + UPPID.Length + 2 + UProcess.Length + 2 + UImageFileName.Length + 2 ; if (CalcBufferSize >= BufferSize) { RtlCopyMemory(Buffer, NotePage, 18 ); NonPage = 0 ; break ; } RtlCopyMemory(NewBuffer, UName.Buffer, UName.Length); NewBuffer += UName.Length / 2 ; RtlCopyMemory(NewBuffer, &Zero, 1 ); NewBuffer += 1 ; RtlCopyMemory(NewBuffer, UId.Buffer, UId.Length); NewBuffer += UId.Length / 2 ; RtlCopyMemory(NewBuffer, &Zero, 1 ); NewBuffer += 1 ; RtlCopyMemory(NewBuffer, UPPID.Buffer, UPPID.Length); NewBuffer += UPPID.Length / 2 ; RtlCopyMemory(NewBuffer, &Zero, 1 ); NewBuffer += 1 ; RtlCopyMemory(NewBuffer, UImageFileName.Buffer, UImageFileName.Length); NewBuffer += UImageFileName.Length / 2 ; RtlCopyMemory(NewBuffer, &Zero, 1 ); NewBuffer += 1 ; RtlCopyMemory(NewBuffer, UProcess.Buffer, UProcess.Length); NewBuffer += UProcess.Length / 2 ; RtlCopyMemory(NewBuffer, &Zero, 1 ); NewBuffer += 1 ; RtlFreeUnicodeString(&UId); RtlFreeUnicodeString(&UName); RtlFreeUnicodeString(&UImageFileName); RtlFreeUnicodeString(&UPPID); RtlFreeUnicodeString(&UProcess); ObDereferenceObject(pEProc); } ProcessList = ProcessList->Flink; Process = (PEPROCESS)((PUCHAR)ProcessList - 0x00b8 ); } if (NonPage) { int End = 0x10 ; RtlCopyMemory(NewBuffer, &End, 1 ); NewBuffer += 1 ; } }
隐藏进程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink ; struct _LIST_ENTRY *Blink ; } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY; LONG HideCurrentProcess (ULONG ID) { PLIST_ENTRY ListEntry = NULL ; PLIST_ENTRY HeadEntry = NULL ; PEPROCESS EProcess = NULL ; PEPROCESS v1 = NULL ; ULONG ProcessId = 0 ; EProcess = PsGetCurrentProcess(); if (EProcess == NULL ) { return 0 ; } HeadEntry = (PLIST_ENTRY)((ULONG_PTR)EProcess + 0x0b8 ); ListEntry = (PLIST_ENTRY)((ULONG_PTR)EProcess + 0x0b8 ); v1 = (PEPROCESS)((ULONG_PTR)ListEntry->Flink - 0x0b8 ); while (v1 != EProcess) { ProcessId = *(ULONG*)((ULONG_PTR)v1 + 0x00b4 ); UNICODE_STRING UId = { 0 }; UId.Buffer = (PWSTR)ExAllocatePoolWithTag(PagedPool, 0x400 , 0x88 ); UId.MaximumLength = 0x400 ; RtlIntegerToUnicodeString(ProcessId, 10 , &UId); UNICODE_STRING uId = { 0 }; uId.Buffer = (PWSTR)ExAllocatePoolWithTag(PagedPool, 0x400 , 0x88 ); uId.MaximumLength = 0x400 ; RtlIntegerToUnicodeString(ID, 10 , &uId); ListEntry = (PLIST_ENTRY)((ULONG_PTR)v1 + 0x0b8 ); if (RtlEqualUnicodeString(&UId, &uId, FALSE) == 1 ) { if (ListEntry != NULL ) { ListEntry = ListEntry; RemoveEntryList(ListEntry); break ; } } RtlFreeUnicodeString(&UId); v1 = (PEPROCESS)((ULONG_PTR)(ListEntry->Flink) - 0x0b8 ); } return 1 ; }
结束进程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 LONG KernelKillProcess (HANDLE PID) { HANDLE hProcess = NULL ; CLIENT_ID ClientId = { 0 }; OBJECT_ATTRIBUTES objAttribut = { sizeof (OBJECT_ATTRIBUTES) }; ClientId.UniqueProcess = (HANDLE)PID; ClientId.UniqueThread = 0 ; ZwOpenProcess( &hProcess, 1 , &objAttribut, &ClientId); if (hProcess) { ZwTerminateProcess(hProcess, 0 ); ZwClose(hProcess); return 1 ; }; return 0 ; } VOID EndCurrentProcess (PVOID Buffer, ULONG BufferSize, PIRP Irp) { ULONG* Pid = (ULONG*)Irp->AssociatedIrp.SystemBuffer; LONG Ret = KernelKillProcess((HANDLE)*Pid); RtlZeroMemory(Buffer, BufferSize); if (Ret) RtlCopyMemory(Buffer, L"成功结束\n" , 12 ); else RtlCopyMemory(Buffer, L"结束失败\n" , 12 ); }
遍历线程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 VOID EnumCurrentThread (PVOID Buffer, ULONG BufferSize, PIRP Irp) { DbgBreakPoint(); RtlZeroMemory(Buffer, BufferSize); ULONG haveWriteSize = 0 ; WCHAR* OutBuffer = Buffer; WCHAR NotePage[] = L"NotePage" ; LONG Zero = 0x00 ; PETHREAD Thread = NULL ; for (int id = 4 ; id <= 5000 ; id += 4 ) { if (NT_SUCCESS(PsLookupThreadByThreadId((HANDLE)id, &Thread))) { PEPROCESS Process2 = IoThreadToProcess(Thread); UNICODE_STRING ThreadId = { 0 }; ThreadId.Buffer = (PWSTR)ExAllocatePoolWithTag(PagedPool, 0x400 , 0x80 ); ThreadId.MaximumLength = 0x400 ; RtlIntegerToUnicodeString(id, 10 , &ThreadId); int pid = (int )PsGetProcessId(Process2); UNICODE_STRING ProcessId = { 0 }; ProcessId.Buffer = (PWSTR)ExAllocatePoolWithTag(PagedPool, 0x400 , 0x81 ); ProcessId.MaximumLength = 0x400 ; RtlIntegerToUnicodeString(pid, 10 , &ProcessId); ULONG* Pid = (ULONG*)Irp->AssociatedIrp.SystemBuffer; UNICODE_STRING UId = { 0 }; UId.Buffer = (PWSTR)ExAllocatePoolWithTag(PagedPool, 0x400 , 0x82 ); UId.MaximumLength = 0x400 ; RtlIntegerToUnicodeString(*Pid, 10 , &UId); if (RtlEqualUnicodeString(&ProcessId, &UId, FALSE) == 1 ) { haveWriteSize = haveWriteSize + ThreadId.Length + 2 + ProcessId.Length + 2 ; if (haveWriteSize >= BufferSize) { RtlCopyMemory(Buffer, NotePage, 18 ); ObDereferenceObject(Thread); break ; } RtlCopyMemory(OutBuffer, ThreadId.Buffer, ThreadId.Length); OutBuffer += ThreadId.Length / 2 ; RtlCopyMemory(OutBuffer, &Zero, 1 ); OutBuffer += 1 ; RtlCopyMemory(OutBuffer, ProcessId.Buffer, ProcessId.Length); OutBuffer += ProcessId.Length / 2 ; RtlCopyMemory(OutBuffer, &Zero, 1 ); OutBuffer += 1 ; } RtlFreeUnicodeString(&ThreadId); RtlFreeUnicodeString(&ProcessId); RtlFreeUnicodeString(&UId); ObDereferenceObject(Thread); } } int End = 0x10 ; RtlCopyMemory(OutBuffer, &End, 1 ); OutBuffer += 1 ; }
遍历模块 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 VOID EnumCurrentModule (PVOID Buffer, ULONG BufferSize, PIRP Irp) { PEPROCESS Process = NULL ; WCHAR* OutBuffer = Buffer; ULONG haveWriteSize = 0 ; WCHAR NotePage[] = L"NotePage" ; LONG Zero = 0x00 ; ULONG* Pid = (ULONG*)Irp->AssociatedIrp.SystemBuffer; LONG pId = *Pid; if (NT_SUCCESS(PsLookupProcessByProcessId((HANDLE)pId, &Process))) { PVOID peb = NULL ; struct _LIST_ENTRY pLdrHeader ; __try { peb = PsGetProcessPeb(Process); KeAttachProcess(Process); pLdrHeader = ((PPEB)peb)->Ldr->InLoadOrderModuleList; struct _LIST_ENTRY * pTemp = pLdrHeader.Flink; struct _LIST_ENTRY * pNext = pLdrHeader.Flink; do { struct _LDR_DATA_TABLE_ENTRY pLdrTable = *(struct _LDR_DATA_TABLE_ENTRY*)(pNext->Flink); UNICODE_STRING strAdd = { 0 }; strAdd.Buffer = (PWSTR)ExAllocatePoolWithTag(PagedPool, 0x400 , 0x80 ); strAdd.MaximumLength = 0x400 ; RtlIntegerToUnicodeString((ULONG)pLdrTable.DllBase, 16 , &strAdd); haveWriteSize = haveWriteSize + pLdrTable.BaseDllName.Length + 2 + strAdd.Length + 2 ; if (haveWriteSize >= BufferSize) { RtlCopyMemory(Buffer, NotePage, 18 ); break ; } RtlCopyMemory(OutBuffer, pLdrTable.BaseDllName.Buffer, pLdrTable.BaseDllName.Length); OutBuffer += pLdrTable.BaseDllName.Length / 2 ; RtlCopyMemory(OutBuffer, &Zero, 1 ); OutBuffer += 1 ; RtlCopyMemory(OutBuffer, strAdd.Buffer, strAdd.Length); OutBuffer += strAdd.Length / 2 ; RtlCopyMemory(OutBuffer, &Zero, 1 ); OutBuffer += 1 ; RtlFreeUnicodeString(&strAdd); pNext = pNext->Flink; } while (pNext != pTemp); } __except (EXCEPTION_EXECUTE_HANDLER) {} KeDetachProcess(); ObDereferenceObject(Process); } int End = 0x10 ; RtlCopyMemory(OutBuffer, &End, 1 ); OutBuffer += 1 ; }
IDT&GDT&SSDT 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 VOID EnumIDTTab (PVOID Buffer, ULONG BufferSize) { WCHAR NotePage[] = L"NotePage" ; int Zero = 0x00 ; ULONG haveWriteSize = 0 ; WCHAR* NewBuffer = Buffer; LONG NonPage = 0 ; IDT_INFO IDTInfo = { 0 ,0 ,0 }; PIDT_ENTRY pIDTEntry = NULL ; __asm sidt IDTInfo pIDTEntry = (PIDT_ENTRY)MAKE_LONG(IDTInfo.uLowIdtBase, IDTInfo.uHighIdtBase); for (ULONG i = 0 ; i < 0x100 ; i++) { ULONG idtAddress = MAKE_LONG(pIDTEntry[i].uOffsetLow, pIDTEntry[i].uOffsetHigh); UNICODE_STRING idtNum = { 0 }; idtNum.Buffer = (PWSTR)ExAllocatePoolWithTag(PagedPool, 0x200 , 0x80 ); idtNum.MaximumLength = 0x200 ; RtlIntegerToUnicodeString(i, 10 , &idtNum); UNICODE_STRING idtType = { 0 }; idtType.Buffer = (PWSTR)ExAllocatePoolWithTag(PagedPool, 0x200 , 0x81 ); idtType.MaximumLength = 0x200 ; RtlIntegerToUnicodeString(pIDTEntry[i].GateType, 10 , &idtType); UNICODE_STRING idtAddr = { 0 }; idtAddr.Buffer = (PWSTR)ExAllocatePoolWithTag(PagedPool, 0x200 , 0x82 ); idtAddr.MaximumLength = 0x200 ; RtlIntegerToUnicodeString(idtAddress, 16 , &idtAddr); haveWriteSize = haveWriteSize + idtNum.Length + 2 + idtType.Length + 2 + idtAddr.Length + 2 ; if (haveWriteSize >= BufferSize) { RtlZeroMemory(Buffer, BufferSize); RtlCopyMemory(Buffer, NotePage, 18 ); NonPage = 1 ; break ; } RtlCopyMemory(NewBuffer, idtNum.Buffer, idtNum.Length); NewBuffer += idtNum.Length / 2 ; RtlCopyMemory(NewBuffer, &Zero, 1 ); NewBuffer += 1 ; RtlCopyMemory(NewBuffer, idtType.Buffer, idtType.Length); NewBuffer += idtType.Length / 2 ; RtlCopyMemory(NewBuffer, &Zero, 1 ); NewBuffer += 1 ; RtlCopyMemory(NewBuffer, idtAddr.Buffer, idtAddr.Length); NewBuffer += idtAddr.Length / 2 ; RtlCopyMemory(NewBuffer, &Zero, 1 ); NewBuffer += 1 ; RtlFreeUnicodeString(&idtNum); RtlFreeUnicodeString(&idtType); RtlFreeUnicodeString(&idtAddr); } if (NonPage == 0 ) { int End = 0x10 ; RtlCopyMemory(NewBuffer, &End, 1 ); NewBuffer += 1 ; } } VOID EnumGDTTab (PVOID Buffer, ULONG BufferSize) { RtlZeroMemory(Buffer, BufferSize); WCHAR NotePage[] = L"NotePage" ; int Zero = 0x00 ; WCHAR* NewBuffer = Buffer; ULONG haveWriteSize = 0 ; GDT_INFO GDTInfo = { 0 ,0 ,0 }; PGDT pGDTEntry = NULL ; __asm sgdt GDTInfo pGDTEntry = (PGDT)MAKE_LONG(GDTInfo.uLowIdtBase, GDTInfo.uHighIdtBase); PGDT tempPgdt = ExAllocatePoolWithTag(PagedPool, 0x100 * 8 , 0x80 ); RtlCopyMemory(tempPgdt, pGDTEntry, 0x100 * 8 ); haveWriteSize += 28 ; for (ULONG i = 0 ; i < 0x100 ; i++) { UNICODE_STRING gdtDPL = { 0 }; gdtDPL.Buffer = (PWSTR)ExAllocatePoolWithTag(PagedPool, 0x300 , 0x80 ); gdtDPL.MaximumLength = 0x300 ; RtlIntegerToUnicodeString((ULONG)tempPgdt[i].DPL, 10 , &gdtDPL); UNICODE_STRING gdtType = { 0 }; gdtType.Buffer = (PWSTR)ExAllocatePoolWithTag(PagedPool, 0x300 , 0x80 ); gdtType.MaximumLength = 0x300 ; RtlIntegerToUnicodeString((ULONG)tempPgdt[i].TYPE, 10 , &gdtType); UNICODE_STRING gdtAddr = { 0 }; gdtAddr.Buffer = (PWSTR)ExAllocatePoolWithTag(PagedPool, 0x300 , 0x80 ); gdtAddr.MaximumLength = 0x300 ; RtlIntegerToUnicodeString((ULONG)&tempPgdt[i], 16 , &gdtAddr); haveWriteSize = haveWriteSize + gdtDPL.Length + 2 + gdtType.Length + 2 + gdtAddr.Length + 2 ; if (haveWriteSize >= BufferSize) { RtlZeroMemory(Buffer, BufferSize); RtlCopyMemory(Buffer, NotePage, 18 ); break ; } RtlCopyMemory(NewBuffer, gdtDPL.Buffer, gdtDPL.Length); NewBuffer += gdtDPL.Length / 2 ; RtlCopyMemory(NewBuffer, &Zero, 1 ); NewBuffer += 1 ; RtlCopyMemory(NewBuffer, gdtType.Buffer, gdtType.Length); NewBuffer += gdtType.Length / 2 ; RtlCopyMemory(NewBuffer, &Zero, 1 ); NewBuffer += 1 ; RtlCopyMemory(NewBuffer, gdtAddr.Buffer, gdtAddr.Length); NewBuffer += gdtAddr.Length / 2 ; RtlCopyMemory(NewBuffer, &Zero, 1 ); NewBuffer += 1 ; RtlFreeUnicodeString(&gdtDPL); RtlFreeUnicodeString(&gdtType); RtlFreeUnicodeString(&gdtAddr); } ExFreePoolWithTag(tempPgdt, 0x80 ); } VOID EnumSSDTTab (PVOID Buffer, ULONG BufferSize) { RtlZeroMemory(Buffer, BufferSize); LONG NonPage = 0 ; WCHAR NotePage[] = L"NotePage" ; int Zero = 0x00 ; WCHAR* NewBuffer = Buffer; ULONG haveWriteSize = 0 ; LONG lgSsdtNumber = -1 ; lgSsdtNumber = KeServiceDescriptorTable.NumberOfServices; for (LONG i = 0 ; i < lgSsdtNumber; i++) { LONG ssdtAddr = *KeServiceDescriptorTable.ServiceTableBase; PLONG pSsdtFunAddr = (PLONG)(ssdtAddr + i * 4 ); UNICODE_STRING strAddr = { 0 }; strAddr.Buffer = (PWSTR)ExAllocatePoolWithTag(PagedPool, 0x200 , 0x80 ); strAddr.MaximumLength = 0x200 ; RtlIntegerToUnicodeString(*pSsdtFunAddr, 16 , &strAddr); haveWriteSize = haveWriteSize + strAddr.Length + 2 ; if (haveWriteSize >= BufferSize) { RtlZeroMemory(Buffer, BufferSize); RtlCopyMemory(Buffer, NotePage, 18 ); NonPage = 1 ; break ; } RtlCopyMemory(NewBuffer, strAddr.Buffer, strAddr.Length); NewBuffer += strAddr.Length / 2 ; RtlCopyMemory(NewBuffer, &Zero, 1 ); NewBuffer += 1 ; RtlFreeUnicodeString(&strAddr); } if (NonPage == 0 ) { int End = 0x10 ; RtlCopyMemory(NewBuffer, &End, 1 ); NewBuffer += 1 ; } }
Systener_Hook HOOK思路 如果我们能通过修改 SYSENTER_EIP_MSR寄存器使其指向我们自己的函数,那么我们就可以在自己的函数中对所有来自Ring3层的调用进行第一手过滤
我们可以通过判断EAX中的值从而判断本次将要调用的内核函数,可以通过读取EDX中的内容获取此函数的参数
需要注意的是,Ring3层的栈帧信息会随同DX传入,因此如果我们需要获取Ring3层栈信息的话直接执行“MOV ESP,EDX”就可以得到
要完成一个 SYSENTER钩子其实是非常简单的,我们只需要完成以下几步即可:
A.读取 SYSENTER_EIPMSR寄存器的信息,并备份系统 KiFastCallEntry函数的地址;
B.构建一个我们自己的MyKiFastCallEntry函数应以过滤调用信息;
C.设置 SYSENTEREIP_MSR寄存器指向我们自己构造函数的地址;
D.如果需要摘除钩子,则将备份的系统 iFastCallEntry函数地址写回SYSENTER EIP MSR寄存器即可
示例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 ULONG g_OldKiFastCallEntry = 0 ; ULONG g_pid = 0 ; void _declspec(naked) MyKiFastCallEntry(){ _asm { pushad; cmp eax, 0x0BE ; jne CallEnd; mov eax, [edx + 0x14 ]; mov eax, [eax]; cmp eax, g_pid; jne CallEnd; mov[edx + 0x0c ], 0 ; CallEnd: popad; jmp g_OldKiFastCallEntry; } } VOID Systener_Hook (PVOID Buffer, ULONG BufferSize, PIRP Irp) { ULONG* pid = (ULONG*)Irp->AssociatedIrp.SystemBuffer; g_pid = *pid; if (g_OldKiFastCallEntry == 0 ) { _asm { mov ecx, 0x176 ; rdmsr; mov g_OldKiFastCallEntry, eax; } } _asm { mov ecx, 0x176 ; mov eax, MyKiFastCallEntry; wrmsr; } } void UnHook () { _asm { mov ecx, 0x176 ; mov eax, g_OldKiFastCallEntry; wrmsr; } }
SSDTHooK SSDT回顾 系统服务调度表(System Services Descriptor Table,SDT)可以基于系统调用编号进行索引以定位相应系统函数的内存地址,由于其是用户层与内核层的一扇窗户,因此这里是被恶意软件利用的最频繁的地方
SSDT的作用是用于满足用户模式的程序执行系统函数的这一个需求,用户模式程序可以通过系统提供的机制借助SSDT查找用户请求与系统服务的对应关系,进而使得将用户模式程序的请求迁移到内核模式处理成为可能,这个过程就被称之为系统服务调度
在 Windows操作系统中,应用程序通常使用 SYSENTER、 SYSCALL或INT0x2E执行系统服务调度功能,系统接收到相关请求后会在内核中调用 KiFastCallEntry函数,然后在导取系统调用的编号,并在SSDT中查询该调用出的变量 KeServiceDescriptorTable中获取当前SSDT的地址,此函数会从EAX寄存器中读
HOOK思路 一般情况下来讲,为系统安装一个SSDT钩子要经过以下几步:
A.找到SSDT地址(x86平台可以使用系统导出的符号,x64平台需要自己寻找);
B.导入需要勾住的内核函数;
C.使SSDT所在的内存页可写(可以通过CR方法或创建MDL的方法达成此目的);
D.替换SSDT中指定内核函数为我们自定义的函数;
E.如果需要摘除钩子,将被勾住的函数还原后将SSDT所在的内存页设为初始状态。
F.下面我们将用进程保护为例对这些步骤进行逐一讲解,这里首先给出我们自己实现的过滤函数。
示例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 #include <ntifs.h> #pragma pack(1) typedef struct _ServiceDesriptorEntry { ULONG* ServiceTableBase; ULONG* ServiceCounterTableBase; ULONG NumberOfServices; UCHAR* ParamTableBase; }SSDTEntry, * PSSDTEntry; #pragma pack() NTSYSAPI SSDTEntry KeServiceDescriptorTable; ULONG g_ProtectedId = 0 ; typedef NTSTATUS (*ZWOPENPROCESS) ( OUT PHANDLE ProcessHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectName, IN PCLIENT_ID ClientId OPTIONAL) ;ZWOPENPROCESS g_OldZwOpenProcess = NULL ; NTSTATUS MyZwOpenProcess ( OUT PHANDLE ProcessHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectName, IN PCLIENT_ID ClientId OPTIONAL) { if (ClientId->UniqueProcess == (HANDLE)g_ProtectedId) { return STATUS_ABANDONED; } return g_OldZwOpenProcess(ProcessHandle, DesiredAccess, ObjectName, ClientId); } void OffProtecte () { _asm { push eax; mov eax, CR0; and eax, ~0x10000 ; mov CR0, eax; pop eax; } } void OnProtecte () { _asm { push eax; mov eax, CR0; OR eax, 0x10000 ; mov CR0, eax; pop eax; } } void Init () { g_OldZwOpenProcess = (ZWOPENPROCESS) KeServiceDescriptorTable.ServiceTableBase[0xBE ]; } void OnHook () { OffProtecte(); KeServiceDescriptorTable.ServiceTableBase[0xBE ] = (ULONG)MyZwOpenProcess; OnProtecte(); } void OffHook () { OffProtecte(); KeServiceDescriptorTable.ServiceTableBase[0xBE ] = (ULONG)g_OldZwOpenProcess; OnProtecte(); } void OnHookByMdl () { PMDL pMdl = NULL ; pMdl = MmCreateMdl( NULL , KeServiceDescriptorTable.ServiceTableBase, KeServiceDescriptorTable.NumberOfServices * 4 ); MmBuildMdlForNonPagedPool(pMdl); pMdl->MdlFlags = pMdl->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA; PULONG pVirtualAddr = (PULONG)MmMapLockedPages(pMdl, KernelMode); pVirtualAddr[0xBE ] = (ULONG)MyZwOpenProcess;; MmUnmapLockedPages(pVirtualAddr, pMdl); IoFreeMdl(pMdl); } void OffHookByMdl () { OffProtecte(); KeServiceDescriptorTable.ServiceTableBase[0xBE ] = (ULONG)g_OldZwOpenProcess; OnProtecte(); } VOID DriverUnload (PDRIVER_OBJECT pDriver) ;NTSTATUS DriverEntry ( PDRIVER_OBJECT pDriver, PUNICODE_STRING pPath ) { UNREFERENCED_PARAMETER(pDriver); UNREFERENCED_PARAMETER(pPath); g_ProtectedId = 2164 ; Init(); OnHookByMdl(); pDriver->DriverUnload = DriverUnload; return STATUS_SUCCESS; } VOID DriverUnload (PDRIVER_OBJECT pDriver) { UNREFERENCED_PARAMETER(pDriver); OffHook(); }
内核重载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 #include <ntifs.h> #include <ntimage.h> #pragma pack(1) typedef struct _ServiceDesriptorEntry { ULONG* ServiceTableBase; ULONG* ServiceCounterTableBase; ULONG NumberOfServices; UCHAR* ParamTableBase; }SSDTEntry, * PSSDTEntry; #pragma pack() NTSYSAPI SSDTEntry KeServiceDescriptorTable; PSSDTEntry pNewSSDT; PCHAR g_pHookpointer; PCHAR g_pJmpPointer; VOID DriverUnload (PDRIVER_OBJECT pDriver) ;HANDLE KernelCreateFile ( IN PUNICODE_STRING pstrFile, IN BOOLEAN bIsDir) ;ULONG64 KernelGetFileSize (IN HANDLE hfile) ;ULONG64 KernelReadFile ( IN HANDLE hfile, IN PLARGE_INTEGER Offset, IN ULONG ulLength, OUT PVOID pBuffer) ;PVOID GetModuleBase (PDRIVER_OBJECT pDriver, PUNICODE_STRING pModuleName) ;void GetReloadBuf (PUNICODE_STRING KerPath, PCHAR* pReloadBuf) { LARGE_INTEGER Offset = { 0 }; HANDLE hFile = KernelCreateFile(KerPath, FALSE); ULONG64 uSize = KernelGetFileSize(hFile); PCHAR pKernelBuf = ExAllocatePool(NonPagedPool, (SIZE_T)uSize); RtlZeroMemory(pKernelBuf, (SIZE_T)uSize); KernelReadFile(hFile, &Offset, (ULONG)uSize, pKernelBuf); PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pKernelBuf; PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + pKernelBuf); PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt); *pReloadBuf = ExAllocatePool(NonPagedPool, pNt->OptionalHeader.SizeOfImage); RtlZeroMemory(*pReloadBuf, pNt->OptionalHeader.SizeOfImage); RtlCopyMemory(*pReloadBuf, pKernelBuf, pNt->OptionalHeader.SizeOfHeaders); for (size_t i = 0 ; i < pNt->FileHeader.NumberOfSections; i++) { RtlCopyMemory( *pReloadBuf + pSection[i].VirtualAddress, pKernelBuf + pSection[i].PointerToRawData, pSection[i].SizeOfRawData ); } ExFreePool(pKernelBuf); } void FixReloc (PCHAR OldKernelBase, PCHAR NewKernelBase) { typedef struct _TYPEOFFSET { USHORT Offset : 12 ; USHORT type : 4 ; }TYPEOFFSET, * PTYPEOFFSET; PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)NewKernelBase; PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + NewKernelBase); PIMAGE_DATA_DIRECTORY pDir = (pNt->OptionalHeader.DataDirectory + 5 ); PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION) (pDir->VirtualAddress + NewKernelBase); while (pReloc->SizeOfBlock != 0 ) { ULONG uCount = (pReloc->SizeOfBlock - 8 ) / 2 ; PCHAR pSartAddress = (pReloc->VirtualAddress + NewKernelBase); PTYPEOFFSET pOffset = (PTYPEOFFSET)(pReloc + 1 ); for (ULONG i = 0 ; i < uCount; i++) { if (pOffset->type == 3 ) { ULONG* pRelocAdd = (ULONG*)(pSartAddress + pOffset->Offset); *pRelocAdd += ((ULONG)OldKernelBase - pNt->OptionalHeader.ImageBase); } pOffset++; } pReloc = (PIMAGE_BASE_RELOCATION)((PCHAR)pReloc + pReloc->SizeOfBlock); } } void FixSSDT (PCHAR OldKernelBase, PCHAR NewKernelBase) { ULONG uOffset = (ULONG)NewKernelBase - (ULONG)OldKernelBase; pNewSSDT = (PSSDTEntry)((PCHAR)&KeServiceDescriptorTable + uOffset); pNewSSDT->NumberOfServices = KeServiceDescriptorTable.NumberOfServices; pNewSSDT->ServiceTableBase = (PULONG)((PCHAR)KeServiceDescriptorTable.ServiceTableBase + uOffset); for (ULONG i = 0 ; i < pNewSSDT->NumberOfServices; i++) { pNewSSDT->ServiceTableBase[i] = pNewSSDT->ServiceTableBase[i] + uOffset; } pNewSSDT->ParamTableBase = ((UCHAR*)KeServiceDescriptorTable.ParamTableBase + uOffset); memcpy (pNewSSDT->ParamTableBase, KeServiceDescriptorTable.ParamTableBase, KeServiceDescriptorTable.NumberOfServices); } void * SearchMemory (char * buf, int BufLenth, char * Mem, int MaxLenth) { int MemIndex = 0 ; int BufIndex = 0 ; for (MemIndex = 0 ; MemIndex < MaxLenth; MemIndex++) { BufIndex = 0 ; if (Mem[MemIndex] == buf[BufIndex] || buf[BufIndex] == '?' ) { int MemIndexTemp = MemIndex; do { MemIndexTemp++; BufIndex++; } while ((Mem[MemIndexTemp] == buf[BufIndex] || buf[BufIndex] == '?' ) && BufIndex < BufLenth); if (BufIndex == BufLenth) { return Mem + MemIndex; } } } return 0 ; } PVOID GetKiFastCallEntryAddr () { PVOID pAddr = 0 ; _asm { push ecx; push eax; mov ecx, 0x176 ; rdmsr; mov pAddr, eax; pop eax; pop ecx; } return pAddr; } void OffProtect () { __asm { push eax; mov eax, cr0; and eax, ~0x10000 ; mov cr0, eax; pop eax; } } void OnProtect () { __asm { push eax; mov eax, cr0; OR eax, 0x10000 ; mov cr0, eax; pop eax; } } UCHAR CodeBuf[] = { 0x2b , 0xe1 , 0xc1 , 0xe9 , 0x02 }; UCHAR NewCodeBuf[5 ] = { 0xE9 }; ULONG FilterSSDT (ULONG uCallNum, PULONG FunBaseAddress, ULONG FunAdress) { if (FunBaseAddress == KeServiceDescriptorTable.ServiceTableBase) { if (uCallNum == 190 ) { return pNewSSDT->ServiceTableBase[190 ]; } } return FunAdress; } _declspec(naked) void MyFilterFunction () { _asm { pushad; pushfd; push edx; push edi; push eax; call FilterSSDT; mov dword ptr ds : [esp + 0x18 ] , eax; popfd; popad; sub esp, ecx; shr ecx, 2 ; jmp g_pJmpPointer; } } void OnHookKiFastCall () { PVOID KiFastCallAdd = GetKiFastCallEntryAddr(); g_pHookpointer = SearchMemory(CodeBuf, 5 , KiFastCallAdd, 0x200 ); OffProtect(); *(ULONG*)(NewCodeBuf + 1 ) = ((ULONG)MyFilterFunction - (ULONG)g_pHookpointer - 5 ); memcpy (g_pHookpointer, NewCodeBuf, 5 ); OnProtect(); g_pJmpPointer = g_pHookpointer + 5 ; } NTSTATUS DriverEntry (PDRIVER_OBJECT pDriver, PUNICODE_STRING pPath) { UNREFERENCED_PARAMETER(pPath); DbgBreakPoint(); PCHAR pNtModuleBase = NULL ; UNICODE_STRING pNtModuleName; PCHAR pReloadBuf = NULL ; UNICODE_STRING KerPath; RtlInitUnicodeString(&KerPath, L"\\??\\C:\\windows\\system32\\ntkrnlpa.exe" ); GetReloadBuf(&KerPath, &pReloadBuf); RtlInitUnicodeString(&pNtModuleName, L"ntoskrnl.exe" ); pNtModuleBase = (PCHAR)GetModuleBase(pDriver, &pNtModuleName); FixReloc(pNtModuleBase, pReloadBuf); FixSSDT(pNtModuleBase, pReloadBuf); OnHookKiFastCall(); pDriver->DriverUnload = DriverUnload; return STATUS_SUCCESS; } HANDLE KernelCreateFile ( IN PUNICODE_STRING pstrFile, IN BOOLEAN bIsDir) { HANDLE hFile = NULL ; NTSTATUS Status = STATUS_UNSUCCESSFUL; IO_STATUS_BLOCK StatusBlock = { 0 }; ULONG ulShareAccess = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; ULONG ulCreateOpt = FILE_SYNCHRONOUS_IO_NONALERT; OBJECT_ATTRIBUTES objAttrib = { 0 }; ULONG ulAttributes = OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE; InitializeObjectAttributes( &objAttrib, pstrFile, ulAttributes, NULL , NULL ); ulCreateOpt |= bIsDir ? FILE_DIRECTORY_FILE : FILE_NON_DIRECTORY_FILE; Status = ZwCreateFile( &hFile, GENERIC_ALL, &objAttrib, &StatusBlock, 0 , FILE_ATTRIBUTE_NORMAL, ulShareAccess, FILE_OPEN_IF, ulCreateOpt, NULL , 0 ); if (!NT_SUCCESS(Status)) return (HANDLE)-1 ; return hFile; } ULONG64 KernelGetFileSize (IN HANDLE hfile) { IO_STATUS_BLOCK StatusBlock = { 0 }; FILE_STANDARD_INFORMATION fsi = { 0 }; NTSTATUS Status = STATUS_UNSUCCESSFUL; Status = ZwQueryInformationFile( hfile, &StatusBlock, &fsi, sizeof (FILE_STANDARD_INFORMATION), FileStandardInformation); if (!NT_SUCCESS(Status)) return 0 ; return fsi.EndOfFile.QuadPart; } ULONG64 KernelReadFile ( IN HANDLE hfile, IN PLARGE_INTEGER Offset, IN ULONG ulLength, OUT PVOID pBuffer) { IO_STATUS_BLOCK StatusBlock = { 0 }; NTSTATUS Status = STATUS_UNSUCCESSFUL; Status = ZwReadFile( hfile, NULL , NULL , NULL , &StatusBlock, pBuffer, ulLength, Offset, NULL ); if (!NT_SUCCESS(Status)) return 0 ; return StatusBlock.Information; } 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; } LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY; PVOID GetModuleBase (PDRIVER_OBJECT pDriver, PUNICODE_STRING pModuleName) { PLDR_DATA_TABLE_ENTRY pLdr = (PLDR_DATA_TABLE_ENTRY)pDriver->DriverSection; LIST_ENTRY* pTemp = &pLdr->InLoadOrderLinks; do { PLDR_DATA_TABLE_ENTRY pDriverInfo = (PLDR_DATA_TABLE_ENTRY)pTemp; KdPrint(("%wZ\n" , &pDriverInfo->FullDllName)); if ( RtlCompareUnicodeString(pModuleName, &pDriverInfo->BaseDllName, FALSE) == 0 ) { return pDriverInfo->DllBase; } pTemp = pTemp->Blink; } while (pTemp != &pLdr->InLoadOrderLinks); return 0 ; } VOID DriverUnload (PDRIVER_OBJECT pDriver) { OffProtect(); memcpy (g_pHookpointer, CodeBuf, 5 ); OnProtect(); UNREFERENCED_PARAMETER(pDriver); }
远程调试
复制 C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\Remote Debugger\x86 全部内容到虚拟机
打开msvsmon.exe,以管理员方式打开,第一次打开出现的界面直接点确定
.工具->选项->无身份验证->允许任何用户进行调试->确定
关闭虚拟机防火墙
调试->远程调试
远程命令:目标机器的程序路径
工作目录:目标程序的路径
远程服务器名称:目标机器的机器名:WIN-UL8QQFCU9CJ 或者IP:端口号
连接:不带身份验证的远程访问