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

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

 /********************************************************************
created: 2010/12/06
filename: exploitme.c
author: shineast
purpose: Exploit me driver demo
*********************************************************************/
#include <ntddk.h> #define DEVICE_NAME L"\\Device\\ExploitMe"
#define DEVICE_LINK L"\\DosDevices\\ExploitMe"
#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; //函数返回值 //获取数据
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
Type3InputBuffer = pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer;
UserBuffer = pIrp->UserBuffer;
inputBufferLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
outputBufferLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;
ioControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
IoStatus=&pIrp->IoStatus;
IoStatus->Status = STATUS_SUCCESS;// Assume success
IoStatus->Information = ;// Assume nothing returned //根据 ioControlCode 完成对应的任务
switch(ioControlCode)
{
case IOCTL_EXPLOIT_ME:
if ( inputBufferLength >= && outputBufferLength >= )
{
*(ULONG *)UserBuffer = *(ULONG *)Type3InputBuffer;
IoStatus->Information = sizeof(ULONG);
}
break;
} //返回
IoStatus->Status = ntStatus;
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
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=;
//打印一句调试信息
KdPrint(("DriverEntry: Exploit me driver demo!\n"));
//创建设备
RtlInitUnicodeString(&devName,DEVICE_NAME);
ntStatus = IoCreateDevice( driverObject,
,
&devName,
FILE_DEVICE_UNKNOWN,
, 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 = ; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
driverObject->MajorFunction[i] = DrvDispatch;
}
//返回成功结果
return STATUS_SUCCESS;
}

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

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

        if ( inputBufferLength >=  && outputBufferLength >=  )
{
*(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 的漏洞。能达到这个目的的漏洞主要有三种:任意地址写任意内容、固定地址写任意内容和任意地址写固定内容。其中任意地址写任意内容的内核漏洞必定能实现本地权限提升。

目前常见的内核漏洞利用方法主要有两种:篡改内核内存数据、执行 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 导出的一个函数表):

 // extracted from haltypes.h

 typedef struct {
ULONG Version;
pHalQuerySystemInformation HalQuerySystemInformation;
pHalSetSystemInformation HalSetSystemInformation;
pHalQueryBusSlots HalQueryBusSlots;
ULONG Spare1;
pHalExamineMBR HalExamineMBR;
#if 1 /* Not present in WDK 7600 */
pHalIoAssignDriveLetters HalIoAssignDriveLetters;
#endif
pHalIoReadPartitionTable HalIoReadPartitionTable;
pHalIoSetPartitionInformation HalIoSetPartitionInformation;
pHalIoWritePartitionTable HalIoWritePartitionTable;
pHalHandlerForBus HalReferenceHandlerForBus;
pHalReferenceBusHandler HalReferenceBusHandler;
pHalReferenceBusHandler HalDereferenceBusHandler;
pHalInitPnpDriver HalInitPnpDriver;
pHalInitPowerManagement HalInitPowerManagement;
pHalGetDmaAdapter HalGetDmaAdapter;
pHalGetInterruptTranslator HalGetInterruptTranslator;
pHalStartMirroring HalStartMirroring;
pHalEndMirroring HalEndMirroring;
pHalMirrorPhysicalMemory HalMirrorPhysicalMemory;
pHalEndOfBoot HalEndOfBoot;
pHalMirrorVerify HalMirrorVerify;
pHalGetAcpiTable HalGetCachedAcpiTable;
pHalSetPciErrorHandlerCallback HalSetPciErrorHandlerCallback;
#if defined(_IA64_)
pHalGetErrorCapList HalGetErrorCapList;
pHalInjectError HalInjectError;
#endif
} HAL_DISPATCH, *PHAL_DISPATCH;

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

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

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

 NTSTATUS
NTAPI
NtQueryIntervalProfile(IN KPROFILE_SOURCE ProfileSource,
OUT PULONG Interval)
{
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
ULONG ReturnInterval;
NTSTATUS Status = STATUS_SUCCESS;
PAGED_CODE(); /* Check if we were called from user-mode */
if (PreviousMode != KernelMode)
{
/* Enter SEH Block */
_SEH2_TRY
{
/* Validate interval */
ProbeForWriteUlong(Interval);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
/* Return the exception code */
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
} /* Query the Interval */
ReturnInterval = (ULONG)KeQueryIntervalProfile(ProfileSource); /* Enter SEH block for return */
_SEH2_TRY
{
/* Return the data */
*Interval = ReturnInterval;
}
_SEH2_EXCEPT(ExSystemExceptionFilter())
{
/* Get the exception code */
Status = _SEH2_GetExceptionCode();
}
_SEH2_END; /* Return Success */
return Status;
}
 typedef enum _KPROFILE_SOURCE
{
ProfileTime,
ProfileAlignmentFixup,
ProfileTotalIssues,
ProfilePipelineDry,
ProfileLoadInstructions,
ProfilePipelineFrozen,
ProfileBranchInstructions,
ProfileTotalNonissues,
ProfileDcacheMisses,
ProfileIcacheMisses,
ProfileCacheMisses,
ProfileBranchMispredictions,
ProfileStoreInstructions,
ProfileFpInstructions,
ProfileIntegerInstructions,
Profile2Issue,
Profile3Issue,
Profile4Issue,
ProfileSpecialInstructions,
ProfileTotalCycles,
ProfileIcacheIssues,
ProfileDcacheAccesses,
ProfileMemoryBarrierCycles,
ProfileLoadLinkedIssues,
ProfileMaximum
} KPROFILE_SOURCE;
 ULONG NTAPI KeQueryIntervalProfile    (    IN KPROFILE_SOURCE     ProfileSource    )
Definition at line of file profobj.c. {
HAL_PROFILE_SOURCE_INFORMATION ProfileSourceInformation;
ULONG ReturnLength, Interval;
NTSTATUS Status; /* Check what profile this is */
if (ProfileSource == ProfileTime)
{
/* Return the time interval */
Interval = KiProfileTimeInterval;
}
else if (ProfileSource == ProfileAlignmentFixup)
{
/* Return the alignment interval */
Interval = KiProfileAlignmentFixupInterval;
}
else
{
/* Request it from HAL */
ProfileSourceInformation.Source = ProfileSource;
Status = HalQuerySystemInformation(HalProfileSourceInformation,
sizeof(HAL_PROFILE_SOURCE_INFORMATION),
&ProfileSourceInformation,
&ReturnLength); /* Check if HAL handled it and supports this profile */
if (NT_SUCCESS(Status) && (ProfileSourceInformation.Supported))
{
/* Get the interval */
Interval = ProfileSourceInformation.Interval;
}
else
{
/* Unsupported or invalid source, fail */
Interval = ;
}
} /* Return the interval we got */
return Interval;
}

(以上代码摘自 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 操作系统二进制下的完全应用程序和驱动设备的兼容性,通过使用类似构架和提供完全公共接口。

OD: Kernel Exploit - 1的更多相关文章

  1. OD: Kernel Exploit - 2 Programming

    本节接前方,对 exploitme.sys 进行利用. exploitme.sys 存在任意地址写任意内容的内核漏洞,现在采用执行 Ring0 Shellcode 的方式进行利用. 获取 HalDis ...

  2. OD: Heap Exploit : DWORD Shooting & Opcode Injecting

    堆块分配时的任意地址写入攻击原理 堆管理系统的三类操作:分配.释放.合并,归根到底都是对堆块链表的修改.如果能伪造链表结点的指针,那么在链表装卸的过程中就有可能获得读写内存的机会.堆溢出利用的精髓就是 ...

  3. OD: Shellcode / Exploit & DLL Trampolining

    看到第五章了. 标题中 Dll Tramplining(跳板)名字是从如下地址找到的,写的很好: http://en.wikipedia.org/wiki/Buffer_overflow#The_ju ...

  4. OD: Kernel Vulnerabilities Analyze

    内核漏洞大多出没于 ring3 到 ring0 的交互中.从 ring3 进入 ring0 的通道,以及操作系统提供的 API 都有可能存在漏洞.例如:驱动程序中 IoControl 的处理函数,SS ...

  5. OD: Kernel Vulnerabilities

    内核漏洞概述 内核漏洞的分类 运行在 Ring0 上的操作系统内核.设备驱动.第三方驱动能共享同一个虚拟地址空间,可以完全访问系统空间的所有内存,而不像用户态进程那样拥有独立私有的内存空间.由于内核程 ...

  6. How to exploit the x32 recvmmsg() kernel vulnerability CVE 2014-0038

    http://blog.includesecurity.com/2014/03/exploit-CVE-2014-0038-x32-recvmmsg-kernel-vulnerablity.html ...

  7. [轉]Exploit Linux Kernel Slub Overflow

    Exploit Linux Kernel Slub Overflow By wzt 一.前言 最近几年关于kernel exploit的研究比较热门,常见的内核提权漏洞大致可以分为几类: 空指针引用, ...

  8. An iOS zero-click radio proximity exploit odyssey

    NOTE: This specific issue was fixed before the launch of Privacy-Preserving Contact Tracing in iOS 1 ...

  9. 利用Android的UXSS漏洞完成一次XSS攻击

    黑客攻击的方式思路是先搜集信息,定位漏洞,然后针对不同的漏洞采用不同的方式来黑掉你.下面用metasploit模拟一次跨站脚本攻击(黑掉自己的手机). 1.搜集信息 msf > search a ...

随机推荐

  1. 《C和指针》章节后编程练习解答参考——6.1

    <C和指针>——6.1 6.1 题目: 编写一个函数,在一个字符串中进行搜索,查找另一子字符串中出现的字符. 函数原型如下: char *find_char(char const *sou ...

  2. Maya+3dsMax三维建模

    Maya比较擅长动画,在人物和动物的行为活动方面比较擅长 而3ds Max在建筑物地理地图方面比较擅长,多应用于地理 将两者结合起来将会非常有用

  3. Kent Beck揭秘Facebook开发部署流程

    http://www.infoq.com/cn/news/2013/10/facebook-development-deployment Facebook是世界上最大的社交网站,有超过10亿用户每月至 ...

  4. C# static成员的构造顺序

    熟知的几个原则 1.static字段初始化先于static构造函数 2.static字段按顺序初始化 3.static字段和static构造函数只执行一次 public class Foo { pub ...

  5. oracle中的exists 和not exists 用法详解(转)

    有两个简单例子,以说明 “exists”和“in”的效率问题 1) select * from T1 where exists(select 1 from T2 where T1.a=T2.a) ; ...

  6. Pearls DP

    Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 6647   Accepted: 3241 Description In Pe ...

  7. Curling 2.0(dfs)

    Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 8795   Accepted: 3692 Description On Pl ...

  8. AC+DP练习

    1.HDU 2222 Keywords Search 求目标串中出现了几个模式串. #include<iostream> #include<cstdio> #include&l ...

  9. 线性代数(矩阵乘法):NOI 2007 生成树计数

    这道题就是深搜矩阵,再快速幂. #include <iostream> #include <cstring> #include <cstdio> #include ...

  10. C# 匿名方法 委托 Action委托 Delegate委托

    原文地址:https://msdn.microsoft.com/zh-cn/library/bb882516.aspx 匿名函数是一个“内联”语句或表达式,可在需要委托类型的任何地方使用. 可以使用匿 ...