Analyzing local privilege escalations in win32k

在URP中正确写入Sprite深度以使用DOF 我准备在文字冒险中加入景深的效果,但是因为unity内置shader的缘故,sprite是不会写入深度的,而如果要使用PostProcessing的DOF效果的话,是必须要将半透明物体的深度正确写入Depth Buffer的,这篇消耗了我大量精力的文章就是要介绍正确写入的方法。 其实,如果只是要在2D游戏中实现类似于景深的效果,不写入深度其实也是可以的,我找到的有两种方案:一种是实时对Sprite进行模糊计算;二是直接使用离线预模糊的图片,根据强度进行混合。 离线预模糊 虽然这两种方法都可以比较简 阅读详情

 

mxatone
mxatone@gmail.com

 

Contents

 

 

Foreword

Abstract: This paper analyzes three vulnerabilities that were found in win32k.sys that allow kernel-mode code execution. The win32k.sys driver is a major component of the GUI subsystem in the Windows operating system. These vulnerabilities have been reported by the author and patched in MS08-025[1]. The first vulnerability is a kernel pool overflow with an old communication mechanism called the Dynamic Data Exchange (DDE) protocol. The second vulnerability involves improper use of the ProbeForWrite function within string management functions. The third vulnerability concerns how win32k handles system menu functions. Their discovery and exploitation are covered.

 

Introduction

The design of modern operating systems provides a separation of privileges between processes. This design restricts a non-privileged user from directly affecting processes they do not have access to. This enforcement relies on both hardware and software features. The hardware features protect devices against unknown operations. A secure environment provides only necessary rights by filtering program interaction within the overall system. This control increases provided interfaces and then security risks. Abusing operating system design or implementation flaws in order to elevate a program's rights is called a privilege escalation.

During the past few years, userland code and protection had been ameliorated. The amelioration of operating system understanding has made abnormal behaviour detection easier. The exploitation of classical weakness is harder than it was. Nowadays, local exploitation directly targets the kernel. Kernel local privilege escalation brings up new exploitation methods and most of them are certainly still undiscovered. Even if the Windows kernel is highly protected against known attack vectors, the operating system itself has a lot of different drivers that contribute to its overall attack surface.

On Windows, the graphical user interface (GUI) is divided into both kernel-mode and user-mode components. The win32k.sys driver handles user-mode requests for graphic rendering and window management. It also redirects DirectX calls on to the appropriate driver. For local privilege escalation, win32k represents an interesting target as it exists on all versions of Windows and some features have existed for years without modifications.

This article presents the author's work on analyzing the win32k driver to find and report vulnerabilities that were addressed in Microsoft bulletin MS08-025 [1]. Even if the patch adds an overall protection layer, it concerns three reported vulnerabilities on different parts of the driver. The Windows graphics stack is very complex and this article will focus on describing some of win32k's organization and functionalities. Any reader who is interested in this topic is encouraged to look at MSDN documentation for additional information[2].

The structure of this paper is as follows. In chapter 3, the win32k driver architecture basics will be presented with a focus on vulnerable contexts. Chapter 4 will detail how each of the three vulnerabilities was discovered and exploited. Finally, chapter 5 will discuss possible security improvements for the vulnerable driver.

 


Win32k design

Windows is based on a graphical user interface and cannot work without it. Only Windows Serer 2008 in server core mode uses a minimalist user interface but share the exact same components that typical user interfaces. The win32k driver is a critical component in the graphics stack exporting more than 600 functions. It extends the System Service Descriptor Table (SSDT) with another table called (_W32pServiceTable). This driver is not as big as the main kernel module (ntoskrnl.exe) but its interaction with the user-mode is just as important. The service table for win32k contains less than 300 functions depending on the version of Windows. The win32k driver commonly transfers control to user-mode with a user-mode callback system that will be explained in this part. The interface between user-mode modules and kernel-mode drivers has been built in order to facilitate window creation and management. This is a critical feature of Windows which may explain why exactly the same functions can be seen across multiple operating system versions.

 

General security implementation

The most important part of a driver in terms of security is how it validates user-mode inputs. Each argument passed as a pointer must be a valid user-mode address and be unchangeable to avoid race conditions. This validation is often accomplished by comparing a provided address with an address near the base of kernel memory using functions such as ProbeForRead and ProbeForWrite. Input contained within pointers is also typically cached in local variables (capturing). The Windows kernel design is very strict on this part. When you look deeper into win32k's functions, you will see that they do not follow the same strict integrity verifications made by the kernel. For example, consider the following check made by the Windows kernel (translated to C):

NTSTATUS NTAPI  NtQueryInformationPort(
    HANDLE PortHandle,
    PORT_INFORMATION_CLASS PortInformationClass,
    PVOID PortInformation,
    ULONG PortInformationLength,
    PULONG ReturnLength
    )

[...] // Prepare local variables

if (AccesMode != KernelMode)
{
    try {
        // Check submitted address - if incorrect, raise an exception
        ProbeForWrite( PortInformation, PortInformationLength, 4);

        if (ReturnLength != NULL)
        {
            if (ReturnLength > MmUserProbeAddress)
                *MmUserProbeAddress = 0; // raise exception
        
            *ReturnLength = 0;
        }
        
    } except(1) {  // Catch exceptions
        return exception_code;
    }
}

[...] // Perform actions

We can see that the arguments are tested in a very simple way before doing anything else. The ReturnLength field implements its own verification which relies directly on MmUserProbeAddress. This variable marks the separation between user-mode and kernel-mode address spaces. In case of an invalid address, an exception is raised by writting in this variable which is read-only. The ProbeForRead and ProbeForWrite functions verifications routines raised an exception if an incorrect address is encounter. However, the win32k driver does not allows follow this pattern:

BOOL NtUserSystemParametersInfo(
    UINT uiAction,
    UINT uiParam,
    PVOID pvParam,
    UINT fWinIni)

[...]   // Prepare local variables

