Summary
APT29/Cozy Bear is a Russian actor that has been associated with Russia’s Foreign Intelligence Service (SVR). The US government has blamed this actor for the SolarWinds supply chain compromise operation, as described at https://media.defense.gov/2021/Apr/15/2002621240/-1/-1/0/CSA_SVR_TARGETS_US_ALLIES_UOO13234021.PDF/CSA_SVR_TARGETS_US_ALLIES_UOO13234021.PDF. MiniDuke is a backdoor written in pure assembly that was previously documented by ESET at https://www.welivesecurity.com/wp-content/uploads/2019/10/ESET_Operation_Ghost_Dukes.pdf and Kaspersky at https://securelist.com/miniduke-is-back-nemesis-gemina-and-the-botgen-studio/64107/, however, this sample is the most recent one (June 2019) that we’re aware of and hasn’t been documented before. This malware is pretty obfuscated (control-flow flattening) and implements multiple methods of data exfiltration, such as using POST and PUT HTTP methods in the case of sending data to the C2 server or using a named pipe in the case of no Internet connectivity. The backdoor implements 37 different functions that can be visualized below (some of these are similar/identical and were skipped):
Analyst: @GeeksCyber
Technical analysis
SHA256: 6057b19975818ff4487ee62d5341834c53ab80a507949a52422ab37c7c46b7a1
The malware uses the SetUnhandledExceptionFilter function in order to set the exception filter function to a particular function:
The process retrieves the content of the STARTUPINFO structure by calling the GetStartupInfoA routine, as shown below:
A new thread is created by the malicious file using the CreateThread API:
Thread activity – sub_4036D0
As mentioned by ESET at https://www.welivesecurity.com/wp-content/uploads/2019/10/ESET_Operation_Ghost_Dukes.pdf, the backdoor has added a lot of obfuscation that consists of control-flow flattening (every function is split in a switch/case, and a lot of computation that is useless for the main execution flow is added):
Figure 5 presents an example of an instruction that jumps to a place where a lot of useless computation occurs. We’ve added NOP operations in place of the jump and patched the binary:
The binary forces the system not to display the Windows Error Reporting dialog (0x2 = SEM_NOGPFAULTERRORBOX):
The kernel32.dll, advapi32.dll and winninet.dll DLLs are loaded into the address space using the LoadLibraryA routine:
The functions that will be used during the execution are located using a hashing mechanism. Basically, for each function name from a DLL, the malware computes a 4-byte value that is compared with a hard-coded one:
The following APIs belong to the targeted list: GetProcAddress, GetLongPathNameA, GetLastError, CreateProcessWithLogonW, CryptAcquireContextW, CryptGenRandom, InternetOpenA, InternetConnectA, InternetSetOptionA, HttpOpenRequestA, HttpSendRequestA, HttpQueryInfoA, InternetReadFile, InternetCloseHandle, HttpAddRequestHeadersA. The hashing function is displayed below:
The CryptAcquireContextW API is utilized to get a handle to a key container within a CSP (cryptographic service provider). The function call is presented in figure 10 (0x1 = PROV_RSA_FULL, 0xF0000040 = CRYPT_VERIFYCONTEXT | CRYPT_SILENT):
AllocateAndInitializeSid is used to allocate and initialize a SID with one subauthority:
The file creates a new ACL using the SetEntriesInAclA routine (0x2 = SET_ACCESS):
A new security descriptor is initialized by the malicious process (0x1 = SECURITY_DESCRIPTOR_REVISION):
The malware sets information in a DACL (discretionary access control list) using the SetSecurityDescriptorDacl API:
The following relevant strings are written into memory and will be used later on:
CryptGenRandom is utilized to generate 16 random bytes. The first 15 bytes are encoded using the Base64 algorithm:
The binary writes the file signature of JPEG in the JFIF format into the memory. These bytes will be used in data exfiltration, as we’ll describe in the following paragraphs:
The process creates the “Software\Microsoft\ApplicationManager” registry key using the RegCreateKeyA API (0x80000001 = HKEY_CURRENT_USER):
A new value called “AppID” is created under the above registry key. This value is computed using the output of a GetTickCount function call:
There are 2 more calls to the GetTickCount routine (it retrieves the number of milliseconds that elapsed since the system was started):
One of the outputs from above is transformed and written into a buffer, along with the “AppID” value. This buffer will be encrypted using a custom algorithm that also includes the XOR operator:
The encryption algorithm and the result of the above operation are highlighted in figure 22:
The backdoor initializes the use of the WinINet functions using the InternetOpenA API with a particular user agent:
The proxy is set to 10.1.1.1:8080 using the InternetSetOptionA function (0x26 = INTERNET_OPTION_PROXY):
The connect time-out value for connection requests is set to 11 seconds (0x2 = INTERNET_OPTION_CONNECT_TIMEOUT):
The receive time-out value for connection requests is set to 11 seconds (0x6 = INTERNET_OPTION_RECEIVE_TIMEOUT):
The send time-out value for connection requests is set to 11 seconds (0x5 = INTERNET_OPTION_SEND_TIMEOUT):
InternetConnnectA is utilized to open an HTTP session with the C2 server salesappliances[.]com (0x3 = INTERNET_SERVICE_HTTP):
The malware implements 3 different cases for exfiltrating the buffer that was encrypted earlier (or outputs from backdoor functions), depending on the availability of Internet connectivity.
Case 1 (no Internet availability)
WaitNamedPipeA is used to wait until 11 seconds have elapsed or an instance of the “\\pipe\DefPipe” pipe is available for connection (this pipe is supposed to be utilized between this machine and another machine that has an Internet connection):
The process opens the specified pipe using the CreateFileA routine (0xC0000000 = GENERIC_READ | GENERIC_WRITE, 0x3 = OPEN_EXISTING):
SetNamedPipeHandleState is utilized to set the read mode and the blocking mode of the pipe mentioned above (0x2 = PIPE_READMODE_MESSAGE, 0x0 = PIPE_WAIT):
The binary writes the encrypted buffer to the specified pipe using the TransactNamedPipe API:
Case 2 (Data exfiltration using PUT method)
A new HTTP request handle is created by the file (0x80400100 = INTERNET_FLAG_RELOAD | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_PRAGMA_NOCACHE):
The Referer header is added to the HTTP request handle using HttpAddRequestHeadersA (0x20000000 = HTTP_ADDREQ_FLAG_ADD):
The Accept-Language header is added to the HTTP request handle using HttpAddRequestHeadersA (0x20000000 = HTTP_ADDREQ_FLAG_ADD):
The Accept-Encoding header is added to the HTTP request handle using HttpAddRequestHeadersA (0x20000000 = HTTP_ADDREQ_FLAG_ADD):
The process exfiltrates the encrypted buffer to the C2 server by calling the HttpSendRequestA routine, as shown below:
Case 3 (Data exfiltration using POST method)
A new HTTP request handle is created by the file (0x80400100 = INTERNET_FLAG_RELOAD | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_PRAGMA_NOCACHE):
The Referer header is added to the HTTP request handle using HttpAddRequestHeadersA (0x20000000 = HTTP_ADDREQ_FLAG_ADD):
The Accept-Language header is added to the HTTP request handle using HttpAddRequestHeadersA (0x20000000 = HTTP_ADDREQ_FLAG_ADD):
The Accept-Encoding header is added to the HTTP request handle using HttpAddRequestHeadersA (0x20000000 = HTTP_ADDREQ_FLAG_ADD):
The encrypted buffer is added to a fake JPEG image (note the file signature in the network traffic) and transmitted to the C2 server without raising any suspicion:
HttpOpenRequestA is utilized to create a new HTTP request handle. The HTTP method is set to GET ( 0x80480100 = INTERNET_FLAG_RELOAD | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_PRAGMA_NOCACHE):
The file generates 256 random bytes via a function call to CryptGenRandom (the result will be Base64-encoded, and a small part of the output is used as a parameter in the Referer header):
The Referer, Accept-Language and Accept-Encoding headers are set as described before. The encrypted buffer that was exfiltrated using one of the 3 methods is Base64-encoded:
The Cookie header is set to a string that is obtained from the above using some transformations, and the request is sent to the C2 server, as shown in figure 46.
Here is the network request captured by FakeNet:
It’s important to mention that the backdoor also performs a “cleaning” operation by freeing the memory in order to hide possible IOCs that could be extracted from it:
The status code returned by the server is extracted and compared with 200 (0x20000013 = HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_STATUS_CODE):
The malicious binary retrieves the size of the resource using the HttpQueryInfoA API (0x20000005 = HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_CONTENT_LENGTH):
There is a function call to InternetReadFile, which is utilized to read data received from the C2 server:
The response from the C2 server is parsed, and the byte at position 0x1c (28 in decimal) is extracted. There is also a “checksum” of the 5th-8th bytes that is computed, and the result should match the first 4 bytes. We will describe each case depending on that particular byte.
Byte = 0x11 – read the content of a file specified by the C2 server and compute the MD5 hash of it
The path of the %TEMP% directory is extracted using GetTempPathA:
The path of the %TEMP% directory is converted to its long form by calling the GetLongPathNameA routine, as highlighted below:
GetCurrentDirectoryA is utilized to extract the current directory for the current process:
The response from the C2 server is supposed to contain a file name, which is opened via a CreateFileA function call (0x80000000 = GENERIC_READ, 0x1 = FILE_SHARE_READ, 0x3 = OPEN_EXISTING, 0x80 = FILE_ATTRIBUTE_NORMAL):
The malware creates an unnamed file mapping object using the CreateFileMappingA API (0x8 = PAGE_WRITECOPY):
The malicious binary maps the newly created file mapping into the address space of the calling process, as shown in the next pictures (0x1 = FILE_MAP_COPY):
The MD5 hashing algorithm is implemented by the malware (note the variables from below), which is used to perform hashing of the file content extracted above:
The resulting buffer that will be exfiltrated is similar to the one from figure 21, however, it also contains the MD5 hash value and the file name. The encryption algorithm is the same presented in figure 22 (this is valid for all cases, and we will not repeat it every time):
Byte = 0x12 – create and populate a new file
The backdoor creates a new file specified by the C2 server in the network traffic (0x40000000 = GENERIC_WRITE, 0x1 = FILE_SHARE_READ, 0x1 = CREATE_NEW, 0x80 = FILE_ATTRIBUTE_NORMAL):
The newly created file is populated with content provided by the C2 server as well:
The final buffer that will be exfiltrated contains the file name:
Byte = 0x13 (same execution flow as 0x11)
Byte = 0x14 – write specific bytes into memory depending on the C2 server response
Depending on 2 bytes received from the C2 server, the binary writes 0x100, 0x200, 0x400, 0x800, 0x1000 or 0x2000 into memory. The first 3 cases are highlighted in figure 65:
The buffer that will be exfiltrated contains a 4-byte value computed from a GetTickCount function call, the “AppID” value and a marker value (0x81 in this case):
Byte = 0x15 – listen on port 8080 and record all connections that are established on this port
A new thread is created using the CreateThread routine:
The process creates a new socket using the socket API. The inet_addr function is utilized to convert a string containing an IP dotted-decimal address into a proper address for the IN_ADDR structure, as shown below:
There is a mistake done by the malware developers because they’ve called the inet_addr routine with the C2 server as the parameter (and not an IP as above). This function call returns INADDR_NONE (0xFFFFFFFF):
The binary associates the local address with the socket created before using the bind API:
The listen function is used to place the socket in a listening state for incoming connections:
The malware was supposed to connect to the C2 server using the connect API, however, due to the implementation mistake, this function call fails:
For the sake of the analysis, we’ve emulated an external connection from a remote IP to the local host on port 8080. The getpeername API is utilized to extract the address of the peer to which the socket is connected:
The inet_ntoa routine is the opposite of inet_addr and it’s used to convert an IP from a hex form into an ASCII string (dotted-decimal format):
getsockname is utilized to retrieve the local name for the socket:
inet_ntoa is used again to convert the IP address from hex to dotted-decimal format, as highlighted in figure 76:
The final buffer that will be exfiltrated contains some details about the network connection (source and destination IPs/ports) and the string “listen”:
It’s important to mention that because of the bug, only this behavior is expected, however, there are other execution flows as well. For example, if no connection is established, the malware only copies the string “idle” in the buffer. If the connection to the C2 server is successful, then the string “connect” would have been copied into the final buffer. Finally, if the connection is successful and the process accepts another connection on port 8080, the string “accept” is copied into the buffer as well.
Byte = 0x16 – create a named pipe and wait for connections
The file creates a new named pipe using the CreateNamedPipeA routine (0x40040003 = FILE_FLAG_OVERLAPPED | WRITE_DAC | PIPE_ACCESS_DUPLEX, 0x6 = PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE):
A new unnamed event object is created by the backdoor:
The binary enables the pipe to wait for connections from client processes, as displayed below:
Whether the C2 server specifies the “off” parameter in the network traffic, the malware calls the DisconnectNamedPipe API:
The buffer that will be exfiltrated is similar to the one presented in figure 66.
Byte = 0x20 – extract timestamps for a file mentioned by the C2 server
The FindFirstFileA routine is utilized to locate a file specified by the C2 server in the network traffic:
The malicious process converts the file time to system time format using FileTimeToSystemTime:
The GetTimeFormatA API is utilized to convert the time from above to a time string (0x800 = LOCALE_SYSTEM_DEFAULT, 0x80000000 = LOCALE_NOUSEROVERRIDE):
The GetDateFormatA API is utilized to convert the date from above to a date string (0x800 = LOCALE_SYSTEM_DEFAULT, 0x80000000 = LOCALE_NOUSEROVERRIDE):
The final buffer that will be exfiltrated contains the file name, file creation date and time, and the length of the file content:
If any error occurs during an operation such as creating a file, opening a file, and so in all studied cases, the malware formats the error message using FormatMessageA and copies it to the final buffer (0x1000 = FORMAT_MESSAGE_FROM_SYSTEM, 0x2 = ERROR_FILE_NOT_FOUND):
Byte = 0x21 – move a file to a new file
The response from the C2 server contains 2 file names. The process moves the first file to the second one by calling the MoveFileA API:
The buffer that will be exfiltrated is similar to the one presented in figure 66.
Byte = 0x22 – copy a file to a new file
The response from the C2 server contains 2 file names. The malware copies the first file to the second one by calling the CopyFileA API:
The buffer that will be exfiltrated is similar to the one presented in figure 66.
Byte = 0x23 – delete a file
The response from the C2 server contains a file name. The binary deletes the file using the DeleteFileA function:
The buffer that will be exfiltrated is similar to the one presented in figure 66.
Byte = 0x24 – retrieve the current directory for the process
GetCurrentDirectoryA is used to extract the current directory for the process:
The final buffer that will be exfiltrated contains the path extracted above:
Byte = 0x25 – create a directory
The response from the C2 server contains a directory name. The backdoor creates the new directory using the CreateDirectoryA routine:
The buffer that will be exfiltrated is similar to the one presented in figure 66.
Byte = 0x26 – delete a directory
The response from the C2 server contains a directory name. The binary deletes the directory using the RemoveDirectoryA routine:
The buffer that will be exfiltrated is similar to the one presented in figure 66.
Byte = 0x27 – change the current directory for the process
The response from the C2 server contains a directory name. The process changes the current directory for the process to this directory:
The buffer that will be exfiltrated is similar to the one presented in figure 66.
Byte = 0x28 – set the current directory for the process to the %TEMP% folder
GetTempPathA is utilized to retrieve the path of the %TEMP% directory:
The file changes the current directory for the process using the SetCurrentDirectoryA API:
Byte = 0x29 – retrieve the valid drives on the system and their type
The valid drives on the system are extracted by calling the GetLogicalDriveStringsA function:
The backdoor extracts the type of the drive using the GetDriveTypeA API, as displayed in figure 99.
The final buffer that will be exfiltrated contains the drives name and a string that categorizes their type:
The following strings are hard-coded and indicate the type of drives: unk (DRIVE_UNKNOWN), nrt (DRIVE_NO_ROOT_DIR), rmv (DRIVE_REMOVABLE), fix (DRIVE_FIXED), net (DRIVE_REMOTE), cdr (DRIVE_CDROM), ram (DRIVE_RAMDISK) and und (most likely UNDEFINED).
Byte = 0x2A – retrieve the computer Uptime and encrypt the value
The malware calls the GetTickCount function and stores the result in a separate buffer:
The 4-byte value extracted above is encrypted using a custom algorithm:
The final buffer that will be exfiltrated contains the result of the above encryption:
Byte = 0x30 – retrieve the path of an executable that corresponds to a particular process ID
The response from the C2 server contains a string with a process ID. The atoi function is used to convert the string to a number:
The malicious binary opens the local process object that corresponds to the process ID using the OpenProcess routine (0x1F0FFF = PROCESS_ALL_ACCESS):
EnumProcessModules is utilized to enumerate the modules of the targeted process:
GetModuleFileNameExA is used to retrieve the path of the file that contains a specific module. This is an interesting way to find out the path to the executable that corresponds to the targeted process ID:
The final buffer that will be exfiltrated contains the address of the module from above and the path to the executable:
Byte = 0x31 – kill a process
The response from the C2 server contains a string with a process ID. The backdoor opens the local process object that corresponds to the process ID using the OpenProcess routine (0x1= PROCESS_TERMINATE):
The binary kills the targeted process using TerminateProcess, as described in figure 110:
The final buffer that will be exfiltrated contains the string “term” (which probably refers to terminate) and the process ID:
Byte = 0x32 – create a new process
The response from the C2 server contains a process name. The malware creates this process by calling the CreateProcessA API:
The final buffer that will be exfiltrated contains the ID of the process created earlier:
Byte = 0x33 – create a new process in the security context of the credentials received from the C2 server
The response from the C2 server contains the following data: user, password, Windows domain, and process name. Two anonymous pipes are created using the CreatePipe API:
GetCurrentDirectoryW is utilized to retrieve the current directory for the process:
The binary creates a new process that runs in the context of the credentials extracted from the network traffic via a CreateProcessWithLogonW function call:
The executable extracts a handle for each module in the process created above by calling the EnumProcessModules routine:
There is a call to GetModuleFileNameExA that extracts the path of the file containing the above module:
The buffer that will be exfiltrated is similar to the one presented in figure 108.
Byte = 0x34 (same execution flow as 0x33)
Byte = 0x40 – retrieve the current process ID, the path of the executable, the hostname, the username, and the default locale
GetModuleFileNameA is utilized to extract the path of the executable of the current process:
The malware retrieves the NetBIOS name of the local computer and the user name by calling the GetComputerNameA and GetUserNameA functions, respectively:
The current process ID is extracted using the GetCurrentProcessId routine:
GetLocaleInfoA is utilized to retrieve information about the default locale for the user or process (0x400 = LOCALE_USER_DEFAULT):
The final buffer that will be exfiltrated contains the current process ID, the path of the executable, the hostname, the username, the “AppID” value, the C2 server, the HTTP method used during network communications, the pipe name mentioned in Case1 of Data Exfiltration, and the user’s language (English – United States):
Byte = 0x41 – retrieve the current process ID, the path of the executable, the hostname, the username, and the default locale
GetModuleFileNameA is utilized to extract the path of the executable of the current process:
The GetComputerNameA and GetUserNameA APIs are used to retrieve the hostname and the username associated with the current thread:
GetCurrentProcessId is utilized to extract the ID of the current process:
The default locale for the user or process is extracted using GetLocaleInfoA (0x400 = LOCALE_USER_DEFAULT):
The final buffer that will be exfiltrated contains the current process ID, the path of the executable, the hostname, the username, and the user’s language (English – United States):
Byte = 0x48 – retrieve the hostname and username
The malware extracts the username and hostname as before:
The final buffer that will be exfiltrated contains the hostname and username, as highlighted in figure 134:
Byte = 0x49 – exfiltrate the C2 domain name and port number
The final buffer that will be exfiltrated contains the C2 server and the port number:
Byte = 0x85 – close open handles
There is only a FindClose function call regarding this case. The buffer that will be exfiltrated is similar to the one presented in figure 66.
Byte = 0x8B – close open handles
This is also a “cleaning” case because the backdoor calls the CloseHandle API a few times. The buffer that will be exfiltrated is similar to the one presented in figure 66.
Byte = 0x98 calculate the MD5 hash of the empty string
The process computes the MD5 hash of the empty string and saves the result to the buffer that will be exfiltrated:
Byte = 0xC4 – close open handles
No notable activity regarding this case. The buffer that will be exfiltrated is similar to the one presented in figure 66.
Byte = 0xC7 – close open handles and unmap a mapped view of a file
The binary performs 2 function calls to CloseHandle and a call to UnmapViewOfFile. The buffer that will be exfiltrated is similar to the one presented in figure 66.
Byte = 0xCA – close open handles
No notable activity regarding this case. The buffer that will be exfiltrated is similar to the one presented in figure 66.
Byte = 0xE1 – resume a suspended thread and copy data from a pipe
The malicious process resumes a thread that was previously suspended using the ResumeThread routine:
PeekNamedPipe is utilized to copy data from a named or anonymous pipe into a buffer without removing it from the pipe:
Whether more data is available in the pipe, the malware reads it using the ReadFile API:
The buffer that will be exfiltrated contains the data received from the pipe:
Byte = 0xE2 – copy data from a pipe
The executable copies data from a named or anonymous pipe into a buffer via a PeekNamedPipe function call:
The ReadFile function is utilized to read more data from the pipe if it’s available:
The buffer that will be exfiltrated contains the data received from the pipe:
Byte = 0xE3 – kill a process
The backdoor kills a specific process, whose handle is read from memory:
The buffer that will be exfiltrated is similar to the one presented in figure 66.
Byte = 0xFE – close open handles
The binary performs a function call to CloseHandle and closesocket. The buffer that will be exfiltrated is similar to the one presented in figure 66.
Byte = 0xFF – copy a string that probably represents the exit of the program
The malware copies the string “Exiting…” to the final buffer that will be exfiltrated:
References
MSDN: https://docs.microsoft.com/en-us/windows/win32/api/
VirusTotal: https://www.virustotal.com/gui/file/6057b19975818ff4487ee62d5341834c53ab80a507949a52422ab37c7c46b7a1
Fakenet: https://github.com/fireeye/flare-fakenet-ng
MalwareBazaar: https://bazaar.abuse.ch/sample/6057b19975818ff4487ee62d5341834c53ab80a507949a52422ab37c7c46b7a1/
ESET: https://www.welivesecurity.com/wp-content/uploads/2019/10/ESET_Operation_Ghost_Dukes.pdf
Kaspersky: https://securelist.com/miniduke-is-back-nemesis-gemina-and-the-botgen-studio/64107/
Cybersecurity Advisory: https://media.defense.gov/2021/Apr/15/2002621240/-1/-1/0/CSA_SVR_TARGETS_US_ALLIES_UOO13234021.PDF/CSA_SVR_TARGETS_US_ALLIES_UOO13234021.PDF
INDICATORS OF COMPROMISE
C2 server: salesappliances[.]com
SHA256: 6057b19975818ff4487ee62d5341834c53ab80a507949a52422ab37c7c46b7a1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36 (prone to False Positives)
Named Pipe: \\pipe\DefPipe