This post is a follow up on the last one on BAZARLOADER. If you’re interested in how to unpack the initial stages of this malware, you can check it out here

In this post, we’ll cover the final stage of this loader, which has the capability to download and executes remote payloads such as Cobalt Strike and Conti ransomware. To follow along, you can grab the sample as well as the PCAP files for it on Malware-Traffic-Analysis.net.

Step 1: Checking System Languages

Similar to a lot of malware, BAZARLOADER manually checks the system’s languages to avoid executing on machines in Russia and nearby countries.

It calls GetSystemDefaultLangID to retrieve the system’s default language and GetKeyboardLayoutList to iterate through the system’s keyboard layouts.

For each of these languages, the malware checks if it’s valid using a bitmask.

If the language identifier is greater than 0x43 or less than 0x18, it’s treated as valid and BAZARLOADER proceeds with its execution.

If it’s in the range between 0x18 and 0x43, the difference between the language identifier and 0x18 is used as the index of the bit to be checked in the bitmask.

The bitmask that BAZARLOADER uses is 0xD8080190C03, which is 11011000000010000000000110010000110000000011 in binary. The first bit in the bitmask is checked if the language ID is 0x18. The second bit is checked if the language ID is 0x19, and so on…

Below is the list of all languages from the bitmask that the malware avoids.

Romanian, Russian, Ukrainian, Belarusian, Tajik, Armenian, Azerbaijani, Georgian, Kazakh, Kyrgyz, Turkmen, Uzbek

Step 2: Run-Once Mutex

To check for multiple running instances of itself, BAZARLOADER first extracts the subauthority of a SID from its process. It does this by calling GetTokenInformation to retrieve the process’s token integrity level and calling GetSidSubAuthorityCount and GetSidSubAuthority to access the subauthority of a SID.

If the SID’s subauthority is SECURITY_MANDATORY_SYSTEM_RID or SECURITY_MANDATORY_PROTECTED_PROCESS_RID, BAZARLOADER checks if the mutex “{b837ef4f-10ee-4821-ac76-2331eb32a23f}” is currently owned by any other process by calling CreateMutexA.

If it is, the malware terminates itself. However, there is a small bug with the condition to check if the mutex object exists, which assumes it fails to open the mutex when it actually succeeds.

After this, the malware resolves the string “{0caa6ebb-cf78-4b01-9b0b-51032c9120ce}” and tries to create a mutex with that name.

If this mutex object already exists, the malware also terminates itself.

If the SID’s subauthority is not SECURITY_MANDATORY_SYSTEM_RID or SECURITY_MANDATORY_PROTECTED_PROCESS_RID, BAZARLOADER still uses these two mutex names but adds the string “Global” in front of them. This checks for the mutexes in the global namespace instead of the per-session namespace, which allows the malware to check if it has instances running in other users’ sessions.

Step 4: Generating Random Internet Traffic

To generate Internet activities to hide its communication with C2 servers, BAZARLOADER first calls InternetOpenA to initialize the use of WinINet functions with the following string as the HTTP user agent.

Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko

The malware then spawns a thread to periodically connect to random URLs and generate noises to hide the main C2 traffic by utilizing the following structure.

struct random_internet_thread_struct
{
    HINTERNET internet_sess_handle;
    HANDLE thread_handle;
    random_internet_thread_struct *self;
    LPCRITICAL_SECTION critical_section;
    __int64 padding[4];
    int creation_flag;
};

First, BAZARLOADER calls InitializeCriticalSection to initialize the structure’s critical section object, which is later used to protect accesses to the creation_flag field.

Next, it sets the self field to point to the structure, the creation_flag field to TRUE, and calls CreateThread to spawn a thread to perform these random Internet operations. If it fails to create a thread, the creation_flag field is set to FALSE.

The thread first tries to obtain ownership of the critical section object and check if the creation flag is enabled. If it is, the malware resolves the following URLs as stack strings.

https://google.com/api/get
https://yahoo.com/api/get
https://amazon.com/api/get
https://bing.com/api/get

Next, the thread enters an infinite loop to start generating the traffic noises. For random number generation, BAZARLOADER uses different functions that call the Windows API BCryptGenRandom to generate a set number of random bytes.

