Brute Ratel v2.3 (codename Flux) is now available for download. A key focus of this release was complete redevelopment of the Badger implant using a custom-built compiler, designed to improve operational security (OpSec) and significantly reduce its memory footprint. During development, I noticed several limitations in the existing MinGW and Clang toolchains, particularly in how they handle certain memory regions and stack layouts. To address these issues, the MinGW/Clang compiler provided limited support, as ideally, these are not options that a general developer would ideally want to mess with. But the built-in optimizations were a tad bit problematic when dealing with my stealth requirements. Thus, I ended up creating a tailored variant, capable of optimizing the Badger’s compilation process to meet the required stealth requirements. As a result, the size of the full Badger implant has been reduced by 30% in this release, with further reductions expected to reach around 60% in the next iteration — all while retaining the full feature set.
Due to the extended development cycle and the one-month delay already incurred, not all optimizations from the new compiler could be fully integrated into this release, however they are scheduled for inclusion in the upcoming version. It is strongly recommended to review the accompanying blog post and documentation before deploying the new release.
A new option called safe_http has been added under the OpSec tab in listener and payload profiles. When enabled, this feature uses an extremely lightweight 2–3 Kilobyte PIC stub to send HTTP requests backed by a valid stack designed by the operator, while keeping the Badger’s entire code and heap encrypted. In practice, this means that during the request, neither the Badger nor its thread actually exists in memory until the HTTP transaction is fully parsed and completed. When the HTTP request is made, everything related to the badger is encrypted, making it extremely difficult to have yara detections as the PIC stub doesn’t have anything in it, that can be used to build a detection on. This approach helps mitigate certain detection techniques, particularly from EDRs that leverage ETW-based memory scans triggered by network activity through wininet or winhttp libraries. Do note that safe_http does not work with disable_http_telemetry option, since that feature is not required when safe_http is enabled.
BOF execution in Brute Ratel has always been asynchronous, but until now it lacked a way to hide the Badger while the BOF ran. This release overhauls BOF behavior. Thanks to the custom compiler I developed, I was able to rewrite the BOF loader from the ground up to support new BOF APIs and a redesigned BOF loader.
We’ve added a new command, coffexec_async, which is separate from the existing coffexec. coffexec_async runs independently of the Badger and exposes new APIs that enable the BOF to operate while the Badger remains hidden. The updated BOF APIs also provide improved stealth controls and sleep-masking interaction with the Badger. Advanced async BOFs can even run concurrently i.e. multiple BOFs can execute while the Badger stays masked and dormant.
The core logic of the AAB would be:
Badger Main Thread {
coffexec_async {
void coffee(...) {
BadgerForceSleep(dispatch, 60); // induce forced sleep for 60 seconds
BadgerDispatch(dispatch, "Hello World 1"); // print hello world 1
BadgerDispatch(dispatch, "Hello World 2"); // print hello world 2
BadgerWakeupAndSend(dispatch); // send buffered data instantly before proceeding ahead
BadgerWakeupAndExit(dispatch); // Wake up the badger interrupting the above forced sleep and return to badger's main thread
}
}
}
// To clarify the pseudo code above, when the Badger executes an asynchronous BOF, the operator can invoke the BadgerForceSleep API to place the Badger into sleep masking mode. This allows any sensitive tasks to be executed entirely without the Badger being present in memory. Once the task is complete, the operator can terminate the async BOF using BadgerWakeupAndExit.
Another API, BadgerWakeupAndSend, can be used in both coffexec and coffexec_async to pause BOF execution midway and send any buffered content queued via BadgerDispatch. However, calling BadgerWakeupAndSend within coffexec_async will wake the Badger and break its sleep mask, so it should be used carefully.
A few additional notes:
Further details on the new BOF APIs are provided below.
DECLSPEC_IMPORT BOOL BadgerForceSleep(WCHAR** dispatch, DWORD seconds);
This API instructs the Badger to encrypt and conceal itself in memory. It returns TRUE if masking is successfully initiated, and FALSE otherwise. Masking only occurs when no other active tasks are running within the Badger. For example, if an asynchronous task such as sharpinline is active, invoking this API will not trigger sleep masking — the sharpinline thread must return to its designated execution context, and therefore, its executable region cannot be encrypted. The API accepts a parameter specifying the number of sleep seconds, independent of the Badger’s configured sleep/jitter settings. Once invoked, masking remains in effect until either the specified time expires or one of the wakeup APIs — BadgerWakeupAndExit or BadgerWakeupAndSend — is called. If this API is not executed within coffexec_async, the Badger will continue using its standard sleep masking behavior as defined by the operator’s sleep/jitter configuration. Essentially, this API overrides the existing sleep mask configuration and enforces a forced sleep state.
DECLSPEC_IMPORT VOID BadgerWakeupAndExit(WCHAR** dispatch);
This API terminates the Badger’s sleep mask and returns control from the BOF to the Badger’s main thread. It is critical to invoke this API at the end of every asynchronous BOF you implement. Failing to call this API at the end of the coffee function will cause the Badger to terminate.
DECLSPEC_IMPORT VOID BadgerWakeupAndSend(WCHAR** dispatch);
This API is compatible with both coffexec and coffexec_async commands. It takes the data buffered by BadgerDispatch and instructs the Badger’s main thread to immediately transmit it to the Ratel server. If the Badger is currently in a sleep-masked state, invoking this API will temporarily break the sleep mask to allow the buffered content to be sent.
DECLSPEC_IMPORT BOOL BadgerStopTask(WCHAR** dispatch);
This API is supported by both coffexec and coffexec_async commands. It allows a BOF to check whether the operator has issued a stop_task command. By integrating this API into your code, you can detect when a stop request has been made—if the function returns TRUE, you can gracefully exit your routine or perform any necessary cleanup operations. This approach gives the operator greater control over task management without forcefully terminating a BOF thread, which could otherwise lead to memory leaks and compromise both OpSec and overall stability.
DECLSPEC_IMPORT BOOL BadgerDownloadBuffer(WCHAR** dispatch, CHAR *fileName, PVOID fileBuffer, unsigned long long fileSize);
This API is compatible with both coffexec and coffexec_async commands. It accepts multiple arguments — primarily the filename, the complete file buffer, and the actual file size to be uploaded to the Ratel server. The data is transmitted using the Badger’s main HTTP thread and is automatically sent once the BOF execution completes.
DECLSPEC_IMPORT VOID BadgerSetToken(WCHAR** dispatch, HANDLE hToken);
This API is supported by both coffexec and coffexec_async commands. While BadgerSetToken is not a new API, this release introduces a minor update — it now expects two arguments: the first being dispatch, and the second the token value. This adjustment was implemented to ensure compatibility with the coffexec_async command. If you’re using this API in BOFs built with earlier BRc4 versions, it is recommended to update your code to reflect this change.
With the introduction of the new Async BOF framework, it made sense to make certain Badger features more autonomous. After gathering feedback and discussing ideas with our customers in the Discord channel, I decided to transition several commands to run as Async BOFs. This allows them to operate independently while the Badger remains in its sleep-masked state.
When executing any of the above commands, it’s recommended to configure the Badger with a sleep interval of at least 30 to 60 seconds. This ensures you gain the full benefit of the sleep masking feature during their execution. And in order to make it easy to understand how Async BOFs work, there is an example BOF present in the sample_config directory in the brc4 package named async_bof_test.c, which might be more helpful to operators.
While this post highlights many of the major updates in the Flux release, several additional changes have intentionally been left out to keep certain features away from EDRs. A significant portion of this update also involved deep backend improvements that strengthen stability, performance (for socks/rportfwd), and OpSec capabilities.
The next release will push things even further — with the new compiler framework designed to push Badger’s stealth and resilience against modern EDRs to an entirely new level. It’s going to be a crazy one.