switch(uiAction)
{
    case SPI_1:
        // Custom checks
        break;
    case SPI_2:
        size = sizeof(Stuct2);
        goto prob_read;
    case SPI_3:
        size = sizeof(Stuct3);
        goto prob_read;
    case SPI_4:
        size = sizeof(Stuct4);
        goto prob_read;
    case SPI_5:
        size = sizeof(Stuct5);
        goto prob_read;
    case SPI_6:
        size = sizeof(Struct6);

prob_read:
        ProbeForRead(pvParam, size, 4)

        [...]
}

[...] // Perform actions

This function is very complex and this example presents only a small part of the checks. Some parameters need only classic verification while others implement their own. This elaborate code can create confusion which improves the chances of a local privilege escalation. The issues comes from unordinary kernel function that handles multiple features at the same time without implementing a standardized function prototype. The Windows kernel solved this issue on NtSet* and NtQuery* functions by using two simple arguments. The first argument is a classical buffer and the second argument is its size. For example, the NtQueryInformationPort function will check the buffer in a generic way and then only verify that the size correspond to the specified feature. The win32k design implementation ameliorates GUI development but make code review very difficult.

 

KeUsermodeCallback utilization

Typical interaction between user-mode and kernel-mode is done via syscalls. A user-mode module may request that the kernel execute an action and return needed information. The win32k driver has a callback system to do the exact opposite. The KeUsermodeCallback function calls a user-mode function from kernel-mode. This function is undocumented and provided by the kernel module in a secure way in order to switch into user-mode properly[4]. The win32k driver uses this functionality for common task such as loading a dll module for event catching or retrieving information. The prototype of this function:

NTSTATUS KeUserModeCallback (
    IN ULONG ApiNumber,
    IN PVOID InputBuffer,
    IN ULONG InputLength,
    OUT PVOID *OutputBuffer,
    IN PULONG OutputLength
    );

Microsoft did not make a system to retrieve arbitrary user-mode function addresses from the kernel. Instead, the win32k driver has a set of functions that it needs to call. This list is kept in an undocumented function table in the Process Environment Block (PEB) structure for each process. The ApiNumber argument refers to an index into this table.

In order to return on user-mode, KeUserModeCallback retrieves the user-mode stack address from saved user-mode context stored in a thread's KTRAP_FRAME structure. It saves current stack level and uses ProbeForWrite to check if there is enough room for the input buffer. The Inputbuffer argument is then copied into the user stack and an argument list is created for the function being called. The KiCallUserMode function prepares the return in user-mode by saving important information in the kernel stack. This callback system works as a normal syscall exit procedure except than stack level and eip register has been changed. The callback start in the KiUserCallbackDispatcher function.

VOID KiUserCallbackDispatcher(
    IN ULONG ApiNumber,
    IN PVOID InputBuffer,
    IN ULONG InputLength
    );

The user-mode function KiUserCallbackDispatcher receives an argument list which contains ApiNumber, InputBuffer, and InputLength. It does appropriate function dispatching using the PEB dispatch table. When it is finished the routine invokes interrupt 0x2b to transfer control back to kernel-mode. In turn, the kernel inspects three registers:

 

  • ecx contains a user-mode pointer for OutputBuffer
  • edx is for OutputLength
  • eax contains return status.

The KiCallbackReturn kernel-mode function handles the 0x2B interrupt and passes important registers as argument for the NtCallbackReturn function. Everything is cleaned using saved information within the kernel stack and it transfers to previously called KeUsermodeCallback function with proper output argument sets.

The reader should notice that nothing is done to check ouput data. Each kernel function that uses the user-mode callback system is responsible for verifying output data. An attacker can simply hook the KiUserCallbackDispatcher function and filter requests to control output pointer, size and data. This user-mode call can represent an important issue if it was not verified as seriously as system call functions.

 


Discovery and exploitation

The win32k driver was patched by the MS08-025 bulletin [1]. This bulletin did not disclose any details about the issues but it did talk about a vulnerability which allows privilege elevation though invalid kernel checks. This patch increases the overall driver security by adding multiple verifications. In fact, this patch was due to three different reported vulnerabilities. The following sections explain how these vulnerabilities were discovered and exploited.

 

DDE Kernel pool overflow

The Dynamic Data Exchange (DDE) protocol is a GUI integrated message system [5]. Despite Windows operating system has already many different message mechanisms, this one share data across process by sharing GUI handles and memory section. This feature is quite old but still supported by Microsoft application as Internet explorer [6] and used in application firewalls bypass technique. During author's research on win32k driver, he investigated how the KeUsermodeCallback function was used. As described previously, this function does not verify directly output data. This lack of validation is what leads to this vulnerability.

 

Vulnerability details

The vulnerability exists in the xxxClientCopyDDEIn1 win32k function. It is not called directly but it is used internally in the kernel when messages are exchanged between processes using the DDE protocol. In this context, the OutputBuffer verification is analyzed.

In xxxClientCopyDDEIn1 function:

lea     eax, [ebp+OutputLength]
push    eax
lea     eax, [ebp+OutputBuffer]
push    eax
push    8                                ; InputLength
lea     eax, [ebp+InputBuffer]
push    eax             
push    32h                              ; ApiNumber
call    ds:__imp__KeUserModeCallback@20 
mov     esi, eax                         ; return < 0 (error ?)
call    _EnterCrit@0    
cmp     esi, edi
jl      loc_BF92C6D4

cmp     [ebp+OutputLength], 0Ch          ; Check output length
jnz     loc_BF92C6D4

mov     [ebp+ms_exc.disabled], edi       ; = 0
mov     edx, [ebp+OutputBuffer]
mov     eax, _Win32UserProbeAddress
cmp     edx, eax                         ; Check OutputBuffer address
jb      short loc_BF92C5DC

[...]

