吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 13998|回复: 26
收起左侧

[漏洞分析] Windows内核漏洞利用实践

  [复制链接]
Netfairy 发表于 2016-1-3 15:19
本帖最后由 Netfairy 于 2016-1-3 15:33 编辑

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

0x01 关于驱动程序
驱动程序一般指的是设备驱动程序(Device Driver),是一种可以使计算机和设备通信的特殊程序。相当于硬件的接口,操作系统只有通过这个接口,才能控制硬件设备的工作,假如某设备的驱动程序未能正确安装,便不能正常工作。下面来介绍驱动程序的框架,学习一门语言,我看到的第一个例子总是 " Hello world ",因此,我还还是以hello world来介绍驱动编程,但是注意,本文不是讲如何编写驱动程序,如果你想了解更多关于驱动编程,也叫内核编程,我推荐:《天书夜读》。现在,让我们开始看第一个驱动程序吧:
[Asm] 纯文本查看 复制代码
/********************************************************************
        时间:                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 = DrvDispatch;
        }
        //返回成功结果
        return STATUS_SUCCESS;
}

程序很简单:首先看

[Asm] 纯文本查看 复制代码
ntStatus = IoCreateDevice( driverObject,0,&devName,FILE_DEVICE_UNKNOWN,0, TRUE,&p_DeviceObject );
驱动本质是用来完成一定任务的,那么它必然要能和Ring3进行交互,所以在驱动中调用此函数来创建设备对象,标示这个驱动本身。
下面接着看
[Asm] 纯文本查看 复制代码
ntStatus = IoCreateSymbolicLink( &symLinkName,&devName )

IoCreateSymbolicLink函数创建一个符号链接,以后跟驱动进行交互就使用这个符号链接名HelloWorld。驱动处理程序发来的消息,是通过
[Asm] 纯文本查看 复制代码
driverObject->MajorFunction = DrvDispatch;
派遣的,有点类似WIN32编程的消息派遣,由于这个程序简单,我把全部的消息都派遣给DrvDispatch这个函数处理。更多请参考http://www.programlife.net/io_stack_location-irp.html。
这个驱动加载后会输出 HelloWorld,请用Dbgview.exe观察输出而不是控制台。

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

[i]#define DEVICE_NAME L"\\Device\\ExploitMe"[/i]
[i]#define DEVICE_LINK L"\\DosDevices\\DRIECTX1"[/i]
[i]#define FILE_DEVICE_EXPLOIT_ME 0x00008888[/i]
[i]#define IOCTL_EXPLOIT_ME (ULONG)CTL_CODE(FILE_DEVICE_EXPLOIT_ME,0x800,METHOD_NEITHER,FILE_WRITE_ACCESS)[/i]

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

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

[i]        //获取数据[/i]
[i]        pIrpStack = IoGetCurrentIrpStackLocation(pIrp);[/i]
[i]        Type3InputBuffer = pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer;[/i]
[i]        UserBuffer = pIrp->UserBuffer;[/i]
[i]        inputBufferLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength; [/i]
[i]        outputBufferLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength; [/i]
[i]        ioControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;[/i]
[i]        IoStatus=&pIrp->IoStatus;[/i]
[i]        IoStatus->Status = STATUS_SUCCESS;// Assume success[/i]
[i]        IoStatus->Information = 0;// Assume nothing returned[/i]

[i]        //根据 ioControlCode 完成对应的任务[/i]
[i]        switch(ioControlCode)[/i]
[i]        {[/i]
[i]        case IOCTL_EXPLOIT_ME: [/i]
[i]                if ( inputBufferLength >= 4 && outputBufferLength >= 4 )[/i]
[i]                {[/i]
[i]                        *(ULONG *)UserBuffer = *(ULONG *)Type3InputBuffer;[/i]
[i]                        IoStatus->Information = sizeof(ULONG);[/i]
[i]                }[/i]
[i]                break;[/i]
[i]        }  [/i]

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


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

[Asm] 纯文本查看 复制代码
Type3InputBuffer = pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer;

获取用户态输入缓冲区的地址,用户态程序传给驱动的数据就保存在这个地址。

[Asm] 纯文本查看 复制代码
outputBufferLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;

获取用户态输出缓冲区地址,驱动向这个地址填充数据返回给用户态程序,完成交互。ioControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;IoControlCode是用户态传给驱动去执行某一子分支的代码,所以接下来我们看到

[Asm] 纯文本查看 复制代码
switch(ioControlCode)对IoControlCode

进行识别,然后执行相应的分支,因此,用户态程序传过来的IoControlCode和驱动定义的IoControlCode必须对应,否则驱动无法识别。那么,驱动定义的IoControlCode都有哪些呢?在这个例子中,我们只看到了 IOCTL_EXPLOIT_ME。关于它的十六进制数值计算,请看

[Asm] 纯文本查看 复制代码
#define IOCTL_EXPLOIT_ME (ULONG)CTL_CODE(FILE_DEVICE_EXPLOIT_ME,0x800,METHOD_NEITHER,FILE_WRITE_ACCESS)

并且用我写的一个小工具计算
1.png
下面看驱动如何处理8888a003这个控制代码

[Asm] 纯文本查看 复制代码
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开头定义了一个全局变量

[Asm] 纯文本查看 复制代码
//验证shellcode执行成功的标志int flag=0;在Ring0Shellcode把flag修改为 88,最后在程序末尾打印flag
printf("flag=%d\n",flag);  //验证shellcode是否调用成功        if(flag==88)        {                printf("Ring0ShellCode执行成功\n");        }如果Ring0Shellcode执行成功,应该能看到 Ring0ShellCode执行成功 输出。

好,下面我们试一下,首先把exploitme.sys加载到系统(请用虚拟机
2.png
然后把Exploit编译出来,放到测试机器上,运行,在我的机器上,结果
3.png
0x04 参考
0day安全:软件漏洞分析技术
暗战亮剑-软件漏洞发掘与安全
内核漏洞的利用与防范
IO_STACK_LOCATION与IRP的一点笔记:http://www.programlife.net/io_stack_location-irp.html


原文:http://www.netfairy.net/?post=171



免费评分

参与人数 3吾爱币 +1 热心值 +3 收起 理由
bouc + 1 + 1 谢谢@Thanks!
wxcstc + 1 写得挺好的,学习了
菜鸟也想飞 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| Netfairy 发表于 2016-1-3 15:51
aikuimail 发表于 2016-1-3 15:46
这不都是书上的入门内容吗?怎么还写得这么仔细。。。。。。炒冷饭。。。。不过还是支持楼主,加油

是书上的内容,但是自己总结动手做一遍的感觉还是不一样的

免费评分

参与人数 1热心值 +1 收起 理由
aikuimail + 1 我也只会NT的扩展驱动,楼主给力啊,会EXP

查看全部评分

猫携 发表于 2016-1-3 15:25
蓦留 发表于 2016-1-3 15:33
学霸 发表于 2016-1-3 15:36

好吧,看不懂
啊骚。 发表于 2016-1-3 15:40
看不懂哇
阿志 发表于 2016-1-3 15:40
泰牛拉
雖然看不懂但是很厲害的感覺
a569421 发表于 2016-1-3 15:44
看不懂,,,,大神一个~~~
aikuimail 发表于 2016-1-3 15:46
这不都是书上的入门内容吗?怎么还写得这么仔细。。。。。。炒冷饭。。。。不过还是支持楼主,加油
枫MapleLCG 发表于 2016-1-3 16:01
不懂觉厉
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-3-29 19:17

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表