22.1 利用实验之 exploitme.sys

  • 在helloword.c的基础上,修改派遣例程DrvDispatch函数,代码如下: ```c

    include

define DEVICE_NAME L”\Device\ExploitMe”

define DEVICE_LINK L”\DosDevices\DRIECTX1”

define FILE_DEVICE_EXPLOIT_ME 0x00008888

define IOCTL_EXPLOIT_ME (ULONG)CTL_CODE(FILE_DEVICE_EXPLOIT_ME,0x800,METHOD_NEITHER,FILE_WRITE_ACCESS)

//创建的设备对象指针 PDEVICE_OBJECT g_DeviceObject;

/** 驱动派遣例程函数 输入:驱动对象的指针,Irp指针 输出:NTSTATUS类型的结果 **/ NTSTATUS DrvDispatch(IN PDEVICE_OBJECT driverObject,IN PIRP pIrp) { PIO_STACK_LOCATION pIrpStack;//当前的pIrp栈 PVOID Type3InputBuffer;//用户态输入地址 PVOID UserBuffer;//用户态输出地址 ULONG inputBufferLength;//输入缓冲区的大小 ULONG outputBufferLength;//输出缓冲区的大小 ULONG ioControlCode;//DeviceIoControl的控制号 PIO_STATUS_BLOCK IoStatus;//pIrp的IO状态指针 NTSTATUS ntStatus=STATUS_SUCCESS;//函数返回值

  1. //获取数据
  2. pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
  3. Type3InputBuffer = pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer;
  4. UserBuffer = pIrp->UserBuffer;
  5. inputBufferLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
  6. outputBufferLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;
  7. ioControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
  8. IoStatus=&pIrp->IoStatus;
  9. IoStatus->Status = STATUS_SUCCESS;// Assume success
  10. IoStatus->Information = 0;// Assume nothing returned
  11. //根据 ioControlCode 完成对应的任务
  12. switch(ioControlCode)
  13. {
  14. case IOCTL_EXPLOIT_ME:
  15. if ( inputBufferLength >= 4 && outputBufferLength >= 4 )
  16. {
  17. *(ULONG *)UserBuffer = *(ULONG *)Type3InputBuffer;
  18. IoStatus->Information = sizeof(ULONG);
  19. }
  20. break;
  21. }
  22. //返回
  23. IoStatus->Status = ntStatus;
  24. IoCompleteRequest(pIrp,IO_NO_INCREMENT);
  25. return ntStatus;

} /** 驱动卸载函数 输入:驱动对象的指针 输出:无 **/ VOID DriverUnload( IN PDRIVER_OBJECT driverObject ) { UNICODE_STRING symLinkName; KdPrint((“DriverUnload: 88!\n”)); RtlInitUnicodeString(&symLinkName,DEVICE_LINK); IoDeleteSymbolicLink(&symLinkName); IoDeleteDevice( g_DeviceObject ); } /* 驱动入口函数(相当于main函数) 输入:驱动对象的指针,服务程序对应的注册表路径 输出:NTSTATUS类型的结果 **/ NTSTATUS DriverEntry( IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath ) { NTSTATUS ntStatus; UNICODE_STRING devName; UNICODE_STRING symLinkName; int i=0; //打印一句调试信息 KdPrint((“DriverEntry: Exploit me driver demo!\n”)); //创建设备 RtlInitUnicodeString(&devName,DEVICE_NAME); ntStatus = IoCreateDevice( driverObject, 0, &devName, FILE_DEVICE_UNKNOWN, 0, TRUE, &g_DeviceObject ); if (!NT_SUCCESS(ntStatus)) { return ntStatus;
} //创建符号链接
RtlInitUnicodeString(&symLinkName,DEVICE_LINK); ntStatus = IoCreateSymbolicLink( &symLinkName,&devName ); if (!NT_SUCCESS(ntStatus)) { IoDeleteDevice( g_DeviceObject ); return ntStatus; } //设置该驱动对象的卸载函数 driverObject->DriverUnload = DriverUnload; //设置该驱动对象的派遣例程函数 for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) { driverObject->MajorFunction[i] = DrvDispatch; } //返回成功结果 return STATUS_SUCCESS; } ```

  • 在派遣例程DrvDispatch函数中:
    • IOCTL_EXPLOIT_ME 这个 IoControlCode 指定的 Ring3/Ring0 内存访问为METHOD_NEITHER 方式。