It randomly chooses one of the 4 URLs listed above, randomly generates the URL path segments for that, and combines the two to build the full URL.

To generate the path segments, the function takes in the minimum and maximum numbers of path segments to generate and the minimum and maximum length for each path segment.

It generates a count for the path segments randomly in the given range. For each of the segments, the malware randomly generates a string with a random length in the given range that contains numbers and uppercase/lowercase letters.

Finally, the malware calls InternetOpenURLA to establish a connection with the generated URL. It calls HTTPQueryInfoA with the HTTP_QUERY_CONTENT_LENGTH flag to retrieve the content’s length, allocates a buffer with that size, and calls InternetReadFile to read data from that URL.

This is done repeatedly until C2 communication and payload injection are finished, which generates a lot of noise to mask the main traffic coming to and from C2 servers.

Step 4: Cryptographic Structure Population

BAZARLOADER mainly uses the following structure for communication with C2 servers. The fields of the structure will be explained as we go along analyzing the code.

struct __declspec(align(8)) BazarLoader_struct
{
    C2_connection_struct C2_connection_struct;
    HINTERNET C2_request_handle;
    HINTERNET C2_temp_request_handle;
    crypto_struct crypto_struct;
    SYSTEMTIME curr_system_time;
    char *datetime_string;
    _QWORD datetime_string_hash;
    unsigned int *datetime_string_hash_len;
    opennic_server_struct opennic_DNS_server_struct;
    string_struct_list C2_addr_list;
};

First, it populates the crypto_struct field in the main structure. This structure contains cryptographic handles that are later used to decrypt executables being sent from C2 servers.

The structure can be reconstructed as below.

struct crypto_struct
{
    BCRYPT_ALG_HANDLE RSA_algo_handle;
    BCRYPT_ALG_HANDLE SHA384_algo_handle;
    BCRYPT_KEY_HANDLE RSA_public_key_handle;
    BCRYPT_KEY_HANDLE RSA_private_key_handle;
    DWORD RSA_public_block_length;
    DWORD RSA_private_block_length;
};

The malware resolves the strings “RSA” and “SHA384” and calls BCryptOpenAlgorithmProvider to retrieve handles for these two algorithms. The handles are stored in the corresponding fields in the crypto_struct structure.

Next, it resolves its hard-coded RSA public and private key blobs in memory to import their corresponding key handles.

For each blob, the malware resolves one of the strings “RSAFULLPRIVATEBLOB” or “RSAPUBLICBLOB” and uses it to specify the blob’s type when calling BCryptImportKeyPair to import the corresponding key handle.

Finally, it calls BCryptGetProperty to retrieve the length of the RSA public and private cipher blocks. With this structure fully populated, BAZARLOADER can now perform RSA encryption/decryption as well as SHA384 hashing.

Step 5: C2 Connection Through Raw IP Addresses

Prior to communicating with C2 servers, BAZARLOADER first resolves a list of raw IP addresses and writes them into the C2_addr_list field in the main structure.

This field is a structure representing a list of string structures, both of which can be reconstructed as below.

struct string_struct
{
    char *buffer;
    char *length;
    char *max_length;
};

struct string_struct_list
{
    string_struct *list_ptr;
    __int64 count;
    __int64 max_count;
};

Below is the list of all IP addresses for the C2 servers used in this sample.

https://5[.]182[.]207[.]28:443
https://80[.]71[.]158[.]42:443
https://198[.]252[.]108[.]16:443
https://84[.]32[.]188[.]136:443

For each of these addresses, the malware attempts to communicate with the corresponding server and download the next stage executable.

To establish a connection, it populates the following structure.

struct C2_connection_struct
{
    URL_COMPONENTSA C2_URL_components;
    HINTERNET connection_handle;
    __int64 connection_last_error;
};

The malware calls InternetCrackUrlA to retrieve the C2’s URL components and InternetConnectA to connect to the server.

This connection structure’s fields are then copied into the main structure’s C2_connection_struct. Here, I’m not entirely sure why they don’t just populate the main structure directly instead.

Similarly, BAZARLOADER populates the structure below to create an HTTP request to C2. The request’s object name and HTTP verb are resolved to be “/data/service” and “GET”.

struct C2_request_struct
{
    HINTERNET request_handle;
    __int64 request_error;
};

