Summary
Symbiote is a Linux threat that hooks libc and libpcap functions to hide the malicious activity. The malware hides processes and files that are used during the activity by implementing two functions called hidden_proc and hidden_file. It can also hide network connections based on a list of ports and by hijacking any injected packet filtering bytecode. The malware’s purpose is to steal credentials from the SSH and SCP processes by hooking the libc read function. The extracted credentials are encrypted using RC4, stored in a file on the system, and then exfiltrated to the C2 server via DNS requests.
Analyst: @GeeksCyber
Technical analysis
SHA256: 121157e0fcb728eb8a23b55457e89d45d76aa3b7d01d3d49105890a00662c924
This is a 64-bit ELF shared object that appears to be an early development build for Symbiote malware. Newer versions of this malware have even more functionalities which are described in BlackBerry’s analysis. The file is not stripped.
The malware hooks the following functions: fopen, fopen64, pam_authenticate, pam_set_item, read, readdir, readdir64, and recvmsg. We will give details about the hooks implementation.
The dlsym function is utilized to obtain the address of fopen, and then the process calls the original function (0xFFFFFFFFFFFFFFFF = RTLD_DEFAULT):
When an application tries to open the “/proc/net/tcp” file, which contains all TCP connections, the execution flow of the hooked function is different:
The ELF file creates a temporary file by calling the tmpfile method, reads the first line from the above file, and writes it to the newly created file:
The file is read line-by-line using the getline function. In the case of returning -1 because of a failure (including end-of-file condition), the process closes the file and frees the memory area allocated to the line:
There is a function called gen_proc_net_port implemented by the malware. The purpose of this function is to retrieve a list of ports that should be hidden. Whether a line read above contains any of the ports, the line is not written to the temporary file, and the process moves to the next line:
The function implementation is displayed in the figure below:
The hooked function returns the file descriptor corresponding to the temporary file. The fopen64 function is hooked in a similar way.
The dlsym function is utilized to obtain the address of pam_authenticate, and then the process calls the original function:
The malware calls the pam_get_item method in order to obtain the following information: 0x1 = PAM_SERVICE – the service name, 0x4 = PAM_RHOST – the requesting hostname, 0x2 = PAM_USER – the username. There is also a function call to getaddrlist, which will be explained in the upcoming paragraphs:
Based on the information extracted above, the process constructs the following string “pam|<getaddrlist result>|<PAM_SERVICE>|<PAM_RHOST>|<PAM_USER>|<cs:pampassword>”. There is a call to a function named saveline with the “/usr/include/linux/usb/usb.h” parameter (see figure 9). This particular function will be dissected in the upcoming paragraphs.
The ELF binary implements an erase function called erasefree. It overwrites an area with zeros, and then the pointer which points to this area will be freed:
The dlsym function is utilized to obtain the address of pam_set_item, and then the process calls the original function:
The process expects that the item_type value is equal to 0x6 (PAM_AUTHTOK), which is the authentication token (usually it’s a password):
The dlsym function is utilized to obtain the address of readdir, as highlighted below:
The malware implements a function called check_proc, which will be explained in a bit. Depending on the boolean value returned by this function, the process calls the original readdir method and then hidden_file or hidden_proc:
The readdir64 function is hooked in a similar way.
The dlsym function is utilized to obtain the address of recvmsg, and then the process calls the original function:
The malicious process expects a specific message structure i.e. message[8] = 0xc, message[16] != 0, as displayed in the figure below:
The ELF binary converts unsigned short integers from host byte order to network byte order using htons. The message is copied to another memory area using the memcpy method:
In the check_proc function, the malware gets the directory stream file descriptor and computes the following path “/proc/self/fd/<File descriptor>”:
The path constructed above points to a symbolic link that is read using the readlink method. The function returns 1 whether the symbolic link contains “/proc” and 0 otherwise:
The malware implements a function called check_ssh_scp. It obtains the location of an executable by calling the readlink function with the “/proc/self/exe” parameter:
The purpose of this function is to detect the presence of the SCP/SSH executable and returns 0 if that’s the case:
Symbiote implements the CRC32b algorithm in a function called crc32b. The algorithm can be identified using the 0xEDB88320 constant:
There is a function called create_file that can be used to create files. It calls the open method (0x441 = O_WRONLY | O_CREAT | O_APPEND):
The process changes the permissions of a file to 0x1B6 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, which means that all users can read and write but cannot execute the file:
In the function called dns, the ELF binary retrieves the current process ID that is converted from host byte order to network byte order using htons:
The malware calls a function named ChangetoDnsNameFormat that will be explained below:
The malicious process creates a socket that will be used to communicate with the C2 server (0x2 = AF_INET, 0x2 = SOCK_DGRAM, 0x11 = IPPROTO_UDP). The C2 server address is converted from dotted decimal notation to an integer using the inet_addr method:
The sendto function is utilized to send data to the C2 server:
The function called ChangetoDnsNameFormat prepares the structure of the request for DNS data exfiltration:
In the function named getaddrlist, the ELF binary extracts a linked list of structures containing the network interfaces of the local machine using the getifaddrs method:
Based on the structures extracted above, the process extracts the IP addresses by calling the getnameinfo function:
The interfaces IP addresses are concatenated together using the strcat method:
In the getserver function, the malicious binary tries to open a file called “/tmp/resolv.conf”:
The malware is looking for a nameserver in the above file. If there is no nameserver, then the process will use the Google DNS server (8.8.8.8) to send the DNS request as a UDP broadcast:
The process compares two strings (file names) in the hidden_file function and returns 0 if they match:
The hidden_proc function expects a process ID as an argument. It calls the strspn and strlen functions in order to ensure that the process ID consists of digits only:
The ELF binary retrieves information about a process from the “/proc/<pid>/status” file, as shown in figure 38.
The purpose of this function is to compare two process names and to return 0 if they match (see figure 39). Symbiote’s objective is to hide some processes that are related to the malware such as: certbotx64, certbotx86, javautils, javaserverx64, javaclientex64, javanodex86 (BlackBerry’s article).
The dlsym function is utilized to obtain the address of the read method. If an SSH or SCP process is calling the libc read function, then hook_read is set to keylogger, which is explained below:
In the keylogger function, the process calls the original read function with a file descriptor corresponding to SSH or SCP. It also performs a call to the isatty method in order to ensure that the file descriptor is referring to a terminal:
The executable calls a function named log_cmd_line and then getaddrlist. The first function will be detailed in the following paragraphs:
The malware constructs a string with the following structure “<getaddrlist result>|<log_cmd_line result>|pw_5673”. It calls the saveline function with the “/usr/include/linux/usb/usb.h” parameter:
The log_cmd_line method is called only for the SSH or SCP process. The command line of one of these processes is read from “/proc/self/cmdline”:
The realloc method is utilized to deallocate the old object and to return a pointer to a new object:
The credentials extracted from the SSH or SCP process are encrypted using the RC4 algorithm (key = “suporte42atendimento53log”). The encrypted content will be used to construct DNS requests in the sendlinedns function:
The malicious process creates a file called “/usr/include/linux/usb/usb.h” by calling create_file:
The encrypted credentials are written to the file created above:
In the sendlinedns function, the malware obtains information about the current kernel using the uname method. Based on the resulting buffer, the process computes a machine ID which consists of 4 bytes generated using crc32b (stored in id_6274):
The encrypted credentials are hex-encoded and splitted to be exfiltrated via DNS requests to a domain owned by the threat actor. The A DNS request has the following format:
- <Packet number – starts from 0x2B67 = 11111>.<Machine ID – Crc32b hash>.<Hex-encoded data>.px32.nss.atendimento-estilo[.]com
Finally, the executable calls the dns function that will exfiltrate data:
The implementation of the RC4 algorithm can be identified below:
INDICATORS OF COMPROMISE
C2 domain: px32.nss.atendimento-estilo[.]com
SHA256: 121157e0fcb728eb8a23b55457e89d45d76aa3b7d01d3d49105890a00662c924
Files created: /usr/include/linux/usb/usb.h