loc_BF92C5DC:
mov     ecx, [edx]
loc_BF92C5DE:
mov     [ebp+var_func_return_value], ecx
or      [ebp+ms_exc.disabled], 0FFFFFFFFh
push    2
pop     esi
cmp     ecx, esi                         ; first OutputBuffer ULONG must be 2
jnz     loc_BF92C6D4
xor     ebx, ebx
inc     ebx
mov     [ebp+ms_exc.disabled], ebx       ; = 1
mov     [ebp+ms_exc.disabled], esi       ; = 2
mov     ecx, [edx+8]                     ; OutputBuffer - user mode ptr
cmp     ecx, eax                         ; Win32UserProbeAddress - check user mode ptr
jnb     short loc_BF92C602

[...]

loc_BF92C602:
push    9
pop     ecx
mov     esi, eax
lea     edi, [ebp+copy_output_data]
rep movsd
mov     [ebp+ms_exc.disabled], ebx             ; = 1
push    0
push    'EdsU'
mov     ebx, [ebp+copy_output_data.copy1_size] ; we control this
mov     eax, [ebp+copy_output_data.copy2_size] ; and this
lea     eax, [eax+ebx+24h]                     ; integer overflow right here
push    eax                                    ; NumberOfBytes
call    _HeavyAllocPool@12
mov     [ebp+allocated_buffer], eax
test    eax, eax
jz      loc_BF92C6B6

mov     ecx, [ebp+var_2C]
mov     [ecx], eax                             ; save allocation addr
push    9
pop     ecx
lea     esi, [ebp+copy_output_data]
mov     edi, eax
rep movsd                                      ; Copy output data
test    ebx, ebx
jz      short loc_BF92C65A

mov     ecx, ebx
mov     esi, [ebp+copy_output_data.copy1_ptr]
lea     edi, [eax+24h]
mov     edx, ecx
shr     ecx, 2
rep movsd                                      ; copy copy1_ptr (with copy1_size)
mov     ecx, edx
and     ecx, 3
rep movsb

loc_BF92C65A:
mov     ecx, [ebp+copy_output_data.copy2_size]
test    ecx, ecx
jz      short loc_BF92C676
mov     esi, [ebp+copy_output_data.copy2_ptr]
lea     edi, [ebx+eax+24h]
mov     edx, ecx
shr     ecx, 2
rep movsd movsd                                ; copy copy2_ptr (with copy2_size)
mov     ecx, edx
and     ecx, 3
rep movsb

The DDE copydata buffer contains two different buffers with their respective sizes. These sizes are used to calculate the size of a buffer that is allocated. However, appropriate checks are not made to detect if an integer overflow occurs. An integer overflow exists when an arithmetic operation is done between different integers that would go behind maximum integer value and then create a lower integer[7]. As such, the allocated buffer may be smaller than each buffer size which leads to a kernel pool overflow. The pool is the name used to designated the Windows kernel heap.

 

Pool overflow exploitation

The key to exploiting this issue is more about how to exploit a kernel pool overflow. Previous work has described the kernel pool system and exploitation[8] [9]. This paper will focus on the exploiting the vulnerability being described.

The kernel pool can be thought of as a heap. Memory is allocated by the ExAllocatePoolWithTag function and then freed using the ExFreePoolWithTag function. Depending of memory size, a header chunk precedes memory data. Exploiting a pool overflow involves replacing the next chunk header with a crafted version. This header is available though ntoskrnl module symbols as:

typedef struct _POOL_HEADER
{
    union
    {
        struct 
        {
            USHORT PreviousSize : 9;
            USHORT PoolIndex : 7;
            USHORT BlockSize : 9;
            USHORT PoolType : 7;
        }
        ULONG32 Ulong1;
    }
    union
    {                                                                               
        struct _EPROCESS* ProcessBilled;                                            
        ULONG PoolTag;                                                       
        struct
        {                                                                           
            USHORT AllocatorBackTraceIndex;                                   
            USHORT PoolTagHash;                                               
        }                                                                          
    }      
} POOL_HEADER, *POOL_HEADER; // sizeof(POOL_HEADER) == 8

Size fields are a multiple of 8 bytes as an allocated block will always be 8 byte aligned1. The PoolIndex field is not important for our overflow and can be set to 0. The PoolType field contains chunk state with multiple possible flags. The busy flag changes between operating system version but free chunk always got the PoolType field to zero.

During a pool overflow, the next chunk header is overwritten with malicious values. When the allocated block is freed, the ExFreePoolWithTag function will look at the next block type. If the next block is free it is coalesced by unlinking and merging it with current block. The LIST_ENTRY structure links blocks together and is adjacent to the POOL_HEADER structure if current chunk is free. The unlinking procedure is exactly the same as the behavior of the user-mode heap except that no safe unlinking check is done. This procedure is repeated for previous block. Many papers already explained unlinking exploitation which allows writing 4 bytes to a controlled address. However, this attack breaks a pool's internal linked list and exploitation must take this into consideration. As such, it is necessary to restore the pool's list integrity to prevent the system from crashing.

There are a number of different addresses that may be overwritten such as directly modifying code or overwriting the contents of a function pointer. In local kernel exploitation, the target address should be uncommonly unused by the kernel to prevent operating system instability. In his paper, Rubén Santamarta used a function pointer accessible though an exported kernel variable named HalDispatchTable[10]. This function pointer is used by KeQueryIntervalProfile which is called by the system call NtQueryIntervalProfile. Overwriting the function pointer at HalDispatchTable+4 does not break system behavior as this function is unsupported2 in default configuration. For our exploitation, this is the best choice as it is easy to launch and target.

The exploitation code for this this particular vulnerability should produce this fake chunk:

Fake next pool chunk header for Windows XP / 2003:

PreviousSize = (copy1_size + sizeof(POOL_HEADER)) / 8
PoolIndex    = 0
BlockSize    = (sizeof(POOL_HEADER) + 8) / 8
PoolType     = 0 // Free chunk

Flink = Execute address - 4    // in userland - call +4 address
Blink = HalDispatchTable + 4   // in kernelland

Modification for Windows 2000 support:

PreviousSize = (copy1_size + sizeof(POOL_HEADER)) / 16
BlockSize    = (sizeof(POOL_HEADER) + 15) / 16

