Summary
SALTWATER is a backdoor that has been used in the exploitation of the Barracuda 0-day vulnerability CVE-2023-2868. It is a module for the Barracuda SMTP daemon called bsmtpd. The malware hooked the recv, send, and close functions using an open-source hooking library called funchook. The following functionalities are implemented: execute arbitrary commands, download and upload files, proxy functionality, and tunneling functionality.
Technical analysis
SHA256: 1c6cad0ed66cf8fd438974e1eac0bc6dd9119f84892930cb71cb56a5e985f0a4
The malware implements hooks on the recv, send, and close functions in a method called cc_init, as shown below:
It obtains the address of the original functions using the dlsym method. The function called get_symbol_by_name returns the address (see Figure 2).
As Mandiant mentioned in their report, the binary uses the funchook hooking library to construct the hooks. The following functions are called: funchook_create, funchook_prepare, and funchook_install.
We will now describe the hooking functions my_recv, my_send, and my_close.
Firstly, my_recv calls the original recv function, as highlighted in Figure 5:
The value stored at the gIsRecvAlready_ptr address should be 0. The process allocates a new memory area that will store the buffer to be received:
The server response is copied to the gConnectedData_ptr address and is overwritten with the “quit\r\n” string. The value stored at gIsRecvAlready_ptr is set to 1, which means that a successful receive operation occurred:
The malware retrieves the IP address of the peer connected to the socket passed as a parameter to the send function using getpeername:
The original send function is called if any error occurred, as shown below:
The process implements a function called CheckRemoteIp, which converts the IP addresses from binary to text form using inet_ntop and then compares them with the value found at the gConnectedAddr_ptr address. It’s worth mentioning that the socket family is also expected to be 0xA (AF_CCITT), which corresponds to CCITT protocols:
In the my_close function, the binary searches for the socket descriptor passed as a parameter in a socket descriptors list:
The malware compares the socket descriptor with the value stored at gConnectedfd_ptr and expects them to be equal; otherwise, it calls the original close method (Figure 13).
A new thread that executes the cc_worker or Connected2Vps functions is created using the pthread_create method:
In the Connected2Vps function, the binary implements a method called OpenConnection, which takes two parameters (IP adddress and port number) that are supposed to indicate the threat actor’s VPS infrastructure:
The process obtains a structure of type hostent for the IP address using gethostbyname and the addrinfo structure using the getaddrinfo method:
The binary creates a new socket and connects to the C2 server using the connect method:
The malware sends the buffer found at the gConnectedData_ptr address to the server via a function call to write (see Figure 18).
The select method is utilized to monitor multiple socket file descriptors, waiting for the read file descriptor to be ready for reading:
Finally, the process reads the server response using the read method:
It creates a new SSL structure and sets the socket descriptor as the input/output for network connections:
The malware expects a 21-byte structure that contains the command to be executed. SSL_read is utilized to read data from the connections:
The first byte extracted from the response corresponds to the backdoor functionalities named “Channels”:
- 0 (ShellChannel)
- 1 (DownloadChannel)
- 2 (UploadChannel)
- 3 (ProxyChannel)
- 4 (TunnelArgs)
ShellChannel
The remaining structure received from the C2 server called SHELL has 20 bytes.
The server can specify a command that will be executed on the infected device:
The received command is compared with “exit” and “exit\n”, which means that the process just exits. In any other case, the command is passed to the run_cmd function, as highlighted below:
The popen function is used to run the desired command on the device, and the output is read using the fgets method (Figure 28).
The malware implements a function called MyWriteAll, which calls the SSL_write method. It sends 4 NULL bytes to the C2 server:
DownloadChannel
The remaining structure received from the C2 server called TRANSFILE has 20 bytes.
The process allocates 5MB of memory and expects that TRANSFILE[0:4], which is the file name’s length that will be created, to be <= 1022:
The file name to be created is read using the MyReadAll function, as shown in Figure 32.
The open64 routine is used to create the file on the device:
The file is populated with content received from the C2 server using the lseek64 and write functions:
UploadChannel
The remaining structure received from the C2 server called TRANSFILE has 20 bytes.
The binary expects that TRANSFILE[4:8], which is the file’s length to be exfiltrated, to be <= 5MB. It calls MyReadAll in order to read the file path that will be exfiltrated to the C2 server:
The file’s length is obtained using a function called GetFileSize, which uses the stat64 method to retrieve it. The malware opens the target file via a function call to open64:
The MyWriteAll function is used again to send 21 bytes to the C2 server, as shown in the figure below.
Finally, the target file content is read using the lseek64 and read routines, and then sent to the C2 server:
ProxyChannel
The remaining structure received from the C2 server called PROXY has 20 bytes.
The process allocates 6MB of memory and expects PROXY[12:16] to be 0, as shown below:
The first 4 bytes (PROXY[0:4]) are used to construct the Proxy IP address. The Proxy port number is derived from PROXY[10:12]:
The OpenConnection function presented above uses the recv method to receive data until a new line is received from the socket (see Figure 43).
The binary creates a new SSL structure, connects it with the socket descriptor initially passed as parameter to the my_close function, and initiates the TLS/SSL handshake with the Proxy server:
The process requests a 21-byte structure from the C2 server and sends it to the Proxy server using the MyReadAll and MyWriteAll functions:
As we’ve already mentioned, the first byte determines the command to be executed. A special case occurs if the byte is equal to 2, which leads to a call to the DownloadByProxyChannel routine:
In the DownloadByProxyChannel function, the binary reads a buffer from the C2 server and transmits it to the Proxy server:
Moving forward, the malware can read a 21-byte structure corresponding to a command from the Proxy server and sent it to the C2 server (Figure 48).
TunnelArgs
The remaining structure received from the C2 server called TUNNEL has 20 bytes.
The value TUNNEL[16:20] is expected to be different than 1 and TUNNEL[12:16] could be 0, 1, or 2:
In the first case, the bytes extracted from TUNNEL[0:4] and TUNNEL[8:12] are used to construct two IP addresses, and TUNNEL[4:8] is converted to a port number. The resulting values are stored at the gRemoteAddr_ptr, gRemotePort_ptr, and gConnectedAddr_ptr addresses (Figure 50).
The process sends the command ID (4) and a 20-byte buffer that contains NULL bytes to the C2 server:
In the second case, the binary extracts the IP addresses found at gRemoteAddr_ptr and gConnectedAddr_ptr, and the port number found at gRemotePort_ptr and converts them to integer using the atoi function. The 20-byte buffer that is exfiltrated to the C2 server contains the converted values:
In the third case, the values stored at gRemoteAddr_ptr, gRemotePort_ptr, and gConnectedAddr_ptr are overwritten with NULL bytes (Figure 54).
References
https://www.mandiant.com/resources/blog/barracuda-esg-exploited-globally