[04] HEVD 内核漏洞之IntegerOverflow

 做者:huity
出处http://www.javashuo.com/article/p-gruicjtm-gz.html
版权:本文版权归做者全部。文章在博客园、看雪、我的博客同时发布。
转载:欢迎转载,但未经做者赞成,必须保留此段声明;必须在文章中给出原文链接;不然必究法律责任。html

0x00 前言

这一节开始,咱们学习整数溢出相关漏洞。
实验环境:Win10专业版+VMware Workstation 15 Pro+Win7 x86 sp1
实验工具:VS2015+Windbg+KmdManager+DbgViewer

0x01 漏洞原理

整数溢出

整数分为有符号和无符号两种类型,有符号数以其最高为做为其符号位,即正整数最高为0,负整数最高为为1,而无符号数无此类状况,其取值范围是非负数。日常在编程时。最经常使用到的整型变量有 short int(2B),unsigned int(2B),int(4B)和long int(4B)。不一样类型的整数在内存中均有不一样的取值范围,当咱们向其存储的数据超过该类型整数的最大值,就会致使整数溢出。溢出后的数据,可能会被截断,而形成程序出现错误。

Demo(基于栈的整数溢出)

#include "stdio.h"
#include "string.h"

int main()
{
    int i;
    char buf[8];
    unsigned short int size;
    char overflow[65550];

    memset(overflow,65,sizeof(overflow));

    printf("Input the num:\n");
    scanf("%d",&i);

    size=i;
    printf("size:%d\n",size);
    printf("i:%d\n",i);

    if(size>8)
        return -1;
    memcpy(buf,overflow,i);//栈溢出

    return 0;
}

上面的demo中,size为无符号短整数(0-65535),当咱们输入大于65535是就会形成溢出,例如咱们输入65536,最终获得的size为0,从而绕过边界检查,可是在memcpy函数复制数据时,使用的是int类型的i参数,致使栈溢出。git

 

Demo(基于堆的整数溢出)

#include "stdio.h"
#include "string.h"
#include "Windows.h"
int main()
{
    int heap;
    unsigned short int size;
    char* v1,*v2;
    HANDLE HeapHandle;

    printf("int put size: \n");
    scanf("%d",&size);

    HeapHandle = HeapCreate(HEAP_GENERATE_EXCEPTIONS,0x100,0xfff);

    if(size <=0x50)
    {
        size-=5;
        printf("size:%d\n",size);
        v1=(char*)HeapAlloc(HeapHandle,0,size);
        v2=(char*)HeapAlloc(HeapHandle,0,0x50);
    } 
    HeapFree(HeapHandle,0,v1);
    HeapFree(HeapHandle,0,v2);
    return 0;
}

上述demo中size为unsigned short int,小于5时,例如,当size=2时,size减去5则获得负数,但size取值范围致使没法识别负数,而获得正数65533,而分配获得大的堆块,从而溢出致使覆盖到后面的堆管理结构。github

分析

打开漏洞驱动程序 源码,找到漏洞函 TriggerIntegerOverflow,以下:

 

__declspec(safebuffers)
NTSTATUS TriggerIntegerOverflow( _In_ PVOID UserBuffer, _In_ SIZE_T Size)
{
    ULONG Count = 0;
    NTSTATUS Status = STATUS_SUCCESS;
    ULONG BufferTerminator = 0xBAD0B0B0;
    ULONG KernelBuffer[BUFFER_SIZE] = { 0 };//512*4=2048
    SIZE_T TerminatorSize = sizeof(BufferTerminator);//4

    PAGED_CODE();

    __try
    {
        //
        // UserBuffer 为Ring3地址 其中前面均用A填充,倒数8字节开始的4字节为Payload地址  最后四字节为0xBAD0B0B0
        ProbeForRead(UserBuffer, sizeof(KernelBuffer), (ULONG)__alignof(UCHAR));

        DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
        DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);
        DbgPrint("[+] KernelBuffer: 0x%p\n", &KernelBuffer);
        DbgPrint("[+] KernelBuffer Size: 0x%X\n", sizeof(KernelBuffer));

#ifdef SECURE
        //
        // 安全注意:这是安全的,由于开发人员没有对用户提供的值进行任何算术运算。 
        //相反,开发人员从KernelBuffer的大小减去ULONG的大小,即x86上的4。 所以,不会发生整数溢出,而且此检查不会失败

        if (Size > (sizeof(KernelBuffer) - TerminatorSize))
        {
            DbgPrint("[-] Invalid UserBuffer Size: 0x%X\n", Size);

            Status = STATUS_INVALID_BUFFER_SIZE;
            return Status;
        }
#else
        DbgPrint("[+] Triggering Integer Overflow (Arithmetic Overflow)\n");

      
        // 注意这里是有漏洞的版本
        if ((Size + TerminatorSize) > sizeof(KernelBuffer))//FFFFFFFF+4 = 00000003
        {
            DbgPrint("[-] Invalid UserBuffer Size: 0x%X\n", Size);

            Status = STATUS_INVALID_BUFFER_SIZE;
            return Status;
        }
#endif

       //实现拷贝操做
        while (Count < (Size / sizeof(ULONG)))
        {
            if (*(PULONG)UserBuffer != BufferTerminator)
            {
                KernelBuffer[Count] = *(PULONG)UserBuffer;
                UserBuffer = (PULONG)UserBuffer + 1;
                Count++;
            }
            else
            {
                break;
            }
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }
    return Status;
}