The Flink field points on a user-mode address less 4 that will be called from the kernel address space once the Blink function pointer would be replaced. When called by the kernel, the user-mode address will execute at ring0 and is able to modify operating system behavior.

In this specific vulnerability, to avoid a crash and control copied data in target memory buffer, copy2_ptr should point to a NO_ACCESS memory page. When the copy occurs, an exception will be raised which will be caught by a try/except block in the function. For this exception, the allocated buffers are freed. Copied memory size would be controlled by copy1_size field and integer overflow will be done by copy2_size field. This configuration allows to overflow only the necessary part.

 

Delayed free pool overflow on Windows Vista

The allocation pool type in win32k on Windows Vista uses an undocumented DELAY_FREE flag. With this flag, the ExFreePoolWithTag function does not liberate a memory block but instead pushes it into a deferred free list. If the kernel needs more memory or the deferred free list is full it will pop an entry off the list and liberate it through normal means. This can cause problems because the actual free may not occur until many minutes later in a potentially different process context. Due to this problem, both Flink and Blink pointers must be in the kernel mode address space.

The HalDispatchTable overwrite technique can be reused to support this configuration. The KeQueryIntervalProfile function disassembly shows how the function pointer is used. This context is always the same across Windows versions.

mov     [ebp+var_C], eax
lea     eax, [ebp+arg_0]
push    eax
lea     eax, [ebp+var_C]
push    eax
push    0Ch
push    1
call    off_47503C       ; xHalQuerySystemInformation(x,x,x,x)

The first and the second arguments points into user-mode in the NULL page. This page can be allocated using the NtAllocateVirtualMemory function with an unaligned address in NULL page. The kernel function will realign this pointer on lower page and allocate this page3. In order to exploit this context, a stub of machine code must be found which returns on first argument and where next 4 bytes can be overwritten. This is the case of function epilogues as for wcslen function:

.text:00463B4C                 sub     eax, [ebp+arg_0]
.text:00463B4F                 sar     eax, 1
.text:00463B51                 dec     eax
.text:00463B52                 pop     ebp
.text:00463B53                 retn
.text:00463B54                 db 0CCh ; alignement padding
.text:00463B55                 db 0CCh
.text:00463B56                 db 0CCh
.text:00463B57                 db 0CCh
.text:00463B58                 db 0CCh

In this example, the 00463B51h address fits our needs. The pop instruction pass the return address and the retn instruction return in 1. The alert reader noticed that the selected address start at dec instruction. The unlinking procedure unlinks the next 4 bytes and the 00463B54h address has 5 padding bytes. Without this padding, overwriting unknown assembly could lead to a crash compromising the exploitation. The location of this target address changes depending on operating system version but this type of context can be found using pattern matching. On Windows Vista, the vulnerability exploitation loops calling the NtQueryIntervalProfile function until deferred free occurs and exploitation is successful. This loop is mandatory as pool internal structure must be corrected.

 

NtUserfnOUTSTRING kernel overwrite vulnerability

The NtUserfnOUTSTRING function is accessible through an internal table used by NtUserMessageCall exported function. Functions starting by "NtUserfn" can be called with SendMessage function exported by user32.dll module. For this function the WM_GETTEXT window message is necessary. Notice that in some cases a direct call is needed for successful exploitation. Verifications made by SendMessage function are trivial as it is used for different functions but should be considered. The MSDN website describes SendMessage utilization [3].

 

Evading ProbeForWrite function

The ProbeForWrite function verifies that an address range resides in the user-mode address space and is writable. If not, it will raise an exception that can be caught by a try / except code block. This function is used by a lot by drivers which deal with user-mode inputs. THe following is the start of the ProbeForWrite function assembly:

void __stdcall ProbeForWrite(PVOID Address, SIZE_T Length, ULONG Alignment)

mov     edi, edi
push    ebp
mov     ebp, esp
mov     eax, [ebp+Length]
test    eax, eax
jz      short loc_exit                 ; Length == 0

[...]

loc_exit:
pop     ebp
retn    0Ch

This short assembly dump underlines one way to evade ProbeForWrite function. If Length argument is zero, no verification is done on Address argument. It means that Microsoft considers that a zero length input do not require address to point in userland. Microsoft made a blog post on MS08-025[12] and why ProbeForWrite was not modified as expected. Microsoft compatibility concern is understandable but at least ProbeForWrite documentation should be updated for this case.

 

Vulnerability details

This vulnerability touches not only this function but a whole class of string management functions. Some functions make sure that length argument is not zero before its modification. Others do not even check the length argument. A proof of concept has been made on this vulnerability by Rubén Santamarta [11].

The NtUserfnOUTSTRING function vulnerability evades the ProbeForWrite function and overwrites 1 or 2 bytes of kernel memory. This function disassembly is below:

In NtUserfnOUTSTRING (WM_GETTEXT)

xor     ebx, ebx
inc     ebx
push    ebx                      ; Alignment = 1
and     eax, ecx                 ; eax = our size | ecx = 0x7FFFFFFF
push    eax                      ; If our size is 0x80000000 then
                                 ; Length is zero (avoid any check)
push    esi                      ; Our kernel address
call    ds:__imp__ProbeForWrite@12 
or      [ebp+var_4], 0FFFFFFFFh
mov     eax, [ebp+arg_14]
add     eax, 6
and     eax, 1Fh
push    [ebp+arg_10]
lea     ecx, [ebp+var_24]
push    ecx
push    [ebp+arg_8]
push    [ebp+arg_4]
push    [ebp+arg_0]
mov     ecx, _gpsi
call    dword ptr [ecx+eax*4+0Ch] ; Call appropriate sub function
mov     edi, eax
test    edi, edi
jz      loc_BF86A623              ; Something goes wrong

[...]

loc_BF86A623:
cmp     [ebp+arg_8], eax          ; Submit size was 0 ? (no)
jz      loc_BF86A6D1

[...]

push    [ebp+arg_18]              ; Wide or Multibyte mode
push    esi                       ; Our address
call    _NullTerminateString@8    ; <== 0 byte or short overwriting