image.png

  • 对“IOCTL_EXPLOIT_ME”的处理,实际上就是将 Ring3 输入缓冲区中的第一个 ULONG数据写入 Ring3 输出缓冲区的第一个 ULONG 数据中。

image.png

  • Type3InputBuffer 表示 Ring3 输入缓冲区指针。
  • UserBuffer 表示 Ring3 输出缓冲区指针。
  • inputBufferLength 表示 Ring3 输入缓冲区的大小(字节数)。
  • outputBufferLength 表示 Ring3 输出缓冲区的大小(字节数)。

image.png

  • 使用METHOD_NEITHER方式来进行Ring3/Ring0间的通信的脆弱点:输入、输出地址都是由 Ring3 程序来指定的,读写却是在 Ring0 完成的。因此 Ring3 可以将输出缓冲区地址指定为内核高端地址,这相当于篡改了内核中任意地址的数据,而且可以篡改为任何数值。

    22.2 内核漏洞利用思路

    image.png

    22.3 内核漏洞利用方法

  • 目前常见的内核漏洞利用方法主要有两种:一是篡改内核内存数据二是执行 Ring0 Shellcode

  • 执行Ring0 Shellcode的思路是:Ring0 中是有很多内核 API函数,这些函数地址大多保存于一些表中,并且这个表也是由内核导出的。如果能修改这些表中的内核 API 函数地址为事先准备好的 ShellCode 存放的地址(本进程空间内存地址),然后在本进程中调用这个内核 API函数,这样便实现了在 Ring0 权限下执行 Shellcode 的目的。
  • 对 22.1 节中 exploitme.sys 的漏洞进行利用。该漏洞能够使得向任意地址写入任意数据。利用的思路是:首先在当前进程(exploit.exe)的 0x0 地址处申请内存,并存放 Ring0 Shellcode 代码,然后利用漏洞将 HalDispatchTable 中的HalQuerySystemInformation 函数地址改写为 0x0,最后再调用该函数的上层封装函数NtQueryIntervalProfile,于是事先准备好的 Ring0 Shellcode 将会被执行。
  • 要修改HalQuerySystemInformation函数的入口地址为0x0,首先需要知道:
    • HalQuerySystemInformation函数是HalDispatchTable表中的第一个函数。
    • HalDispatchTable表是内核模块 hal.dll 导出的一个函数表
  • 要想调用HalQuerySystemInformation函数,需要知道:
    • 需要通过HalQuerySystemInformation函数的上层封装函数NtQueryIntervalProfile去调用它。
    • NtQueryIntervalProfile函数和HalQuerySystemInformation函数之间的关系:
      • NtQueryIntervalProfile 函数有两个参数,一个输入参数 ProfileSource 和一个输出参数 Interval。ProfileSource 是一个 KPROFILE_SOURCE 枚举类型变量,如下图所示:

image.pngimage.png

  1. - KeQueryIntervalProfile 函数的处理流程:如果输入的 ProfileSource 参数既不等于ProfileTime,也不等于 ProfileAlignmentFixup,那么就会调用HalQuerySystemInformation 函数来获取结果。

image.png

  1. - 所以只要调用 NtQueryIntervalProfile 函数,输入的第一个参数 ProfileSource不等于 ProfileTime,也不等于 ProfileAlignmentFixup,那么最终会调用到HalQuerySystemInformation 函数。
  • Ring0 Shellcode 常见的用法:
    • 提权到 SYSTEM:修改当前进程的 token 为 SYSTEM 进程的 token,这样当前进程便具备了系统最高权限,可以完全控制整个系统。
    • 恢复内核 Hook/Inline Hook:安全软件也大部分是通过Hook/Inline Hook 系统内核函数来实现防御的。因此可以通过恢复这些内核 Hook/Inline Hook来突破安全软件,甚至瓦解其整个防御体系。
    • 添加调用门/中断门/任务门/陷阱门:四门机制是出入 Ring0/Ring3 的重要手段。若能在系统中成功添加一个门,就能在后续代码中,自由出入 Ring0 和 Ring3。