In this post we will explore the use of direct system calls within Cobalt Strike Beacon Object Files (BOF). In detail, we will:
- Explain how direct system calls can be used in Cobalt Strike BOF to circumvent typical AV and EDR detections.
- Release InlineWhispers: a script to make working with direct system calls more easy in BOF code.
- Provide Proof-of-Concept BOF code which can be used to enable WDigest credential caching and circumvent Credential Guard by patching LSASS process memory.
Source code of the PoC can be found here:
Source code of InlineWhispers can be found here:
Beacon Object Files
Cobalt Strike recently introduced a new code execution concept named Beacon Object Files (abbreviated to BOF). This enables a Cobalt Strike operator to execute a small piece of compiled C code within a Beacon process.
What’s the benefit of this? Most importantly, we get rid of a concept named fork & run. Before Beacon Object Files, this concept was the default mechanism for running jobs in Cobalt Strike. This means that for execution of most post-exploitation functionality a sacrificial process was started (specified using the spawnto parameter) and subsequently the offensive capability was injected to that process as a reflective DLL. From an AV/EDR perspective, this has various traits that can be detected, such as process spawning, process injection and reflective DLL memory artifacts in a process. In many modern environments fork & run can easily turn into an OPSEC disaster. With Beacon Object Files we run compiled position independent code within the context of Beacon’s current process, which is much more stealthy.
Although the concept of BOF is a great step forward in avoiding AV/EDR for Cobalt Strike post-exploitation activity, we could still face the issue of AV/EDR products hooking API calls. In June 2019 we published a blogpost about Direct System Calls and showed an example how this can be used to bypass AV/EDR software. So far, we haven’t seen direct system calls being utilized within Beacon Object files, so we decided to write our own implementation and share our experiences in this blog post.
Direct syscalls and BOF practicalities
Many Red Teams will be familiar by now with the concept of using system calls to bypass API hooks and avoid AV/EDR detections.
In our previous system call blog we showed how we can utilize the Microsoft Assembler (MASM) within Visual Studio to include system calls within a C/C++ project. When we build a Visual Studio project that contains assembly code, it generates two object files using the assembler and C compiler and link all pieces together to form a single executable file.
To create a BOF file, we use a C compiler to produce a single object file. If we want to include assembly code within our BOF project, we need inline-assembly in order to generate a single object file. Unfortunately, inline-assembly is not supported in Visual Studio for x64 processors, so we need another C compiler which does supports inline-assembly for x64 processors.
Mingw-w64 and inline ASM
Mingw-w64 is the Windows version of the GCC compiler and can be used to create 32- and 64-bit Windows application. It runs on Windows, Linux or any other Unix based OS. Best of all, it supports inline-assembly even for x64 processors. So, now we need to understand how we can include assembly code within our BOF source code.
If we look at the man page of the Mingw-w64 or GCC compiler, we notice that it supports assembly using the -masm=dialect syntax:
Using the intel dialect, we are able to write assembly code via the same dialect like we did with the Microsoft Assembler in Visual Studio. To include inline-assembly within our code we can simply use the following assembler template syntax:
- The starting asm keyword is either asm or __asm__
- Instructions must be separated by a newline (literally \n).
More information about the GCC’s assembler syntax can be found in the following guide:
From __asm__ to BOF
Let’s put this together in the following example which shows a custom version of the NtCurrentTeb() routine using inline-assembly. This routine can be used to return a pointer to the Thread Environment Block (TEB) of the current thread, which can then be used to resolve a pointer to the ProcessEnvironmentBlock (PEB):
To make this assembly function available within our C code and to declare its name, return type and parameters we use the EXTERN_C keyword. This preprocessor macro specifies that the function is defined elsewhere, has C linkage and uses the C-language calling convention. This methodology can also be used to include assembly system call functions within our code. Just transform the system calls invocation written in assembly to the assembler template syntax, add the function definition using the EXTERN_C keyword and save this in a header file, which can be included within our project.
Although it is perfectly valid to have an implementation of a function in a header file, this is not best practise to do. However, compiling an object file using the -o option allows us to use one source file only, so in order not to bloat our main source file with assembly functions we put these in a separate header file.
To compile a BOF source code which includes inline assembly we use the following compiler syntax:
x86_64-w64-mingw32-gcc -o bof.o -c bof.c -masm=intel
To demonstrate the whole concept, we wrote a Proof-of-Concept code which includes direct system calls using inline-assembly and can be compiled to a Beacon Object File.
This code shows how we can enable WDigest credential caching by toggling the g_fParameter_UseLogonCredential global parameter to 1 within the Lsass process (wdigest.dll module). Furthermore, it can be used to circumvent Credential Guard (if enabled) by toggling the g_IsCredGuardEnabled variable to 0 within the Lsass process.
Both tricks enable us to make plaintext passwords visible again within LSASS, so they can be displayed using Mimikatz. With the UseLogonCredential patch applied you only need a user to lock and unlock his session for plaintext credentials to be available again.
This PoC is based on the following excellent blogposts by _xpn_ and N4kedTurtle from Team Hydra. These blogs are a must read and contain all necessary details:
Both blogposts include PoC code to patch LSASS, so from that viewpoint our code is nothing new. Our PoC builds on this work and only demonstrates how we can utilize direct system calls within a Beacon Object file to provide a more OPSEC safe way of interacting with the LSASS process and bypassing API hooks from Cobalt Strike.
The memory patches applied using this PoC are not reboot persistent, so after a reboot you must rerun the code. Furthermore, the memory offsets to the g_fParameter_UseLogonCredential and g_IsCredGuardEnabled global variables within the wdigest.dll module could change between Windows versions and revisions. We provided some offsets for different builds within the code, but these can change in future releases. You can add your own version offsets which can be found using the Windows debugger tools.
To detect credential theft through LSASS memory access, we could use a tool like Sysmon to monitor for processing opening a handle to LSASS. We can monitor for suspicious processes accessing the LSASS process and thereby create telemetry for detecting possible credential dumping activity.
Of course, there are more options to detect credential theft, for example using an advanced detection platform like Windows Defender ATP. But if you don’t have the budget and luxury of using these fancy platforms, then Sysmon is that free tool that can help fill the gap.
A few months after we published our Direct System Call blogpost, @Jackson_T published a great tool named SysWhispers. Sourced from the SysWhispers Git repository:
“SysWhispers helps with evasion by generating header/ASM files implants can use to make direct system calls”.
It is a great tool to automate the process of generating header/ASM pairs for any system call, which can then be used within custom built Red Teaming tools.
The .asm output file generated by the tool can be used within Visual Studio using the Microsoft Macro Assembler. If we want to use the system call functions generated from the SysWhispers output within a BOF project, we need some sort of conversion so they match the assembler template syntax.
Our colleague @_DaWouw wrote a Python script that can be used to transform the .asm output file generated by SysWhispers to an output file that is suitable within a BOF project.
It converts the output to match the assembler template syntax, so the functions can be used from your BOF code. We can manually enter which system calls are used in our BOF to prevent including unused system functions. The script is available within the InlineWhispers repository on our Github page:
In this blog we showed how we can use direct system calls within Cobalt Strike Beacon Object Files. To use direct system calls we need to write assembly using the assembler template syntax, so we can include assembly functions as inline-assembly. Visual Studio does not support inline-assembly for x64 processors but fortunately Mingw-w64 does.
To demonstrate the usage of direct system calls within a Beacon object file, we wrote a Proof-of-Concept code which can be used to enable WDigest credential caching. Furthermore, we wrote a script called InlineWhispers that can be used to convert .asm output generated by SysWhispers to an inline assembly header file suitable for BOF projects.
We hope this blogpost helps understanding how direct system calls can be implemented within BOF projects to improve OPSEC safety.