Analyzing Mimikatz

Source: gentilkiwi/mimikatz @ 306bc6b

How It Works

Mimikatz is a modular Windows security tool best known for credential extraction from LSASS memory, but its scope extends far beyond that — Kerberos ticket manipulation, DPAPI decryption, certificate theft, Active Directory remote attacks, and kernel-level interaction. Written in pure C targeting Visual Studio 2010+, it operates through a command-line REPL where users type module::command /option:value to invoke capabilities.

The tool is structured around a static module registry and a shared utility library called “kulluk” (a play on the French word “cul” and author Benjamin Delpy’s handle “gentilkiwi”). All 26 built-in modules register themselves at compile time via a mimikatz_modules[] array. Commands are dispatched by splitting input on ::, scanning the module table linearly, then scanning the command table within the matched module. There is no dynamic loading — every capability is compiled in.

The project also ships sister sub-projects: mimidrv (a kernel driver for memory reads and SSDT hooks), mimilib (a multi-purpose DLL with an SSP that logs credentials, a password filter, and DNS/DHCP/print hooks), and mimispool (a print spooler driver exploiting PrintNightmare, CVE-2021-34527).

Architecture

┌─────────────────────────────────────────────────┐
│                  mimikatz.exe                    │
│  wmain() → REPL → mimikatz_dispatchCommand()    │
│                                                   │
│  ┌─────────┐  ┌──────────┐  ┌───────────────┐   │
│  │ ! prefix │  │ * prefix │  │ no prefix     │   │
│  │ kernel   │  │ rpc      │  │ local module  │   │
│  └────┬─────┘  └────┬─────┘  └──────┬────────┘   │
│       │              │                │            │
│       ▼              ▼                ▼            │
│  ┌──────────────────────────────────────────┐    │
│  │        kulluk shared library             │    │
│  │  kull_m_memory | kull_m_patch | crypto   │    │
│  │  kull_m_string | kull_m_process | etc.   │    │
│  └──────────────────────────────────────────┘    │
│                     │                            │
│         ┌───────────┼───────────┐                │
│         ▼           ▼           ▼                │
│    LSASS      Kernel (drv)  Remote (RPC)         │
│    (process)  (mimidrv)    (MimiCom protocol)    │
└─────────────────────────────────────────────────┘

Key modules and their purpose:

ModulePurpose
sekurlsaLSASS memory parsing — extracts credentials from msv1_0, wdigest, tspkg, kerberos, cloudap, livessp, etc.
kerberosTicket manipulation — pass-the-ticket, golden tickets, list/purge/ask, ccache support
lsadumpSAM/LSA Secrets/CACHE parsing, DCSync, DCShadow, zerologon exploitation
cryptoCertificate and key extraction from CAPI/CNG stores
dpapiDPAPI master key decryption with sub-packages for Chrome, SSH, WLAN, SCCM, Citrix, Vault, etc.
rpcRemote control via custom MimiCom RPC protocol (Diffie-Hellman key exchange, RFC 2409)
kernelInteraction with mimidrv kernel driver for privileged memory reads
token, privilege, process, serviceWindows security object manipulation

The Spine

The entry point is mimikatz.c → wmain(). It iterates over command-line arguments, dispatching each through mimikatz_dispatchCommand(). If no arguments are provided, it enters an interactive REPL loop reading from stdin.

Command dispatch logic:

// Prefix routing
if(command[0] == L'!')
    mimikatz_doLocal(...);  // Actually still local; ! routes to kernel module
else if(command[0] == L'*')
    mimikatz_doRpc(...);    // Remote execution via RPC
else
    mimikatz_doLocal(...);  // Standard local module dispatch

The local dispatch splits on ::, linearly scans mimikatz_modules[] by short name, then linearly scans the module’s KUHL_M_C[] command table. Each command handler receives the argument string and uses kull_m_string_args_byName() to parse /name:value option pairs.

Three build targets exist: standalone EXE, DLL (via _WINDLL define), and PowerShell reflective DLL (via _POWERKATZ).

Key Patterns

