{
	"id": "83ee984f-ab92-417b-951a-7130d5654ccf",
	"created_at": "2026-04-06T00:07:33.529269Z",
	"updated_at": "2026-04-10T03:37:17.186517Z",
	"deleted_at": null,
	"sha1_hash": "95bfd86e06b503f3827af1514e98212d4d8518d8",
	"title": "PART 3: How I Met Your Beacon - Brute Ratel - MDSec",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 18474440,
	"plain_text": "PART 3: How I Met Your Beacon - Brute Ratel - MDSec\r\nBy Admin\r\nPublished: 2022-08-03 · Archived: 2026-04-05 18:19:18 UTC\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 1 of 43\n\nAdversary Simulation\r\nOur best in class red team can deliver a holistic cyber attack simulation to provide a true evaluation of your\r\norganisation’s cyber resilience.\r\nApplication\r\nSecurity\r\nLeverage the team behind the industry-leading Web Application and Mobile Hacker’s Handbook series.\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 2 of 43\n\nPenetration\r\nTesting\r\nMDSec’s penetration testing team is trusted by companies from the world’s leading technology firms to\r\nglobal financial institutions.\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 3 of 43\n\nResponse\r\nOur certified team work with customers at all stages of the Incident Response lifecycle through our range\r\nof proactive and reactive services.\r\nResearch\r\nMDSec’s dedicated research team periodically releases white papers, blog posts, and tooling.\r\nTraining\r\nMDSec’s training courses are informed by our security consultancy and research functions, ensuring you\r\nbenefit from the latest and most applicable trends in the field.\r\nInsights\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 4 of 43\n\nView insights from MDSec’s consultancy and research teams.\r\nIntroduction\r\nIn part one, we introduced generic approaches to performing threat hunting of C2 frameworks and then followed it\r\nup with practical examples against Cobalt Strike in part two.\r\nIn part three of this series, we will analyse Brute Ratel, a command and control framework developed by Dark\r\nVortex. As the C2 is lesser known, we can see it describes itself as follows:\r\nThe framework has come under close scrutiny in the past few months, having been allegedly abused by APT29\r\nand the ransomware group BlackCat in recent times. Having an understanding of how we can generically detect\r\nthis emerging C2 in our infrastructure is therefore useful intelligence for defenders.\r\nOriginally, all analysis was performed on Brute Ratel v1.0.7; the latest at the time of original review. However, a\r\ncursory update (contained at the end of this article) was performed discussing findings pertinent to v1.1 which was\r\nreleased shortly after our initial x33fcon presentation. One thing that should be noted with Brute Ratel is that the\r\nbadger has only limited malleability and primarily from the perspective of the c2 channels; with the exception of\r\nv1.1 which added malleability for the sleep obfuscation techniques. As such it makes it possible to create very\r\nspecific detections for the tool.\r\nBrute Ratel’s Loader\r\nBrute Ratel’s badger comes in a number of forms, including exe, DLL and shellcode. When the badger is injected,\r\nits reflective loader will instantly load all dependencies required for the badger. As the badger bundles a large\r\namount of post-exploitation features, this leads to a significant number of DLLs being loaded on initialisation:\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 5 of 43\n\nAs we can see, the DLLs highlighted are all the DLLs that are loaded when the badger is injected. This list\r\nincludes the loading of winhttp.dll and wininet.dll, which are not necessarily nefarious but are traditional loads for\r\nan egress beacon. There are however a number of less common DLLs loaded, such as dbghelp.dll, credui.dll\r\nsamcli.dll and logoncli.dll amongst others.\r\nThis behaviour allows us to create a signature for the image loads and leads to a high signal indicator that can be\r\nhunted for through image load telemetry.\r\nFor example, using Elastic Query Language, we can search for the sequence of credui.dll, dbghelp.dll and\r\nwinhttp.dll load events occurring in a process within 60 seconds of each other:\r\nsequence by Image with maxspan=1m\r\n[any where ImageLoaded == 'C:\\\\Windows\\\\System32\\\\credui.dll']\r\n[any where ImageLoaded == 'C:\\\\Windows\\\\System32\\\\dbghelp.dll']\r\n[any where ImageLoaded == 'C:\\\\Windows\\\\System32\\\\winhttp.dll']\r\nUsing the EQL tool, or Elastic’s cloud, we can search our event data, such as the following which was extracted\r\nfrom sysmon logs. Note, we’re explicitly excluding the badger executable itself so we can only identify the\r\ninjected badgers:\r\neql query -f sysmon-data.json \"sequence by Image with maxspan=2m [any where ImageLoaded == 'C:\\\\Windows\\\\System\r\nThis leads to the following which shows the detection of the badger being injected in to notepad.exe:\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 6 of 43\n\nThis query is particularly powerful as it allows us to retrospectively hunt for indicators of Brute Ratel badgers in\r\nthe network, without directly running code on the endpoints.\r\nBrute Ratel In Memory\r\nAs most beacons remain memory resident, it is important to understand the footprint that is left behind in order to\r\nhunt for them. Reviewing the Brute Ratel documentation for the 1.0 release, it details its own implementation of\r\nobfuscate and sleep:\r\nAccording to the release post, BRc4 uses a mixture of “Asynchronous Procedure Calls, Windows Event Creation,\r\nWait Objects and Timers”. However, analysis of the badger was only able to find evidence of APC based\r\nexecution; more on this later.\r\nIn order to analyse the badger in memory, we first inject it to a process using the pcinject command, then put the\r\nbadger to sleep using the sleep command:\r\nOnce the badger is sleeping, we can recover the strings from the process using Process Hacker. Interestingly, while\r\nthe badger is sleeping we can see strings such as the following:\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 7 of 43\n\nInitially this was quite surprising given the aforementioned purported sleep and obfuscate strategies described on\r\nthe Brute Ratel blog.\r\nDigging deeper, we can find that some interesting design decisions have been made where by many of the strings\r\ndisplayed in the operator’s UI, are populated from the badger itself. For example, we can see the following in the\r\nmemory of the badger while it is sleeping:\r\nAnd these strings are then returned to the UI as we can see below:\r\nDigging deeper in to the badger, it was quickly apparent that only the .text section was being obfuscated on sleep,\r\nleaving the badger susceptible to all manner of signatures against strings and data.\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 8 of 43\n\nTo illustrate this, reversing the badger we can see the entry point for the loader as “bruteloader”:\r\nSearching for this string in memory while the badger is sleeping, we can quickly find it inside our notepad\r\nprocess:\r\nThese strings provide a good point on which to base a Yara rule for memory scanning on. For example, the\r\nfollowing rule will search for either the bruteloader or bhttp_x64.dll strings in memory of a process:\r\nrule brc4_badger_strings\r\n{\r\nmeta:\r\n author = \"@domchell\"\r\n description = \"Identifies strings used in Badger v1.0.x rDLL, even while sleeping\"\r\nstrings:\r\n $a = \"bruteloader\"\r\n $b = \"bhttp_x64.dll\"\r\ncondition:\r\n 1 of them\r\n}\r\nWe can test these against our notepad process while the badger is sleeping to evidence its effectiveness:\r\nIt is unlikely the strings will exist in other processes, and using a simple one liner we can quickly find all the\r\ninjected badgers on our test system:\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 9 of 43\n\nPlugging this Yara rule in to virus total, we can quickly find other samples, such as:\r\nPage Permissions\r\nAnalysis of the Brute Ratel obfuscate and sleep strategy observed the badger to shuffle the page permissions for\r\nthe badger during sleep in an attempt to evade prolonging executable permissions while the badger sleeps.\r\nBelow, we can see the badger operating on a sleep 0, the page permissions for the badger are\r\nPAGE_EXECUTE_READ on an unmapped page; this is necessary in order to perform tasking:\r\nPutting the badger to sleep, we can see that the obfuscate and sleep strategy obfuscates the .text section and resets\r\nthe page permissions for the badger to to PAGE_READWRITE:\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 10 of 43\n\nInterestingly, we however note that this behaviour is not replicated while a SMB pivot is being performed, that is\r\nwhen two badgers are linked. Here we can see our two badgers linked and both on a 60 second sleep:\r\nAnalysis of the page permissions while two badgers are linked reveals that both remain\r\nPAGE_EXECUTE_READ, irrespective of the sleep time:\r\nThe conclusion is that the obfuscate and sleep strategy is only applicable to the .text section, and while no peer-to-peer pivot is present.\r\nCurious to how the obfuscate and sleep functionality worked, we began to reverse engineer it. Walking through\r\nthe sleep routine in windbg, we can get an initial flavour of what’s happening; the badger is using\r\nWaitForSingleObjectEx to delay execution during a series of asynchronous procedure calls (APC), and leveraging\r\nan indirect syscall to execute NtTestAlert and force an alert on the thread:\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 11 of 43\n\nDiving in to IDA, we can get a better feel for what is happening. First it creates a new thread with the start address\r\nspoofed to a fixed location of TpReleaseCleanupGroupMembers+550:\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 12 of 43\n\nA series of context structures are then created for a number of function calls, to NtWaitForSingleObject,\r\nNtProtectVirtualMemory, , SystemFunction032, NtGetContextThread and SetThreadContext:\r\nNext, a number of APCs are queued against the NtContinue, with the intention of using it to proxy calls to the\r\naforementioned context structures; this technique acts as a rudimentary form of ROP:\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 13 of 43\n\nHaving reverse engineered the sleeping technique, we soon realised that it it was very similar to @ilove2pwn_’s\r\nFoliage project, with the exception of the hardcoded thread start address.\r\nDespite extensive debugging and reverse engineering of the badger, we unable to reveal any evidence of the\r\n“Windows Event Creation, Wait Objects and Timers” techniques referenced in the v1.0 blog post; indeed the APIs\r\nrequired for these techniques did not appear to be imported via the badger’s hashed imports.\r\nBrute Ratels Threads\r\nTo analyse how Brute Ratel threads look in memory, we injected the badger in to a fresh copy of notepad.\r\nImmediately, we can see there are some suspicious indicators in the threads used by the sleeping badger.\r\nFirstly, we note that there is a suspicious looking thread with a 0x0 start address, and a single frame calling\r\nWaitForSingleObjectEx in the call stack:\r\nWe can speculate that this thread is used for the HTTP comms based on analysis of the thread call stack while the\r\nbadger is now sleeping:\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 14 of 43\n\nBased on the information we gained from reverse engineering the obfuscate and sleep strategy, we noted that new\r\nthreads were created with a hardcoded spoofed start address of ntdll!TpReleaseCleanupGroupMembers+0x550:\r\nWe were unable to find any instances of this occurring as a start address naturally, and as such leads to a trivial\r\nindicator for hunting Brute Ratel threads. In practice this looks as follows within our injected notepad process:\r\nThe call stack for the thread is also slightly irregular as it not only contains calls to delay execution, but also the\r\nfirst frame points to ntdll.dll!NtTerminateJobObject+0x1f. A deeper look at why NtNerminateJobObject is used\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 15 of 43\n\nhighlights that this is simply a ROP gadget for NtTestAlert and is used to execute pending APCs on the thread:\r\nMemory Hooks\r\nIn our first post in this series, we detailed two potential approaches for detecting in-memory beacons based on\r\nmemory hooks; by looking for signatures of known patches (e.g. ret to ntdll.dll!EtwEventWrite) and by detecting\r\ncopy on write operations.\r\nApplying these concepts to Brute Ratel, we note that the badger does not apply any memory hooks until its post-exploitation functionality is used by the operator. An example of this, would be the sharpinline command, which\r\nruns a .NET assembly in the current process:\r\nOnce the assembly has completed and the beacon gone back to sleep, we can get a better understanding of whats\r\ngoing on by attaching a debugger and disassembling the values of ntdll.dll!EtwEventWrite and\r\namsi.dll!AmsiScanBuffer:\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 16 of 43\n\nAs shown above, these are simple and persistent patches to disable .NET ETW data and inhibit AMSI. As the\r\npatches are persistent, we can detect them by either of the aforementioned techniques, since not only will we\r\nreceive a high signal detection due to the first instruction of EtwEventWrite being a ret, but also an indicator that\r\nthe pages where EtwEventWrite resides have been modified due to the clearing of the shared bit.\r\nUsing BeaconHunter, we can rapidly detect these hooks based on resolving the exports on the modified pages,\r\nproviding a strong indicator that malicious tampering has taken place:\r\nBrute Ratel C2 Server\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 17 of 43\n\nMoving away from the endpoint, as hunters we also have an interest in detecting the command-and-control\r\ninfrastructure as this may assist in providing us with sufficient intelligence to detect beaconing based on network\r\ntelemetry.\r\nThe C2 server for Brute Ratel is developed in golang, and by default only allows the operator to modify the\r\ndefault landing page for the C2. To fingerprint the C2 server, we discovered it was possible to generate an\r\nunhandled exception when sending a POST request containing base64 to any URI. For example, consider the\r\nfollowing base64 POST data compared with the the plaintext:\r\nIt is likely this occurs as the expected input for the base64 decoded POST data should conform to the C2 traffic\r\nformat. A simple Nuclei rule might help us in scanning for this kind of infrastructure:\r\nid: brc4-ts\r\ninfo:\r\n name: Brute Ratel C2 Server Fingerprint\r\n author: Dominic Chell\r\n severity: info\r\n description: description\r\n reference:\r\n - https://\r\n tags: tags\r\nrequests:\r\n - raw:\r\n - |-\r\n POST / HTTP/1.1\r\n Host: {{Hostname}}\r\n Content-Length: 8\r\n Zm9vYmFy\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 18 of 43\n\nOutside of direct interaction with the C2, it is also possible to detect C2 infrastructure where the operator has not\r\nmanually redefined the default landing page based on a hash of the HTML (http.html_hash=-1957161625).\r\nUsing a simple Shodan query, we can quickly find live infrastructure exposed to the Internet:\r\nAlthough only around 40 team servers were identified, we can get a better picture of where these are located based\r\non the geographical spread:\r\nIt is quite likely some of these techniques are already known, as based on reports against our test infrastructure,\r\ndefenders are actively hunting these C2 servers:\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 19 of 43\n\nBrute Ratel Configurations\r\nAnalysis of the Badger revealed that Brute Ratel maintains an encrypted configuration structure in memory which\r\nincludes details on the C2 endpoints. Being able to extract this from either artifacts or from running processes can\r\nprove helpful for defenders. Our analysis revealed that this configuration is held in a base64 and RC4 encrypted\r\nblob using a fixed key of “bYXJm/3#M?:XyMBF” in the artifacts for the badger. While the configuration is stored\r\nplaintext in memory for the sleeping badger.\r\nWe developed the following config extractor that can be used against both on-disk artifacts for BRC4 v1.0.x or\r\ninjected sleeping badgers with Brute Ratel 1.0.x and 1.1.x:\r\n#define _CRT_SECURE_NO_WARNINGS\r\n#include \u003cstdio.h\u003e\r\n#include \u003cstdlib.h\u003e\r\n#include \u003cWindows.h\u003e\r\n#include \u003cstring\u003e\r\n#include \u003cvector\u003e\r\n#pragma comment(lib, \"Crypt32.lib\")\r\nstd::string HexDump(void* pBuffer, DWORD cbBuffer)\r\n{\r\nPBYTE pbBuffer = (PBYTE)pBuffer;\r\nstd::string strHex;\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 20 of 43\n\n#define FORMAT_APPEND_1(a) { char szTmp[256]; sprintf(szTmp, a); strHex += szTmp; }\r\n#define FORMAT_APPEND_2(a,b) { char szTmp[256]; sprintf(szTmp, a, b); strHex += szTmp; }\r\nfor (DWORD i = 0; i \u003c cbBuffer;)\r\n{\r\nFORMAT_APPEND_2(\"0x8x \", i);\r\nDWORD n = ((cbBuffer - i) \u003c 16) ? (cbBuffer - i) : 16;\r\nfor (DWORD j = 0; j \u003c n; j++)\r\n{\r\nFORMAT_APPEND_2(\"%02X \", pbBuffer[i + j]);\r\n}\r\nfor (DWORD j = 0; j \u003c (16 - n); j++)\r\n{\r\nFORMAT_APPEND_1(\" \");\r\n}\r\nFORMAT_APPEND_1(\" \");\r\nfor (DWORD j = 0; j \u003c n; j++)\r\n{\r\nFORMAT_APPEND_2(\"%c\", (pbBuffer[i + j] \u003c 0x20 || pbBuffer[i + j] \u003e 0x7f) ? '.' : pbBu\r\n}\r\nFORMAT_APPEND_1(\"\\n\");\r\ni += n;\r\n}\r\nreturn strHex;\r\n}\r\nBOOL ReadAllBytes(std::string strFile, PBYTE* ppbBuffer, UINT* puiBufferLength)\r\n{\r\nBOOL bSuccess = FALSE;\r\nPBYTE pbBuffer = NULL;\r\n*ppbBuffer = NULL;\r\n*puiBufferLength = 0;\r\nFILE* fp = fopen(strFile.c_str(), \"rb\");\r\nif (fp)\r\n{\r\nfseek(fp, 0, SEEK_END);\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 21 of 43\n\nlong lFile = ftell(fp);\r\nfseek(fp, 0, SEEK_SET);\r\nif (!(pbBuffer = (PBYTE)malloc(lFile)))\r\ngoto Cleanup;\r\nif (fread(pbBuffer, 1, lFile, fp) != lFile)\r\ngoto Cleanup;\r\n*ppbBuffer = pbBuffer;\r\n*puiBufferLength = (UINT)lFile;\r\npbBuffer = NULL;\r\nbSuccess = TRUE;\r\n}\r\nCleanup:\r\nif (fp) fclose(fp);\r\nif (pbBuffer) free(pbBuffer);\r\nreturn bSuccess;\r\n}\r\nvoid Brc4DecodeString(BYTE* pszKey, BYTE* pszInput, BYTE* pszOutput, int cchInput)\r\n{\r\nBYTE szCharmap[0x100];\r\nfor (UINT i = 0; i \u003c sizeof(szCharmap); i++)\r\n{\r\nszCharmap[i] = (char)i;\r\n}\r\nUINT cchKey = strlen((char*)pszKey);\r\nBYTE l = 0;\r\nfor (UINT i = 0; i \u003c sizeof(szCharmap); i++)\r\n{\r\nBYTE x = szCharmap[i];\r\nBYTE k = pszKey[i % cchKey];\r\nBYTE y = x + k + l;\r\nl = y;\r\nszCharmap[i] = szCharmap[y];\r\nszCharmap[y] = x;\r\n}\r\nl = 0;\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 22 of 43\n\nfor (UINT i = 0; i \u003c cchInput; i++)\r\n{\r\nBYTE x = szCharmap[i + 1];\r\nBYTE y = x + l;\r\nl = y;\r\nBYTE z = szCharmap[y];\r\nszCharmap[i + 1] = z;\r\nszCharmap[y] = x;\r\nx = x + szCharmap[i + 1];\r\nx = szCharmap[x];\r\nx = x ^ pszInput[i];\r\npszOutput[i] = x;\r\n}\r\n}\r\nBOOL MatchPattern(PBYTE pbInput, PBYTE pbSearch, DWORD cbSearch, BYTE byteMask)\r\n{\r\nBOOL bMatch = TRUE;\r\nfor (DWORD j = 0; j \u003c cbSearch; j++)\r\n{\r\nif (pbSearch[j] != byteMask \u0026\u0026 pbInput[j] != pbSearch[j])\r\n{\r\nbMatch = FALSE;\r\nbreak;\r\n}\r\n}\r\nreturn bMatch;\r\n}\r\nPBYTE FindPattern(PBYTE pbInput, UINT cbInput, PBYTE pbSearch, DWORD cbSearch, BYTE byteMask, UINT* pcSkipMatche\r\n{\r\nif (cbInput \u003e cbSearch)\r\n{\r\nfor (UINT i = 0; i \u003c cbInput - cbSearch; i++)\r\n{\r\nBOOL bMatch = MatchPattern(pbInput + i, pbSearch, cbSearch, byteMask);\r\nif (bMatch)\r\n{\r\nif (!*pcSkipMatches)\r\n{\r\nreturn \u0026pbInput[i];\r\n}\r\n(*pcSkipMatches)--;\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 23 of 43\n\n}\r\n}\r\n}\r\nreturn NULL;\r\n}\r\nBOOL LocateBrc4Config(PBYTE pbInput, UINT cbInput, PBYTE* ppbConfig)\r\n{\r\n#define XOR_RAX_RAX 0x48, 0x31, 0xC0,\r\n#define PUSH_RAX 0x50,\r\n#define MOV_EAX_IMM32 0xB8, 0xab, 0xab, 0xab, 0xab,\r\n#define MOV_RAX_IMM64 0x48, 0xB8, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,\r\n#define PUSH_IMM32 0x68, 0xab, 0xab, 0xab, 0xab,\r\n#define MOV_EAX_0 0xB8, 0x00, 0x00, 0x00, 0x00,\r\nBYTE Pattern1[] =\r\n{\r\nXOR_RAX_RAX\r\nPUSH_RAX\r\nMOV_EAX_IMM32\r\nPUSH_RAX\r\nMOV_RAX_IMM64\r\nPUSH_RAX\r\nMOV_RAX_IMM64\r\nPUSH_RAX\r\nMOV_RAX_IMM64\r\nPUSH_RAX\r\nMOV_RAX_IMM64\r\nPUSH_RAX\r\nMOV_RAX_IMM64\r\nPUSH_RAX\r\nMOV_RAX_IMM64\r\n},\r\nPattern2[] =\r\n{\r\nXOR_RAX_RAX\r\nPUSH_RAX\r\nMOV_RAX_IMM64\r\nPUSH_RAX\r\nMOV_RAX_IMM64\r\nPUSH_RAX\r\nMOV_RAX_IMM64\r\nPUSH_RAX\r\nMOV_RAX_IMM64\r\nPUSH_RAX\r\nMOV_RAX_IMM64\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 24 of 43\n\nPUSH_RAX\r\nMOV_RAX_IMM64\r\nPUSH_RAX\r\nMOV_RAX_IMM64\r\n};\r\nUINT cSkipMatches = 0;\r\nif (cbInput \u003c 100)\r\n{\r\nreturn FALSE;\r\n}\r\nPBYTE pbConfigStart = FindPattern(pbInput, cbInput, Pattern1, sizeof(Pattern1), 0xab, \u0026cSkipMatches);\r\nif (!pbConfigStart)\r\n{\r\ncSkipMatches = 0;\r\npbConfigStart = FindPattern(pbInput, cbInput, Pattern2, sizeof(Pattern2), 0xab, \u0026cSkipMatches)\r\nif (!pbConfigStart)\r\n{\r\nreturn FALSE;\r\n}\r\n}\r\nBYTE Pattern3[] = {\r\nPUSH_IMM32\r\nMOV_EAX_0\r\nPUSH_RAX\r\nMOV_EAX_0\r\nPUSH_RAX\r\nMOV_EAX_0\r\nPUSH_RAX\r\n};\r\ncSkipMatches = 0;\r\nPBYTE pbConfigEnd = FindPattern(pbConfigStart, cbInput - (pbConfigStart - pbInput), Pattern3, sizeof(Pa\r\nif (!pbConfigEnd)\r\n{\r\nreturn FALSE;\r\n}\r\n*ppbConfig = (PBYTE)malloc(pbConfigEnd - pbConfigStart);\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 25 of 43\n\nif (!*ppbConfig)\r\n{\r\nreturn FALSE;\r\n}\r\nmemset(*ppbConfig, 0, pbConfigEnd - pbConfigStart);\r\npbConfigStart += 4; // skip: XOR_RAX_RAX / PUSH_RAX\r\nBYTE Pattern4[] = {\r\nMOV_EAX_IMM32\r\nPUSH_RAX\r\n},\r\nPattern5[] = {\r\nMOV_RAX_IMM64\r\nPUSH_RAX\r\n};\r\nfor (UINT uiIndex = 0, i = 0; i \u003c pbConfigEnd - pbConfigStart;)\r\n{\r\nif (MatchPattern(pbConfigStart + i, Pattern4, sizeof(Pattern4), 0xab))\r\n{\r\n(*ppbConfig)[uiIndex++] = pbConfigStart[i + 4];\r\n(*ppbConfig)[uiIndex++] = pbConfigStart[i + 3];\r\n(*ppbConfig)[uiIndex++] = pbConfigStart[i + 2];\r\n(*ppbConfig)[uiIndex++] = pbConfigStart[i + 1];\r\ni += sizeof(Pattern4);\r\n}\r\nelse if (MatchPattern(pbConfigStart + i, Pattern5, sizeof(Pattern5), 0xab))\r\n{\r\n(*ppbConfig)[uiIndex++] = pbConfigStart[i + 9];\r\n(*ppbConfig)[uiIndex++] = pbConfigStart[i + 8];\r\n(*ppbConfig)[uiIndex++] = pbConfigStart[i + 7];\r\n(*ppbConfig)[uiIndex++] = pbConfigStart[i + 6];\r\n(*ppbConfig)[uiIndex++] = pbConfigStart[i + 5];\r\n(*ppbConfig)[uiIndex++] = pbConfigStart[i + 4];\r\n(*ppbConfig)[uiIndex++] = pbConfigStart[i + 3];\r\n(*ppbConfig)[uiIndex++] = pbConfigStart[i + 2];\r\ni += sizeof(Pattern5);\r\n}\r\nelse if (MatchPattern(pbConfigStart + i, Pattern3, sizeof(Pattern3), 0xab))\r\n{\r\nbreak;\r\n}\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 26 of 43\n\nelse\r\n{\r\nreturn FALSE;\r\n}\r\n}\r\nstd::string config = (char*)*ppbConfig;\r\nstd::reverse(config.begin(), config.end());\r\nstrcpy((char*)*ppbConfig, config.c_str());\r\nreturn TRUE;\r\n}\r\nBOOL FromBase64(char* pszString, PBYTE* ppbBinary, UINT* pcbBinary)\r\n{\r\nDWORD cbBinary = 0;\r\nif (FAILED(CryptStringToBinaryA(pszString, 0, CRYPT_STRING_BASE64, NULL, \u0026cbBinary, NULL, NULL)))\r\n{\r\nreturn FALSE;\r\n}\r\n*ppbBinary = (PBYTE)malloc(cbBinary + 1);\r\nif (!*ppbBinary)\r\n{\r\nreturn FALSE;\r\n}\r\nif (FAILED(CryptStringToBinaryA(pszString, 0, CRYPT_STRING_BASE64, *ppbBinary, \u0026cbBinary, NULL, NULL)))\r\n{\r\nreturn FALSE;\r\n}\r\n*pcbBinary = cbBinary;\r\nreturn TRUE;\r\n}\r\nBOOL ScanProcessForBadgerConfig(HANDLE hProcess, std::string\u0026 badgerId, std::vector\u003cstd::wstring\u003e\u0026 configStrings\r\n{\r\nSIZE_T nBytesRead;\r\nPBYTE lpMemoryRegion = NULL, pbBadgerStateStruct = NULL;\r\nprintf(\"[+] Searching process memory for badger state ...\\n\");\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 27 of 43\n\nwhile (1)\r\n{\r\nMEMORY_BASIC_INFORMATION mbi = { 0 };\r\nif (!VirtualQueryEx(hProcess, lpMemoryRegion, \u0026mbi, sizeof(mbi)))\r\n{\r\nbreak;\r\n}\r\nif ((mbi.State \u0026 MEM_COMMIT) \u0026\u0026 !(mbi.Protect \u0026 PAGE_GUARD) \u0026\u0026\r\n((mbi.Protect \u0026 PAGE_READONLY) || (mbi.Protect \u0026 PAGE_READWRITE) || (mbi.Protect \u0026 PA\r\n{\r\n//printf(\"[+] Searching process memory at 0x%p (size 0x%x)\\n\", lpMemoryRegion, mbi.Re\r\nPBYTE pbLocalMemoryCopy = (PBYTE)malloc(mbi.RegionSize);\r\nif (!ReadProcessMemory(hProcess, lpMemoryRegion, pbLocalMemoryCopy, mbi.RegionSize, \u0026\r\n{\r\n//printf(\"[!] Unable to read memory at 0x%p\\n\", lpMemoryRegion);\r\n}\r\nelse\r\n{\r\nfor (UINT i = 0; i \u003c mbi.RegionSize - 128 \u0026\u0026 !pbBadgerStateStruct; i++)\r\n{\r\nif (memcmp(pbLocalMemoryCopy + i, \"b-\", 2) == 0)\r\n{\r\nchar* pszEndPtr = NULL;\r\nint badgerId = strtoul((char*)pbLocalMemoryCopy + i + 2, \u0026\r\nif (pszEndPtr != (char*)pbLocalMemoryCopy + i + 2 \u0026\u0026 pszEn\r\n{\r\npbBadgerStateStruct = lpMemoryRegion + i;\r\nbreak;\r\n}\r\n}\r\n}\r\n}\r\nfree(pbLocalMemoryCopy);\r\npbLocalMemoryCopy = NULL;\r\n}\r\nlpMemoryRegion += mbi.RegionSize;\r\n}\r\nif (!pbBadgerStateStruct)\r\n{\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 28 of 43\n\nprintf(\"[!] Failed to find badger state\\n\");\r\nreturn FALSE;\r\n}\r\nprintf(\"[+] Found badger state at 0x%p\\n\", pbBadgerStateStruct);\r\nBYTE BadgerState[0x1000];\r\nmemset(BadgerState, 0, sizeof(BadgerState));\r\nif (!ReadProcessMemory(hProcess, pbBadgerStateStruct, BadgerState, 0x1000, \u0026nBytesRead))\r\n{\r\nif (GetLastError() != ERROR_PARTIAL_COPY)\r\n{\r\nprintf(\"[!] Unable to read badger state at 0x%p\\n\", pbBadgerStateStruct);\r\nreturn FALSE;\r\n}\r\n}\r\nbadgerId = (char*)BadgerState;\r\nBYTE ConfigString[1024];\r\nmemset(ConfigString, 0, sizeof(ConfigString));\r\nfor (UINT i = 0x100 + (0x10 - ((DWORD64)pbBadgerStateStruct \u0026 0xf)); i \u003c sizeof(BadgerState); i += size\r\n{\r\nDWORD64 pMem = *(DWORD64*)(BadgerState + i);\r\nif (pMem)\r\n{\r\nConfigString[0] = 0;\r\nif (!ReadProcessMemory(hProcess, (LPVOID)pMem, ConfigString, 1024, \u0026nBytesRead) || nB\r\n{\r\ncontinue;\r\n}\r\nBOOL bIsValid = ConfigString[0] != 0;\r\nstd::wstring badgerString;\r\n#define MIN_STRING_LENGTH 5\r\nif (bIsValid)\r\n{\r\nchar* pszConfigString = (char*)ConfigString;\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 29 of 43\n\nfor (UINT j = 0; j \u003c nBytesRead \u0026\u0026 pszConfigString[j] != 0; j++)\r\n{\r\nif (!isprint(pszConfigString[j]) \u0026\u0026 !(pszConfigString[j] == '\\t' ||\r\n{\r\nbreak;\r\n}\r\nbadgerString.push_back(pszConfigString[j]);\r\n}\r\nbIsValid = badgerString.size() \u003e= MIN_STRING_LENGTH;\r\n}\r\nif (!bIsValid)\r\n{\r\nbadgerString.clear();\r\nbIsValid = TRUE;\r\nWCHAR* pwszConfigString = (WCHAR*)ConfigString;\r\nfor (UINT j = 0; j \u003c nBytesRead / sizeof(WCHAR) \u0026\u0026 pwszConfigString[j] != 0;\r\n{\r\nif (!iswprint(pwszConfigString[j]) \u0026\u0026 !(pwszConfigString[j] == '\\t'\r\n{\r\nbreak;\r\n}\r\nbadgerString.push_back(pwszConfigString[j]);\r\n}\r\nbIsValid = badgerString.size() \u003e= MIN_STRING_LENGTH;\r\n}\r\nif (bIsValid)\r\n{\r\nconfigStrings.push_back(badgerString);\r\n}\r\n}\r\n}\r\nreturn TRUE;\r\n}\r\nint main(int argc, char *argv[])\r\n{\r\nPBYTE key = (PBYTE)\"bYXJm/3#M?:XyMBF\";\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 30 of 43\n\nprintf(\"BruteRatel v1.x Config Extractor\\n\");\r\nif (argc \u003c 2)\r\n{\r\nprintf(\r\n\"Usage: Brc4ConfigExtractor.exe \u003cfile\u003e [key]\\n\"\r\n\" \u003cfile|pid\u003e - file to scan for config, or running process ID\\n\"\r\n\" [key] - key if not default\\n\"\r\n);\r\nreturn 1;\r\n}\r\nif (argc \u003e 2)\r\n{\r\nkey = (PBYTE)argv[2];\r\n}\r\nif (atoi(argv[1]) == 0)\r\n{\r\nPBYTE pbBadger = NULL;\r\nUINT cbBadger = 0;\r\nif (!ReadAllBytes(argv[1], \u0026pbBadger, \u0026cbBadger))\r\n{\r\nprintf(\"[!] Input file '%s' not found\\n\", argv[1]);\r\nreturn 1;\r\n}\r\nprintf(\"[+] Analysing file '%s' (%u bytes)\\n\", argv[1], cbBadger);\r\nPBYTE pbConfigText = NULL;\r\nif (!LocateBrc4Config(pbBadger, cbBadger, \u0026pbConfigText))\r\n{\r\nprintf(\"[!] Failed to locate BRC4 config\\n\");\r\nreturn 1;\r\n}\r\nprintf(\"[+] Located BRC4 config: %s\\n\", pbConfigText);\r\nPBYTE pbBinaryConfig = NULL;\r\nUINT cbBinaryConfig = 0;\r\nif (!FromBase64((char*)pbConfigText, \u0026pbBinaryConfig, \u0026cbBinaryConfig))\r\n{\r\nprintf(\"[!] Failed to decode BRC4 config from base64\\n\");\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 31 of 43\n\nreturn 1;\r\n}\r\nBrc4DecodeString(key, pbBinaryConfig, pbBinaryConfig, cbBinaryConfig);\r\nprintf(\"[+] Decoded config: %.*s\\n\", cbBinaryConfig, pbBinaryConfig);\r\n}\r\nelse\r\n{\r\nDWORD dwPid = atoi(argv[1]);\r\nprintf(\"[+] Analysing process with ID %u\\n\", dwPid);\r\nHANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);\r\nif (!hProcess)\r\n{\r\nprintf(\"[!] Failed to open process\\n\");\r\nreturn 1;\r\n}\r\nstd::string badgerId;\r\nstd::vector\u003cstd::wstring\u003e configStrings;\r\nif (!ScanProcessForBadgerConfig(hProcess, badgerId, configStrings))\r\n{\r\nprintf(\"[!] Failed to locate badger configuration in memory\\n\");\r\nreturn 1;\r\n}\r\nprintf(\"[+] Badger '%s' found...\\n\", badgerId.c_str());\r\nfor (auto configString : configStrings)\r\n{\r\nprintf(\" : %S\\n\", configString.c_str());\r\n}\r\nCloseHandle(hProcess);\r\n}\r\nreturn 0;\r\n}\r\nRunning the extractor tool on either an artifact or a running process (even while sleeping), will extract the Brute\r\nRatel configuration state for the process or artifact:\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 32 of 43\n\nUpdated v1.1 Analysis\r\nShortly after our talk on this subject at x33fcon, Brute Ratel announced a new version of the software. As such, it\r\nseemed appropriate to analyse this to ensure defenders have accurate advice given the recent uptake in Brute Ratel\r\nby threat actors.\r\nAnalysis of Obfuscate and Sleep Techniques\r\nOne of the things that struck us about the v1.1 release, was the declaration that the author had discovered new\r\nsleep and obfuscate techniques. As stated in this YouTube video “Brute Ratel C4 v/s Nighthawk and Open\r\nSource Sleep Obfuscation Techniques“, the author says “I didn’t even knew (SIC) about this technique until\r\nAustin released the blog post on this. However, Brute Ratel does not use either of these two techniques that we\r\nhave seen over here.” in reference to the APC technique used in Foliage and the Timer based technique as used in\r\nMDSec’s Nighthawk and as reverse engineered here and a proof of concept implementation released here. Noting\r\nthat this video appeared a short time after the Ekko release.\r\nReverse engineering of the obfuscate in sleep techniques used within Brute Ratel v1.1 reveal that three sleeping\r\nstrategies are now available. The first, as we have previously documented is an extremely similar implementation\r\nto @ilove2pwn_’s Foliage, if not an exact copy.\r\nThe second implementation, reverse engineering revealed to be an almost identical implementation of @c5pider’s\r\nEkko code (and originally discovered by Peter Winter-Smith and used in MDSec’s Nighthawk). For example,\r\nconsider the following taken from Ekko:\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 33 of 43\n\nCompare this with the technique implemented inside Brute Ratel:\r\nAs you can see, the code is almost identical; indeed the few changes include replacing the WinApi calls for\r\nCreateTimerQueueTimer with the Rtl wrapper RtlCreateTimer, noting that the breakpoints for Rtl wrappers were\r\navoided (likely intentionally) in the aforementioned video demonstration.\r\nThis brings us to the third technique used by Brute Ratel which is a variation of timers and is not publicly\r\ndocumented. We can see here that this technique uses a subtle variation on timers and instead proxies the timer\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 34 of 43\n\nthrough RtlRegisterWait:\r\nWhile this technique is not publicly documented, it has been available in Nighthawk for some time, coincidentally\r\nwith the same values used for many of the constants. Further coincidences arise with other\r\nundocumented/unpublished features arising in the Brute Ratel v1.1 release.\r\nSo far, we have only discussed the sleeping techniques available in the x64 implementation of Brute Ratel.\r\nAnalysis of the x86 implementation shows that the obfuscate and sleep strategies are fixed to the aforementioned\r\nAPC Foliage based implementation (noting the breakpoints never hit):\r\nTo date there are no public or open source x86 implementations of obfuscate and sleep strategies that use timers,\r\nlimiting the available opportunities to easily integrate such code without custom development.\r\nIn Memory Detections\r\nOne of the updates in the v1.1 release implies that the .rdata section is now also obfuscated, in order to hide\r\nstrings such as “[+] AMSI Patched” which were exposed in the memory of the sleeping badger. However, even\r\ncursory memory analysis shows there remains many exposed strings within the memory of the sleeping badger. As\r\na result, this means there are many opportunities to pluck out Brute Ratel processes on an endpoint, even while the\r\nbadger is sleeping. For example, consider the Brute Ratel C2 data which is stored in a JSON format, simply\r\nsearching for one of its unique parameters in memory such as “chkin” will allow us to spot a badger:\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 35 of 43\n\nOr simply searching for the badger identifier (e.g. b-) will find them scattered all over both the heap and the stack.\r\nAs a bonus, this can act as simple mechanism to spot the thread that Brute Ratel is operating from, for example:\r\nHere we can see the presence of the “b-4\\” on the stack of thread 4344. We can confirm that is indeed the thread\r\nfor Brute Ratel from the UI:\r\nWith this in mind, we’re able to build a simple but effective Yara rule to pluck sleeping Brute Ratel processes\r\nfrom memory:\r\nrule brc4_badger_strings\r\n{\r\nmeta:\r\n author = \"@domchell\"\r\n description = \"Identifies strings from Brute Ratel v1.1\"\r\nstrings:\r\n $a = \"\\\"chkin\\\":\"\r\ncondition:\r\n $a\r\n}\r\nExecuting the Yara rule, we can spot the sleeping badger:\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 36 of 43\n\nThe detections documented in v1.0 for post-exploitation actions such as suspicious copy on write operations\r\nremain relevant and still offer an effective means of detection for BRC4 post-exploitation.\r\nThread Stack Spoofing\r\nIn the v1.0 release of Brute Ratel, as we noted the start address of the thread is hardcoded to\r\nntdll!TpReleaseCleanupGroupMembers+0x550. Version 1.1 proclaims to offer “full thread stack masquerading”.\r\nAnalysis of the stack spoofing for Brute Ratel reveals a simplistic implementation of rewriting the threads call\r\nstack. This process occurs just prior to the badger going to sleep, using the aforementioned timer technique. In an\r\nattempt to make the thread appear more legitimate, a new thread stack is created with hardcoded addresses for the\r\nfirst two frames. The addresses hardcoded are at offsets 0xa and 0x12 from RtlUserThreadStart and\r\nBaseThreadInitThunk respectively:\r\nWe were able to identify any other threads using these hardcoded start addresses, as such it becomes trivial to\r\nidentify any Brute Ratel threads on a system. To detect these threads, we updated BeaconHunter accordingly to\r\nidentify threads with the first two frames at RtlUserThreadStart+0xa and BaseThreadInitThunk+0x12:\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 37 of 43\n\nUpdated rDLL Extraction\r\nShortly after our analysis at x33fcon, Brute Ratel announced an update to the method in which the artifacts hide\r\nthe reflective DLL. Analysis of these artifacts revealed that this is achieved using RC4 to encrypt the reflective\r\nDLL with a random key; the PE header is then stomped. The 8 byte RC4 key is appended to the encrypted\r\nreflective DLL, followed by 400 bytes of base64 configuration file.\r\nWe developed the following tool targeting Brute Ratel v1.1 to extract the reflective DLL from DLL and EXE\r\nartifacts:\r\n//\r\n// only works with BRC4 1.1 binaries.\r\n//\r\n#include \u003calgorithm\r\n#include \u003cwindows.h\u003e\r\n#include \u003ccstdio\u003e\r\n#include \u003cstring\u003e\r\n#include \u003ciostream\u003e\r\n#include \u003cfstream\u003e\r\n#include \u003csstream\u003e\r\n#include \u003cvector\u003e\r\n#include \u003ciomanip\u003e\r\ntypedef struct _RC4_CTX {\r\n BYTE x, y;\r\n BYTE s[256];\r\n} RC4_CTX, *PRC4_CTX;\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 38 of 43\n\nstd::vector\u003cBYTE\u003e\r\nReadData(std::string path) {\r\n std::ifstream instream(path, std::ios::in | std::ios::binary);\r\n std::vector\u003cBYTE\u003e input((std::istreambuf_iterator\u003cchar\u003e(instream)), std::istreambuf_iterator\u003cchar\u003e());\r\n return input;\r\n}\r\nbool\r\nWriteData(std::string path, std::vector\u003cBYTE\u003e data) {\r\n std::ofstream outstream(path, std::ios::out | std::ios::binary);\r\n std::copy(data.begin(), data.end(), std::ostreambuf_iterator\u003cchar\u003e(outstream));\r\n return outstream.good();\r\n}\r\nBYTE\r\nstart_sig[]={\r\n#if defined(_WIN64)\r\n 0x55, 0x50, 0x53, 0x51, 0x52, 0x56, 0x57, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53, 0x41,\r\n 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, 0x48, 0x89, 0xE5, 0x48, 0x83, 0xE4, 0xF0, 0x48, 0x31,\r\n 0xC0, 0x50\r\n#else\r\n 0x60, 0x89, 0xE5, 0x83, 0xE4, 0xF8, 0x31, 0xC0, 0x50\r\n#endif\r\n};\r\nBYTE\r\nend_sig[]={\r\n#if defined(_WIN64)\r\n 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D, 0x41, 0x5C, 0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58,\r\n 0x5F, 0x5E, 0x5A, 0x59, 0x5B, 0x58, 0x5D, 0xC3\r\n#else\r\n 0x83, 0xC4, 0x10, 0x61, 0xC3\r\n#endif\r\n};\r\nvoid\r\nRC4_set_key(\r\n PRC4_CTX c,\r\n PVOID key,\r\n UINT keylen)\r\n{\r\n UINT i;\r\n UCHAR j;\r\n PUCHAR k=(PUCHAR)key;\r\n for (i=0; i\u003c256; i++) {\r\n c-\u003es[i] = (UCHAR)i;\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 39 of 43\n\n}\r\n \r\n c-\u003ex = 0; c-\u003ey = 0;\r\n \r\n for (i=0, j=0; i\u003c256; i++) {\r\n j = (j + (c-\u003es[i] + k[i % keylen]));\r\n UCHAR t = c-\u003es[i];\r\n c-\u003es[i] = c-\u003es[j];\r\n c-\u003es[j] = t;\r\n }\r\n}\r\nvoid\r\nRC4_crypt(\r\n PRC4_CTX c,\r\n PUCHAR buf,\r\n UINT len)\r\n{\r\n UCHAR x = c-\u003ex, y = c-\u003ey, j=0, t;\r\n for (UINT i=0; i\u003clen; i++) {\r\n x = (x + 1);\r\n y = (y + c-\u003es[x]);\r\n t = c-\u003es[x];\r\n c-\u003es[x] = c-\u003es[y];\r\n c-\u003es[y] = t;\r\n j = (c-\u003es[x] + c-\u003es[y]);\r\n buf[i] ^= c-\u003es[j];\r\n }\r\n c-\u003ex = x;\r\n c-\u003ey = y;\r\n}\r\nstd::vector\u003cBYTE\u003e\r\nextract_encrypted_rdll(PBYTE ptr, DWORD maxlen) {\r\n std::vector\u003cBYTE\u003e outbuf;\r\n printf(\"Searching %ld bytes.\\n\", maxlen);\r\n \r\n for (DWORD i=0; i\u003cmaxlen;) {\r\n if (!memcmp(\u0026ptr[i], end_sig, sizeof(end_sig))) {\r\n printf(\"Reached end of signature...\\n\");\r\n break;\r\n }\r\n #if defined(_WIN64)\r\n if ((ptr[i] \u0026 0x40) == 0x40 \u0026\u0026 (ptr[i+1] \u0026 0xB0) == 0xB0)\r\n {\r\n BYTE buf[8];\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 40 of 43\n\nbuf[0] = ptr[i + 9];\r\n buf[1] = ptr[i + 8];\r\n buf[2] = ptr[i + 7];\r\n buf[3] = ptr[i + 6];\r\n buf[4] = ptr[i + 5];\r\n buf[5] = ptr[i + 4];\r\n buf[6] = ptr[i + 3];\r\n buf[7] = ptr[i + 2];\r\n \r\n outbuf.insert(outbuf.end(), buf, buf + sizeof(buf));\r\n i += (ptr[i + 10] == 0x41) ? 12 : 11;\r\n } else i++;\r\n #else\r\n if ((ptr[i] \u0026 0xB0) == 0xB0 \u0026\u0026 (ptr[i+5] \u0026 0x50) == 0x50) {\r\n BYTE buf[4];\r\n \r\n buf[0] = ptr[i + 4];\r\n buf[1] = ptr[i + 3];\r\n buf[2] = ptr[i + 2];\r\n buf[3] = ptr[i + 1];\r\n \r\n outbuf.insert(outbuf.end(), buf, buf + sizeof(buf));\r\n i += 6;\r\n } else i++;\r\n #endif\r\n }\r\nstd::reverse(outbuf.begin(), outbuf.end());\r\n return outbuf;\r\n}\r\nint\r\nmain(int argc, char *argv[]) {\r\n if (argc != 2) {\r\n printf(\"usage: decrypt_brc4 \u003cDLL|EXE\u003e\\n\");\r\n return 0;\r\n }\r\n \r\n std::vector\u003cBYTE\u003e inbuf, infile = ReadData(argv[1]);\r\n DWORD len=0, ptr=0;\r\n \r\n if (infile.empty()) {\r\n printf(\"Nothing to read.\\n\");\r\n return 0;\r\n }\r\n \r\n do {\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 41 of 43\n\nauto dos = (PIMAGE_DOS_HEADER)infile.data();\r\n auto nt = (PIMAGE_NT_HEADERS)(infile.data() + dos-\u003ee_lfanew);\r\n auto s = IMAGE_FIRST_SECTION(nt);\r\n \r\n for (DWORD i=0; i\u003cnt-\u003eFileHeader.NumberOfSections; i++) {\r\n char Name[IMAGE_SIZEOF_SHORT_NAME + 1] = {0};\r\n memcpy(Name, s[i].Name, IMAGE_SIZEOF_SHORT_NAME);\r\n \r\n if (std::string(Name) == \".data\") {\r\n len = s[i].SizeOfRawData;\r\n ptr = s[i].PointerToRawData;\r\n break;\r\n }\r\n }\r\n \r\n if (!len) {\r\n printf(\"Unable to locate .data section.\\n\");\r\n break;\r\n }\r\n \r\n printf(\"Searching %ld bytes for loader...\\n\", len);\r\n \r\n for (DWORD idx=0; idx\u003clen - sizeof(start_sig); idx++) {\r\n if(!memcmp(infile.data() + ptr + idx, start_sig, sizeof(start_sig))) {\r\n printf(\"Found signature : %08lX\\n\", ptr + idx);\r\n inbuf = extract_encrypted_rdll(infile.data() + ptr + idx, len - idx);\r\n break;\r\n }\r\n }\r\n \r\n if (inbuf.size()) {\r\n printf(\"size : %zd\\n\", inbuf.size());\r\n RC4_CTX c;\r\n BYTE key[8+1] = {0};\r\n memcpy((char*)key, inbuf.data() + inbuf.size() - 400 - 8, 8);\r\n \r\n //\r\n // Decrypt RDLL. The additional 400 bytes are base64 configuration.\r\n //\r\n RC4_set_key(\u0026c, key, 8);\r\n RC4_crypt(\u0026c, inbuf.data(), inbuf.size() - 400);\r\n \r\n //\r\n // Fix DOS header.\r\n //\r\n inbuf[0] = 'M';\r\n inbuf[1] = 'Z';\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 42 of 43\n\nWriteData(std::string(argv[1]) + \".dll\", inbuf);\r\n }\r\n } while (FALSE);\r\n \r\n return 0;\r\n}\r\nConclusion\r\nIn summary, we’ve highlighted a number of techniques to detect Brute Ratel both in its artifacts, in-memory,\r\nthrough threat hunting and across the network. As this framework grows in popularity with threat actors, it is\r\nimportant to understand the many ways in which it can be detected. As a side note, we have also illustrated how\r\nthe framework takes close inspiration from the many available open source community tools; knowledge of these\r\ncan assist in reverse engineering the framework and provide a better understanding of its capabilities (and by\r\nvirtue its detection points).\r\nThis blog post was written Dominic Chell.\r\nReady to engage\r\nwith MDSec?\r\nStay updated with the latest\r\nnews from MDSec.\r\nSource: https://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nhttps://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/\r\nPage 43 of 43",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE",
		"Malpedia"
	],
	"references": [
		"https://www.mdsec.co.uk/2022/08/part-3-how-i-met-your-beacon-brute-ratel/"
	],
	"report_names": [
		"part-3-how-i-met-your-beacon-brute-ratel"
	],
	"threat_actors": [
		{
			"id": "5b748f86-ac32-4715-be9f-6cf25ae48a4e",
			"created_at": "2024-06-04T02:03:07.956135Z",
			"updated_at": "2026-04-10T02:00:03.689959Z",
			"deleted_at": null,
			"main_name": "IRON HEMLOCK",
			"aliases": [
				"APT29 ",
				"ATK7 ",
				"Blue Kitsune ",
				"Cozy Bear ",
				"The Dukes",
				"UNC2452 ",
				"YTTRIUM "
			],
			"source_name": "Secureworks:IRON HEMLOCK",
			"tools": [
				"CosmicDuke",
				"CozyCar",
				"CozyDuke",
				"DiefenDuke",
				"FatDuke",
				"HAMMERTOSS",
				"LiteDuke",
				"MiniDuke",
				"OnionDuke",
				"PolyglotDuke",
				"RegDuke",
				"RegDuke Loader",
				"SeaDuke",
				"Sliver"
			],
			"source_id": "Secureworks",
			"reports": null
		},
		{
			"id": "a241a1ca-2bc9-450b-a07b-aae747ee2710",
			"created_at": "2024-06-19T02:03:08.150052Z",
			"updated_at": "2026-04-10T02:00:03.737173Z",
			"deleted_at": null,
			"main_name": "IRON RITUAL",
			"aliases": [
				"APT29",
				"Blue Dev 5 ",
				"BlueBravo ",
				"Cloaked Ursa ",
				"CozyLarch ",
				"Dark Halo ",
				"Midnight Blizzard ",
				"NOBELIUM ",
				"StellarParticle ",
				"UNC2452 "
			],
			"source_name": "Secureworks:IRON RITUAL",
			"tools": [
				"Brute Ratel C4",
				"Cobalt Strike",
				"EnvyScout",
				"GoldFinder",
				"GoldMax",
				"NativeZone",
				"RAINDROP",
				"SUNBURST",
				"Sibot",
				"TEARDROP",
				"VaporRage"
			],
			"source_id": "Secureworks",
			"reports": null
		},
		{
			"id": "46b3c0fc-fa0c-4d63-a38a-b33a524561fb",
			"created_at": "2023-01-06T13:46:38.393409Z",
			"updated_at": "2026-04-10T02:00:02.955738Z",
			"deleted_at": null,
			"main_name": "APT29",
			"aliases": [
				"Cloaked Ursa",
				"TA421",
				"Blue Kitsune",
				"BlueBravo",
				"IRON HEMLOCK",
				"G0016",
				"Nobelium",
				"Group 100",
				"YTTRIUM",
				"Grizzly Steppe",
				"ATK7",
				"ITG11",
				"COZY BEAR",
				"The Dukes",
				"Minidionis",
				"UAC-0029",
				"SeaDuke"
			],
			"source_name": "MISPGALAXY:APT29",
			"tools": [
				"SNOWYAMBER",
				"HALFRIG",
				"QUARTERRIG"
			],
			"source_id": "MISPGALAXY",
			"reports": null
		},
		{
			"id": "20d3a08a-3b97-4b2f-90b8-92a89089a57a",
			"created_at": "2022-10-25T15:50:23.548494Z",
			"updated_at": "2026-04-10T02:00:05.292748Z",
			"deleted_at": null,
			"main_name": "APT29",
			"aliases": [
				"APT29",
				"IRON RITUAL",
				"IRON HEMLOCK",
				"NobleBaron",
				"Dark Halo",
				"NOBELIUM",
				"UNC2452",
				"YTTRIUM",
				"The Dukes",
				"Cozy Bear",
				"CozyDuke",
				"SolarStorm",
				"Blue Kitsune",
				"UNC3524",
				"Midnight Blizzard"
			],
			"source_name": "MITRE:APT29",
			"tools": [
				"PinchDuke",
				"ROADTools",
				"WellMail",
				"CozyCar",
				"Mimikatz",
				"Tasklist",
				"OnionDuke",
				"FatDuke",
				"POSHSPY",
				"EnvyScout",
				"SoreFang",
				"GeminiDuke",
				"reGeorg",
				"GoldMax",
				"FoggyWeb",
				"SDelete",
				"PolyglotDuke",
				"AADInternals",
				"MiniDuke",
				"SeaDuke",
				"Sibot",
				"RegDuke",
				"CloudDuke",
				"GoldFinder",
				"AdFind",
				"PsExec",
				"NativeZone",
				"Systeminfo",
				"ipconfig",
				"Impacket",
				"Cobalt Strike",
				"PowerDuke",
				"QUIETEXIT",
				"HAMMERTOSS",
				"BoomBox",
				"CosmicDuke",
				"WellMess",
				"VaporRage",
				"LiteDuke"
			],
			"source_id": "MITRE",
			"reports": null
		}
	],
	"ts_created_at": 1775434053,
	"ts_updated_at": 1775792237,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/95bfd86e06b503f3827af1514e98212d4d8518d8.pdf",
		"text": "https://archive.orkl.eu/95bfd86e06b503f3827af1514e98212d4d8518d8.txt",
		"img": "https://archive.orkl.eu/95bfd86e06b503f3827af1514e98212d4d8518d8.jpg"
	}
}