In this function, a high size (0x80000000) can bypass ProbeForWrite function verification. After this verification, it calls a function based on win32k internal function pointer table. This function depends of the calling context. If it is in the same thread that submitted handle it will go directly on retrieval function, otherwise it can be cached by another function waiting for proprietary thread handling this request. This assembly sample highlights null byte overwriting if other functions failed. The null byte assures that a valid string is returned. This is not the only way to overwrite memory. By using an edit box, we could overwrite kernel memory with a custom string but the first way fit the need.

The exploitation is trivial and will not be detailed in this part. The first vulnerability already exposed a target address and the way to allocate the NULL page which were used to demonstrate this vulnerability.

 

LoadMenu handle table corruption

The win32k driver implements its own handle mechanism. This system shares a handle table between user-mode and kernel-mode. This table is mapped into the user mode address space as read-only and is modified in kernel mode address space. The MS07-017 bulletin found by Cesar Cerrudo during Month of Kernel Bugs (MOKB) [13] describes this table and how its modification could permit kernel code execution. This chapter addresses another vulnerability based on GDI handle shared table entry misuse.

 

Handle table

In the GUI architecture, an handle contains different information as an index in the shared handle table and the object type. The handle table is an array of the undocumented HANDLE_TABLE_ENTRY structure.

typedef struct _HANDLE_TABLE_ENTRY
{
    union 
    {
        PVOID pKernelObject;    
        ULONG NextFreeEntryIndex; // Used on free state
    };
    WORD  ProcessID; 
    WORD  nCount;
    WORD  nHandleUpper;
    BYTE  nType;
    BYTE  nFlag;
    PVOID pUserInfo;
} HANDLE_TABLE_ENTRY; // sizeof(HANDLE_TABLE_ENTRY) == 12

The nType field defines the table entry type. A free entry has the type zero and nFlag field which defines if it is destroyed or currently in destroy procedure. Normal handle verification routines check this value before getting pKernelInfo field which points to the associated kernel handle. In a free entry, the NextFreeEntryIndex field contains the next free entry index which is not a pointer but a simple unsigned long value.

The GUI object structure depends of object type but starts with the same structure which contains corresponding index in the shared handle table. This architecture lies on both elements. It switches between each table entry and kernel object depending of needs. A security issue exists if the handle table is not used as it should.

 

Vulnerability details

The vulnerability itself exists in win32k's xxxClientLoadMenu function which does not correctly validate a handle index. This function is called by the GetSystemMenu function and returns to user-mode using the KeUsermodeCallback function to retrieve a handle index. The following assembly shows how this value is used.

and     eax, 0FFFFh         ; eax is controlled
lea     eax, [eax+eax*2]    ; index * 3
mov     ecx, gSharedTable
mov     edi, [ecx+eax*4]    ; base + (index * 12)

This assembly sample uses an unchecked handle index and return pKernelObject field value of target entry. This pointer is returned by the xxxClientLoadMenu function. Proper verification are not made which permit deleted handle manipulation. A deleted handle has its NextFreeEntryIndex field set between 0x1 and 0x3FFF. The return value will be in first memory pages.

A system menu is linked to a window object. This window object is designated by an handle passed as an argument of the GetSystemMenu function. The spmenuSys field of the window object is set with the returned value of the xxxClientLoadMenu function. In this specific context, the spmenuSys value is hardly predictable inside the NULL page. During thread exit, the Window liberation will look at spmenuSys object and using its index in the shared table, toggle nFlag field state to destroyed and nType as free. In the case the NULL page is filled with zero value, it will destroy the first entry in the GDI shared handle table.

Exploitation is achieved by reusing vulnerable functions once the first entry has been destroyed. The GetSystemMenu function locks and unlocks the GDI shared handle table entry linked with kernel object returned by the xxxClientLoadMenu function. If the entry flag is destroyed the unlock function calls the type destroy callback. For the first entry, the flag has been set to destroyed. There is no callback for this type as it is not supposed to be unlocked. The unlock function will call zero which allows kernel code execution. This specific handle management architecture stay undocumented. The purpose of liberation callback inside the thread unlocking procedure is unusual.

Exploitation steps:

 

  • Allocate NULL address
  • Exploitation loop - second iteration trigger call zero:
    • Create a dialog
    • Set NULL page data to zero
    • Set a relative jmp at zero address
    • Create a menu graphic handle (or another type).
    • Destroy this menu handle
    • Call GetSystemMenu
    • Intercept user callback and return destroyed menu handle index (mask 0x3fff of the handle)
    • Exit this thread - set zero handle entry as free and destroyed.

There are multiple ways to exploit this vulnerability. The author truly believes that exploiting the locking procedure could be used on handle leak vulnerabilities as it was for this vulnerability. Indeed this vulnerability exploitation stays complex and unusual. This specific context made exploitation even more interesting.

 


GUI architecture protection

Create a safe software is a hard task that is definitely harder than find vulnerabilities. This work is even harder when it concerns old components which must respect compatibility rules. This article does not blame Microsoft for those vulnerabilities; it presents global issues on Windows architecture. In Windows Vista, Microsoft starts securing its operating system environment. The Windows Vista base code is definitely safer than it was. Some kernel components as the win32k driver are not safe enough and should be considered as a priority in local operating system security.

The GUI architecture does not respect security basics. Starting from scratch would certainly be a good option if it was possible. The global organization of this driver make security audits a mess. In the other hand, the Windows API shows it responses developer needs. There is a big abstraction layer between userland API and kernel functions. It can be use to rebuild the win32k driver without breaking compatibility. The API must follow user needs and be as easy as it can be. There is no reason that kernel driver exported function could not be changed in a secure way. It represents an enormous work which would be achieved only across operating system version. Nevertheless this is necessary. This modification could also increase performance by reducing unneeded context switching. There is no clever reason going in the kernel to ask userland a value that will be returned to userland. The user-mode callback system does not fit in a consistent GUI architecture.

