内核空间与内核模块
内核空间
内核空间:
每个进程拥有4GB的独立的内存空间,在低2G是不同的,但是高2G内存空间对于所有的进程来说都是相同的。
内核模块
每个驱动程序每一个都是一个模块,称为”内核模块“,都可以加载到内核中,遵守PE结构。但是本质上讲,任意一个.sys文件与内核文件没有区别。
内核并不是一个孤立的整体,而是由多个模块一起组成的。比如我们逆向的ntoskrnl.exe文件,其实也只是其中的一个模块,我们自己编译的sys文件也是,但是并不是每一个模块都对应着一块驱动,比如安全人员写的驱动只是编写了一块驱动,很多时候并没有对应的硬件。
DRIVER_OBJECT
每个内核模块都有一个对应的结构体,来描述这个模块在内核中的: 位置、大小、名称等。DriverEnrty第一个参数就是这个结构体。
遍历内核模块
我们通过_DRIVER_OBJECT 结构可以获取当前自己驱动的一些信息,有没有方式可以获取其他内核模块的信息呢。
通过_DRIVER_OBJECT 结构体中的DriverSection可以实现。
DriverSection是一个指针,实际上对应一个结构体:_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY
是一个链表,就是将各个内核模块一起串起来。
+0x018 DllBase : Ptr32 Void
是当前模块的首地址。
+0x020 SizeOfImage : Uint4B
有多大。
+0x024 FullDllName : _UNICODE_STRING
这个驱动文件的完整路径。
通过InLoadOrderLinks可以查询到其他内核模块的信息。
例:
内核模块信息
获取当前
通过InLoadOrderLinks可以查询到其他模块信息:
IRQL
试想:当cpu在执行代码时,什么能够打断它?
答案就是中断,但当一个中断还没有执行结束,又产生了新的中断,这时候cpu应该继续执行之前的中断呢,还是执行新的中断呢?
答案就是IRQL。等级高的中断可以打断等级低的中断。
IRQL 全称: Interrupt Request Level。 是一个由windows虚拟出来的概念,划分在Windows下中断的优先级,中断包括了硬中断和软中断。硬中断是由硬件产生,而软中断是完全虚拟出来的。
常见的中断等级:
#define PASSIVE_LEVEL 0
#define APC_LEVEL 1
#define DISPATCH_LEVEL 2
#define PROFILE_LEVEL 27
#define CLOCK1_LEVEL 28
#define CLOCK2_LEVEL 28
#define IPI_LEVEL 29
#define POWER_LEVEL 30
#define HIGH_LEVEL 31
假设现在有一个中断等级为PASSIVE_LEVEL ,正在被执行,此时产生了一个中断DISPATCH_LEVEL,那么中断等级为DISPATCH_LEVEL的程序异常处理将会被执行。反之则不然,这也是为什么众多内核api要求中断等级的原因,一个不注意将会导致蓝屏。
比如当你想要hook一个函数的等级是DISPATCH_LEVEL,那么你在hook的代码中就不能使用分页内存。
因为一旦你的内存为分页内存,那么对应的物理页很有可能已经被写到了硬盘上,正常情况下,会产生页异常(中断)去寻找到硬盘中存储的内存,而该中断等级为DISPATCH_LEVEL,那么当hook的代码执行的时候,就根本不会理会这个页异常的中断,因为此时他两的中断等级相同,所以会导致内存访问错误,这样就直接蓝屏了。
一般情况下我们自己写的代码都在PASSIVE_LEVEL 等级下,如果跑着跑着等级就上去了,那么大概率是你在hook别人的函数,别人的函数等级是DISPATCH_LEVEL。
ring0 与 ring3通信
设备对象:
我们在开发窗口程序的时候,消息被封装为一个结构体: MSG ,在内核开发时,消息被封装成另一个结构体: IRP。
在窗口 程序中,能够接受消息的只能是窗口对象。在内核中,能够接受IRP消息的只能是设备对象,不能是驱动对象。
创建设备对象:
//创建设备名称
UNICODE_STRING Devicename;
RtlInitUnicodeString(&Devicename,L"\\Device\\MyDevice"); //“Device”不要随便改,最后的名字可以改
//创建设备
IoCreateDevice(
pDriver, //当前设备所属的驱动对象
0,
&Devicename, //设备对象的名称
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&pDeviceObj //设备对象指针
);
交互数据的三种方式:
pDeviceObj->Flags = DO_BUFFERED_IO;
DO_BUFFERED_IO:
该方式为:缓冲区方式读写
当与ring3程序通信时,有可能因为进程的切换,导致ring0在向ring3读数据时候读取到错误地址,这种方式将会把ring3程序地址对应的数据复制一份到ring0地址中。这样就能 保证读取到正确的地址。但缺点就是速度较慢。需要复制。
DO_DIRECT_IO
该方式为: 直接方式读写
操作系统会将用户模式下的缓冲区锁住。然后操作系统将这段缓冲区在内核模式地址再次映射一次(挂上物理页)
这样,用户模式的缓冲区和内核模式的缓冲区指向的是同一区域的物理内存。缺点就是要单独占用物理页面,无法将物理页面存到硬盘上去,但是在需要大量数据交互的时候此方法是比较好的。
其他方式读写:
即不指定DO_BUFFERED_IO 和 DO_DIRECT_IO
这种方式是不建议的,而且非常危险
在驱动程序中,直接操作应用程序的缓冲地址是很危险的,因为三环进程会不断切换,无法保证读取到的地址是一个正确的地址。只有驱动程序与应用程序运行在相同线程上下文的情况下,才能使用这种方式。
创建符号链接
//创建符号链接名称
RtlInitUnicodeString(&SymbolicLinkName,L"\\??\\MyTestDriver");
//创建符号链接
IoCreateSymbolicLink(&SymbolicLinkName,&Devicename);
特别说明:
1、设备名称的作用是给内核对象用的,如果要在Ring3访问,必须要有符号链接,其实就是一个别名,没有这个别名,在Ring3不可见。
2、内核模式下,符号链接是以“??\”开头的,如C 盘就是“??\C:”
3、而在用户模式下,则是以“\.\”开头的,如C 盘就是“\.\C”:
IRP与派遣函数
一个单击鼠标的操作会被封装为一个MSG结构传给窗口对象,窗口对象找到单机鼠标对应的回调函数进行处理。
CreateFile 函数封装成IRP 传给设备对象,设备对象找到CreateFile的派遣函数进行处理,所以这里派遣函数可以理解为回调函数,是给设备对象用的。设备对象收到什么样的IRP就用对应的派遣函数处理。
IRP类型:
当应用层通过CreateFile,ReadFile,WriteFile,CloseHandle等函数打开、从设备读取数据、向设备写入数据、关闭设备的时候,会使操作系统产生出IRP_MJ_CREATE,IRP_MJ_READ,IRP_MJ_WRITE,IRP_MJ_CLOSE等不同的IRP。
但如ReadFile和WriteFile这些函数功能太过于单一,只能读或者只能写,有时候我们需要又读又写和干别的事情。更为常用的是DeviceIoControl函数和他对应的IRP类型IRP_MJ_DEVICE_CONTROL。
_DRIVER_OBJECT 结构体中的DriverUnload即为卸载函数。
MajorFunction为派遣函数。派遣函数是一个数组:
派遣函数的格式:
NTSTATUS MyDispatchFunction(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
//处理自己的业务...
//设置返回状态
pIrp->IoStatus.Status = STATUS_SUCCESS; // getlasterror()得到的就是这个值
pIrp->IoStatus.Information = 0; // 返回给3环多少数据 没有填0
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
通过IRP_MJ_DEVICE_CONTROL交互数据
例:
ring0:
#include <ntddk.h>
#define DEVICE_NAME L"\\Device\\MYDEVICE"
#define SYMBOL_NAME_LINK L"\\??\\devicesd"
//自定义消息
#define CODE_READ CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define CODE_WRITE CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)
VOID DriverUpload(PDRIVER_OBJECT pDriver)
{
UNICODE_STRING symbolLink;
RtlInitUnicodeString(&symbolLink, SYMBOL_NAME_LINK);
IoDeleteSymbolicLink(&symbolLink);
IoDeleteDevice(pDriver->DeviceObject);
KdPrint(("卸载了\n"));
return;
}
NTSTATUS CreatCallBack(_In_ struct _DEVICE_OBJECT* DeviceObject, _Inout_ struct _IRP* Irp)
{
KdPrint(("创建我了\n"));
Irp->IoStatus.Status = STATUS_SUCCESS; //3环调用getlasterror可以获取
Irp->IoStatus.Information = STATUS_SUCCESS;
IofCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS CloseCallBack(_In_ struct _DEVICE_OBJECT* DeviceObject, _Inout_ struct _IRP* Irp)
{
KdPrint(("关闭我了\n"));
Irp->IoStatus.Status = STATUS_SUCCESS; //3环调用getlasterror可以获取
Irp->IoStatus.Information = STATUS_SUCCESS;
IofCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DispathCallBack(_In_ struct _DEVICE_OBJECT* DeviceObject, _Inout_ struct _IRP* Irp)
{
PIO_STACK_LOCATION ps1 = IoGetCurrentIrpStackLocation(Irp);
ULONG code = ps1->Parameters.DeviceIoControl.IoControlCode;
PVOID systembuf = Irp->AssociatedIrp.SystemBuffer;
ULONG inLen = ps1->Parameters.DeviceIoControl.InputBufferLength;
ULONG outLen = ps1->Parameters.DeviceIoControl.OutputBufferLength;
switch (code)
{
case CODE_READ:
KdPrint(("CODE_READ: %x\n",CODE_READ));
if (outLen > 20)
{
memcpy(systembuf, "12345678", sizeof("12345678"));
Irp->IoStatus.Information = sizeof("12345678");
}
else
{
memcpy(systembuf,"1",sizeof("1"));
Irp->IoStatus.Information = 1;
}
break;
case CODE_WRITE:
KdPrint(("CODE_READ: %x\n", CODE_WRITE));
KdPrint(("write: %s\n", systembuf));
Irp->IoStatus.Information = 0;
break;
}
Irp->IoStatus.Status = STATUS_SUCCESS;
IofCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
UNICODE_STRING deviceName;
UNICODE_STRING symbolNameLink;
PDEVICE_OBJECT pDeviceObj;
NTSTATUS status;
pDriver->DriverUnload = DriverUpload;
RtlInitUnicodeString(&deviceName, DEVICE_NAME);
RtlInitUnicodeString(&symbolNameLink, SYMBOL_NAME_LINK);
status = IoCreateDevice(pDriver,0, &deviceName,FILE_DEVICE_UNKNOWN,FILE_DEVICE_SECURE_OPEN,TRUE,&pDeviceObj);
if (!NT_SUCCESS(status))
{
KdPrint(("创建设备失败\n"));
return status;
}
status = IoCreateSymbolicLink(&symbolNameLink,&deviceName);
if (!NT_SUCCESS(status))
{
IoDeleteDevice(pDeviceObj);
KdPrint(("创建符号链接失败\n"));
return status;
}
//设置数据交互的方式
pDeviceObj->Flags = DO_BUFFERED_IO;
pDriver->MajorFunction[IRP_MJ_CREATE] = CreatCallBack;
pDriver->MajorFunction[IRP_MJ_CLOSE] = CloseCallBack;
pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispathCallBack;
return STATUS_SUCCESS;
}
ring3层:
#include <windows.h>
#include <winioctl.h>
#include <stdlib.h>
#include <stdio.h>
#define CODE_READ CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define CODE_WRITE CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define SYMBOL_NAME_LINK L"\\\\.\\devicesd"
BOOLEAN openDevice(HANDLE* handle)
{
HANDLE _hHandle = CreateFile(SYMBOL_NAME_LINK, GENERIC_READ | GENERIC_WRITE,0,
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
*handle = _hHandle;
return (int)_hHandle > 0;
}
VOID CloseDevice(HANDLE handle)
{
CloseHandle(handle);
}
VOID SendCode(HANDLE handle, DWORD code, PVOID inData,
ULONG Inlen, PVOID outData, ULONG outLen, LPDWORD resultLen)
{
//驱动句柄,操作码,要向0环传入多少数据和长度,要向0环取多少数据和长度,实际长度, OVERLAPPED指针(此处为0)
DeviceIoControl(handle, code, inData, Inlen, outData, outLen, resultLen,NULL);
}
int main(int argc, char* argv[])
{
HANDLE handle;
if (!openDevice(&handle))
{
printf("打开设备失败\n");
return 0;
}
char buf[30] = { 0 };
DWORD len = 0;
SendCode(handle,CODE_READ,buf,30,buf,30,&len);
CloseDevice(handle);
printf("buf = %s\n",buf);
system("pause");
return 0;
}