Unified memory abstraction. The most important architectural decision in mimikatz is kull_m_memory. It defines a KULL_M_MEMORY_TYPE enum (Process, MiniDump, File, Kernel) and a KULL_M_MEMORY_HANDLE struct with function pointers for read/write/alloc/close operations. The same credential extraction code in sekurlsa works against a live LSASS process, a minidump file on disk, or memory read through the kernel driver — with zero code changes in the extraction logic.

Per-build binary patching. LSA key material locations change with every Windows build. The kull_m_patch system stores byte-pattern/patch pairs keyed by minimum build number (KULL_M_PATCH_GENERIC structs). Sekurlsa maintains massive tables mapping Windows build numbers (from XP build 2600 through Windows 10 2004 build 19041) to specific offsets and structures needed to find credentials in LSASS memory.

Module self-registration. Modules define themselves as KUHL_M structs and are collected into a static array at link time. Each module has optional init/clean callbacks, a short name, description, and a command table. Adding a new capability means adding a struct — no registration function calls needed.

Global mutable state. The codebase relies heavily on globals: cLsass (handle to LSASS process), g_hLSA (loaded advapi32), MIMIKATZ_PRIMARY_COMMAND_NAME. This works for a single-threaded CLI tool but precludes safe concurrent use.

Non-Obvious Details

The ANTI_MIMIKATZ structures

Sekurlsa includes a variant of its Windows 10 1607 structures labeled ANTI_MIMIKATZ — this directly counters Microsoft’s Credential Guard and other anti-credential-theft measures. The author tracks defensive changes and ships workarounds.

The patch system is the maintenance bottleneck. Every Windows feature update can shift internal LSASS structures, requiring new byte patterns in sekurlsa. This is why mimikatz sometimes “breaks” after Patch Tuesday — the patterns no longer match. The globals.h file contains an extensive Windows build number lookup table used throughout for version-specific branching.

RPC module rolls its own crypto. The remote control module implements its own Diffie-Hellman key exchange using the Second Oakley Group from RFC 2409, providing encrypted command channels even without Kerberos authentication. This is separate from the CNG/CAPI wrappers in kull_m_crypto.

Compile-time capability flags. LSASS_DECRYPT enables additional credential decryption routines. KERBEROS_TOOLS enables extended Kerberos functionality. These gates exist presumably for legal/compliance reasons — the default build ships with everything enabled.

The SSP in mimilib is devastatingly simple. kssp.c (the “kssp” SSP — another pun) is ~200 lines that intercepts every authentication call and writes credentials to kiwissp.log. Deployed as an SSP DLL, it captures all interactive logins on a machine with no additional tooling required.

Multiple build outputs from one codebase. The same source compiles to three targets: a standard EXE, a standard DLL, and a reflective DLL for PowerShell injection (_POWERKATZ). The reflective loading is done inline rather than through an external loader.

Assessment

Strengths. The unified memory abstraction is genuinely elegant — writing credential extraction once that works across live process, minidump, and kernel driver is a non-trivial design achievement. The per-build pattern matching is a pragmatic approach to the fundamentally undocumented problem of locating Windows internal structures. The module system is simple and effective for the tool’s purpose.

Concerns. Zero test infrastructure. Global mutable state throughout. Linear command lookup (irrelevant at this scale, but reflects the tool’s origins). Memory management is raw LocalAlloc/LocalFree with no RAII — though acceptable for C on Windows. The deep coupling to Windows internal structures means a single OS update can break multiple modules simultaneously, making maintenance a perpetual cat-and-mouse game.

Long-term viability

Microsoft has progressively locked down LSASS access (Credential Guard, RunAsPPL, encrypted LSASS in recent builds). The fundamental approach of reading LSASS memory directly is becoming increasingly restricted at the platform level.

Developer experience. Poor for external contributors. Minimal comments, no documentation of internal data structures, and undocumented argument parsing conventions (/name:value syntax is inferred, not explained). The code is readable if you know Windows internals well — inscrutable otherwise.

  • analyzing-sharphound — complementary AD security tool; BloodHound (via SharpHound) maps attack paths while mimikatz exploits them