Local exploitation techniques also highlight unsecure components as kernel pool and how overwriting some function pointers allow kernel code execution. In the past, the userland has been hardened as exploitation was too easy and third parties software could permit compromising a computer. The kernel performance is critical and adds verification routines and security measure could break this advantage. The solution should be in operating system evolution which does not restrict user experience. The hardware improvement does not forgive that modern operating system requires more resources than before.

Software development follows fastest way except when a specific result is expected. A company does not search the better way but something that cost less for almost the same result. Microsoft did not choose readiness by starting Security Development Lifecycle (SDL) [14] and should continue in this way.

 

Conclusion

The Windows kernel components have unequal security verification level. The main kernel module (ntoskrnl.exe) respects a standard verification dealing with userland data. The win32k driver does not follow the same rules which creates messy verification algorithms. This driver has an important interaction with userland by different mechanism from usual syscall to userland callback system. This architecture increase attack surface. The vulnerable parts do not concern usual vulnerabilities but also internal mechanism as GUI handle system.

Chapter 4 exposed vulnerabilities discovery and exploitation. Local exploitation has many different attack vectors. Nowadays, the exploitation is fast and sure, it works at any attempts. The kernel exploitation is possible though different techniques.

The win32k driver was not built with a secure design and now it becomes so huge, with so many compatibility restrictions, that every release just implements new features without changing anything else. Windows Vista introduces many modifications but most of them are just automatic integer overflow checks. It will solve many unknown issues but interaction between user-mode and kernel-mode is hardly predictable. Vulnerabilities are not always a matter of proper checks but also system interaction and custom context.

Implementing usual userland protections is not a good solution as kernel exploitation is larger than overflows. The win32k driver could change by using userland abstract layer in order to keep compatibility. This choice is not the easier as it asks more time and work. The patch evoked in this paper ameliorates a little bit win32k security as it goes deeper than reported vulnerabilities. However the Windows Vista version of the win32k driver was concerned by two vulnerabilities even if it was already more secure. Minor modifications do not solve security issues. The overall kernel security has been discussed on different paper about vulnerabilities but also rootkits. Everyone agree that operating systems must evolve. Windows Seven could introduce a new right architecture which secure critical component or just improve win32k driver security.

 

Bibliography

 

 

1
Microsoft Corporation. Microsoft Security Bulletin MS08-025

http://www.microsoft.com/technet/security/Bulletin/MS08-025.mspx

 

 

2
Microsoft Corporation. Windows User Interface.

http://msdn.microsoft.com/en-us/library/ms632587(VS.85).aspx

 

 

3
Microsoft Corporation. SendMessage function.

http://msdn.microsoft.com/en-us/library/ms644950.aspx

 

 

4
ivanlef0u. You failed (blog entry about KeUsermodeCallback function in French).

http://www.ivanlef0u.tuxfamily.org/?p=68

 

 

5
Microsoft Corporation. About Dynamic Data Exchange.

http://msdn.microsoft.com/en-us/library/ms648774.aspx

 

 

6
Microsoft Corporation. DDE Support in Internet Explorer Versions (still supported in ie7).

http://support.microsoft.com/kb/160957

 

 

7
Wikipedia. Integer overflow.

http://en.wikipedia.org/wiki/Integer_overflow

 

 

8
mxatone and ivanlef0u. Stealth hooking : Another way to subvert the Windows kernel.

http://www.phrack.org/issues.html?issue=65&id=4#article

 

 

9
Kostya Kortchinsky. Kernel pool exploitation (Syscan Hong Kong 2008).

http://www.syscan.org/hk/indexhk.html

 

 

10
Rubén Santamarta. Exploiting common flaws in drivers.

http://www.reversemode.com/index.php?option=com_remository&Itemid=2&func=fileinfo&id=51

 

 

11
Rubén Santamarta. Exploit for win32k!ntUserFnOUTSTRING (MS08-25/n).

http://www.reversemode.com/index.php?option=com_content&task=view&id=50&Itemid=1

 

 

12
Microsoft Corporation. MS08-025: Win32k vulnerabilities.

http://blogs.technet.com/swi/archive/2008/04/09/ms08-025-win32k-vulnerabilities.aspx

 

 

13
Cesar Cerrudo. Microsoft Windows kernel GDI local privilege escalation.

http://projects.info-pull.com/mokb/MOKB-06-11-2006.html

 

 

14
Microsoft Corporation. Steve Lipner and Michael Howard. The Trustworthy Computing Security Development Lifecycle

http://msdn.microsoft.com/en-us/library/ms995349.aspx
uniapp中使用canvas生成海报,并下载保存到手机相册 如下图,是在H5中,canvas生成后的效果,首先说明,H5不支持返回图片的临时路径,不能用于保存,APP端生成成功后,会返回图片的临时路径用来保存到相册。本地测试,用的网络图片,没有出现跨域的问题,APP端也正常显示。 完整代码:可以将整个代码复制到你得uniapp项目中直接运行。代码中做了详细的注册,关于API的用法,这里只标注的简单的提示,具体还是参考 官方链接uniapp-canvas吧 <template> <view class="demo"> <c. 阅读详情

相关推荐

【AI实战派】从零部署YOLOv8:香橙派AIpro边缘端目标检测全流程解析

本文详细解析了在香橙派AIpro边缘设备上从零部署YOLOv8目标检测模型的全流程。内容涵盖硬件开箱、环境搭建、模型转换(ONNX到昇腾OM格式)以及编写Python推理脚本,并提供了性能优化与实战技巧,帮助开发者高效实现实时边缘AI应用。

weixin_29183637的博客 331

linux内核调试指南

 linux内核调试指南一些前言作者前言知识从哪里来为什么撰写本文档为什么需要汇编级调试***第一部分:基础知识***总纲:内核世界的陷阱源码阅读的陷阱代码调试的陷阱原理理解的陷阱建立调试环境发行版的选择和安装安装交叉编译工具bin工具

Hunnad的专栏 2万+

BLDC方波控制 vs. FOC:技术选型指南与未来演进

