OD: Kernel Exploit

第 22 章,内核漏洞利用技术

首先编写具有漏洞的驱动 exploitme.sys,再展开内核漏洞利用思路和方法:

  1 /********************************************************************
  2     created:    2010/12/06
  3     filename:     exploitme.c
  4     author:        shineast
  5     purpose:    Exploit me driver demo 
  6 *********************************************************************/
  7 #include <ntddk.h>
  8 
  9 #define DEVICE_NAME L"\Device\ExploitMe"
 10 #define DEVICE_LINK L"\DosDevices\ExploitMe"
 11 #define FILE_DEVICE_EXPLOIT_ME 0x00008888
 12 #define IOCTL_EXPLOIT_ME (ULONG)CTL_CODE(FILE_DEVICE_EXPLOIT_ME,0x800,METHOD_NEITHER,FILE_WRITE_ACCESS)
 13 
 14 //创建的设备对象指针
 15 PDEVICE_OBJECT g_DeviceObject;
 16 
 17 /**********************************************************************
 18  驱动派遣例程函数
 19     输入:驱动对象的指针,Irp指针
 20     输出:NTSTATUS类型的结果
 21 **********************************************************************/
 22 NTSTATUS DrvDispatch(IN PDEVICE_OBJECT driverObject,IN PIRP pIrp)
 23 { 
 24     PIO_STACK_LOCATION pIrpStack; //当前的pIrp栈
 25     PVOID Type3InputBuffer;       //用户态输入地址
 26     PVOID UserBuffer;             //用户态输出地址 
 27     ULONG inputBufferLength;      //输入缓冲区的大小
 28     ULONG outputBufferLength;     //输出缓冲区的大小 
 29     ULONG ioControlCode;          //DeviceIoControl的控制号
 30     PIO_STATUS_BLOCK IoStatus;    //pIrp的IO状态指针
 31     NTSTATUS ntStatus=STATUS_SUCCESS;   //函数返回值 
 32 
 33     //获取数据
 34     pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
 35     Type3InputBuffer = pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer;
 36     UserBuffer = pIrp->UserBuffer;
 37     inputBufferLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength; 
 38     outputBufferLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength; 
 39     ioControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
 40     IoStatus=&pIrp->IoStatus;
 41     IoStatus->Status = STATUS_SUCCESS;// Assume success
 42     IoStatus->Information = 0;// Assume nothing returned
 43 
 44     //根据 ioControlCode 完成对应的任务
 45     switch(ioControlCode)
 46     {
 47     case IOCTL_EXPLOIT_ME: 
 48         if ( inputBufferLength >= 4 && outputBufferLength >= 4 )
 49         {
 50             *(ULONG *)UserBuffer = *(ULONG *)Type3InputBuffer;
 51             IoStatus->Information = sizeof(ULONG);
 52         }
 53         break;
 54     }  
 55 
 56     //返回
 57     IoStatus->Status = ntStatus; 
 58     IoCompleteRequest(pIrp,IO_NO_INCREMENT);
 59     return ntStatus;
 60 }
 61 /**********************************************************************
 62  驱动卸载函数
 63     输入:驱动对象的指针
 64     输出:无
 65 **********************************************************************/
 66 VOID DriverUnload( IN PDRIVER_OBJECT  driverObject )
 67 { 
 68     UNICODE_STRING symLinkName; 
 69     KdPrint(("DriverUnload: 88!
")); 
 70     RtlInitUnicodeString(&symLinkName,DEVICE_LINK);
 71     IoDeleteSymbolicLink(&symLinkName);
 72     IoDeleteDevice( g_DeviceObject ); 
 73 } 
 74 /*********************************************************************
 75  驱动入口函数(相当于main函数)
 76     输入:驱动对象的指针,服务程序对应的注册表路径
 77     输出:NTSTATUS类型的结果
 78 **********************************************************************/
 79 NTSTATUS DriverEntry( IN PDRIVER_OBJECT  driverObject, IN PUNICODE_STRING  registryPath )
 80 { 
 81     NTSTATUS       ntStatus;
 82     UNICODE_STRING devName;
 83     UNICODE_STRING symLinkName;
 84     int i=0; 
 85     //打印一句调试信息
 86     KdPrint(("DriverEntry: Exploit me driver demo!
"));
 87     //创建设备 
 88     RtlInitUnicodeString(&devName,DEVICE_NAME);
 89     ntStatus = IoCreateDevice( driverObject,
 90         0,
 91         &devName,
 92         FILE_DEVICE_UNKNOWN,
 93         0, TRUE,
 94         &g_DeviceObject );
 95     if (!NT_SUCCESS(ntStatus))
 96     {
 97         return ntStatus;  
 98     }
 99     //创建符号链接  
100     RtlInitUnicodeString(&symLinkName,DEVICE_LINK);
101     ntStatus = IoCreateSymbolicLink( &symLinkName,&devName );
102     if (!NT_SUCCESS(ntStatus)) 
103     {
104         IoDeleteDevice( g_DeviceObject );
105         return ntStatus;
106     }
107     //设置该驱动对象的卸载函数
108     driverObject->DriverUnload = DriverUnload; 
109     //设置该驱动对象的派遣例程函数
110     for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
111     {
112         driverObject->MajorFunction[i] = DrvDispatch;
113     }
114     //返回成功结果
115     return STATUS_SUCCESS;
116 }
View Code

exploitme.sys 创建的设备名称为 DeviceExploitMe,符号链接为 DosDevicesExploitMe。在 Ring3 可以通过设备名称 \.ExploitMe 打开设备得到设备句柄,进而使用 DeviceIoControl() 来调用驱动派遣例程,与驱动交互。

exploitme.sys 的派遣例程只处理了一个 IoControlCode,即 IOCTL_EXPLOIT_ME(0x8888A003),处理方式十分简单,没有使用 ProbeForRead() 和 ProbeForWrite() 来探测 IO 地址是否可读写:

        if ( inputBufferLength >= 4 && outputBufferLength >= 4 )
        {
            *(ULONG *)UserBuffer = *(ULONG *)Type3InputBuffer;
            IoStatus->Information = sizeof(ULONG);
        }

IOCTL_EXPLOIT_ME 这个 IoControlCode 指定的 Ring3/Ring0 内存访问为 METHOD_NEITHER 方式(最后两位为 0x03)。因此 Type3InputBuffer 表示 Ring3 的输入缓冲区指针,UserBuffer 表示 Ring3 的输出缓冲区指针,inputBufferLength 表示 Ring3 的输入缓冲区大小(字节数),ouputBufferLength 表示 Ring3 的输出缓冲区大小(字节数)。

对 IOCTL_EXPLOIT_ME 的处理,实际上就是将 Ring3 的输入输出缓冲区的第一个 ULONG 数据写入 Ring3 输出缓冲区的第一个 ULONG 数据中。输入、输出都是由 Ring3 程序来指定的,读写却是在 Ring0 完成的。因此 Ring3 可以将输出缓冲区地址指定为内核高端地址,相当于篡改内核中任意地址的数据为任意值。

很多驱动程序漏洞最终都可以归纳为此类模型,属于向任意地址写任意数据的内核漏洞。

利用思路

从公布的内核漏洞数量来看,远程任意代码执行漏洞已经很少见了,更多的是本地权限提升和 DOS 类漏洞。驱动程序编译器默认都开启了 GS 选项,直接利用缓冲区溢出比较困难,阻碍重重。因此更希望看到能篡改系统内核内存数据或执行 Ring0 Shellcode 的漏洞。能达到这个目的的漏洞主要有三种:任意地址写任意内容、固定地址写任意内容和任意地址写固定内容。其中任意地址写任意内容的内核漏洞必定能实现本地权限提升。

OD: Kernel Exploit

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

实际利用中,不推荐篡改内存数据,因为很多重要的内存内核数据都不可直接被改写,如果内存属性被标记为只读,并且 CR0 寄存器的 WP 位设置为 1,是不能直接写入该内存的。如果一定要篡改,需要在 Ring0 Shellcode 中,首先将 CR0 的 WP 位置 0,从而禁用内存保护以篡改数据,改完后再恢复 WP 位

第二各利用方法,是在 Ring0 中执行 Shellcode。Ring0 中有很多内核 API 函数,这些函数大多保存于一些表中,并且这个表也是内核导出的。例如 SSDT 表(System Service Dispatch Table)、HalDispatchTable 等。如果能修改这些表中的内核 API 函数地址为事先准备好的 Shellcode 存放的地址(本进程空间内存地址),然后在本进程中调用这个内核 API 函数,就能在 Ring0 权限下执行 Shellcode。需要注意的是,选用内核 API 时要选择不常被调用的函数。因为 Shellcode 保存在本进程空间的 Ring3 内存地址中,别的进程无法访问到。如果别的进程再调用篡改过的 API 函数,就会导致内存访问错误或内核崩溃,这是相当危险的。

接下来对 exploitme.sys 进行利用。利用思路为:首先在当前进程(exploit.exe)的 0x0 地址处申请内存,并存放好 Ring0 Shellcode,然后利用漏洞将 HalDispatchTable 中的 HalQuerySystemInformation() 的地址改写为 0x0,最后再调用该函数的上层封装函数 NtQueryIntervalProfile(),于是 Shellcode 会在 Ring0 执行。

首先看 HalDispatchTable(内核模块 hal.dll 导出的一个函数表):

 1 // extracted from haltypes.h
 2 
 3 typedef struct {
 4   ULONG                       Version;
 5   pHalQuerySystemInformation  HalQuerySystemInformation;
 6   pHalSetSystemInformation    HalSetSystemInformation;
 7   pHalQueryBusSlots           HalQueryBusSlots;
 8   ULONG                       Spare1;
 9   pHalExamineMBR              HalExamineMBR;
10 #if 1 /* Not present in WDK 7600 */
11   pHalIoAssignDriveLetters    HalIoAssignDriveLetters;
12 #endif
13   pHalIoReadPartitionTable    HalIoReadPartitionTable;
14   pHalIoSetPartitionInformation HalIoSetPartitionInformation;
15   pHalIoWritePartitionTable   HalIoWritePartitionTable;
16   pHalHandlerForBus           HalReferenceHandlerForBus;
17   pHalReferenceBusHandler     HalReferenceBusHandler;
18   pHalReferenceBusHandler     HalDereferenceBusHandler;
19   pHalInitPnpDriver           HalInitPnpDriver;
20   pHalInitPowerManagement     HalInitPowerManagement;
21   pHalGetDmaAdapter           HalGetDmaAdapter;
22   pHalGetInterruptTranslator  HalGetInterruptTranslator;
23   pHalStartMirroring          HalStartMirroring;
24   pHalEndMirroring            HalEndMirroring;
25   pHalMirrorPhysicalMemory    HalMirrorPhysicalMemory;
26   pHalEndOfBoot               HalEndOfBoot;
27   pHalMirrorVerify            HalMirrorVerify;
28   pHalGetAcpiTable            HalGetCachedAcpiTable;
29   pHalSetPciErrorHandlerCallback  HalSetPciErrorHandlerCallback;
30 #if defined(_IA64_)
31   pHalGetErrorCapList         HalGetErrorCapList;
32   pHalInjectError             HalInjectError;
33 #endif
34 } HAL_DISPATCH, *PHAL_DISPATCH;

这个结构中第一个 ULONG 是版本号,第二个 ULONG 是需要利用的 HalQuerySystemInformation() 的地址。

如果要将 HalQuerySystemInformation() 篡改为 0,则需要构造如下的 DeviceIoControl 函数参数:

OD: Kernel Exploit

接下来看看 NtQueryIntervalProfile() 和 HalQuerySystemInformation() 的关系,这里参考 ReactOS 0.3.11 源码文件 toskrnlexprofile.c 中的 NtQueryIntervalProfile():

 1 NTSTATUS
 2 NTAPI
 3 NtQueryIntervalProfile(IN KPROFILE_SOURCE ProfileSource,
 4                        OUT PULONG Interval)
 5 {
 6     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
 7     ULONG ReturnInterval;
 8     NTSTATUS Status = STATUS_SUCCESS;
 9     PAGED_CODE();
10 
11     /* Check if we were called from user-mode */
12     if (PreviousMode != KernelMode)
13     {
14         /* Enter SEH Block */
15         _SEH2_TRY
16         {
17             /* Validate interval */
18             ProbeForWriteUlong(Interval);
19         }
20         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
21         {
22             /* Return the exception code */
23             _SEH2_YIELD(return _SEH2_GetExceptionCode());
24         }
25         _SEH2_END;
26     }
27 
28     /* Query the Interval */
29     ReturnInterval = (ULONG)KeQueryIntervalProfile(ProfileSource);
30 
31     /* Enter SEH block for return */
32     _SEH2_TRY
33     {
34         /* Return the data */
35         *Interval = ReturnInterval;
36     }
37     _SEH2_EXCEPT(ExSystemExceptionFilter())
38     {
39         /* Get the exception code */
40         Status = _SEH2_GetExceptionCode();
41     }
42     _SEH2_END;
43 
44     /* Return Success */
45     return Status;
46 }
 1 typedef enum _KPROFILE_SOURCE
 2 {
 3     ProfileTime,
 4     ProfileAlignmentFixup,
 5     ProfileTotalIssues,
 6     ProfilePipelineDry,
 7     ProfileLoadInstructions,
 8     ProfilePipelineFrozen,
 9     ProfileBranchInstructions,
10     ProfileTotalNonissues,
11     ProfileDcacheMisses,
12     ProfileIcacheMisses,
13     ProfileCacheMisses,
14     ProfileBranchMispredictions,
15     ProfileStoreInstructions,
16     ProfileFpInstructions,
17     ProfileIntegerInstructions,
18     Profile2Issue,
19     Profile3Issue,
20     Profile4Issue,
21     ProfileSpecialInstructions,
22     ProfileTotalCycles,
23     ProfileIcacheIssues,
24     ProfileDcacheAccesses,
25     ProfileMemoryBarrierCycles,
26     ProfileLoadLinkedIssues,
27     ProfileMaximum
28 } KPROFILE_SOURCE;
 1 ULONG NTAPI KeQueryIntervalProfile    (    IN KPROFILE_SOURCE     ProfileSource    )    
 2 Definition at line 219 of file profobj.c.
 3 
 4 {
 5     HAL_PROFILE_SOURCE_INFORMATION ProfileSourceInformation;
 6     ULONG ReturnLength, Interval;
 7     NTSTATUS Status;
 8 
 9     /* Check what profile this is */
10     if (ProfileSource == ProfileTime)
11     {
12         /* Return the time interval */
13         Interval = KiProfileTimeInterval;
14     }
15     else if (ProfileSource == ProfileAlignmentFixup)
16     {
17         /* Return the alignment interval */
18         Interval = KiProfileAlignmentFixupInterval;
19     }
20     else
21     {
22         /* Request it from HAL */
23         ProfileSourceInformation.Source = ProfileSource;
24         Status = HalQuerySystemInformation(HalProfileSourceInformation,
25                                            sizeof(HAL_PROFILE_SOURCE_INFORMATION),
26                                            &ProfileSourceInformation,
27                                            &ReturnLength);
28 
29         /* Check if HAL handled it and supports this profile */
30         if (NT_SUCCESS(Status) && (ProfileSourceInformation.Supported))
31         {
32             /* Get the interval */
33             Interval = ProfileSourceInformation.Interval;
34         }
35         else
36         {
37             /* Unsupported or invalid source, fail */
38             Interval = 0;
39         }
40     }
41 
42     /* Return the interval we got */
43     return Interval;
44 }

(以上代码摘自 ReactOS 官网)

可见,NtQueryIntervalProfeile() 中并没有做实际的操作,而是将第一个输入参数作为 KeQueryIntervalProfile() 的参数并获取结果。只要输入 KeQueryIntervalProfile() 中的第一个参数 ProfileSource 不等于 ProfileTime,也不等于 ProfileAlignmentFixup,就会调用到 HalQuerySystemInformation()。

Ring0 Shellcode:运行在 Ring0 环境下的 shellcode 可以为所欲为,因为已经具有了最高权限,可以完全控制带个系统。其常见的用法有:

* 提权到 SYSTEM:修改当前进程的 token 为 SYSTEM 进程的 token,这样当前进程便具备了 SYSTEM 权限。
* 恢复内核 Hook/Inline Hook:大部分安全软件通过 Hook 系统内核函数实现防御。可以通过恢复这些 Hook 来突破安全软件,甚至瓦解整个防御体系。
* 添加调用门/中断门/任务门/陷阱门:四门机制是出入 Ring0/Ring3 的重要手段。在系统中成功添加一个门,就能在后续代码中*出入 Ring0、Ring3。

-----------------------------------------------------------------------------------

* 注-1 *
HAL,Hardware Abstraction Layer,硬件抽象层。HAL 高度依赖于机器,它必须与其所装入的系统完全匹配,Windows 的安装光盘上提供了许多种版本的 HAL。系统安装时,选择一种合适的 HAL 并以 hal.dll 为名复制到硬盘上的 %systemroot%system32 下。之后的启动都使用该版本 HAL,删除该文件将导致系统无法启动。

尽管 HAL 已经相当高效,但对于多媒体应用而言,它的速度可能还不够快。为此,微软另外提供了 DirectX,用附加的过程增强了 HAL,并允许用户对硬件进行更直接的访问。

* 注-2 *

ReactOS 是开源免费的 Windows NT 系列(含 NT4.0/2000/XP/2003)克隆操作系统,保持了与 Windows 的系统级兼容性,旨在实现和 NT 与 XP 操作系统二进制下的完全应用程序和驱动设备的兼容性,通过使用类似构架和提供完全公共接口。