首页
论坛
专栏
课程

[原创]Windows内核漏洞初探

2015-12-12 00:20 1876

[原创]Windows内核漏洞初探

2015-12-12 00:20
1876
0x00 前言
传统的Ring3层漏洞由于多种保护技术的结合使得利用越来越困难,这时候,攻击者把目光转到内核也就顺理成章了。内核代码运行在Ring0,拥有最高的特权级,可以执行所有的指令,包括特权指令。所以,一旦内核出现漏洞,危害是极其严重的。一旦攻击者成功利用内核漏洞执行了shellcode,因为这些代码运行在Ring0,所以攻击者可以做任何事。内核漏洞一般也叫驱动漏洞,驱动一般以.sys为后缀,因为我们的机器包含了各种各样的外设,而这些外设和机器的通信是通过驱动来交互的,还有杀毒软件等一些程序也自带了驱动。本文我们把目光聚焦在驱动上,分析驱动漏洞的表现形式及利用技术。

0x01 关于驱动程序

驱动程序一般指的是设备驱动程序(Device Driver),是一种可以使计算机和设备通信的特殊程序。相当于硬件的接口,操作系统只有通过这个接口,才能控制硬件设备的工作,假如某设备的驱动程序未能正确安装,便不能正常工作。下面来介绍驱动程序的框架,学习一门语言,我看到的第一个例子总是 " Hello world ",因此,我还还是以hello world来介绍驱动编程,但是注意,本文不是讲如何编写驱动程序,如果你想了解更多关于驱动编程,也叫内核编程,我推荐:《天书夜读》。现在,让我们开始看第一个驱动程序吧:
/********************************************************************
  时间:    2015/9/11
  文件:     helloworld.c
  作者:    Netfairy
*********************************************************************/
#include <NTDDK.h>
//创建的设备对象指针
PDEVICE_OBJECT p_DeviceObject;

//驱动卸载函数
VOID DriverUnload( IN PDRIVER_OBJECT  driverObject )
{
  //什么都不做,只是打印一句话
  KdPrint(("驱动卸载,再见!\n"));
} 

//驱动派遣例程函数
NTSTATUS DrvDispatch(IN PDEVICE_OBJECT driverObject,IN PIRP pIrp)
{ 
  
  //设置IRP状态
  pIrp->IoStatus.Status=STATUS_SUCCESS;

  //设置IRP操作字节数
  pIrp->IoStatus.Information=0;

  //完成IRP的处理
  IoCompleteRequest(pIrp,IO_NO_INCREMENT);

  return STATUS_SUCCESS;
}

//驱动入口函数(类似于main或WinMain)
NTSTATUS DriverEntry( IN PDRIVER_OBJECT  driverObject, IN PUNICODE_STRING  registryPath )
{ 

  NTSTATUS       ntStatus;
  UNICODE_STRING devName;
  UNICODE_STRING symLinkName;
  int i=0; 

  //打印一句调试信息
  KdPrint(("Hello world!!!\n"));

  //设置该驱动对象的卸载函数
  //driverObject->DriverUnload = DriverUnload; 

  //创建设备 
  RtlInitUnicodeString(&devName, L"\\Device\\HelloWorld");
  ntStatus = IoCreateDevice( driverObject,0,&devName,FILE_DEVICE_UNKNOWN,0, TRUE,&p_DeviceObject );
  
  //创建符号链接  
  RtlInitUnicodeString(&symLinkName,L"\\DosDevices\\HelloWorld");
  ntStatus = IoCreateSymbolicLink( &symLinkName,&devName );
  
  //设置该驱动对象的派遣例程函数
  for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
  {
    driverObject->MajorFunction[i] = DrvDispatch;
  }
  //返回成功结果
  return STATUS_SUCCESS;
}

程序很简单:首先看
ntStatus = IoCreateDevice( driverObject,0,&devName,FILE_DEVICE_UNKNOWN,0, TRUE,&p_DeviceObject );

驱动本质是用来完成一定任务的,那么它必然要能和Ring3进行交互,所以在驱动中调用此函数来创建设备对象,标示这个驱动本身。
下面接着看
ntStatus = IoCreateSymbolicLink( &symLinkName,&devName )

IoCreateSymbolicLink函数创建一个符号链接,以后跟驱动进行交互就使用这个符号链接名HelloWorld。驱动处理程序发来的消息,是通过
driverObject->MajorFunction[i] = DrvDispatch;

派遣的,有点类似WIN32编程的消息派遣,由于这个程序简单,我把全部的消息都派遣给DrvDispatch这个函数处理。更多请参考http://www.programlife.net/io_stack_location-irp.html。这个驱动加载后会输出 HelloWorld,请用Dbgview.exe观察输出而不是控制台。