本文深入对比BLDC电机方波控制与FOC技术的核心差异,分析两者在成本、性能和应用场景上的权衡。方波控制凭借低成本和易实现性,在电动工具和家电领域优势明显;而FOC则以平滑高效控制见长,适用于高端精密应用。文章为工程师提供了实用的技术选型指南,并展望了未来技术融合的发展趋势。

yhn4567890的博客 1172

Intel SSE / MMX2 / KNI documentation

转自 http://intel80386.com/simd/mmx2-doc.html Please note, this is a work-in-progress (ie BETA). Timings are of approximate throughput cycles using average from TSC, the latency and ranges are

理性的幻想 1301

RTOS任务调度原理:高优先级任务为何在低优先级任务执行时无法响应

实时操作系统(RTOS)的核心在于其确定性的任务调度机制,它通过优先级抢占确保关键任务及时执行。其基本原理是调度器仅在特定调度点(如任务主动阻塞、中断退出时)运行,比较就绪态任务的优先级并切换上下文。这一机制的技术价值在于为嵌入式系统提供可预测的响应时间,广泛应用于工业控制、汽车电子和物联网设备等对实时性要求高的场景。当低优先级任务执行**长时间非阻塞运算**且无中断发生时,调度器无法被触发,导致高优先级任务虽处于就绪态却无法获得CPU,造成系统响应延迟。理解**任务状态机**与调度点,是优化实时性、避免此

weixin_33508411的博客 287

STM32F103C8T6心电采集实战:从PCB设计到上位机开发的完整指南(附TI杯开源资料)

本文详细介绍了基于STM32F103C8T6的心电采集系统开发全流程,从PCB设计到上位机开发,特别适合大学生电子设计大赛参赛者参考。内容涵盖硬件设计精要、软件算法优化及上位机开发技巧,并附TI杯开源资料,帮助开发者低成本实现医疗级心电信号采集。

kafka6courier的博客 194

Some issues caused by memory parameter not well configed

本文记录了一些因为内存参数配置引起的一些issue. 在分析这些issue的时候, 因为不能肯定root cause是硬件引起且涉及分工, 所以有些语句表达的比较委婉, 仅从技术角度分析问题所在. 在得到内存参数配置反馈后, 增加了root cause分析部分内容. 可以做为分析native crash方法的一些总结.

zirconsdu的专栏 1万+

关于42步进电机驱动——基于STM32 HAL库实现

我找了好多资料,好多关于42步进电机的都是文档,这么文档都需要积分,需要积分就算了内容还非常差,浪费时间和电脑的内存。关于大部分是基于Arduino和51去驱动的 42步进电机驱动硬件如下: 首先你要了解步进电机的驱动原理,我也不啰嗦了,请看这边博客: 二相四线,四相五线,四相六线步进电机接线及驱动方法 , 看完这篇你就知道驱动和接线的原理了。 起初我也为如何接线烦恼,因为有些博客在讲述这些问题的时候没有讲的很透彻。 方法如下: ①用万用表调到欧姆档,用其中一个表笔搭在一个引脚上,依次测其它.

Jodan132的博客 1万+

Linux 内核启动过程--head.S(arch/xxx/kernel下的)

由上篇的分析可以知道,uImage是zImage加上64字节的头信息得到的,而zImage又是compressed下的vmlinux经过objcopy得到的,compressed下的vmlinux是由vmlinux.lds、 head.S 和 piggy.gzip.S misc.c编译而成的,其实就是在piggy.gzip中添加了解压代码。piggy.gzip是Image经过gzip -n -f -

linchuanzhi_886的专栏 3409

QtWebApp的使用【在Qt中搭建HTTP服务器】(一)