The request’s HTTP version is resolved to be “HTTP/1.1”, and BAZARLOADER calls HttpOpenRequestA to create this request for the C2 server using the connection handle retrieved above.

It also calls InternetSetOptionA to set the timeout for receiving a response and sending the request to 300 seconds and the timeout for connecting to C2s to 120 seconds.

BAZARLOADER then generates the HTTP header to be appended to the request. It does this by calling GetSystemTime to populate the curr_system_time and the datetime_string field of the main structure with the current date and time.

It also generates the SHA384 hash of the datetime string to populate the structure’s datetime_string_hash and datetime_string_hash_len fields.

Next, BAZARLOADER signs the generated hash with its RSA private by calling BCryptSignHash and uses this hash signature to randomly generate the HTTP header.

Below is the form of the random HTTP header.

BAZARLOADER’s HTTP Header

Date: Tue, 17 May 2022 20:18:27 GMT

Cookie: CGIC=YKK%2BIFrld%2FC5FqKj%2Fq1F9a06T0WgC4cOvCqqo3cfsyww1EwAb2TNFWqy8wBcDtObrgkjKtmIBSnsD%2Bmn2eR6MzQeUvHqOBJqA%2FqYS3KEkUQoKUgpOfuW%2BTtuz2OjhVeOJ1oMWnellOIJ4IuNBO5aVtzV8AI3CD4H8Z9tgjhG7i6cXrsAh5EUwKjHqbeEBszHmUhSsCNx0vypRWWd6MkM1HBeENiww7W8GjhoXvX8AwZ5VDTRSX3QSr9jpUuWYXHmJDhOtdG%2FW9UAWEHYkv6IpRsGPKWtAdR%2FzHNzWmZRC3HsMP4LyIOXCs5O8lvUZH2Bv2gtZ1SGZOD0Kd7Oi4nFwA%3D%3D;HSID=gbN30kmxz%2BKKygRK6DJjGJ%2FzGQcMDOSmyQoYi9p2ITFNy6zMnfCERvugTwb5O%2FZyAJ5lGvveYZlNK3N%2BRoNPqCThC%2Fwr2OQbPA3aFJeXZerPSF7bWmhHVGGunlw8HqV3dcjs67tLT7irrJLctXzonzWE%2B6Ukd9kdFTNWW5j7s2JEEAcW9Dp6x6WIdRemz6HC3BifjKQ%2BmwvryFaI%2BsLRi9T042hwKQqV7ikOLOsHOOCqlCKs0vcpFS5vUXzdGwSWdONWLHMEkuApe5B%2BUC7bULbJvnoqnT1jFFEE3vm5gSU%2BGKIbVqBCseLx42av7mxWCmJfB1mpDH3K6tJof0YKpQ%3D%3D;DV=arPk%2BRhtodeAHMzW%2FqoqfkfCFpUR1CYM5oW%2FK7onqCf46Xqk2OR0LjLK%2BQL7jCS5yrcZA2r2hYulBxnpVa%2B8tO%2BJImuPOM9B8JkIbEAMHp%2BvNq8vcSj78wA%2FoW0Wgx8Sw9lSMno4CgPoNgF1mrkm9m9cd7U6wQS7WGKR6Wufxuvjlyt6zf5xtU2gq9WtsxOglu%2BXOYApwXiTIEO853sEnzlVNopwMbYkBm5Mu2LSEycyBm4A9nWKVCr9r0xisHCGzde3sBO4hY7WyykPK6vsU3Ds36FVNH3Kv7jx1Kf6tCVe9ukypCd8j%2FXJWAgPwv%2BsWcyA6ky9skKq1GDuoMJQ5A%3D%3D;ANID=modcr%2B3xO4FT10Fb3jE3afiK3eFqJCs1ylPBjge%2F6lgEM3Li5i5UZ%2BviQCBS1cuiaiQ%2F9Cj62z%2BapXfRMVWSFEeZTv9rkmPKmp5FZHUq5VLHNyhrBtjScNXwDZVakrx9ZYt1uzzh2X93c6Z6YQsyNWlkfdmlZS1FCGOf0aaVZfIRiUW%2FXX6zNVph%2FBpYTicXqZ1qaiPAWb5FNF%2BfJylCjmVDOFCZHJ4E6M0Pph%2BvvgjuCO9bRHWjI2Ouz%2B8ETyMBoQOkSDGzzInMeapRKmXYNDhdcEogwNmRkJqoRJjBhHzvbIYgCXGNJFWVnxywcZSaHfQwGonWClY3vT3PSlEnHg%3D%3D;

