DonPAPI/lazagne/config/winstructure.py
Pierre-Alexandre Vandewoestyne f27f527410 beta release commit
2021-09-27 11:20:43 +02:00

716 lines
20 KiB
Python

# Vault Structure has been taken from mimikatz
from ctypes.wintypes import *
from ctypes import *
import sys
import os
try:
import _winreg as winreg
except ImportError:
import winreg
LPTSTR = LPSTR
LPCTSTR = LPSTR
PHANDLE = POINTER(HANDLE)
HANDLE = LPVOID
LPDWORD = POINTER(DWORD)
PVOID = c_void_p
INVALID_HANDLE_VALUE = c_void_p(-1).value
NTSTATUS = ULONG()
PWSTR = c_wchar_p
LPWSTR = c_wchar_p
PBYTE = POINTER(BYTE)
LPBYTE = POINTER(BYTE)
PSID = PVOID
LONG = c_long
WORD = c_uint16
# #############################- Constants ##############################
# Credential Manager
CRYPTPROTECT_UI_FORBIDDEN = 0x01
CRED_TYPE_GENERIC = 0x1
CRED_TYPE_DOMAIN_VISIBLE_PASSWORD = 0x4
# Regedit
HKEY_CURRENT_USER = -2147483647
HKEY_LOCAL_MACHINE = -2147483646
KEY_READ = 131097
KEY_ENUMERATE_SUB_KEYS = 8
KEY_QUERY_VALUE = 1
# custom key to read registry (not from msdn)
ACCESS_READ = KEY_READ | KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE
# Token manipulation
PROCESS_QUERY_INFORMATION = 0x0400
STANDARD_RIGHTS_REQUIRED = 0x000F0000
READ_CONTROL = 0x00020000
STANDARD_RIGHTS_READ = READ_CONTROL
TOKEN_ASSIGN_PRIMARY = 0x0001
TOKEN_DUPLICATE = 0x0002
TOKEN_IMPERSONATE = 0x0004
TOKEN_QUERY = 0x0008
TOKEN_QUERY_SOURCE = 0x0010
TOKEN_ADJUST_PRIVILEGES = 0x0020
TOKEN_ADJUST_GROUPS = 0x0040
TOKEN_ADJUST_DEFAULT = 0x0080
TOKEN_ADJUST_SESSIONID = 0x0100
TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY)
tokenprivs = (
TOKEN_QUERY | TOKEN_READ | TOKEN_IMPERSONATE | TOKEN_QUERY_SOURCE | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | (
131072 | 4))
TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY |
TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE |
TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT |
TOKEN_ADJUST_SESSIONID)
SE_DEBUG_PRIVILEGE = 20
# ############################# Structures ##############################
class CREDENTIAL_ATTRIBUTE(Structure):
_fields_ = [
('Keyword', LPSTR),
('Flags', DWORD),
('ValueSize', DWORD),
('Value', LPBYTE)
]
PCREDENTIAL_ATTRIBUTE = POINTER(CREDENTIAL_ATTRIBUTE)
class CREDENTIAL(Structure):
_fields_ = [
('Flags', DWORD),
('Type', DWORD),
('TargetName', LPSTR),
('Comment', LPSTR),
('LastWritten', FILETIME),
('CredentialBlobSize', DWORD),
# ('CredentialBlob', POINTER(BYTE)),
('CredentialBlob', POINTER(c_char)),
('Persist', DWORD),
('AttributeCount', DWORD),
('Attributes', PCREDENTIAL_ATTRIBUTE),
('TargetAlias', LPSTR),
('UserName', LPSTR)
]
PCREDENTIAL = POINTER(CREDENTIAL)
class DATA_BLOB(Structure):
_fields_ = [
('cbData', DWORD),
('pbData', POINTER(c_char))
]
class GUID(Structure):
_fields_ = [
("data1", DWORD),
("data2", WORD),
("data3", WORD),
("data4", BYTE * 6)
]
LPGUID = POINTER(GUID)
class VAULT_CREDENTIAL_ATTRIBUTEW(Structure):
_fields_ = [
('keyword', LPWSTR),
('flags', DWORD),
('badAlign', DWORD),
('valueSize', DWORD),
('value', LPBYTE),
]
PVAULT_CREDENTIAL_ATTRIBUTEW = POINTER(VAULT_CREDENTIAL_ATTRIBUTEW)
class VAULT_BYTE_BUFFER(Structure):
_fields_ = [
('length', DWORD),
('value', PBYTE),
]
class DATA(Structure):
_fields_ = [
# ('boolean', BOOL),
# ('short', SHORT),
# ('unsignedShort', WORD),
# ('int', LONG),
# ('unsignedInt', ULONG),
# ('double', DOUBLE),
('guid', GUID),
('string', LPWSTR),
('byteArray', VAULT_BYTE_BUFFER),
('protectedArray', VAULT_BYTE_BUFFER),
('attribute', PVAULT_CREDENTIAL_ATTRIBUTEW),
# ('Sid', PSID)
('sid', DWORD)
]
class Flag(Structure):
_fields_ = [
('0x00', DWORD),
('0x01', DWORD),
('0x02', DWORD),
('0x03', DWORD),
('0x04', DWORD),
('0x05', DWORD),
('0x06', DWORD),
('0x07', DWORD),
('0x08', DWORD),
('0x09', DWORD),
('0x0a', DWORD),
('0x0b', DWORD),
('0x0c', DWORD),
('0x0d', DWORD)
]
class VAULT_ITEM_DATA(Structure):
_fields_ = [
# ('schemaElementId', DWORD),
# ('unk0', DWORD),
# ('Type', DWORD),
# ('unk1', DWORD),
('data', DATA),
]
PVAULT_ITEM_DATA = POINTER(VAULT_ITEM_DATA)
# From https://github.com/gentilkiwi/mimikatz/blob/b008188f9fe5668b5dae80c210290c7efa872ffa/mimikatz/modules/kuhl_m_vault.h#L157
class VAULT_ITEM_WIN8(Structure):
_fields_ = [
('id', GUID),
('pName', PWSTR),
('pResource', PVAULT_ITEM_DATA),
('pUsername', PVAULT_ITEM_DATA),
('pPassword', PVAULT_ITEM_DATA),
('pPackageSid', PVAULT_ITEM_DATA),
('LastWritten', FILETIME),
('Flags', DWORD),
('cbProperties', DWORD),
('Properties', PVAULT_ITEM_DATA),
]
PVAULT_ITEM_WIN8 = POINTER(VAULT_ITEM_WIN8)
# From https://github.com/gentilkiwi/mimikatz/blob/b008188f9fe5668b5dae80c210290c7efa872ffa/mimikatz/modules/kuhl_m_vault.h#L145
class VAULT_ITEM_WIN7(Structure):
_fields_ = [
('id', GUID),
('pName', PWSTR),
('pResource', PVAULT_ITEM_DATA),
('pUsername', PVAULT_ITEM_DATA),
('pPassword', PVAULT_ITEM_DATA),
('LastWritten', FILETIME),
('Flags', DWORD),
('cbProperties', DWORD),
('Properties', PVAULT_ITEM_DATA),
]
PVAULT_ITEM_WIN7 = POINTER(VAULT_ITEM_WIN7)
class OSVERSIONINFOEXW(Structure):
_fields_ = [
('dwOSVersionInfoSize', c_ulong),
('dwMajorVersion', c_ulong),
('dwMinorVersion', c_ulong),
('dwBuildNumber', c_ulong),
('dwPlatformId', c_ulong),
('szCSDVersion', c_wchar * 128),
('wServicePackMajor', c_ushort),
('wServicePackMinor', c_ushort),
('wSuiteMask', c_ushort),
('wProductType', c_byte),
('wReserved', c_byte)
]
class CRYPTPROTECT_PROMPTSTRUCT(Structure):
_fields_ = [
('cbSize', DWORD),
('dwPromptFlags', DWORD),
('hwndApp', HWND),
('szPrompt', LPCWSTR),
]
PCRYPTPROTECT_PROMPTSTRUCT = POINTER(CRYPTPROTECT_PROMPTSTRUCT)
class LUID(Structure):
_fields_ = [
("LowPart", DWORD),
("HighPart", LONG),
]
PLUID = POINTER(LUID)
class SID_AND_ATTRIBUTES(Structure):
_fields_ = [
("Sid", PSID),
("Attributes", DWORD),
]
class TOKEN_USER(Structure):
_fields_ = [
("User", SID_AND_ATTRIBUTES), ]
class LUID_AND_ATTRIBUTES(Structure):
_fields_ = [
("Luid", LUID),
("Attributes", DWORD),
]
class TOKEN_PRIVILEGES(Structure):
_fields_ = [
("PrivilegeCount", DWORD),
("Privileges", LUID_AND_ATTRIBUTES),
]
PTOKEN_PRIVILEGES = POINTER(TOKEN_PRIVILEGES)
class SECURITY_ATTRIBUTES(Structure):
_fields_ = [
("nLength", DWORD),
("lpSecurityDescriptor", LPVOID),
("bInheritHandle", BOOL),
]
PSECURITY_ATTRIBUTES = POINTER(SECURITY_ATTRIBUTES)
class SID_NAME_USE(DWORD):
_sid_types = dict(enumerate('''
User Group Domain Alias WellKnownGroup DeletedAccount
Invalid Unknown Computer Label'''.split(), 1))
def __init__(self, value=None):
if value is not None:
if value not in self.sid_types:
raise ValueError('invalid SID type')
DWORD.__init__(value)
def __str__(self):
if self.value not in self._sid_types:
raise ValueError('invalid SID type')
return self._sid_types[self.value]
def __repr__(self):
return 'SID_NAME_USE(%s)' % self.value
PSID_NAME_USE = POINTER(SID_NAME_USE)
# ############################# Load dlls ##############################
advapi32 = WinDLL('advapi32', use_last_error=True)
crypt32 = WinDLL('crypt32', use_last_error=True)
kernel32 = WinDLL('kernel32', use_last_error=True)
psapi = WinDLL('psapi', use_last_error=True)
ntdll = WinDLL('ntdll', use_last_error=True)
# ############################# Functions ##############################
RevertToSelf = advapi32.RevertToSelf
RevertToSelf.restype = BOOL
RevertToSelf.argtypes = []
ImpersonateLoggedOnUser = advapi32.ImpersonateLoggedOnUser
ImpersonateLoggedOnUser.restype = BOOL
ImpersonateLoggedOnUser.argtypes = [HANDLE]
DuplicateTokenEx = advapi32.DuplicateTokenEx
DuplicateTokenEx.restype = BOOL
DuplicateTokenEx.argtypes = [HANDLE, DWORD, PSECURITY_ATTRIBUTES, DWORD, DWORD, POINTER(HANDLE)]
AdjustTokenPrivileges = advapi32.AdjustTokenPrivileges
AdjustTokenPrivileges.restype = BOOL
AdjustTokenPrivileges.argtypes = [HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, POINTER(DWORD)]
LookupPrivilegeValueA = advapi32.LookupPrivilegeValueA
LookupPrivilegeValueA.restype = BOOL
LookupPrivilegeValueA.argtypes = [LPCTSTR, LPCTSTR, PLUID]
ConvertSidToStringSid = advapi32.ConvertSidToStringSidW
ConvertSidToStringSid.restype = BOOL
ConvertSidToStringSid.argtypes = [DWORD, POINTER(LPWSTR)]
LookupAccountSid = advapi32.LookupAccountSidW
LookupAccountSid.restype = BOOL
LookupAccountSid.argtypes = [LPCWSTR, PSID, LPCWSTR, LPDWORD, LPCWSTR, LPDWORD, PSID_NAME_USE]
LocalAlloc = kernel32.LocalAlloc
LocalAlloc.restype = HANDLE
LocalAlloc.argtypes = [PSID, DWORD]
GetTokenInformation = advapi32.GetTokenInformation
GetTokenInformation.restype = BOOL
GetTokenInformation.argtypes = [HANDLE, DWORD, LPVOID, DWORD, POINTER(DWORD)]
OpenProcess = kernel32.OpenProcess
OpenProcess.restype = HANDLE
OpenProcess.argtypes = [DWORD, BOOL, DWORD]
OpenProcessToken = advapi32.OpenProcessToken
OpenProcessToken.restype = BOOL
OpenProcessToken.argtypes = [HANDLE, DWORD, POINTER(HANDLE)]
CloseHandle = kernel32.CloseHandle
CloseHandle.restype = BOOL
CloseHandle.argtypes = [HANDLE]
CredEnumerate = advapi32.CredEnumerateA
CredEnumerate.restype = BOOL
CredEnumerate.argtypes = [LPCTSTR, DWORD, POINTER(DWORD), POINTER(POINTER(PCREDENTIAL))]
CredFree = advapi32.CredFree
CredFree.restype = PVOID
CredFree.argtypes = [PVOID]
LocalFree = kernel32.LocalFree
LocalFree.restype = HANDLE
LocalFree.argtypes = [HANDLE]
CryptUnprotectData = crypt32.CryptUnprotectData
CryptUnprotectData.restype = BOOL
CryptUnprotectData.argtypes = [POINTER(DATA_BLOB), POINTER(LPWSTR), POINTER(DATA_BLOB), PVOID,
PCRYPTPROTECT_PROMPTSTRUCT, DWORD, POINTER(DATA_BLOB)]
# these functions do not exist on XP workstations
try:
prototype = WINFUNCTYPE(ULONG, DWORD, LPDWORD, POINTER(LPGUID))
vaultEnumerateVaults = prototype(("VaultEnumerateVaults", windll.vaultcli))
prototype = WINFUNCTYPE(ULONG, LPGUID, DWORD, HANDLE)
vaultOpenVault = prototype(("VaultOpenVault", windll.vaultcli))
prototype = WINFUNCTYPE(ULONG, HANDLE, DWORD, LPDWORD, POINTER(c_char_p))
vaultEnumerateItems = prototype(("VaultEnumerateItems", windll.vaultcli))
prototype = WINFUNCTYPE(ULONG, HANDLE, LPGUID, PVAULT_ITEM_DATA, PVAULT_ITEM_DATA, PVAULT_ITEM_DATA, HWND, DWORD,
POINTER(PVAULT_ITEM_WIN8))
vaultGetItem8 = prototype(("VaultGetItem", windll.vaultcli))
prototype = WINFUNCTYPE(ULONG, HANDLE, LPGUID, PVAULT_ITEM_DATA, PVAULT_ITEM_DATA, HWND, DWORD, POINTER(PVAULT_ITEM_WIN7))
vaultGetItem7 = prototype(("VaultGetItem", windll.vaultcli))
prototype = WINFUNCTYPE(ULONG, LPVOID)
vaultFree = prototype(("VaultFree", windll.vaultcli))
prototype = WINFUNCTYPE(ULONG, PHANDLE)
vaultCloseVault = prototype(("VaultCloseVault", windll.vaultcli))
def get_vault_objects_for_this_version_of_windows():
"""
@return: Tuple[
Type of vault item,
Pointer to type of vault item,
VaultGetItem function as Callable[[vault_handle, vault_item_prt, password_vault_item_ptr], int]
]
"""
os_version_float = float(get_os_version())
if os_version_float == 6.1:
# Windows 7
return (
VAULT_ITEM_WIN7,
PVAULT_ITEM_WIN7,
lambda hVault, pVaultItem, pPasswordVaultItem:
vaultGetItem7(hVault, byref(pVaultItem.id), pVaultItem.pResource, pVaultItem.pUsername,
None, 0, byref(pPasswordVaultItem))
)
elif os_version_float > 6.1:
# Later than Windows7
return (
VAULT_ITEM_WIN8,
PVAULT_ITEM_WIN8,
lambda hVault, pVaultItem, pPasswordVaultItem:
vaultGetItem8(hVault, byref(pVaultItem.id), pVaultItem.pResource, pVaultItem.pUsername,
pVaultItem.pPackageSid, # additional parameter compared to Windows 7
None, 0, byref(pPasswordVaultItem))
)
raise Exception("Vault is not supported for this version of OS")
except Exception:
pass
GetModuleFileNameEx = psapi.GetModuleFileNameExW
GetModuleFileNameEx.restype = DWORD
GetModuleFileNameEx.argtypes = [HANDLE, HMODULE, LPWSTR, DWORD]
# ############################# Custom functions ##############################
def EnumProcesses():
_EnumProcesses = psapi.EnumProcesses
_EnumProcesses.argtypes = [LPVOID, DWORD, LPDWORD]
_EnumProcesses.restype = bool
size = 0x1000
cbBytesReturned = DWORD()
unit = sizeof(DWORD)
dwOwnPid = os.getpid()
while 1:
ProcessIds = (DWORD * (size // unit))()
cbBytesReturned.value = size
_EnumProcesses(byref(ProcessIds), cbBytesReturned, byref(cbBytesReturned))
returned = cbBytesReturned.value
if returned < size:
break
size = size + 0x1000
ProcessIdList = list()
for ProcessId in ProcessIds:
if ProcessId is None:
break
if ProcessId == dwOwnPid:
continue
ProcessIdList.append(ProcessId)
return ProcessIdList
def LookupAccountSidW(lpSystemName, lpSid):
# From https://github.com/MarioVilas/winappdbg/blob/master/winappdbg/win32/advapi32.py
_LookupAccountSidW = advapi32.LookupAccountSidW
_LookupAccountSidW.argtypes = [LPSTR, PSID, LPWSTR, LPDWORD, LPWSTR, LPDWORD, LPDWORD]
_LookupAccountSidW.restype = BOOL
ERROR_INSUFFICIENT_BUFFER = 122
cchName = DWORD(0)
cchReferencedDomainName = DWORD(0)
peUse = DWORD(0)
success = _LookupAccountSidW(lpSystemName, lpSid, None, byref(cchName), None, byref(cchReferencedDomainName),
byref(peUse))
error = GetLastError()
if not success or error == ERROR_INSUFFICIENT_BUFFER:
lpName = create_unicode_buffer(u'', cchName.value + 1)
lpReferencedDomainName = create_unicode_buffer(u'', cchReferencedDomainName.value + 1)
success = _LookupAccountSidW(lpSystemName, lpSid, lpName, byref(cchName), lpReferencedDomainName,
byref(cchReferencedDomainName), byref(peUse))
if success:
return lpName.value, lpReferencedDomainName.value, peUse.value
return None, None, None
def QueryFullProcessImageNameW(hProcess, dwFlags=0):
_QueryFullProcessImageNameW = kernel32.QueryFullProcessImageNameW
_QueryFullProcessImageNameW.argtypes = [HANDLE, DWORD, LPWSTR, POINTER(DWORD)]
_QueryFullProcessImageNameW.restype = bool
ERROR_INSUFFICIENT_BUFFER = 122
dwSize = MAX_PATH
while 1:
lpdwSize = DWORD(dwSize)
lpExeName = create_unicode_buffer('', lpdwSize.value + 1)
success = _QueryFullProcessImageNameW(hProcess, dwFlags, lpExeName, byref(lpdwSize))
if success and 0 < lpdwSize.value < dwSize:
break
error = GetLastError()
if error != ERROR_INSUFFICIENT_BUFFER:
return False
dwSize = dwSize + 256
if dwSize > 0x1000:
# this prevents an infinite loop in Windows 2008 when the path has spaces,
# see http://msdn.microsoft.com/en-us/library/ms684919(VS.85).aspx#4
return False
return lpExeName.value
def RtlAdjustPrivilege(privilege_id):
"""
privilege_id: int
"""
_RtlAdjustPrivilege = ntdll.RtlAdjustPrivilege
_RtlAdjustPrivilege.argtypes = [ULONG, BOOL, BOOL, POINTER(BOOL)]
_RtlAdjustPrivilege.restype = LONG
Enable = True
CurrentThread = False # enable for whole process
Enabled = BOOL()
status = _RtlAdjustPrivilege(privilege_id, Enable, CurrentThread, byref(Enabled))
if status != 0:
return False
return True
def getData(blobOut):
cbData = blobOut.cbData
pbData = blobOut.pbData
buffer = create_string_buffer(cbData)
memmove(buffer, pbData, sizeof(buffer))
LocalFree(pbData);
return buffer.raw
def get_full_path_from_pid(pid):
if pid:
filename = create_unicode_buffer("", 256)
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, False, int(pid))
if not hProcess:
return False
size = GetModuleFileNameEx(hProcess, None, filename, 256)
CloseHandle(hProcess)
if size:
return filename.value
else:
return False
python_version = 2
if sys.version_info[0]:
python_version = sys.version_info[0]
def Win32CryptUnprotectData(cipherText, entropy=False, is_current_user=True, user_dpapi=False):
if python_version == 2:
cipherText = str(cipherText)
decrypted = None
if is_current_user:
bufferIn = c_buffer(cipherText, len(cipherText))
blobIn = DATA_BLOB(len(cipherText), bufferIn)
blobOut = DATA_BLOB()
if entropy:
bufferEntropy = c_buffer(entropy, len(entropy))
blobEntropy = DATA_BLOB(len(entropy), bufferEntropy)
if CryptUnprotectData(byref(blobIn), None, byref(blobEntropy), None, None, 0, byref(blobOut)):
decrypted = getData(blobOut)
else:
if CryptUnprotectData(byref(blobIn), None, None, None, None, 0, byref(blobOut)):
decrypted = getData(blobOut)
if not decrypted:
can_decrypt = True
if not (user_dpapi and user_dpapi.unlocked):
from lazagne.config.dpapi_structure import are_masterkeys_retrieved
can_decrypt = are_masterkeys_retrieved()
if can_decrypt:
try:
decrypted = user_dpapi.decrypt_encrypted_blob(cipherText)
except:
# The encrypted blob cannot be parsed - weird (could happen with chrome v80)
return None
if decrypted is False:
decrypted = None
else:
# raise ValueError('MasterKeys not found')
pass
if not decrypted:
if not user_dpapi:
# raise ValueError('DPApi unavailable')
pass
elif not user_dpapi.unlocked:
# raise ValueError('DPApi locked')
pass
return decrypted
def get_os_version():
"""
return major anr minor version
https://msdn.microsoft.com/en-us/library/windows/desktop/ms724832(v=vs.85).aspx
"""
os_version = OSVERSIONINFOEXW()
os_version.dwOSVersionInfoSize = sizeof(os_version)
retcode = windll.Ntdll.RtlGetVersion(byref(os_version))
if retcode != 0:
return False
return '%s.%s' % (str(os_version.dwMajorVersion.real), str(os_version.dwMinorVersion.real))
def isx64machine():
archi = os.environ.get("PROCESSOR_ARCHITEW6432", '')
if '64' in archi:
return True
archi = os.environ.get("PROCESSOR_ARCHITECTURE", '')
if '64' in archi:
return True
return False
def OpenKey(key, path, index=0, access=KEY_READ):
if isx64:
return winreg.OpenKey(key, path, index, access | winreg.KEY_WOW64_64KEY)
else:
return winreg.OpenKey(key, path, index, access)
isx64 = isx64machine()
def string_to_unicode(string):
if python_version == 2:
return unicode(string)
else:
return string # String on python 3 are already unicode
def chr_or_byte(integer):
if python_version == 2:
return chr(integer)
else:
return bytes([integer]) # Python 3
def int_or_bytes(integer):
if python_version == 2:
return integer
else:
return bytes([integer]) # Python 3
def char_to_int(string):
if python_version == 2 or isinstance(string, str):
return ord(string)
else:
return string # Python 3
def convert_to_byte(string):
if python_version == 2:
return string
else:
return string.encode() # Python 3