用Qt和QtWebApp能够实现在C++中开发HTTP Web服务器应用程序。 首先,需要先安装Qt的软件开发工具包。点此下载 其次,下载最新的QtWebApp库。点此下载 下载并解压缩QtWebApp ZIP文件。你将会看到: 如何使用QtWebApp 1)将QtWebApp.zip文件解压到文件夹中,并创建一个名为“ MyFirstWebApp”的新Qt控制台项目。然后,建立如下文件结构: 2)将以下行添加到MyFirstWebApp项目的项目文件中: QT += network include(

饭的笔记本 2万+

基于STM32F4通用定时器实现Livox激光雷达PPS硬件同步的工程实践

本文详细介绍了基于STM32F4通用定时器实现Livox激光雷达PPS硬件同步的工程实践。通过分析PPS信号的时序要求,对比不同定时器的特性,并提供完整的代码实现与调试技巧,帮助开发者解决多传感器数据融合中的时间同步问题。实测表明,该方案在车载环境中稳定运行超过2000小时。

weixin_30540691的博客 326

c语言只有编译没有汇编,keil中查看C语言对应汇编语言问题

在keil上运行一段GPIO的程序,查看C语言及对应的汇编程序如下:0x0800028A 2120 MOVS r1,#0x200x0800028C 4811 LDR r0,[pc,#68]; @0x080002D40x0800028E F000F935BL.W GPIO_ResetBits (0x080004FC)17: ...

weixin_33695631的博客 2047

YOLOv9原创自研:卷积魔改 | 一种新颖的风车形卷积(PConv)融合注意力机制,加强微弱小目标检测能力

为了进一步提升小目标检测能力,风车形卷积(PConv)融合注意力机制进一步提升小目标检测能力

①答疑群聊服务;②YOLO大模型知识问答系统;③计算机视觉论文生成智能体; 364

assembly8_10

实验报告 实验内容  运行并验证Blum’s Book: Sample programs in Chapter 08, 10 实验环境  Ubuntu 20.04.2.0(64位) 实验过程 一. Sample programs in Chapter 08 (一)addtest1.s  命令行: $ as -gstabs --32 -o addtest1.o addtest1.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o addtest1

zwx54088的博客 938

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

转载_linux内核分析(某位大牛的文章)

启动     当PC启动时,Intel系列的CPU首先进入的是实模式,并开始执行位于地址0xFFFF0处的代码,也就是ROM-BIOS起始位置的代码。BIOS先进行一系列的系统自检,然后初始化位于地址0的中断向量表。最后BIOS将启动盘的第一个扇区装入到0x7C00,并开始执行此处的代码.这就是对内核初始化过程的一个最简单的描述。     最初,Linux核心的最开始部分是用8086汇

williamwanglei的专栏 2万+

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

关于STM32中CAN1_RX0_IRQn和CAN1_RX1_IRQn的使用

关于STM32中CAN1_RX0_IRQn和CAN1_RX1_IRQn的使用  2012-12-04 16:51:29|  分类: STM32 |  标签:can  |举报|字号 订阅     “如果你用的CAN引脚是PA11和PA12,接收中断用CAN1_RX0_IRQn。如果CAN引脚用的是PB8和PB9,也就是用重定义的引脚

niepangu的专栏 1万+

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

汇编语言笔记(全)(长文警告)

汇编语言 最近系统的学了下汇编语言,下面是学习笔记,用的书是清华大学出版社出版的汇编语言第三版,作者王爽(最经典的那版)。 汇编语言 基础知识 汇编语言指令组成 CPU与外部器件交互需要 总线 小结 接口卡 存储器 内存地址空间 寄存器 内部总线 通用寄存器 字 简单的汇编指令 CPU位结构 CPU表示物理地址 段寄存器与指令指针寄存器 修改CS,IP 小结 实验 寄...

Breeze的博客 3万+

arm linux kernel 从入口到start_kernel 的代码分析

转载自:http://bbs.chinaunix.net/thread-2039668-1-1.html 本文针对arm linux, 从kernel的第一条指令开始分析,一直分析到进入start_kernel()函数. 我们当前以linux-2.6.19内核版本作为范例来分析,本文中所有的代码,前面都会加上行号以便于和源码进行对照, 例: 在文件init/main.c中: 00478:

xiaohuoxiaoer的博客 1543

Hying's PeLock v0.7x 外壳完全分析篇

    Hyings PeLock v0.7x 外壳完全分析篇前言:以前版分析的一个版本,通过对旧版的了解可以很快的掌握新版的变化的,新版刚看完,谢谢hnhuqiong的帮助,脚本太多了,我自己看不下去:-(,我还是选择了自己的方法,走了一程。走了后感觉新版变化也不是很大,虽然多乱序IAT和特殊代码加密转换了一种比较方式,不过去乱序iat感觉就像鸡肋,特殊代码处理虽然有一定难度,但很可惜,花指令

loveboom's Reverse Enginering 4264

[crackme]018-crackme_0006

mul ecx 在累乘之后,高位存放在 edx,低位在eax,如果高位 edx 有值,会将edx 加到eax的低位上,所以输入的用户名累乘相加只要在4字节以内就没问题,如果溢出 4 字节就是另外的情况。这段程序在获取磁盘的序列号,然后进行了逻辑左移,循环左移的操作。在 0x004011D0 下个断(程序能运行起来,输入信息后check后断下)随后使用系统 API GetDlgItemTextA 获取输入的用户名,直接使用的是磁盘序列号。

qq_42363151的博客 375

arm linux iw reg set 00,ARM LINUX内核初始化分析

转载一. 启动条件通常从系统上电到执行到linux kenel这部分的任务是由boot loader来完成.关于boot loader的内容,本文就不做过多介绍.这里只讨论进入到linux kernel的时候的一些限制条件,这一般是boot loader在最后跳转到kernel之前要完成的:1. CPU必须处于SVC(supervisor)模式,并且IRQ和FIQ中断都是禁止的;2. MMU(内存...

weixin_30651409的博客 708

组合语言之艺术5

 第四章   程式设计    学习任何一种艺术,只能从了解观念及熟练地运用工具、技巧上下功夫。至于个人的成就及表现,有待于不断地自我要求和探索,不是仅仅通过学习就可以轻易获得的。    组合程式的写作亦然,虽说尚有时、空效率的客观标准,但是一个功能复杂、应用广泛的程式,其价值的全面评估却非如此单纯。要言之,目前这种组合程式艺术仅在启蒙阶段,尚无成规可循,必须到大众都认识到其价值时,才会

dot99的专栏 2460

Linux系统启动过程分析

操作系统的启动过程,实际上是控制权移交的过程。Linux 系统启动包含四个主要的阶段:BIOS initialization, boot loader, kernel initialization,and init startup.见下图:   阶段一、BIOS initialization(BIOS初始化),主要功能如下: 1.    Peripherals detected (外围设

sunny_unix 1561

Xcode高级调试技巧(1)

在苹果的官方文档中列出了我们在调试中能用到的一些命令,我们在这重点讲一些常用的命令 一、基本操作 1.1.视图层次 打印视图层次 po [self.contentView recursiveDescription] 1.2.改变某个取值 int a = 1; //Console expr a=2 NSLog(@"实际值: %d", a); 1.3.call 改

fishmai的专栏 1万+

汇编命令解释:MOV AX,89H 和 INC AX

立即数是指在指令中直接给出的操作数,通常紧跟在操作码之后。它可以是一个8位、16位或32位的常数,表示在内存地址中立即寻址方式给出的数据。在汇编语言中,立即数不能作为指令中的第一操作数(目的操作数),通常用于对通用寄存器或内存单元赋初值。例如,在指令“MOV AX, 89H”中,89H就是立即数,被加载到AX寄存器中。MOV AX,89H 和 INC AX 是两条汇编指令,用于在。将AX寄存器中的值加1,即增加到8AH(即138)将立即数89H(即137)加载到AX寄存器中。INC AX:这条指令。

赖格宝的博客 1414

linux下machine_desc结构体中的phys_io与io_pg_offst 的作用及使用方法

*************************************************************************************************************************** 作者:EasyWave

嵌入式与人工智能天地 2938
上一篇: MS09-001漏洞基本分析
下一篇: CPU级RootKit,目前无药可医
iiprogram
博客等级 码龄25年 1068粉丝 1515原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值