{
	"id": "9186ff46-0206-4b7f-8ca4-33351688ae79",
	"created_at": "2026-04-06T00:18:23.440761Z",
	"updated_at": "2026-04-10T03:24:29.826484Z",
	"deleted_at": null,
	"sha1_hash": "f99ce383d7c1b1158098b7e6a6ebf79f13f55897",
	"title": "Plague: A Newly Discovered PAM-Based Backdoor for Linux",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 602979,
	"plain_text": "Plague: A Newly Discovered PAM-Based Backdoor for Linux\r\nBy Pierre-Henri Pezier\r\nPublished: 2026-03-30 · Archived: 2026-04-05 20:55:22 UTC\r\nAs part of our ongoing threat hunting efforts, we identified a stealthy Linux backdoor that appears to have gone publicly\r\nunnoticed so far. We named it Plague. The implant is built as a malicious PAM (Pluggable Authentication Module), enabling\r\nattackers to silently bypass system authentication and gain persistent SSH access.\r\nWhat caught our attention: although several variants of this backdoor have been uploaded to VirusTotal over the past year,\r\nnot a single antivirus engine flags them as malicious (see screenshot). To our knowledge, there are no public reports or\r\ndetection rules available for this threat, suggesting that it has quietly evaded detection across multiple environments.\r\nVirusTotal submissions of Plague samples – 0/66 detections\r\nPlague integrates deeply into the authentication stack, survives system updates, and leaves almost no forensic traces.\r\nCombined with layered obfuscation and environment tampering, this makes it exceptionally hard to detect using traditional\r\ntools.\r\nIts ability to persist over long periods without raising suspicion highlights the danger of backdoors targeting foundational\r\nsystem components like PAM. Similar threats have been described in Stealth in 100 Lines: Analyzing PAM Backdoors in\r\nLinux, underlining the broader relevance of this attack vector.\r\nThis case reinforces the importance of proactive detection through YARA-based hunting and behavioral analysis –\r\nespecially for implants that operate silently at the core of Linux systems.\r\nThreat Landscape\r\nThe presence of multiple samples, compiled over a long period and across different environments, demonstrates active\r\ndevelopment and adaptation by the threat actors.\r\nSHA-256 Size Filename\r\nFirst\r\nsubmission\r\nSubmit\r\nfrom\r\nCom\r\nartif\r\n85c66835657e3ee6a478a2e0b1fd3d87119bebadc43a16814c30eb94c53766bb\r\n36.18\r\nKB\r\nlibselinux.so.8\r\n2024-07-29\r\n17:55:52 USA\r\nGCC\r\n10.2\r\n2021\r\nhttps://www.nextron-systems.com/2025/08/01/plague-a-newly-discovered-pam-based-backdoor-for-linux/\r\nPage 1 of 10\n\nSHA-256 Size Filename\r\nFirst\r\nsubmission\r\nSubmit\r\nfrom\r\nCom\r\nartif\r\n7c3ada3f63a32f4727c62067d13e40bcb9aa9cbec8fb7e99a319931fc5a9332e\r\n41.65\r\nKB\r\nlibselinux.so.8\r\n2024-08-02\r\n21:10:51 USA\r\nGCC\r\n10.2\r\n2021\r\n9445da674e59ef27624cd5c8ffa0bd6c837de0d90dd2857cf28b16a08fd7dba6\r\n49.55\r\nKB\r\nlibselinux.so.8\r\n2025-02-04\r\n16:53:45 USA\r\nGCC\r\n13.3\r\n6ubu\r\n13.3\r\n5e6041374f5b1e6c05393ea28468a91c41c38dc6b5a5230795a61c2b60ed14bc\r\n58.77\r\nKB\r\nlibselinux.so.8\r\n2025-02-09\r\n21:27:32 USA\r\nGCC\r\n13.3\r\n6ubu\r\n13.3\r\n6d2d30d5295ad99018146c8e67ea12f4aaa2ca1a170ad287a579876bf03c2950\r\n49.59\r\nKB\r\nhijack\r\n2025-02-10\r\n03:07:24 CHINA\r\nGCC\r\n9.4.0\r\n1ubu\r\n9.4.0\r\ne594bca43ade76bbaab2592e9eabeb8dca8a72ed27afd5e26d857659ec173261\r\n109.67\r\nKB\r\nlibselinux.so.8\r\n2025-02-13\r\n22:58:43\r\nUTC\r\nUSA\r\nstripp\r\n14b0c90a2eff6b94b9c5160875fcf29aff15dcfdfd3402d953441d9b0dca8b39\r\n41.77\r\nKB\r\nlibse.so\r\n2025-03-22\r\n18:46:36 USA\r\nGCC\r\n4.8.5\r\n(Red\r\nThe binaries still contain compiler version metadata, which is consistent with a continuously maintained backdoor that has\r\nbeen running for an extended period. Some binaries are not fully stripped, further supporting this observation.\r\nAttribution for this backdoor remains unclear. However, the presence of a sample named hijack , which appears to be one\r\nof the earliest, may hint at the malware’s origin.\r\nThe authors left a reference to the movie Hackers visible only after deobfuscation. This is printed after pam_authenticate\r\nand serves as a motd message:\r\n“Uh. Mr. The Plague, sir? I think we have a hacker.”\r\nCapabilities and Impact\r\nPlague backdoor is equipped with several features:\r\nAntidebug: Hinders analysis and reverse engineering.\r\nString obfuscation: Protects sensitive strings and offsets, complicating detection.\r\nStatic password: Allows covert access for attackers.\r\nHidden Session artifacts: Erases traces of attacker activity.\r\nTechnical details\r\nObfuscation\r\nThe Plague backdoor employs evolving string obfuscation techniques to hinder detection and analysis. Initial samples used\r\nsimple XOR-based encryption, but later versions adopted more complex methods resembling KSA and PRGA routines. The\r\nmost recent variants add a DRBG layer, further complicating extraction.\r\nThese changes reflect the threat actor’s ongoing efforts to evade both automated and manual analysis. The obfuscation not\r\nonly hides sensitive strings but also their memory offsets, making static analysis unreliable.\r\nTo address this, a custom string deobfuscation tool was developed using Unicorn for safe emulation within IDA Pro. This\r\napproach allows analysts to extract and annotate decrypted strings, even as the obfuscation evolves.\r\nhttps://www.nextron-systems.com/2025/08/01/plague-a-newly-discovered-pam-based-backdoor-for-linux/\r\nPage 2 of 10\n\nA decryption routine called init_phrases is meant to decrypt a block of data containing all the strings. When a string is\r\nneeded, the decrypt_phrase function is called, to retreive its address which is obfuscated too. This is the first xor layer:\r\nxor string decryption\r\nThen, the custom KSA/PRGA, which acts as a layer 2:\r\nhttps://www.nextron-systems.com/2025/08/01/plague-a-newly-discovered-pam-based-backdoor-for-linux/\r\nPage 3 of 10\n\nKey derivation algorithm\r\nAnd finally the third DRBG layer 3 of the obfuscation, which is not yet supported by the decryption tool.\r\nhttps://www.nextron-systems.com/2025/08/01/plague-a-newly-discovered-pam-based-backdoor-for-linux/\r\nPage 4 of 10\n\nDRBG\r\nA custom string extraction tool was developed to handle strings from a sample. Since the obfuscation algorithm has evolved\r\nand it is relatively complex to make a generic decryption tool, the extraction script has been based on unicorn to safely\r\nemulate the code from any platform on IDA Pro. The script has been attached in appendix.\r\nAntidebug\r\nTo guarantee safe execution, the sample verifies that its actual filename is libselinux.so.8 and that ld.so.preload is\r\nnot present in the environment variables. This check allows the sample to evade debuggers and sandbox environments,\r\nwhich often rely on preload mechanisms or rename binaries during analysis.\r\nAntidebug\r\nStealth\r\nAs demonstrated in the disassembly, the malware actively sanitizes the runtime environment to eliminate evidence of an\r\nSSH session. Environment variables such as SSH_CONNECTION and SSH_CLIENT are unset using unsetenv , while\r\nHISTFILE is redirected to /dev/null to prevent shell command logging. This operation ensures that no audit trail or login\r\nmetadata is retained, effectively erasing the attacker’s footprint from both interactive sessions and system history logs.\r\nhttps://www.nextron-systems.com/2025/08/01/plague-a-newly-discovered-pam-based-backdoor-for-linux/\r\nPage 5 of 10\n\nSession stealth\r\nConclusion\r\nThe Plague backdoor represents a sophisticated and evolving threat to Linux infrastructure, exploiting core authentication\r\nmechanisms to maintain stealth and persistence. Its use of advanced obfuscation, static credentials, and environment\r\ntampering makes it particularly difficult to detect using conventional methods.\r\nTHOR is continuously improving to detect even the most stealthy implants.\r\nDetection\r\nArtifacts\r\nThe backdoor includes hardcoded passwords to enable covert access without user authentication. The following passwords\r\nhave been extracted from various samples:\r\nMvi4Odm6tld7\r\nIpV57KNK32Ih\r\nhttps://www.nextron-systems.com/2025/08/01/plague-a-newly-discovered-pam-based-backdoor-for-linux/\r\nPage 6 of 10\n\nchangeme\r\nThe variable bkr=1 acts as a flag indicating whether the sample is running in a safe (non-monitored) environment.\r\nYARA\r\nrule MAL_LNX_PLAGUE_BACKDOOR_Jul25 {\r\n meta:\r\n description = \"Detects Plague backdoor ELF binaries, related to PAM authentication alteration.\"\r\n reference = \"Internal Research\"\r\n author = \"Pezier Pierre-Henri\"\r\n date = \"2025-07-25\"\r\n score = 80\r\n hash = \"14b0c90a2eff6b94b9c5160875fcf29aff15dcfdfd3402d953441d9b0dca8b39\"\r\n hash = \"7c3ada3f63a32f4727c62067d13e40bcb9aa9cbec8fb7e99a319931fc5a9332e\"\r\n strings:\r\n $s1 = \"decrypt_phrase\"\r\n $s2 = \"init_phrases\"\r\n condition:\r\n uint32be(0) == 0x7f454c46\r\n and filesize \u003c 1MB\r\n and all of them\r\n}\r\nAppendix\r\nThe deobfuscation tool below emulates the string decryption routine using Unicorn within IDA Pro 9 and Python \u003e= 3.10.\r\nDecrypted strings are automatically annotated. The sample is not debugged but emulated, that guaranties safe usage on\r\nmultiple platforms.\r\nimport binascii\r\nfrom unicorn import *\r\nfrom unicorn.x86_const import *\r\nimport ida_segment\r\nimport ida_bytes\r\nimport ida_funcs\r\nimport ida_nalt\r\nimport idc\r\nimport idautils\r\nclass Runner:\r\n # Constants for the emulated stack\r\n STACK_ADDR = 0x0FF00000\r\n STACK_SIZE = 0x10000\r\n def __init__(self):\r\n # Initialize Unicorn in 64-bit x86 mode\r\n self.mu = Uc(UC_ARCH_X86, UC_MODE_64)\r\n self.hook_list = {}\r\n # Determine the range of memory to map based on IDA segments\r\n self.low_addr = min(ida_segment.getnseg(i).start_ea for i in range(ida_segment.get_segm_qty()))\r\n self.length = max(self.align(ida_segment.getnseg(i).end_ea - self.low_addr) for i in range(ida_segment\r\n \r\n # Map binary memory and stack into Unicorn\r\n self.mu.mem_map(self.low_addr, self.length)\r\n print(\"Mapped binary memory:\", hex(self.low_addr), \"size:\", hex(self.length))\r\n self.mu.mem_map(self.STACK_ADDR, self.STACK_SIZE)\r\n # Copy IDA's segment bytes into Unicorn memory\r\n for i in range(ida_segment.get_segm_qty()):\r\n seg = ida_segment.getnseg(i)\r\n data = ida_bytes.get_bytes(seg.start_ea, seg.end_ea - seg.start_ea)\r\nhttps://www.nextron-systems.com/2025/08/01/plague-a-newly-discovered-pam-based-backdoor-for-linux/\r\nPage 7 of 10\n\nif data:\r\n self.mu.mem_write(seg.start_ea, data)\r\n # Load imported function thunks to hook\r\n for addr, name in self.get_imports():\r\n self.hook_list[addr] = name\r\n @staticmethod\r\n def align(size, alignment=0x1000):\r\n # Align size to nearest page boundary\r\n return (size + alignment - 1) \u0026 ~(alignment - 1)\r\n def exec_func(self, func_name: str | int) -\u003e int:\r\n # Resolve function address from name or address\r\n if isinstance(func_name, str):\r\n func = ida_funcs.get_func(idc.get_name_ea_simple(func_name))\r\n else:\r\n func = ida_funcs.get_func(func_name)\r\n start_offset = func.start_ea\r\n # Set up the stack with a fake return address (0x0)\r\n rsp = self.STACK_ADDR + self.STACK_SIZE // 2 - 8\r\n self.mu.mem_write(rsp, (0).to_bytes(8, 'little')) # push 0\r\n self.mu.reg_write(UC_X86_REG_RSP, rsp)\r\n # Install instruction hook\r\n self.mu.hook_add(UC_HOOK_CODE, self._hook_code, self)\r\n # Start emulation from the function start\r\n self.mu.emu_start(start_offset, 0)\r\n # Return value from RAX\r\n return self.mu.reg_read(UC_X86_REG_RAX)\r\n def _hook_external_call(self, name):\r\n print(f\"[HOOK] External function: {name}\")\r\n if name.startswith(\"memcpy\"):\r\n dest = self.mu.reg_read(UC_X86_REG_RDI)\r\n src = self.mu.reg_read(UC_X86_REG_RSI)\r\n n = self.mu.reg_read(UC_X86_REG_RDX)\r\n self.mu.mem_write(dest, bytes(self.mu.mem_read(src, n)))\r\n elif name.startswith(\"strlen\"):\r\n rdi = self.mu.reg_read(UC_X86_REG_RDI)\r\n rax = 0\r\n while self.mu.mem_read(rdi + rax, 1)[0] != 0:\r\n rax += 1\r\n self.mu.reg_write(UC_X86_REG_RAX, rax)\r\n else:\r\n print(f\"[!] Unknown external call: {name}\")\r\n self.mu.emu_stop()\r\n return\r\n # Simulate `ret` after external call (pop RIP)\r\n rsp = self.mu.reg_read(UC_X86_REG_RSP)\r\n ret_addr = int.from_bytes(self.mu.mem_read(rsp, 8), 'little')\r\n self.mu.reg_write(UC_X86_REG_RSP, rsp + 8)\r\n self.mu.reg_write(UC_X86_REG_RIP, ret_addr)\r\n print(f\"Returning to 0x{ret_addr:X}\")\r\n @staticmethod\r\nhttps://www.nextron-systems.com/2025/08/01/plague-a-newly-discovered-pam-based-backdoor-for-linux/\r\nPage 8 of 10\n\ndef _hook_code(uc, address, size, self):\r\n # Stop execution if return address is 0\r\n if address == 0:\r\n print(\"[*] Reached return address 0 — stopping emulation\")\r\n self.mu.emu_stop()\r\n return\r\n # Call external hook if the address matches an import\r\n if address in self.hook_list:\r\n self._hook_external_call(self.hook_list[address])\r\n @staticmethod\r\n def get_imports():\r\n # Flatten all imported symbols into a list of (address, name)\r\n result = []\r\n for i in range(ida_nalt.get_import_module_qty()):\r\n def _cb(ea, name, ordinal):\r\n result.append((ea, name or f\"ord_{ordinal}\"))\r\n return True\r\n ida_nalt.enum_import_names(i, _cb)\r\n return result\r\n def get_string_at(self, addr):\r\n # Read a null-terminated string from memory\r\n i = 0\r\n while self.mu.mem_read(addr + i, 1)[0] != 0:\r\n i += 1\r\n return self.mu.mem_read(addr, i).decode()\r\n def dump(self, filename=\"/dev/shm/dump.bin\"):\r\n with open(filename, \"wb\") as fd:\r\n fd.write(bytes(self.mu.mem_read(self.low_addr, self.length)))\r\nmy_runner = Runner()\r\n# Run initialization function\r\nmy_runner.exec_func(\"init_phrases\")\r\n# Get the target address for the decrypt function\r\ntarget = list(idautils.CodeRefsTo(idc.get_name_ea_simple(\"decrypt_phrase\"), 0))[0]\r\n# Feel free to dump to use with `strings`\r\nmy_runner.dump()\r\nprint(\"Decrypting strings...\", hex(target))\r\nfor ref in idautils.CodeRefsTo(target, 0):\r\n print(\"Found ref:\", hex(target))\r\n prev = ref\r\n offset = idc.BADADDR\r\n # Look backward up to 5 instructions to find `mov edi, imm`\r\n for _ in range(5):\r\n if (idc.print_insn_mnem(prev) == \"mov\" and\r\n idc.print_operand(prev, 0) == \"edi\" and\r\n idc.get_operand_type(prev, 1) == idc.o_imm):\r\n offset = idc.get_operand_value(prev, 1)\r\n break\r\n prev = idc.prev_head(prev)\r\n if offset == idc.BADADDR:\r\n print(f\"[!] Could not find argument for call at {hex(ref)}\")\r\n continue\r\nhttps://www.nextron-systems.com/2025/08/01/plague-a-newly-discovered-pam-based-backdoor-for-linux/\r\nPage 9 of 10\n\n# Set EDI for decryption and run function\r\n my_runner.mu.reg_write(UC_X86_REG_EDI, offset)\r\n print(f\"[+] Calling decrypt_phrase({offset}) at 0x{ref:X}\")\r\n result_addr = my_runner.exec_func(target)\r\n # Read decrypted string\r\n decrypted = my_runner.get_string_at(result_addr)\r\n idc.set_cmt(ref, decrypted, 0)\r\n print(f\"[*] Commented: '{decrypted}' at 0x{ref:X}\")\r\nAbout the author:\r\nPierre-Henri Pezier\r\nPierre‑Henri Pezier is an IT Security Engineer and Threat Researcher with over a decade of experience in offensive security,\r\nreverse engineering, malware analysis and secure software development. He began reverse-engineering software in the early\r\n2010s, a passion that expanded into analyzing advanced threats, developing decryptors, and writing detection rules. With a\r\nbackground in both offensive and defensive security, Pierre‑Henri has worked on malware classification engines, sandbox\r\nenvironments, and EDR evasion techniques.\r\nSource: https://www.nextron-systems.com/2025/08/01/plague-a-newly-discovered-pam-based-backdoor-for-linux/\r\nhttps://www.nextron-systems.com/2025/08/01/plague-a-newly-discovered-pam-based-backdoor-for-linux/\r\nPage 10 of 10",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://www.nextron-systems.com/2025/08/01/plague-a-newly-discovered-pam-based-backdoor-for-linux/"
	],
	"report_names": [
		"plague-a-newly-discovered-pam-based-backdoor-for-linux"
	],
	"threat_actors": [
		{
			"id": "aa73cd6a-868c-4ae4-a5b2-7cb2c5ad1e9d",
			"created_at": "2022-10-25T16:07:24.139848Z",
			"updated_at": "2026-04-10T02:00:04.878798Z",
			"deleted_at": null,
			"main_name": "Safe",
			"aliases": [],
			"source_name": "ETDA:Safe",
			"tools": [
				"DebugView",
				"LZ77",
				"OpenDoc",
				"SafeDisk",
				"TypeConfig",
				"UPXShell",
				"UsbDoc",
				"UsbExe"
			],
			"source_id": "ETDA",
			"reports": null
		}
	],
	"ts_created_at": 1775434703,
	"ts_updated_at": 1775791469,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/f99ce383d7c1b1158098b7e6a6ebf79f13f55897.pdf",
		"text": "https://archive.orkl.eu/f99ce383d7c1b1158098b7e6a6ebf79f13f55897.txt",
		"img": "https://archive.orkl.eu/f99ce383d7c1b1158098b7e6a6ebf79f13f55897.jpg"
	}
}