X-Tag: f1DOF2QJkbCTJAUqiSLclK%2BsRkiAFHGmQusq2an%2FAud1WpfWVK%2F6gpLmtEQ38e65ILH8bHOzUd51lMpkh2xmHI5WpDJHgCtz5Q%2F%2BCL5usiShe35weofsQr9dQk%2BLsCl%2FulJlRg4cToY4ffie2MvfrtF4FCCZpbMy66UZUNqShGcbuIlRELkGKCTimwpB2wi4J6r%2FaIWAzINIP413ERqcQJ42Q1eQvvMkrEsWZDGEi4PHVtESHkcKKRVlZ9s3%2BPaQgq6TOR9YJxQOJaQ4NgaNuJAyHJnzkvtlQHNAy%2B%2FuLVa%2FpCuXxLvnw6dCIpmMwNHdzA09SL4T%2Bc5I3S6%2BFcu3mA%3D%3D

Vary: x564msS%2Bd%2BIrc97apj6SftcyuZTeoDUdyeLRN7n%2BkEJYVoJYAeuxpHT1XhTQ%2FywsKB7tZuNCJpid2qbr5DtOphE9Yvu2MfVTPH7nuK3yrk2nl93yuTpwiB%2F2kcFx57oLxtgqXkDlPENI3p31zdp%2B7quVz9C3ZKwA0Pi8%2FjH%2Fj%2BMKFZHpAWxlAWtxWe0xvwPKj3aroG6ujoEB9Fw%2Fq3IvIBsjoCXwIWPCSX7PCIk97ccsOyzqE6jGkgvVhfw%2BcvPL78g7gfvfx6eSDoOQj5M6xcH0aTPEld8rckxqFQmB9Gy%2B%2BNZ0YACTuCn7Aw9ziB9OmeKAy%2B5I%2F1J2ij7aSH3YgA%3D%3D

Var: wG852ANm2aHtGTrbsFHawff1eBZc9MnnPFOLEWeX3o7Ulc0fSj1qhaw%2BFlqpKs6ABhhs4opIe%2Bs%2BKqhT5G3jw9xRH%2FxeEYysL5AYbHsguuOivvXTofbFPPOLM%2ByoYIbq9Navi9C0VsaghxYUgsZ3wt4aHJAWXqvYacWoAowqCaZ7pg3FY0iKgLG403wZRS25%2BOrJL6gdo98qXYntX%2B4ZlPAUa49MiLJVQvI1xFCKLo5WbCb3b3aHQ2hpwvcYoh5P6F2su6Br3YL5DxtmpjdQY9IwLubcax%2BSZ4uZrDhX6MfnqQRa8vwA61OYhQUJM7OMeqPZ9lo%2BqkzrlPux1XxOyw%3D%3D

With the generated HTTP header and the request handle, BAZARLOADER calls HttpSendRequestA to send the request to the C2 server and calls HttpQueryInfoA to retrieve the status code.

If the status code is not HTTP_STATUS_OK, the malware moves on to another C2 address.

If the status code is HTTP_STATUS_OK, BAZARLOADER calls InternetQueryDataAvailable to determine the size of data to read, allocates the memory buffer according to the size, and calls InternetReadFile to read the next-stage payload until everything is written into memory.

Finally, the malware decrypts the payload with its RSA public key by calling BCryptDecrypt and checks to make sure the payload’s size is greater than 64 bytes and that it contains an MZ header.

Step 6: C2 Connection Through Custom URLs

If BAZARLOADER fails to download the next stage executable from the IP addresses listed above, it attempts to resolve custom C2 domains using OpenNIC, a user-owned DNS community service.

To begin querying OpenNIC’s API, the malware first resolves the URL “api.opennicproject.org” and calls InternetConnectA to establish a connection to the site.

Next, it calls HttpOpenRequestA to create a GET request handle with the object name “/geoip/?bare&ipv=4&wl=all&res=8” and send the request using HttpSendRequestA.

