Modifying Certipy to Evade Microsoft Defender for Identity PKINIT Detection
Background#
Exploiting Active Directory Certificate Services (AD CS) was still one of the most common pathways to escalate privileges within an Active Directory environment in 2024. In an attempt to tackle this, Microsoft Defender for Identity (MDI) has improved their ability to detect suspicious certificate usage.
MDI Suspicious Certificate Usage Detection#
MDI detects suspicious certificate usage over the Kerberos protocol by fingerprinting common offensive tools, such as Certipy and Rubeus, which are used to exploit AD CS misconfigurations like ESC 1. When exploiting ESC 1, Certipy leverages PKINIT for Kerberos authentication to obtain a Ticket Granting Ticket (TGT). MDI analyses the AS-REQ
messages generated by Certipy, which differ from legitimate PKINIT pre-authentications generated by the Windows API. I highly recommend reading the following Synacktiv blog post written by Guillaume André to get a better understanding of the topic.
As mentioned in the Synacktiv blog:
“the detection is actually based on the encryption types advertised by the client, in the etype list in the KDC-REQ-BODY field.”
If we look at the PKINIT implementation used by Certipy, we can see that only two etypes are advertised:
AES256-CTS-HMAC-SHA1-96 (18)
AES128-CTS-HMAC-SHA1-96 (17)
def build_pkinit_as_req(
username: str, domain: str, key: rsa.RSAPrivateKey, cert: x509.Certificate
) -> Tuple[AS_REQ, DirtyDH]:
now = datetime.datetime.now(datetime.timezone.utc)
kdc_req_body_data = {}
kdc_req_body_data['kdc-options'] = KDCOptions({'forwardable','renewable','renewable-ok'})
kdc_req_body_data['cname'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': [username]})
kdc_req_body_data['realm'] = domain.upper()
kdc_req_body_data['sname'] = PrincipalName({'name-type': NAME_TYPE.SRV_INST.value, 'name-string': ['krbtgt', domain.upper()]})
kdc_req_body_data['till'] = (now + datetime.timedelta(days=1)).replace(microsecond=0)
kdc_req_body_data['rtime'] = (now + datetime.timedelta(days=1)).replace(microsecond=0)
kdc_req_body_data['nonce'] = getrandbits(31)
kdc_req_body_data['etype'] = [18,17]
kdc_req_body = KDC_REQ_BODY(kdc_req_body_data)
checksum = hash_digest(kdc_req_body.dump(), hashes.SHA1)
This is clear when we view the AS-REQ
packet in Wireshark after issuing the Auth
command in Certipy:
This is followed shortly by an incident and alert being created in MDI:
The encryption type is hardcoded to 18 (AES256-CTS-HMAC-SHA1-96) in the certipy auth
command. However, if the KDC supports and negotiates a different encryption type (e.g., AES128 or RC4-HMAC), Certipy should be able to handle it as it uses Impacket’s Kerberos implementation. This should at the very least support AES256-CTS-HMAC-SHA1-96 (18)
, AES128-CTS-HMAC-SHA1-96 (17)
, and ARCFOUR-HMAC-MD5 (23)
. I have not tested that by trying to change the negotiated encryption type. None the less, changing the hardcoded value from18
to cipher.enctype
should allow for at least two other etypes to be accepted.
new_cipher = _enctype_table[int(tgs["ticket"]["enc-part"]["etype"])]
plaintext = new_cipher.decrypt(session_key, 2, ciphertext)
special_key = Key(18, t_key)
data = plaintext
So let’s modify the list of etypes advertised and include a larger list, such as those used by kinit (as mentioned in the Synacktiv blog post).
Note: when testing in my lab setup, I’ve noticed that repeating the same request for the same user, often results in no more alerts being generated. So it’s best to change something, for example, the user to ensure that a new incident and alert are generated.
This time we don’t get any new incidents or alerts created in MDI 🎉
Wrap Up#
This small modification allows us to bypass the MDI Suspicious certificate usage over Kerberos protocol (PKINIT) high severity alert while still using Certipy. It has been a great help on red team engagements to stay undetected when abusing ESC 1 and as of 22nd of January 2025 still works. I tested this on MDI Sensor 2.240.18385.
This bypass is not foolproof as MDI could easily create additional detections based on Impacket’s Kerberos implementation. During red team engagements, we always setup a lab to mimic (as close to possible) our target environment and test if exploitation of a vulnerability or misconfiguration will generate some sort of security alert. I again reference the work of the Synacktiv team and advise you to check out their PowerShell script Invoke-RunAsWithCert.ps1, which can perform a PKINIT authentication using the Windows API and has a better chance of avoiding detections.
Big shout-out to my colleague Voy Ivancevic, who shared the Synacktiv blog post and worked together to test the concept. He continues to elevate our red team capabilities with every engagement.
As a bonus here is a collection of some other common Indicators of compromise (IoC) within Impacket that should be modified during red team engagements: https://n7wera.notion.site/Modifing-Impacket-to-avoid-detection-4df93e4bdbdc439988d79864774af569#14872a1f169a8089a2f6d218d356863c
Disclaimer#
The information in this article is provided for research and educational purposes only. Aura Information Security does not accept any liability in any form for any direct or indirect damages resulting from the use of or reliance on the information contained in this article.0