0x02 内核漏洞介绍
关于内核漏洞的分类我没有找到权威的说法,所以本文也不打算介绍内核漏洞的类型。我只讲很常见一种,也是下来我要演示的:任意地址写任意数据。这个漏洞允许攻击者在任意地址(包括内核地址)写任意数据,这是很严重的,想想假如我们在驱动程序中把某个地址覆写为我们shellcode的地址,然后想办法跳到这个地址执行,由于驱动运行在Ring0....为了演 示内核漏洞,我决定用0day安全:软件漏洞分析技术里面的一个例子。原代码如下:
/********************************************************************
  created:  2010/12/06
  filename:   D:\0day\ExploitMe\exploitme.c
  author:    shineast
  purpose:  Exploit me driver demo 
*********************************************************************/
#include <ntddk.h>

#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;//函数返回值 

  //获取数据
  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 = 0;// Assume nothing returned

  //根据 ioControlCode 完成对应的任务
  switch(ioControlCode)
  {
  case IOCTL_EXPLOIT_ME: 
    if ( inputBufferLength >= 4 && outputBufferLength >= 4 )
    {
      *(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=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;
}

除去注释信息,实际上也没有多少代码。这个驱动存在任意地址写任意数据漏洞。我们看到驱动首先创建了ExploitMe这个设备,然后把所有的信息都交给DrvDispatch函数处理。在DrvDispatch内部
Type3InputBuffer = pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer;

获取用户态输入缓冲区的地址,用户态程序传给驱动的数据就保存在这个地址。
outputBufferLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;

获取用户态输出缓冲区地址,驱动向这个地址填充数据返回给用户态程序,完成交互。
ioControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;

IoControlCode是用户态传给驱动去执行某一子分支的代码,所以接下来我们看到
switch(ioControlCode)
对IoControlCode进行识别,然后执行相应的分支,因此,用户态程序传过来的IoControlCode和驱动定义的IoControlCode必须对应,否则驱动无法识别。那么,驱动定义的IoControlCode都有哪些呢?在这个例子中,我们只看到了 IOCTL_EXPLOIT_ME。关于它的十六进制数值计算,请看
#define IOCTL_EXPLOIT_ME (ULONG)CTL_CODE(FILE_DEVICE_EXPLOIT_ME,0x800,METHOD_NEITHER,FILE_WRITE_ACCESS)

并且用我写的一个小工具计算

下面看驱动如何处理8888a003这个控制代码
if ( inputBufferLength >= 4 && outputBufferLength >= 4 )
    {
      *(ULONG *)UserBuffer = *(ULONG *)Type3InputBuffer;
      IoStatus->Information = sizeof(ULONG);
    }
    break;

首先判断输入输出缓冲区长度,如果长度都大于4,那么就把输入缓冲区的前四字节内容赋值给输出缓冲区,由于驱动对输入输出缓冲区没有任何检查,且输入输出缓冲区是是我们用户层程序传给驱动的,那么我们可以传递任何值。假设我们输入缓冲区前四个字节是shellcode的地址,输出缓冲区是指向函数A的地址。当我们给驱动发送8888a003这个控制代码发送数据时,原本保存着函数A地址,现在被shellcode地址覆盖了,此时我们再调用函数A,我们的shellcode就被激活。一个任意地址写任意数据漏洞发生了。

0x03 实践ExploitMe漏洞利用
关于这个驱动的利用书上已经讲的很清楚了,需要说明的是,在我的机器上,这个Exploit并没有执行成功(如果你幸运的话,或许可以)为了证明我们的shellcode确实执行了。我在Exploit开头定义了一个全局变量
//验证shellcode执行成功的标志
int flag=0;

在Ring0Shellcode把flag修改为 88,最后在程序末尾打印flag
printf("flag=%d\n",flag);  //验证shellcode是否调用成功
  if(flag==88)
  {
    printf("Ring0ShellCode执行成功\n");
  }

如果Ring0Shellcode执行成功,应该能看到 Ring0ShellCode执行成功 输出。好,下面我们试一下,首先把exploitme.sys加载到系统(请用虚拟机)

然后把Exploit编译出来,放到测试机器上,运行,在我的机器上,结果


0X04 参考
0day安全:软件漏洞分析技术
暗战亮剑-软件漏洞发掘与安全
内核漏洞的利用与防范
IO_STACK_LOCATION与IRP的一点笔记:http://www.programlife.net/io_stack_location-irp.html

[公告]安全测试和项目外包请将项目需求发到看雪企服平台:https://qifu.kanxue.com

上传的附件:
最新回复 (4)
黑客A 2015-12-12 09:42
2
0
mark Windows内核漏洞初探
white、、 2015-12-12 10:24
3
0
膜拜挖漏洞的大婶
dalerkd 1 2015-12-12 23:53
4
0
必须mark呀
elapseyear 2015-12-15 15:42
5
0
赞一下内核漏洞。
游客
登录 | 注册 方可回帖
返回