[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 連接

相關文章
相關標籤/搜索