1. Introduction
The Brute Ratel C4 version 1.2.2 was cracked and publicly leaked on the internet. NinjaParanoid, the developer of Brute Ratel C4, posted on Twitter that it was MdSec who uploaded Brc4 to VT (VirusTotal), and then it was cracked by the Russian Molecules organization, causing the Brute Ratel C4 1.2.2 Scandinavian Defense to spread on the internet, and the Brute Ratel C4 1.2.5 leaked version to be circulated among a few people.
MdSec is a security company from the United Kingdom, mainly engaged in penetration testing against simulation. MdSec developed Nighthawk C2 for sale, and without a doubt, MdSec's Nighthawk and Brute Ratel C4 are competitive products.
It can be seen that the new Nighthawk Licensing fee is 7500 pounds or 10000 US dollars per user per year, and it is necessary to purchase at least 3 user licenses.
2. Sample IOCs
Name: bruteratel-1.2.2-pwn3rzs-cyberarsenal.7z
Size: 82818265 bytes (78.9 MiB)
MD5: 756bf6d0e21d9e8247a08352cd38dffd
SHA1: 10ae61b605a51a71dc87abb9c28d1e2567d9b32e
SHA256: d5b0c42ef9642dce715b252a07fc07ad9917bfdc13bd699d517b78210cc6ec60
3. Malicious Code Analysis
The directory structure of the leaked Brc4 1.2.2 version is as follows:
The Bruteratel team server has two versions: x64 and arm64. We use the file command to view the server, and through Go BuildId, we can judge that it is written in golang.
commander-runme is the graphical interface of the client, and commander-runme is a shortcut to /lib64/commander.
Commander is a graphical interface client written using Qt.
To start the team server, use -a to specify the username, -p to specify the password, -h to specify the service port, -sc to specify the cert.pem certificate, and -sk to specify the key.pem private key.
After that, we start the client connection, Brc4 has only two types of Listeners: HTTP Listener and DOH Listener.
Here we create an HTTP Listener. In my local test, I directly used the IP address, and the port and User-Agent used the default configuration. I added 3 URLs, Sleep Obfuscation used the default APC method, the sleep time is default, and the Common Auth authentication password I chose is randomly generated. If you choose One Time Auth authentication password, after the current Auth authentication password is used once, it will be removed, which means it only supports logging in once. If you check the 'Die if C2 is inaccessible' option, if the connection to C2 fails, it will automatically exit.
We can open the Payload Profiler option Add Payload Profile, and also add TCP and SMB payloads. These two types of payloads cannot be directly connected to the Bruteratel server without first being forwarded through an already online badger. They are generally used in scenarios of internal network lateral movement within the same domain.
To use TCP or SMB payloads, there must be a badger host already online through HTTP or DOH within the domain. Then, use the pivot_tcp command to create a TCP port listener on the online badger host to connect with the TCP payload. Use the pivot_smb command to connect to the SMB payload through a named pipe. Any command executed on the TCP or SMB badger host will be forwarded to the already online HTTP or DOH badger host, which will then forward it to the Bruteratel team server. We can see that b-21 is the SMB badger and b-22 is the TCP badger, and both of these badgers are forwarded through the b-20 HTTP badger. These two types of badgers need to be forwarded through the already online badger to communicate with the Bruteratel server. They are generally used in scenarios of internal network lateral movement within the same domain.
Next, we generate the payload, and here I will first analyze the Shellcode RtlExitUserThread under the Default of x64 architecture:
3.1 badger_x64_rtl.bin
3.1.1 badger shellcode load
Next, we use the following shellcode load code to analyze the shellcode:
DWORD dwOldProtect = 0;
OVERLAPPED ol = { 0 };
HANDLE hFile = CreateFileW(L"shellcode.bin", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
int fileSize = GetFileSize(hFile, NULL);
LPVOID lpShellCode = VirtualAlloc(NULL, fileSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
ReadFileEx(hFile, lpShellCode, fileSize, &ol, NULL);
CloseHandle(hFile);
VirtualProtect(lpShellCode, fileSize, PAGE_EXECUTE_READ, &dwOldProtect);
((void(*)())lpShellCode)();
WaitForSingleObject((HANDLE)-1, -1);
Using ida analysis, this is a characteristic of the shellcode start, after saving the register environment, through a large number ofmov reg, imm
,push reg
A large amount of data is combined and initialized on the stack.
Firstly, initialize 0x12C bytes of base64-encoded Brc4 configuration file on the stack, the size of the configuration file varies according to the Listener's configuration
Next, initialize the 0x39410 bytes of encrypted data on the stack
Next, the encrypted data on the stack and the base64 configuration file data will both be copied to the applied heap space
The main logic of the MemMoveAllocHeap function is to obtain the base address of ntdll after getting the base address, obtain the address of the RtlAllocateHeap function through ror13hash, apply for heap space to copy data to the heap space.
The function GetNdllBaseAddress gets the address of the _PEB_LDR_DATA structure and searches for the MZSignature flag by brute force, then compares if AddressOfNewExeHeader-0x40 is less than 0x3BF and the NtSignature flag is PE, then the base address of ntdll.dll is found.
The address of the RtlAllocateHeap function is obtained through ror13hash, and heap space is applied for to copy data to the heap space
Copy 0x39410 bytes of encrypted data to the heap space
The configuration file data is also copied to the heap space
Next, enter the main function
Debugging prevention is performed through the NtGlobalFlang field at the address PEB+0xBC. If the process is created by a debugger, it will set (FLG_HEAP_ENABLE_TAIL_CHECK (0x10) | FLG_HEAP_ENABLE_FREE_CHECK (0x20) |
The FLG_HEAP_VALIDATE_PARAMETERS (0x40) flag is also 0x70, and the debugger attachment will not set this flag.
After obtaining the ntdll base address, the address of RtlFreeHeap function is obtained through ror13hash, and the syscall id and syscall address of ZwProtectVirtualMemory function are also obtained.
The CheckinlineHookAndGetSyscallId function detects whether the function header bytes are 0xCC (int3 software breakpoint), and also detects whether the 1st and 4th bytes are 0xE9 (jmp) to check if the function is inlineHook. If the current function detects inlineHook, it will continue to detect the previous Ntxxxx function.
If the detection passes, the syscall id is obtained by comparing the opcode features. The syscall id of Windows needs to be stored using a WORD type (2 bytes), so the 5th and 6th bytes are combined into a WORD type (2 bytes) here.
We check the syscall id table of NT10 (Windows 10/11), and we can see that the syscall id of the same function may change under different system version numbers, and the size of the syscall id occupied by different functions is different, so a WORD (2 bytes) is needed for storage.
The opcode features 0x0F05 (syscall) and 0xC3 (ret) of the Ntxxxx function are searched to obtain the syscall address.
Then, the addresses of LdrGetDllHandleEx and LdrGetProcedureAddress functions are obtained.
The GetDllAddrOrGetDllFullPathByHash function determines whether to obtain the dll base address or the UNICODE_STRING FullDllName field based on the flag passed in through parameter 3.
Based on parameter 3, if the parameter 3 is 0, the dll base address is returned; if the parameter 3 is 1, the UNICODE_STRING FullDllName field is returned.
Then, the LdrGetDllHandleEx function is called to load Kernel32.dll and obtain the base address, followed by obtaining the syscall id and syscall address of ZwFlushInstructionCache.
Next, the Rc4Decrypt function is called to decrypt encrypted data of 0x39410 bytes using the rc4 algorithm.
The Rc4Decrypt function first acquires the function address of RtlExitUserThread, GetProcAddress, and LoadLibraryA through ror13hash.
rc4的密钥在加密数据的尾部的8字节也就是This rc4 key is randomly generated
}}
The rc4 key is in the last 8 bytes of the encrypted dataThis rc4 key is randomly generated
We open CyberChef and use the rc4 key
After decrypting the encrypted data from the memory, it is found to be a PE file with the MZSignature flag removed
We use Exeinfo to view the PE file dump and it is a Mingw compiled x64 dll
We check the decrypted data in the memory after decryption in x64dbg and it is the same as the one decrypted using CyberChef
After rc4 decryption, the 8 bytes at the end of the PE file are encrypted, at this time, the first 8 bytes of the 16-byte header are the decrypted rc4 key of the Brc4 configuration filef?zi\)*<
We use the rc4 key
After decrypting the Base64-encoded Brc4 configuration file, the decrypted Brc4 configuration file content can be seen
Next, call the ReflectiveDLLInjection function to invoke the decrypted badger core dll through reflective injection
Using the processhacker tool to view memory, we see that the virtual memory permission at 0x4D0000 has been changed to RW permission
Copy the first 0x400 bytes of the badger core dll in the heap to the RW permission memory at the address 0x4D0000, starting from here, we reuse the virtual memory applied for shellcode load to reflectively inject the badger core dll
Copy all sections of the badger core dll to RW memory
Fill the import table into RW memory
Fix the relocation data
Copy the Base64-encoded configuration file's rc4 key to the allocated heap space
Modify the memory permissions of each section
Release the heap memory used to store the decrypted badger core dll
Call ZwFlushInstructionCache to refresh the code cache
Then call the badger core dll main through call rax
3.1.2 badger core dll
In the main function of the badger core dll, the FreeConsole function is called to hide the console window, and some global variables used to store the dll base addresses are initialized.
The GetSomeDllFunAddress function retrieves the addresses of the functions needed in ntdll.dll, kernel32.dll, kernelbase.dll, advapi32.dll, crypt32.dll, and ws2_32.dll.
The function address in ntdll.dll is obtained through ror13hash
The GetWs2_32DllFunAddress function uses the rc4 keybYXJm/3#M?:XyMBF
The decryption of the ws2_32.dll string is obtained by the GetDllBaseAddress function to get the dll base address and by ror13hash to get the function address.
The GetDllBaseAddress function first calls the GetDllAddrOrFullPathByHash function to obtain the dll base address. If the acquisition fails, it will call the RtlRegisterWait function to execute the LoadLibraryA function through the worker thread of the thread pool, set the CALLBACKFUNC to the address of the LoadLibraryA function, set parameter 4 to the corresponding dll string to be loaded, and then wait for the event object execution through the WaitForSingleObject function. When the LoadLibraryA function loads the corresponding dll into the current process memory, it calls the GetDllAddrOrFullPathByHash function to obtain the base address of the corresponding dll loaded into memory.
The loading of dll through the RtlRegisterWait function is executed by the worker thread of the thread pool to execute the LoadLibraryA function, so it can achieve the effect of hiding the call stack, and can prevent EDR/AV from judging whether the call is malicious by tracing back the LoadLibraryA call stack.
Then, a thread is created through the SyscallZwCreateThreadEx function, the thread CONTEXT is obtained through the SyscallNtGetContextThread function, the thread execution function is set to the address of the ThreadMain function through the SyscallNtSetContextThread function, and the thread execution is resumed through the SyscallNtResumeThread function.
Next, let's look at the ThreadMain function
The initKeyAndFormatInfo function was called first
The initBrc4EncryptAlgorithmArray function initializes 9 arrays used by the custom encryption algorithm of Brc4, each array has 256 elements, and the initialization method is to reduce all elements within the array by 1. For the custom encryption algorithm of Brc4, you can go to mygithubCheck the algorithm I reverse-engineered.
We check the uninitialized ArrayBox1
The elements of the initialized ArrayBox1 are reduced by 1
Next, some formatting characters are initialized for displaying the basic information of the logged-in badger in the commander interface
After decoding Base64, use the rc4 keyf?zi\)*<
Decrypt the Brc4 configuration file
The configuration file is through|
The symbol (0x7C) is used for separation, so it will be judged with 0x7C and parsed field by field
After parsing, some fields will call the AsciiToHexadecimal function to convert the parsed configuration file fields from Ascii to hexadecimal
Next, the GetSystemInfo function retrieves the basic system information for the login package
The WSAStartup function initializes WinSock version 2.2
Next, the attribute of the memory address 0x511000 is queried
Let's take a look at the current memory, the virtual memory permission of 0x511000 is RX, this memory is used to run the code of the reflective injection badger core dll before
Change the memory attribute of 0x511000 to PAGE_READWRITE and clear the memory
Let's take a look at the current memory structure, the memory attribute of 0x511000 is changed to PAGE_READWRITE and the memory is cleared
Next, send the login package to C2 first
Firstly, obtain the address of the exported function of Wininet.dll through the ror13 hash method
Format the login package
Encrypt the login package using Brc4's custom encryption algorithm
After encryption, the login package is encoded using Base64
Then, the encrypted login package will be sent to C2
Firstly, set User-Agent
Set the domain name and port
Set the request method and request path
Send a POST request
Query whether there are tasks in the C2 task queue
Read the data returned by the C2 server if there are tasks in the task queue
base64 decoding
Decrypted using Brc4's custom encryption algorithm, the data returned here is used for heartbeat packets, b-xx represents the number of badgers logged in on the current Listeners, starting from b-0\\
The following are b-cookie, these two data are used to determine the uniqueness of the currently logged in badger
After that, the initCallbackFun function is called to initialize the built-in exploit command function call table of Brc4, using 2 arrays to store function call addresses and function call IDs in sequence
This is the built-in exploit command and corresponding function call ID of Brc4, excluding the 3 commands (help, cls, title) used for the C2 Commander graphical interface, there are a total of 131 commands for interacting with Badger, and the function call IDs of 2 commands are the same
call ID | command |
---|---|
0x3C9F | pwd |
0xD53F | arp |
0x4FFE | userinfo |
0x391 | lockws |
0x609 | lsdr |
0xA01 | uptime |
0xB06 | idletime |
0x703 | exit_process |
0x605 | revtoken |
0x105 | dumpclip |
0xC144 | drivers |
0x2919 | list_downloads |
0xA217 | get_parent |
0x1719 | set_debug |
0x4318 | tasks |
0x5921 | get_child |
0x6154 | psclean |
0x9C41 | screenshot |
0xBA9D | list_tcppivot |
0xA63C | clear_parent |
0x3BA8 | clear_child |
0x71C6 | get_argument |
0x93D6 | clear_argument |
0xE3CB | dcenum |
0x8289 | get_malloc |
0x8146 | get_threadex |
0xF616 | ipstats |
0x4A9E | dll_block |
0xB3E4 | dll_unblock |
0x3793 | get_wmiconfig |
0x2698 | reset_wmiconfig |
0x44B7 | token_vault |
0xED33 | vault_clear |
0x803 | exit_thread |
0x4395 | get_killdate |
0x4934 | shadowcloak |
0xB339 | netstat |
0xD41A | routes |
0xBE9A | local_sessions |
0x38B7 | dnscache |
0xB6A3 | getenv |
0x5248 | sysinfo |
0x6135 | windowlist |
0x73E8 | applist |
0xD9A3 | crisis_monitor |
0xD359 | socks_start, socks_profile_start |
0xD959 | socks_stop |
0x2DA1 | keylogger |
0x2129 | sleep |
0x1139 | cd |
0xA905 | cp |
0x9B84 | mv |
0xE993 | rm |
0x3F61 | mkdir |
0x8F40 | rmdir |
0xA32 | ls |
0xA959 | net |
0xF584 | runas |
0xF999 | make_token |
0xE9B0 | run |
0xEBC0 | kill |
0xBED0 | shellspawn |
0x9DE0 | ps |
0x8AFA | set_parent |
0x6BAE | get_system |
0x6F39 | system_exec |
0xF3D9 | psreflect |
0x3FD4 | loadr, mimikatz |
0x2C74 | download |
0x6C36 | reg |
0xC929 | set_child |
0xB458 | scquery |
0xE2EA | psimport |
0x13A1 | upload |
0x699A | pivot_tcp |
0x73E6 | set_argument |
0x3C4D | pivot_smb |
0xFE37 | psexec |
0x97E9 | sccreate |
0xFA73 | scdelete |
0x3B3E | scdivert |
0x5962 | set_malloc |
0x5761 | set_threadex |
0xC662 | psgrep |
0xE591 | portscan |
0x9881 | dcsync |
0x4953 | netshares |
0x4355 | set_wmiconfig |
0x5213 | wmiquery |
0x81E7 | grab_token |
0xF856 | impersonate |
0xCB46 | vault_remove |
0x4932 | coffexec |
0x6492 | list_modules |
0x2133 | memhunt |
0x7348 | suspended_run |
0x8491 | set_killdate |
0x8044 | sharpinline |
0x3456 | scstart |
0xB98E | query_session |
0x7579 | sentinel |
0xB99A | passpol |
0xB69A | schtquery |
0x29B3 | sharescan |
0xE4A9 | shinject_ex |
0xD8F3 | ps_ex |
0xB6BF | switch_profile |
0xB3A9 | timeloop |
0xE19A | preview |
0xA657 | lookup |
0xA5F1 | memdump |
0xD163 | addpriv |
0xE53A | fileinfo |
0xB1D3 | wmiexec |
0xF83E | lstree |
0xE4B9 | kerberoast |
0xB93A | icmp_ping |
0xDA9C | phish_creds |
0x34AE | start_address |
0xCDE4 | threads |
0xE1BA | phantom_thread |
0xF2ED | stop_task |
0xD2E5 | obfsleep |
0x4A83 | socks_profile |
0x3BD8 | memhook |
0xA23B | samdump |
0xE3D2 | sharpreflect |
0xA7D9 | set_coffargs |
0xD8A9 | clear_coffargs |
Enter the help command in the C2 Commander graphical interface to obtain all built-in commands, but there is no command corresponding to 0xF83E. By reverse engineering brute-ratel-linx64, which is the TeamServer end of C2, the corresponding command is lstree, which may still be under development and cannot be used
Format the heartbeat packet with the unique ID data returned by C2
Use Brc4 custom encryption algorithm to encrypt the heartbeat packet
Use base64 encoding
Send a heartbeat packet to C2
After entering the sleep obfuscation function, use the SystemFunction036 function to generate a 16-byte pseudo-random number for encrypting the rc4 key of the heap space data
The Rc4CryptDecryptHeapData function encrypts all the heap space data used with the randomly generated rc4 key
Select the corresponding sleep obfuscation function according to the sleep obfuscation method selected in the configuration file, we configure it as APC mode
Create a fiber execution sleep obfuscation function
Switch to fiber execution
Firstly, allocate heap space of the size of the CONTEXT structure (0x4D0) for the ROP chain, and use SystemFunction036 to generate a 16-byte pseudo-random number for encrypting the rc4 key of the badger core dll memory in sleep.
Then call the SetProcessValidCallTargets function to disable the CFG (Control Flow Guard) protection for several functions used by the ROP chain, which was included in the Windows 8.1 UPDATE (KB3000850) patch and can prevent the indirect execution of arbitrary code in the program. Enabling the /guard:cf flag in the VS compiler can enable it.
In self-written load programs, CFG protection is generally not enabled during compilation. However, considering that shellcode needs to be injected into the system process space for execution, and system processes are mostly compiled with CFG protection enabled, compatibility considerations require that the CFG protection of the functions used in the ROP chain be disabled.
An Event object is created, a suspended thread is created, and the address of TpReleaseCleanupGroupMembers+0x450 is set as the thread starting address. The ContextFlags of the ROP chain CONTEXT structure are filled with CONTEXT_FULL (0x10000B).
The CONTEXT of the created suspended thread is obtained and copied to the CONTEXT of the ROP chain.
Then, a fake thread stack context is constructed using the NtWaitForWorkViaWorkerFactory, BaseThreadInitThunk, and RtlUserThreadStart functions.
Subsequently, the CONTEXT structure contexts of the ROP chain functions ZwWaitForSingleObject, ZwProtectVirtualMemory, SystemFunction032, and ZwGetContextThread are filled. The ZwTestAlert function is used to immediately execute the suspended APC callback in the thread APC queue.
The CONTEXT structure contexts of the ROP chain functions ZwSetContextThread, WaitForSingleObjectEx, SystemFunction032, and ZwGetContextThread are filled.
The NtQueueApcThread function is used to create an APC queue, with the ZwContinue function as the callback function to execute the ROP chain CONTEXT inserted into the APC queue. The ZwAlertResumeThread function is then used to resume thread execution, and the last block of heap memory is encrypted using the rc4 algorithm. The NtSignalAndWaitForSingleObject function waits for the current process while maintaining unalertability through a notification event.
The prototype of the ZwContinue function is as follows, with the first parameter being a pointer to the CONTEXT structure.
ROP chain starts executing at this point, with RIP pointing to the ZwContinue function and RCX pointing to the ZwWaitForSingleObject function's CONTEXT structure. ZwWaitForSingleObject waits for the current process object as an unalertable object.
NtProtectVirtualMemory modifies the current badger core dll memory permission to PAGE_READWRITE.
SystemFunction032 encrypts the badger core dll memory using the previously randomly generated 16-byte pseudo-random number with the rc4 algorithm
ZwGetContextThread retrieves the current executing ROP chain thread's CONTEXT structure context
ZwSetContextThread sets the fake call stack to the ROP chain thread.
Sleep and wait using the sleep time set in the configuration file through WaitForSingleObjectEx.
When the badger core dll is in sleep, the memory permission is changed to RW and all are encrypted.
The call stack of the欺骗 thread has a fixed function offset, RtlUserThreadStart+0x21, BaseThreadInitThunk+0x14, TpReleaseCleanupGroupMembers+0x747, ZwWaitForWorkViaWorkerFactory+0x14.
After the sleep is completed, the SystemFunction032 function is called to decrypt the badger core dll memory using the rc4 algorithm.
The NtProtectVirtualMemory function changes the memory permission of the badger core dll to PAGE_EXECUTE_READ.
ZwSetContextThread restores the CONTEXT structure context of the ROP chain thread.
RtlExitUserThread exits the current thread and ends the ROP chain.
After the operation is completed, release the heap space of the ROP chain, close the handle, and then switch to Fiber using the SwitchToFiber function.
Brc4 supports 3 types of sleep obfuscation methods. We can switch the sleep obfuscation method using the obfsleep command. obfsleep 0 is the APC method analyzed just now, obfslep 1 and obfsleep 2 correspond to Poling-0 and Poling-1, respectively. Next, we will analyze the sleep obfuscation of the Poling method. First, allocate heap space for the ROP chain's CONTEXT structure (0x4D0 bytes) and use the SystemFunction036 function to generate a 16-byte pseudo-random number for encrypting the rc4 key of the badger core dll memory during sleep.
Then disable the CFG protection of the functions used in the ROP chain and create an Event object. Here, the IsPoling flag is used to determine whether RtlCreateTimer function is used when obfslep 1 is used, or RtlRegisterWait function is used when obfsleep 2 is used. The callback function RtlCaptureContext is used to obtain the CONTEXT structure of RtlCreateTimer or RtlRegisterWait function.
Then copies the CONTEXT structure context obtained by the RtlCaptureContext callback to the CONTEXT of the ROP chain
Constructs a false thread call stack CONTEXT structure context using the NtWaitForWorkViaWorkerFactory, BaseThreadInitThunk, RtlUserThreadStart functions
Then fills the CONTEXT structure context of the ROP chain functions VirtualProtect, SystemFunction032, ZwGetContextThread, ZwSetContextThread, WaitForSingleObject, RSP-8 is used to adjust the offset caused by the RtlCaptureContext callback function call
Fills the CONTEXT structure context of the ROP chain functions ZwSetContextThread, SystemFunction032, VirtualProtect, NtSetEvent
Judges the choice of creating a timer queue with RtlCreateTimer function or registering a wait handle with RtlRegisterWait function to create the ROP chain through the IsPooling flag, uses ZwContinue as the callback function to execute the ROP chain's CONTEXT, the parameter 5 is used to adjust the time interval between ROP chain calls, then encrypts the last block of heap space data with the rc4 algorithm, and waits for the Event object with WaitForSingleObject.
The VirtualProtect function changes the memory permissions of the badger core dll to PAGE_READWRITE
SystemFunction032 encrypts the badger core dll memory using the previously randomly generated 16-byte pseudo-random number with the rc4 algorithm
ZwGetContextThread retrieves the current executing ROP chain thread's CONTEXT structure context
ZwSetContextThread sets a false call thread stack to the ROP chain thread
Sleeps for the duration specified in the configuration file using WaitForSingleObject
ZwSetContextThread restores the execution of the current ROP chain thread's CONTEXT structure context
The SystemFunction032 function decrypts the badger core dll memory using the rc4 algorithm
The VirtualProtect function changes the memory permissions of the badger core dll to PAGE_EXECUTE_READ
The ZwSetEvent function sets the state of the Event object to Signaled
After the end, release the ROP chain memory, close the handle, and switch back to Fiber
Next, analyze the post-exploitation command call of Brc4, we use the mkdir testdir command to test. After sending the heartbeat packet to C2, we query if there is a task in the task queue. If there is a task, it will be parsed. The first layer is base64 encoding, the second layer is decrypted by the Brc4 custom encryption algorithm, and the third layer is base64 decoding, which becomes the actual command call data
We check the decrypted data, 0x3f61 is the function call ID of the mkdir command, 0x20 is the delimiter, and testdir is the parameter of our mkdir command
Next, copy the function call ID and parameters separately to the heap space
Create a task thread to execute the command
Enter TastThread and call the CheckCurrentCallbackFunid function to check if the current function call ID is at the index of the current function call ID table
The function call ID occupies 2 bytes, so the current function call ID and the function call ID in the current index of the function call table are compared separately. If both bytes are the same, the function returns TRUE, indicating a successful comparison
If the current function call ID is not at the index of the current function call ID table, the index will be incremented by 1, and the address of the function call ID table array will be incremented by 3, and the next function call ID in the function call ID table will be compared in a loop
If the ID comparison is successful, make the call throughcall qword ptr [rax+rsi*8]
Make the call, where rax is the first address of the function call address table, and rsi is the index of our current function call ID in the function call ID table
Enter the mkdir function and use the CreateDirectoryA function to create the testdir file
If the folder is created successfully, format the return information string
The formatted return message is encoded with base64 and then encrypted to wait for sending the data to C2
We check the information returned by the COMMAND interface
3.2 badger_x64_stealth_rtl.bin
In the previous section, we analyzed the shellcode of the Exit Method: RtlExitUserThread under Default and the badger core dll payload. Next, we analyze the Exit Method: RtlExitUserThread shellcode under Stealth. We mainly analyze the differences of Stealth and the code that is the same as Default, which will not be analyzed again. Compared to Default's shellcode, Stealth mainly adds the FixDllMemoryHook function
Get the UNICODE_STRING FullDllName of kernelbase.dll
Get the UNICODE_STRING FullDllName of ntdll.dll
After calling the ReadDiskDllFixMemoryHook function, first\??\
Concatenated with FullDllName
The NtOpenFile function opens the dll file handle
If the file is opened successfully, call the NtReadFile function to read the dll into the allocated heap
After parsing the PE file to obtain the .text section addresses of disk dll and memory dll, and changing the memory permission of the .text section of the memory dll to PAGE_EXECUTE_READWRITE
Cover the .text section of the disk dll with the .text section of the memory dll
After changing the memory permission of the .text section of the memory dll to PAGE_EXECUTE_READ, call the GetDiskDllRdataSectionInfo function
The function GetDiskDllRdataSectionInfo gets the .rdata section addresses of disk dll and memory dll by parsing the PE file
Change the memory permission of the .rdata section of the memory dll to PAGE_READWRITE
Cover the .rdata section of the disk dll with the .rdata section of the memory dll
Then call ReadDiskDllFixMemoryHook on kernel32.dll and kernelbase.dll for UnHook
Then call ZwFlushInstructionCache to refresh the code cache
Compared to the Default shellcode, Stealth mainly adds a FixDllMemoryHook function, which mainly reads the .text section and .rdata section of ntdll.dll, kernel32.dll, and kernelbase.dll from the hard disk, and covers the .text section and .rdata section in memory that may be hooked by security software such as AV or EDR
3.3 stage_x64_rtl.bin
The features of the stage shellcode analyzed by ida are the same as those of the badger shellcode
Encrypted data of size 0xC4 initialized on the stack, with the last 8 bytes being the rc4 key
Decryption with CyberChef reveals it to be a configuration file
Both the payloads of stage and stealth have the FixDllMemoryHook function used for Unhooking ntdll.dll, kernel32.dll, and kernelbase.dll
Use the rc4 algorithm keybYXJm/3#M?:XyMBF
Decrypt the wininet.dll string and then call the GetDllBaseAddressRtlRegisterWait function to load the dll and obtain the base address
Firstly, create the Event object
The RtlRegisterWait function calls the LoadLibraryA function through the worker threads of the thread pool, with parameter 3 being the address of LoadLibraryA and parameter 4 being the wininet.dll string
Then wait for the Event object with WaitForSingleObject
Call the GetDllBaseAddressOrGetDllFullPath function to obtain the base address of winnet.dll loaded into memory
Then obtain the addresses of the functions needed in winnet.dll through ror13hash
Use the rc4 algorithm keybYXJm/3#M?:XyMBF
Decrypt the crypt.dll string and then load the base address of the dll
Then obtain the address of the CryptBinaryToStringA function through ror13hash
Use the rc4 algorithm key>s?un&>)
The decryption of the configuration file is the same as the one we just decrypted with CyberChef, and this rc4 key is randomly generated
Then call the ParsingStageConfiguration function to parse each field of the configuration file individually
After parsing the configuration file, call the GetHttpBadgerShellcode function to obtain the next stage Badger Shellcode
Format the authentication package through the auth key in the configuration file
Use the rc4 algorithm key>s?un&>)
Encrypt the authentication package
Use base64 encoding to encrypt the authentication package
Call the InternetOpenA function to set User-Agent
InternetConnectA sets the domain name and port
HttpOpenRequestA sets the request method and path
HttpAddRequestHeadersA attaches the rc4 algorithm encryption key to the HTTP request header
The HttpSendRequestA function sends the specified request to the C2 server
The InternetQueryDataAvailable function queries the size of the data returned by the C2 server
InternetReadFile reads the data returned by the C2 server
Use the rc4 algorithm key>s?un&>)
Decrypt the data returned by the C2 server
The ZwAllocateVirtualMemory function is used to allocate virtual memory with PAGE_READWRITE permissions
The Badger Shellcode is copied to the newly allocated virtual memory
ZwProtectVirtualMemory is called to change the memory permissions of the Badger Shellcode to PAGE_EXECUTE_READ
The Badger Shellcode is called, as the Badger Shellcode has been analyzed before, so there is no need to continue the analysis here
Using BinDiff, it is found that the shellcode obtained by stage is badger_x64_stealth_ret
3.4 badger_x64.dll
Using Exeinfo, it can be seen that badger_x64.dll is an x64 dll compiled with MinGW-w64 and also has a large .data section
Next, analyzing the dll main, mainly through ror13hash to obtain the base address of ntdll, then obtain the syscall id and syscall address of NtProtectVirtualMemory, ZwAllocateVirtualMemroy, ZwWaitForSingleObject, ZwCreateThreadEx functions
ZwAllocateVirtualMemory allocates virtual memory with PAGE_READWRITE permissions for the shellcode, copies the shellcode in the .data section to the allocated virtual memory, clears the shellcode in the .data section, and then changes the virtual memory permissions of the shellcode to PAGE_EXECUTEREAD
We can see that the Badger Shellcode in the .data section is not encrypted at all
After ZwCreateThreadEx creates a thread to execute Badger Shellcode, it can be concluded here that badger_x64.dll is actually a loader used to load Badger Shellcode
Using BinDiff, it can be seen that the shellcode used by badger_x64.dll is badger_x64_ret
3.5 badger_x64_svc.exe
Using Exeinfo, it can be seen that badger_x64_svc.exe is also a 64-bit program compiled with MinGW-w64 and also has a large .data section
Here, the base address of ntdll.dll is obtained not through ror13hash but directly through brute force search
Then, obtain the syscall id and syscall address of the functions NtProtectVirtualMemory, ZwAllocateVirtualMemroy, ZwWaitForSingleObject, and ZwCreateThreadEx through ror13hash.
Next, the same operation is performed: copy the Badger Shellcode from the .data section to the allocated virtual memory, clear the shellcode in the .data section, change the virtual memory permissions to PAGE_EXECUTEREAD, and then call ZwCreateThreadEx to create a thread and execute.
Using BinDiff comparison, it is found that the shellcode used by badger_x64_svc.exe is also badger_x64_ret.
3.6 badger_x64_stealth_svc.exe
Using Exeinfo, it is found that badger_x64_stealth_svc.exe is a 64-bit program compiled with MinGW-w64, and it also has a large .data section. According to this feature, it can be known that the main logic also loads and executes the shellcode from the .data section.
The main logic of badger_x64_stealth_svc is the same as that of badger_x64_svc, and no further elaboration is needed.
After comparing with BinDiff, it is found that the shellcode used by badger_x64_stealth_svc is badger_x64_stealth_ret.
4. Summary
This article analyzes in detail all the Badger Payloads of the x64 architecture of the leaked Brute Ratel C4 Scandinavian Defense 1.2.2 HTTP Listener. As of the time of writing, Brc4 has been updated to the 1.4 Resurgence version. The developers have improved the Bruteratel team server and shellcode core, such as removing the always-used ror13 hash algorithm, changing the storage method of the configuration file from base64 encoding to binary, and removing the rc4 key used to decrypt dll strings in the badger core dll.bYXJm/3#M?:XyMBF
The deletion of all command output strings used in the badger core dll will be directly formatted and output by the Bruteratel server, etc. Due to space limitations, the next article will introduce the detection of badger core dll and Bruteratel team server in sleep mode.

评论已关闭