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:
Figure 1
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).
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.
Figure 3
Figure 4
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:
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:
Figure 6
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:
Figure 7
The malware retrieves the IP address of the peer connected to the socket passed as a parameter to the send function using getpeername:
Figure 8
The original send function is called if any error occurred, as shown below:
Figure 9
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:
Figure 10
Figure 11
In the my_close function, the binary searches for the socket descriptor passed as a parameter in a socket descriptors list:
Figure 12
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).
Figure 13
A new thread that executes the cc_worker or Connected2Vps functions is created using the pthread_create method:
Figure 14
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:
Figure 15
The process obtains a structure of type hostent for the IP address using gethostbyname and the addrinfo structure using the getaddrinfo method:
Figure 16
The binary creates a new socket and connects to the C2 server using the connect method:
Figure 17
The malware sends the buffer found at the gConnectedData_ptr address to the server via a function call to write (see Figure 18).
Figure 18
The select method is utilized to monitor multiple socket file descriptors, waiting for the read file descriptor to be ready for reading:
Figure 19
Finally, the process reads the server response using the read method:
Figure 20
It creates a new SSL structure and sets the socket descriptor as the input/output for network connections:
Figure 21
The malware expects a 21-byte structure that contains the command to be executed. SSL_read is utilized to read data from the connections:
Figure 22
Figure 23
The first byte extracted from the response corresponds to the backdoor functionalities named “Channels”:
- 0 (ShellChannel)
- 1 (DownloadChannel)
- 2 (UploadChannel)
- 3 (ProxyChannel)
- 4 (TunnelArgs)
Figure 24
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:
Figure 25
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:
Figure 26
Figure 27
The popen function is used to run the desired command on the device, and the output is read using the fgets method (Figure 28).
Figure 28
The malware implements a function called MyWriteAll, which calls the SSL_write method. It sends 4 NULL bytes to the C2 server:
Figure 29
Figure 30
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:
Figure 31
The file name to be created is read using the MyReadAll function, as shown in Figure 32.
Figure 32
The open64 routine is used to create the file on the device:
Figure 33
The file is populated with content received from the C2 server using the lseek64 and write functions:
Figure 34
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:
Figure 35
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:
Figure 36
Figure 37
The MyWriteAll function is used again to send 21 bytes to the C2 server, as shown in the figure below.
Figure 38
Finally, the target file content is read using the lseek64 and read routines, and then sent to the C2 server:
Figure 39
Figure 40
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:
Figure 41
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]:
Figure 42
The OpenConnection function presented above uses the recv method to receive data until a new line is received from the socket (see Figure 43).
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:
Figure 44
The process requests a 21-byte structure from the C2 server and sends it to the Proxy server using the MyReadAll and MyWriteAll functions:
Figure 45
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:
Figure 46
In the DownloadByProxyChannel function, the binary reads a buffer from the C2 server and transmits it to the Proxy server:
Figure 47
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).
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:
Figure 49
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).
Figure 50
The process sends the command ID (4) and a 20-byte buffer that contains NULL bytes to the C2 server:
Figure 51
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:
Figure 52
Figure 53
In the third case, the values stored at gRemoteAddr_ptr, gRemotePort_ptr, and gConnectedAddr_ptr are overwritten with NULL bytes (Figure 54).
Figure 54
References
https://www.mandiant.com/resources/blog/barracuda-esg-exploited-globally