第 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. CodeFirst中DB保存时报错:对一个或多个实体的验证失败。

    错误提示如下: 开始以为有字段可能没有添加数据,可是检查了很久,仍然没有任何头绪. 后使用DbEntityValidationException进行调试,问题才得以解决

  2. golang 性能

    服务器: 阿里云ECS:1核1G内存,CentOS6.5 x64 返回内容: {"ErrInfo":{"ErrCode":0,"ErrMsg" ...

  3. BZOJ 1016 最小生成树计数

    Description 现在给出了一个简单无向加权图.你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的最小生成树.(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的 ...

  4. If one session has a shared or exclusive lock on record R in an index, another session cannot insert

    If one session has a shared or exclusive lock on record R in an index, another session cannot insert ...

  5. MFC中菜单栏使用

    1.新建项目: 选择MFC应用程序,应用程序类型选择“基于对话框”: 本文中项目名为:MenuTest 2.新建菜单栏资源: 找到资源视图,右键MenuTest.re选择“添加资源”——选择Menu, ...

  6. Delphi 客户端调用Webservice 的TClientdataset 报出“http://www.borland.com/namespaces/Types-IAppServerSOAP”

    http://www.borland.com/namespaces/Types-IAppServerSOAP 服务器未能识别 HTTP 头 SOAPAction 的值 (2011-04-25 16:4 ...

  7. 微信支付【get_brand_wcpay_request:fail_invalid appid】

    微信支付授权目录一定要注意大小写 艹 如下的WXPay2 千万不要写成WxPay2了 参考地址:http://q.cnblogs.com/q/70405/ 微信支付代码下载:http://files. ...

  8. cocos2d的框架思路

    这是我第一次写cocos的框架思路哈,虽然只是写完了一个程序,按理来说应该再多写一些,多积累一些经验了再来写这个框架的构成,但是我觉得还是把我这次写代码的所有想法先记下来哈,等到以后继续写cocos的 ...

  9. SPJ

    1. ∏sno(δjno='j1'(spj))2. ∏sno(δpno='p1'(δjno='j1'(spj)))3. ∏sno(δjno='j1'(spj)∞δcolor='红'(p))4. ∏jn ...

  10. Java EL 详细用法讲解

    本文主要介绍了Java EL的语法结构以及EL的使用方法,并结合例子代码讲解了Java EL对象.Java EL运算符以及Java EL函数.希望对Java开发者有所帮助. 一.EL简介 1.语法结构 ...