Pointers and Handles -...
Transcript of Pointers and Handles -...
Pointers and Handles
A Story of Unchecked Assumptions in the Windows Kernel
Alex [email protected]
Black Hat USA ‘08
1
About Me• Former lead kernel developer of ReactOS
• Open source implementation of the Windows OS
• Wrote most of the kernel (except Mm and PnP)
• Teaching Windows internals class with David Solomon
• Co-authoring Windows Internals, 5th Edition
• Studying Software Engineering in Canada
2
Greets, Shouts & Respect• Woodmann’s RCE forums, OpenRCE for
being awesome community sites
• Omega_red on the RCE forums for starting all this
• Neill Clift, Jason Geffner, Sarah Blankinship and others, for testing, awesomeness and other help
• Every organizer, speaker, volunteer, and attendee at Blackhat, for making this a success
• Everyone who promotes & contributes to RE
About this Talk
• Two pairs of bugs
• Two are NULL-pointer dereferences
• Two are “special” local DoS bugs
• Second set related to a new class of local DoS attacks
• Requires understanding of several internal NT behaviors
4
Outline
• NULL Pointer Dereferences
• Windows API Design
• Windows GUI Subsystem
• The Assumption
• Exploiting
• Promoting from DoS to Ring Privilege Violation
5
Outline
• Protected Handle Close
• Windows Object Manager
• System Call PreviousMode Mechanism
• The Assumption
• Exploiting
• Developer Guidance and Q&A
6
Windows Simplified Design
7
Windows API ArchitectureWindows
Applications
Core API
Graphics (GDI) & User
API
NTDLL
NTOS Kernel
Console API
Drivers Win32 GUI Subsystem
WindowsSubsystem
High Level API
User-Mode Components• Applications (Notepad.exe)
• High Level Libraries (Msi.dll, Comctl32.dll, .NET)
• Low Level Libraries
• “base” -> Kernel32 & Parts of Advapi32.dll (Registry)
• GDI/User -> User32.dll & Gdi32.dll
• Subsystem Process (Csrss.exe)
• Native Library (Ntdll.dll)
Kernel-Mode Components
• Kernel (Ntoskrnl.exe)
• HAL (Hal.dll)
• Drivers (*.sys)
• GUI Subsystem (Win32k.sys)
• DirectX Kernel Driver (Dxgkernel.sys)
• Video Drivers (atikmdag.sys, nvlddkm.sys)
Behaviors and Code Paths
• Functionality “native” (built-in) to the kernel:
• Translated by Ntdll.dll
• Sent to kernel
• Functionality specialized to Windows API:
• Performed by, or through, Csrss.exe (ie: consoles)
• Sent to Win32k.sys (ie: video card display access)
Windows API ArchitectureWindows
Applications
Core API
Graphics (GDI) & User
API
NTDLL
NTOS Kernel
Console API
Drivers Win32 GUI Subsystem
WindowsSubsystem
Windows API ArchitectureWindows
Applications
Core API
Graphics (GDI) & User
API
NTDLL
NTOS Kernel
Console API
Drivers Win32 GUI Subsystem
WindowsSubsystem
Windows API ArchitectureWindows
Applications
Core API
Graphics (GDI) & User
API
NTDLL
NTOS Kernel
Console API
Drivers Win32 GUI Subsystem
WindowsSubsystem
Ntoskrnl.exe• Interrupts, Traps,
Booting
• Memory Management
• Thread Scheduling
• Process Management
• Synchronization, Queues
• Security & Auditing
• I/O, PnP & Power Management
NTOS Kernel
Process Manager
Scheduler
Registry Manager
Memory Manager
Security Manager
I/O Manager
Win32k.sys - User
• Window Stations
• Desktops
• Input & Message Queues
• Windows & Classes
• Menus & Hooks
• GUI Process & Thread Data
Win32k.sys
Message Passing
User Handles
Window Manager
Mouse & Keyboard
IME & Kbd Layouts
Monitors
Win32k.sys - GDI• Brushes, Pens, Cursors,
Surfaces, Lines, DCs, Paths, Regions, etc...
• ICM Color Matching
• API for Video/Print Drivers
• Floating Point Math Library
• DirectX, OpenGL Support
Win32k.sys
GDI Objects
Graphics Engine
DirectX
Displays
Fonts
Printing
NULL Pointer Dereferences
18
GUI Thread Promotion• Every Windows thread starts as a non-GUI
thread (12 kb stack)
• As soon as the first graphics system call (ID is >= 0x1000) is made, promoted to GUI thread (PsConvertToGuiThread)
• Stack grows to 60kb (KeGrowKernelStack)
• Win32k.sys thread callout is called (W32ThreadCallout)
• Win32k maintains GUI-related thread state until exit
xxxCreateThreadInfo
• Most internal Win32k.sys functions start by “xxx”
• Perverted developers? No, “yyy” and “zzz” functions also exist!
• Very different mindset from strict, organized and clear kernel naming scheme
• xxxCreateThreadInfo initializes the Win32k state (stored in W32THREAD structure) for the new GUI thread
W32THREAD
W32THREAD
PQ (Input Queue)
PKL (Keyboard Layout)
PCTI (Shared Client Data)
PDESKTOP (Owner Desktop)
PSMS (SendMessage Queue)
PPI (Parent Process Info)
PMENUSTATE (Menu State)
PHOOK (Registered Hooks)
...and more!
Our Interest• xxxCreateThreadInfo can be called for threads
that CSRSS owns
• These threads are special
• CSRSS does not belong to a desktop like other threads -- it works across all desktops
• Can’t attach to their input queue
• CSRSS is a trusted, privileged process -- certain Win32k system calls can only be done from within CSRSS
CSRSS Privileged APIs• NtUserSetInformationThread (Process Thread
State)
• NtUserSetInformationProcess (Process GUI State)
• NtUserInitialize & NtUserProcessConnect (Win32k Init)
• NtUserConsoleControl (Console Support)
• NtGdiFullscreenControl (Full-Screen Support)
• xxxRemote* APIs (Remote Control)
• More...
CSRSS & Integrity Level• CSRSS overrides integrity level (can access
any window handle)
• Input queues owned by CSRSS are always Medium IL
• Console applications’ input queues are always accessible
• Allows low IL applications to send messages to high IL command prompt (Edgar Barbarosa, COSEINC, www.coseinc.com/Vista_UIPI.ppt)
CSRSS Security
• CSRSS is a system process
• Only administrators can access it
• Administrators could modify the thread/process state with other methods (such as loading a driver or using the debugger)
• Check is done by comparing the kernel process pointer (EPROCESS) of the current process, with the saved CSRSS pointer -- impossible to fake
Exhibit A
• NtUserGetThreadState:
• Code has ASSERT(CurrentW32Thread->Desktop != NULL);
• Then dereferences Desktop to get the IME Thread ID.
• The developer knew of a risk, and validated it on debug builds... but can this happen?
Exhibit B• NtUserGetDCEx(HWND Window, HRGN
ClipRegion, ULONG Flags):
• Backwards compatibility: if no window handle given, get the desktop’s window handle
• Code blindly dereferences Win32ThreadInfo->Desktop to get the Desktop->Window
• Uses Window PWND when calling _GetDCEx
• All threads are part of a desktop
• Except CSRSS-owned threads
•
Assumption
• “but Microsoft owns that code, and it never calls NtUserGetThreadState or NtUserGetDCEx”
DoS Exploit
• hCsrss = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwCsrssId);
• CreateRemoteThread(hCsrss, NULL, 0, NtUserGetThreadState, 15, 0, NULL);
• This creates a new thread in CSRSS which will immediately execute NtUserGetThreadState with the thread state class which causes the dereference
• Can we do better?
From DoS to Escalation
• In Windows, we can allocate memory at “NULL” with NtAllocateVirtualMemory(Handle, 0x00000001, PAGE_SIZE - 1, ...);
• This means we can control what the kernel reads and writes
• Exploitable if kernel de-references something from the poisoned NULL pointer, then reads/writes to that value or worse -- executes!
Controlling Win32k• NtUserGetThreadState only reads a value from
the Desktop pointer -- useless to us
• However, NtUserGetDCEx dereferences a “Window” from the Desktop pointer.
• We can pwn the PWND ;-)
• No obvious modification of the Window structure was found on Server 2003 and higher
• However, the PWND is cached into the DC object
Controlling the PWND• Cached PWND kept so that the owner of the
DC can be requested later
• GDI sometimes builds WNDOBJs based on the owner
• Scope and size of code that deals with this corrupted PWND needs analysis
• Possibility exists that code could be coerced into ring privilege escalation behavior
• But Admin->Kernel escalation is only an attack against DRM
System Calls
Previous Mode
• How does a kernel system call know if this is the kernel closing the handle, or an application?
• Every system call updates the previous mode
• If user-mode performed the call, it’s set to UserMode (1)
• If Kernel-mode performed the call, it’s set to KernelMode (0)
System Calls• User-mode applications cannot call kernel
functions -- must use system call functionality
• Previous mode always updated
• Kernel-mode code can choose to call kernel function directly if exported (or if Microsoft code within the kernel binary)
• Previous mode not updated
• If kernel-mode code performs system call, previous mode is updated
Previous Mode Bugs• Stale previous mode -- driver failures
• Here, driver gets called from a user application (PM = 1), then directly performs a call to a kernel function (PM = 1) with kernel-mode pointers.
• Call will fail because pointers will be refused -- kernel will believe this is an unprivileged app attacking the kernel.
• Trusted previous mode on untrusted data -- danger
Previous Mode is Kernel• Previous mode is 0, but the driver is still
passing along the raw user data
• Kernel implicitly trusts caller, does not probe parameters
• Leads to code execution and privilege escalation
• Or, kernel performs certain sanity checks on the premise this is a kernel mode operation
• Leads to DoS -- kernel panics for safety
Protected Handle Close
• The Windows kernel manages resources as objects
• Processes, Threads, Files, Keys, Tokens, Events, Mutexes, Semaphores, Timers, ALPC Ports. 30 total!
• Object manager provides private header on top of each object
• Owner unaware and should not free object on its own -- Object Manager does garbage collection (prevents freeing invalid pointer)
Object Manager
Windows Kernel Objects
OBJECT_HEADER
Namespace Information
Quota Information
Security Information
Debug Information
Flags
Reference Count
Object Type
Object Body
Handle Manager
• Can’t export object addresses to user mode applications
• Pointers are only valid to the kernel
• Can’t always export object addresses to kernel mode drivers
• Structure for the object may be opaque
• Kernel provides handles to the objects instead
Handle Features
• Handles can be inherited from the parent
• Must be inheritable
• In Vista, a subset of these can be specified
• Handles can be duplicated
• Must have duplicate rights
• Handles can be protected from close
Handle to Object Mapping
Object
Object
Object
Handle Table
Entry 1
Entry 2
Entry 3
Entry 4
Entry 5
Object
Object
Application
SetEvent(0x4)
Handle to Object Mapping
Object
Object
Object
Handle Table
Entry 1
Entry 2
Entry 3
Entry 4
Entry 5
Object
Object
Application
SetEvent(0x14)
Handle Security• Each object has a security descriptor with
ACLs
• Each time a handle is created, an access mask is given
• So a process can only open a resource if it has access to it
• Handles are per-process (one handle table per process)
• One process cannot typically touch the handles of another
Handle Isolation
• Handles are isolated, but can be duplicated if process grants PROCESS_DUP_HANDLE to you
• Handles created by drivers are part of the kernel’s handle table
• < Vista, only administrators can access
• >= Vista, kernel is protected process, no one can access
Protect From Close
• Set with SetHandleInformation or NtSetInformationObject
• Used for protecting against accidental close of critical handles
• No ACL check done -- anyone can deprotect the handle
• So mostly a debugging/reliability feature -- not security
What Happens at Close
• Logic handled by ObpCloseHandleEntry
• Normal cases: return STATUS_HANDLE_NOT_CLOSABLE
• Process being debugged or FLG_ENABLE_CLOSE_EXCEPTIONS global flag is set or handle has debug information: RaiseException(STATUS_HANDLE_NOT_CLOSABLE)
What if Kernel Closes It?
• You would expect the operation to succeed anyway -- kernel is “god”
• But... remember this is a debugging/reliability feature
• So we’d probably want to know if a critical handle in our driver was closed...
• So what happens? Crash!KeBugCheckEx(INVALID_KERNEL_HANDLE)
Assumption• The kernel will never normally close
protected handles
• Microsoft code makes sure not to do this
• Drivers don’t usually use this functionality
• 3rd-party code would crash on developer’s machines anyway, and they would fix the error
• Again, assumption of controlled environment
• Can it happen?
Closing a Handle• CloseHandle and NtClose when called from
user-mode will set PM = 1.
• Kernel will not crash if handle is protected
• ZwClose when called from kernel-mode will set PM = 0
• But this means it’s a driver bug
• Can’t set protected bit on driver handles
• Driver handles are part of system handle table
Assumption Checks Out?• Are all kernel handles in the system handle
table?
• No! Kernel-mode code must explicitly set OBJ_KERNEL_HANDLE
• Otherwise, handle becomes part of the current process
• Current process can protect the handle...
• ... and wait for the kernel-mode code to free it
Scenario
• Find a handle that we can control, and wait for the kernel to close it.
• Or better yet, have some sort of function that can coerce the kernel to close the handle immediately.
• But all handle closing is done with CloseHandle
• False! Window Stations and Desktops are actually managed by the Object Manager, even if they are Win32k objects
• Cannot normally use CloseHandle on a window station or desktop handle
• Win32k blocks CloseHandle calls with the OkayToClose mechanism
• Provides CloseWindowStation and CloseDesktop APIs (NtUserCloseWindowStation/Desktop)
• NtUserCloseWindowStation is a simple wrapper around...
•
CloseWindowStation
• ZwClose!
• So all we have to do is
• Create a window station with CreateWindowStation
• Protect the handle with SetHandleInformation
• Close it with CloseWindowStation
• Bug was caught in Vista SP1 / Server 2008 timeframe
• Probably due to SDL -- obvious bug
Exploit
Exploit, Take Two
• There exists a second method to expose it, however
• Protect the current window station handle
• Create a new window station
• Switch to it with SetProcessWindowStation
• Why does this crash?
• When a console application starts, the Console API initializes and calls CSRSS, which calls NtUserConsoleControl with the ConsoleWindowStationProcess (6) type
• This calls PsSetProcessWin32WindowStation to cache the handle (used for global atom table)
• When switching to a new window station, xxxSetProcessWindowStation will check if the cached copy is stale and...
•
Window Station Caching
• You guessed it -- ZwClose on the cached handle
Developer Guidance
NULL Pointer Dereferences• Never blindly dereference a pointer
• Make sure nobody can call a function to dereference it after it has been deleted and/or freed
• Make sure nobody can call a function to dereference it before it has been initialized
• ASSERT is your friend, but isn’t perfect
• Do not write your code by basing your entire security on the caller’s context -- remote thread injection exists
System Calls• Never call Nt* system calls directly from a
driver (most are not exported anyway)
• Stale previous mode can lead to weird failures later
• Can also lead to security issues:
• User calls with buffer 0xF00
• You perform some unrelated operation with Zw*
• You now process 0xF00 with Nt*
System Calls
• Always call Zw*
• This makes sure the previous mode is never stale
• But never call with user-accessible or user-owned data or parameters
• Always probe and capture
Handles• Always create kernel handles with
OBJ_KERNEL_HANDLE
• Unless you want to share the handle with a trusted application
• Make sure the application and driver are secured against non privileged access
• Do not perform operations based on the data referenced by the handle
• Do not close the handle with a kernel mode Previous Mode
Thank You!
• Slides and PoC will be at http://www.alex-ionescu.com
• Email me at [email protected] for
• Rants
• Raves
• Flames
• Questions and Comments
Q & A