By examining OpenNIC’s APIs, we can break down this object name to see what BAZARLOADER is requesting. The “bare” parameter requests to only list the DNS server IP address, the “ipv” parameter requests to only list IPv4 servers, the “wl” parameter requests to only list whitelisted servers, and the “res” parameter requests to list 8 servers only.

To test this, we can simply paste the path below to a browser of our choosing.

api.opennicproject.org/geoip/?bare&ipv=4&wl=all&res=8

The malware then enters a loop to call InternetQueryDataAvailable and InternetReadFile to read the 8 OpenNIC’s DNS servers into memory. 

For each DNS server IP address, BAZARLOADER parses it from string to int and populates the opennic_server_struct field in the main structure. Below is the structure used to store OpenNIC IP addresses.

struct opennic_server_struct
{
   _QWORD init_server_count;
   HINTERNET opennic_internet_handle;
   DWORD opennic_server_IP_list[7];
   _BYTE gap2C[28];
   _QWORD server_count;
};

Finally, the malware decodes the following custom C2 domains, attempts to resolve them using the DNS servers, and downloads the next-stage executable.

reddew28c[.]bazar
bluehail[.]bazar
whitestorm9p[.]bazar

For each of these custom domains, BAZARLOADER calls DnsQuery_A to query a DNS Resource Record from OpenNIC’s servers to resolve the C2 server’s IP address.

After checking if the IP address is valid, the malware tries connecting to it and requests to download the next stage executable similar to what we have seen in the previous step.

Step 5: Injection Through Process Hollowing

After successfully downloading the next stage executable, BAZARLOADER begins the injection functionality to launch it from another process.

For this functionality, BAZARLOADER populates the following structure.

struct injection_struct
{
   HANDLE browser_proc_handle;
   PVOID full_exec_command;
   PVOID thread_curr_directory;
   PVOID browser_environment_struct;
   STARTUPINFOA thread_startup_info;
   LPPROC_THREAD_ATTRIBUTE_LIST proc_thread_attr_list;
};

First, it checks if its process is elevated with admin privileges. It calls GetCurrentProcess and OpenProcessToken to retrieve its own process token handle and GetTokenInformation to get the token’s elevation information.

If the process is not elevated, it resolves the following processes’ names and tries to populate the injection structure’s fields.

chrome.exe
firefox.exe
msedge.exe

For each process name, the malware enumerates the process’s snapshot to retrieve its ID and calls OpenProcess to get its handle.

To populate the full_exec_command and thread_curr_directory fields which contain the process’s command line and full path, BAZARLOADER first extracts the process parameters from the Process Environment Block (PEB).

To access the PEB, the malware calls NtQueryInformationProcess to retrieve the PEB’s adress and calls ReadProcessMemory to read the PEB into memory.

Next, it calls ReadProcessMemory to read the process parameters from the process’s memory.

With the process parameter RTL_USER_PROCESS_PARAMETERS structure, BAZARLOADER reads the process’s command line and full path to populate the injection structure.

Similarly, it also uses the process parameter to access the browser’s environment block and writes it to the injection structure.

If BAZARLOADER has admin privilege, instead of a browser’s process, it tries to populate the injection structure with a svchost.exe process from the following command line.

\system32\svchost.exe -k unistackSvcGroup

Next, using the injection struct, the malware calls CreateProcessA to create the target process in the suspended state to perform process hollowing.

We won’t dive too deep into this process hollowing implementation, since it’s almost the exact same implementation as seen here.

We can quickly spot that process hollowing is taking place through the Windows APIs being called. NtUnmapViewOfSection is called to unmap and carve out the parent’s memory. VirtualAllocEx and WriteProcessMemory are then called to allocate virtual memory in the parent’s process and write the malicious payload into it.

We can also see that the malware iterates through the parent’s section header to find the “.reloc” section and performs relocation on the injected image in memory.

Finally, BAZARLOADER calls SetThreadContext to set the new entry point for the parent process and calls ResumeThread to resume the parent’s process again, which will execute the injected executable.

And with that, we have analyzed how BAZARLOADER downloads a remote executable and executes it using process hollowing! If you have any questions regarding the analysis, feel free to reach out to me via Twitter.