Update: I recorded a video on this! OPS108: Windows authentication internals in a hybrid world (syfuhs.net)

Have you ever wondered what happens behind the scenes when you type your password into the Windows logon screen and hit enter? I'm waiting for a build to complete, so I'm gonna tell you. pic.twitter.com/62dvNKWeGv

— Steve Syfuhs (@SteveSyfuhs) August 24, 2020

Twitter warning: Like all good things this is mostly correct, with a few details fuzzier than others for reasons: a) details are hard on twitter; b) details are fudged for greater clarity; c) maybe I'm just dumb.

First things first. Windows needs to know how it'll accept your creds. It uses Credential Providers for this. They are the thing that ferries your human-entered credentials into LSA. All they do is take keystrokes and convert them to data structures LSA understands. For a bit of a detour into building Credential Providers see Creating Custom Windows Credential Providers in .NET.

A Windows Credential Provider input field

Windows has a list of maybe 2 dozen credential providers to do various tasks, like take your password, handle your smart card, scan your fingerprint, etc. Windows knows what credentials are supported on this machine, so it enumerates them and shows them.

Okay, so you've selected your credential provider and entered your password. Now what? Well, the credential provider takes that password and converts it into a data structure and makes an RPC call to LSA.

Now we're in LSA, and LSA has a list of these things called authentication packages. There's only a handful of them. Maybe you've heard of them: negotiate, Kerberos, msv1_0, etc. Well, LSA itself doesn't really know what any of these do so it just loops through all of them.

A list of credential providers built into Windows

LSA asks each package "hey can you deal with these credentials??". Some packages say "pfft, no." Others say "why yes, yes I can". One of these, msv1_0, chimes in and says it'll give it a try, and takes the password and runs it through a KDF (pbkdf2).

Here is, for instance, all the different functions a package can expose to LSA. The one in question here is LogonUser.

