Jektor – A Windows User-Mode Shellcode Execution Tool That Demonstrates Various Techniques That Malware Uses
This utility focuses on shellcode injection techniques to demonstrate methods that malware may use to execute shellcode on a victim system
- Dynamically resolves API functions to evade IAT inclusion
- Includes usage of undocumented NT Windows API functions
- Supports local shellcode execution via CreateThread
- Supports remote shellcode execution via CreateRemoteThread
- Supports local shellcode injection via QueueUserAPC
- Supports local shellcode injection via EnumTimeFormatsEx
- Supports local shellcode injection via CreateFiber
Pre-pending a set of NOPs to a Msfvenom XOR encrypted shellcode payload while using dynamic function address resolutions seems to bypass Windows Defender.
IAT Import Evasion
Jektor makes use of dynamic function address resolutions using LoadLibrary and GetProcessAddress to make static analysis more difficult.
Important functions such as VirtualAlloc are not directly called which makes debugging and dumping the shellcode through breakpoints more difficult.
Local shellcode execution via CreateThread
On Windows when you want to create a new thread for the current process you can call the CreateThread function, this is the most basic technique for executing malicious code or shellcode within a process. You can simply allocate a region of memory for your shellcode, move your shellcode into the allocated region, and then call CreateThread with a pointer to the address of the allocated region. When you call CreateThread you pass the lpStartAddress parameter which is a pointer to the application-defined function that will be executed by the newly created thread.
- Allocate a region of memory big enough for the shellcode using VirtualAlloc
- Move the globally defined shellcode buffer into the newly allocated memory region with memcpy/RtlCopyMemory
- Create a new thread that includes the base address of the allocated memory region with CreateThread
- Wait for the new thread to be created/executed with WaitForSingleObject to ensure the payload detonates
After the memory region for the shellcode payload is allocated as RWX and the payload is moved into it, you can easily discover this region of memory by looking for any region of memory in the process that is marked as RWX, then if you inspect it you can seen the shellcode payload was moved into it, highlighted below are the first five bytes of the shellcode payload that executes a calculator on the victim system.
Hunting for RWX regions of memory is a quick way to identify potentially malicious activity on your system.
Remote shellcode execution via CreateRemoteThread
Another technique to create threads for shellcode execution is to call the CreateRemoteThread function, this will allow you to create threads remotely in another process. But the catch is that you will also want to allocate and write the shellcode payload into the remote process as well, since you’ll create a thread remotely that executes the payloads address that’s allocated within that process. In order to allocate the payload remotely, you’ll need to use the VirtualAllocEx function, this function is different from VirtualAlloc in that it can allocate memory regions in remote processes. To do this, Jektor creates a new process with the CREATE_NO_WINDOW flag set using CreateProcessW, this is used to spawn a new hidden notepad process. One the new process is spawned it remotely allocated memory in it and then uses WriteProcessMemory to write the shellcode payload into the allocated memory region. After this it calls CreateRemoteThread to execute the shellcode payload.
- Spawn a new process using CreateProcessW with CREATE_NO_WINDOW set
- Open a HANDLE to the newly spawed process by PID with OpenProcess and dwProcessId from PROCESS_INFORMATION
- Allocate memory remotely in the spawned process for the shellcode with VirtualAllocEx
- Write the shellcode payload into the allocated memory region with WriteProcessMemory
- Detonate the remotely created shellcode payload with CreateRemoteThread and the HANDLE from OpenProcess
Local shellcode execution via EnumTimeFormatsEx
- Allocate memory locally for the shellcode payload with VirtualAlloc
- Move the shellcode payload into the newly allocated region with memcpy/RtlCopyMemory
- Detonate the shellcode by passing it as the lpTimeFmtEnumProcEx parameter for EnumTimeFormatsEx
Local shellcode execution via CreateFiber
- Get a HANDLE to the current thread using GetCurrentThread
- Convert the main thread to a Fiber using ConvertThreadToFiber
- Allocate memory for the shellcode payload with VirtualAlloc
- Copy the shellcode buffer into the newly allocated memory region with memcpy
- Create a new fiber with the base address of the allocated memory region as the lpStartAddress parameter for CreateFiber
- Detonate the shellcode by scheduling the fiber with SwitchToFiber
- Perform cleanup by deleting the created fiber with DeleteFiber
Local shellcode execution via QueueUserAPC
- Allocate memory for the shellcode buffer with VirtualAlloc
- Get a handle to the current process with GetCurrentProcess
- Write the shellcode payload into the newly allocated memory region with WriteProcessMemory
- Get a handle to the current thread with GetCurrentThread
- Queue a new APC routine pass the address of the allocated memory region as the pfnAPC parameter to QueueUserAPC
- Trigger the shellcode payload by calling the undocumented NtTestAlert function which clears the APC queue for the current thread
- Perform cleanup by closing the handles to the current thread and current process