函数中比较了用户提交缓冲区长度和内核缓冲区长度,在有漏洞的版本中,这一比较采用了:shell

if ((Size + TerminatorSize) > sizeof(KernelBuffer))
TerminatorSize为4字节,当用户传过来的缓冲区大小为0xfffffffc~0xffffffff时,假发以后结果溢出,而且巧妙地经过了长度检查!
在Powershell中,咱们能够清晰地看到这一问题。

 

咱们在Windbg中一样能够看到这一问题:编程

97c4ba9f 8b450c          mov     eax,dword ptr [ebp+0Ch]
97c4baa2 0345d4          add     eax,dword ptr [ebp-2Ch]
97c4baa5 3d00080000      cmp     eax,800h


kd> r eax
eax=00000003
检查完以后,则进入循环,将用户缓冲区的数据拷贝到内核缓冲区。当内核地址不等于缓冲区结 束标志0xBAD0B0B0时,拷贝数据。
在拷贝数据前,查看寄存器,当前ebp为0x9398fab0,查看内存,得知当前返回地址为0x9398fab0。
kd> r
eax=00000003 ebx=00000000 ecx=ffffffff edx=0000004d esi=86e61528 edi=860fff80
eip=97c80adb esp=9398f268 ebp=9398fab0 iopl=0         nv up ei ng nz na pe cy
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000287
HEVD!TriggerIntegerOverflow+0x16b:
97c80adb 8b450c          mov     eax,dword ptr [ebp+0Ch] ss:0010:9398fabc=ffffffff
kd> dd 9398fab0 
9398fab0  9398fad4 97c80956 00408028 ffffffff
9398fac0  00000001 c0000001 ffffffff 00000000
9398fad0  00408028 9398fafc 97c800ae 86e6a870
9398fae0  86e6a8e0 00000001 00000000 00222027
9398faf0  00000024 c00000bb 86e6a8e0 9398fb14
9398fb00  83e7f593 86e61528 86e6a870 86e6a870
9398fb10  86e61528 9398fb34 8407399f 860fff80
9398fb20  86e6a870 86e6a8e0 00000094 0498fbac

0x02 漏洞利用

利用防方法与以前无二,主要是覆盖函数返回地址。
可是亲自测试事后,感受函数内存结构与咱们所构想的有所区别:

 

上面为覆盖前内核内存,红色为ebp地址,后面的0x97c5a956为返回地址,能够看到,覆盖后的内存并无修改到返回地址,那么是怎么利用的呢?这让我很费解。
仔细想了一下,应该是VS编译器的缘由,致使堆栈数据之间有保护间隙,因此实际的内存状况与咱们所构造的并不一致。整个的利用思路和栈溢出是相似的。
使用HackSys Team发布的 Release第二版,测试一哈,能够看到,此时提权利用是能够成功的。

0x03 漏洞反思

尽管利用的过程不太顺利,可是就原理来说,参照安全版本,咱们能够预估数据大小的状况下,若是传入数据较大,有可能向上溢出时,咱们采用减法判断,相似的若是可能向下溢出,则咱们采用加法判断。
固然了,若是没法判断大小那应该进行更为周密的考虑。

0x04 连接

相关文章
相关标签/搜索