typedef struct _SECPKG_FUNCTION_TABLE {
  PLSA_AP_INITIALIZE_PACKAGE              InitializePackage;
  PLSA_AP_LOGON_USER                      LogonUser;
  PLSA_AP_CALL_PACKAGE                    CallPackage;
  PLSA_AP_LOGON_TERMINATED                LogonTerminated;
  PLSA_AP_CALL_PACKAGE_UNTRUSTED          CallPackageUntrusted;
  PLSA_AP_CALL_PACKAGE_PASSTHROUGH        CallPackagePassthrough;
  PLSA_AP_LOGON_USER_EX                   LogonUserEx;
  PLSA_AP_LOGON_USER_EX2                  LogonUserEx2;
  SpInitializeFn                          *Initialize;
  SpShutdownFn                            *Shutdown;
  SpGetInfoFn                             *GetInfo;
  SpAcceptCredentialsFn                   *AcceptCredentials;
  SpAcquireCredentialsHandleFn            *AcquireCredentialsHandle;
  SpQueryCredentialsAttributesFn          *QueryCredentialsAttributes;
  SpFreeCredentialsHandleFn               *FreeCredentialsHandle;
  SpSaveCredentialsFn                     *SaveCredentials;
  SpGetCredentialsFn                      *GetCredentials;
  SpDeleteCredentialsFn                   *DeleteCredentials;
  SpInitLsaModeContextFn                  *InitLsaModeContext;
  SpAcceptLsaModeContextFn                *AcceptLsaModeContext;
  SpDeleteContextFn                       *DeleteContext;
  SpApplyControlTokenFn                   *ApplyControlToken;
  SpGetUserInfoFn                         *GetUserInfo;
  SpGetExtendedInformationFn              *GetExtendedInformation;
  SpQueryContextAttributesFn              *QueryContextAttributes;
  SpAddCredentialsFn                      *AddCredentials;
  SpSetExtendedInformationFn              *SetExtendedInformation;
  SpSetContextAttributesFn                *SetContextAttributes;
  SpSetCredentialsAttributesFn            *SetCredentialsAttributes;
  SpChangeAccountPasswordFn               *ChangeAccountPassword;
  SpQueryMetaDataFn                       *QueryMetaData;
  SpExchangeMetaDataFn                    *ExchangeMetaData;
  SpGetCredUIContextFn                    *GetCredUIContext;
  SpUpdateCredentialsFn                   *UpdateCredentials;
  SpValidateTargetInfoFn                  *ValidateTargetInfo;
  LSA_AP_POST_LOGON_USER                  *PostLogonUser;
  SpGetRemoteCredGuardLogonBufferFn       *GetRemoteCredGuardLogonBuffer;
  SpGetRemoteCredGuardSupplementalCredsFn *GetRemoteCredGuardSupplementalCreds;
  SpGetTbalSupplementalCredsFn            *GetTbalSupplementalCreds;
  PLSA_AP_LOGON_USER_EX3                  LogonUserEx3;
  PLSA_AP_PRE_LOGON_USER_SURROGATE        PreLogonUserSurrogate;
  PLSA_AP_POST_LOGON_USER_SURROGATE       PostLogonUserSurrogate;

The blob from this is then compared to the verifier stored in the SAM cache and if it matches it tells LSA the user is logged on. This logon triggers your desktop to load. You've signed in and all you've done is touched the cache. What about Active Directory? What about kerberos??

Authenticating to Active Directory

Well, it turns out cached logon always happens first. You're at the desktop and LSA moves on to the next package: Kerberos. Kerberos uses the password to do an AS-REQ to Active Directory and gets a TGT. That TGT gets cached in-memory. It doesn't get stored in the SAM.

At this point you're at the desktop, you now have a TGT and can run around the network, but how exactly did the local computer know anything about you, like your name, your groups, any of that? The TGT is encrypted where the client can't ever read any contents, so what gives?

Identifying Yourself to the the Local Machine

Well, the next thing Kerberos does is uses the TGT to make a TGS-REQ to AD for the machine you just logged on to: host/yourdesktop.your.domain.com. Your machine is registered as a computer object in AD and has a password. The ticket for this machine is encrypted to that password.

The contents of an AS-REQ message


Well LSA knows that password so it decrypts the ticket! Within that ticket is your PAC -- Privilege Attribute Certificate -- yeah, silly name. It contains all your user details including your display name and group memberships.

LSA takes that information and merges it into your logon token. Now you're logged in. LSA buttons everything up, updates the cache with any additional details it needs, and you're on your way.

BUT how can you access all your local files and stuff if you don't get your PAC until after you've hit the desktop? Well, it turns out some details get cached with your password verifier in the SAM and they're used until AD can tell you otherwise.

That's why you don't need line of sight to a DC to access your stuff while out on the road for instance.

The contents of a TGS-REQ message

Okay, but what happens when you've never logged onto this machine before or done something silly like disabled cache logon. Well, it skips the cache check and waits for Kerberos to complete. Then it continues on as normal from here. <See section: Identifying Yourself to the the Local Machine>

What if your password changes or your account has been disabled? Nothing changes. You get to the desktop, and then the AS-REQ fails. Windows recognizes the error if disabled starts flagging the account so the cache will fail the next time around.

If it's just a blip or your password requires changing you'll get the "Windows requires your credentials" notification that asks you to lock and unlock. That way it can kick off the flow again. But what about that notification "your password is going to expire in 5 days"?

...the notification that everyone, myself included, ignores. Well, your PAC includes this information and once Windows reads the PAC and recognizes its nearing <5 days it'll prompt. Incidentally this is why it takes a moment after logon to see the prompt.

Well, one of the reasons.

On Windows Hello

Okay, great, I'm not using a password. I'm using Windows Hello. How does that work? Well, you do the cred prov thing and you pick your face. The cred prov invokes the magic sauce that scans your face and gets a template. It looks up your Windows Hello keys on disk and...

Asks the TPM to decrypt them using the template plus additional information as the key and entropy. The key is then converted into one of those structs and is sent off to LSA and LSA begins its dance again.

LSA loops through everything and hits the cache verifier, does it's thing, and is now at the desktop. From there it invokes our friend Kerberos, but this time does PKINIT, and away we go. <See section: Authenticating to Active Directory>

On Kerberos PKINIT

Maybe lets dig into that a bit: PKINIT. It's almost a four letter word. How do you make a symmetric crypto protocol work with asymmetric keys? Well, first the client generates a Diffie-Hellman key pair and signs the public key using the client certificate private key.

The client fires that message (plug other goo) off to the KDC and the KDC checks the certificate to see if it matches anyone on file in the directory. Suppose it does, and it generates a TGT. The KDC then generates its own Diffie-Hellman key pair, and signs it with the KDC cert.

The KDC then grabs the client DH key, takes the KDC DH key, and derives a shared secret. The TGT is encrypted to that shared key, and then returned to the client. The client receives it, checks the KDC cert is one it trusts, and uses the KDC DH key to derive the same shared key.

The client decrypts the message, gets the TGT, and away we go, again.

On Credential Guard

Okay okay, what about Credential Guard? How does that fit into this madness? Well, we have to go back to when LSA is receiving those creds from the cred prov. LSA has a special channel (well, RPC, it's always some form of RPC) to the secure world LsaIso process.

Anyway, LSA fires the password off to Credential Guard, and when the various packages need to use the password they kindly ask Cred Guard to do whatever it is they needed the password for. In the case of Kerberos LSA asks Cred Guard to encrypt the AS-REQ PA timestamp.

And then LSA asks Cred Guard to decrypt the AS-REP response. Cred Guard then knows the TGT session key is super special so it protects it in such a way that only it can decrypt. Then whenever Kerberos needs the session key it asks Cred Guard.

This basically works like an oracle. Cred Guard returns an opaque blob to LSA. Later LSA asks Cred Guard to decrypt something so LSA hands it the opaque blob and the thing to decrypt. Cred Guard decrypts the opaque blob, then using the key in that blob, decrypts the thing.

It's all very abstract, but this is why you can't steal TGTs when using Cred Guard. The TGT itself is kinda worthless, what you need is the TGT plus the session key, otherwise you can't decrypt the KDC response.

And of course, we already did FIDO the other day.