23 comments

[ 2.7 ms ] story [ 53.7 ms ] thread
Oh yeah, I've once discovered this method myself. With execveat() one can even avoid going through procfs.

It doesn't work worth shbang-based scripts, though; that just returns ENOENT. Makes sense, I suppose.

Would you be willing to share how you did that (avoiding procfs too)?
Instead of

    snprintf(pathbuf, sizeof(pathbuf), "/proc/self/fd/%u", fd);
    execve(pathbuf, argv, envp);
you do

    execveat(fd, "", argv, envp, AT_EMPTY_PATH);
and that's it.
And when would you want to do that ? Really curious as I fail to see a usecase where the same goal could not be achieve otherwise. But there must be some otherwise the API would not have been introduced i guess.
Organizing and executing code via a storage scheme other than a file system?

Security issues aside, I can imagine maintaining compiled code for stored procedures in database BLOBs.

Similarly, in an appropriate trust environment, I might want to ship work units to remote machines in the form of raw ELF. Copying the bits to disk just to execute them is unneeded overhead.

But a tmpfs (the usual way to do this) isn't persisted to a disk
It can be; swap and suspend-to-disk / hibernation can cause tmpfs to be written.

I wonder if there's a way to execute from mlock'd or madvised memory. I can imagine this be particularly useful, if you wanted to prevent your injected program (malware? valuable IP?) from being written to disk or appearing in a core dump.

True, but those caveats apply to OP's technique too. What I am saying is that there is no specific behavior of a tmpfs which causes things in it to be necessarily persisted to disk like the parent was suggesting. It works just like any other anonymous memory does.
In the example given "We've managed to get access to a victim at 10.131.66.89", it's implied that nefariously injecting arbitrary binaries into a locked-down host is one use case.

> otherwise the API would not have been introduced

I don't think memfd_create was added specifically for the purpose of running binaries from memory; it's more generally useful in cases where you want a memory-backed file but don't want to rely on the presence of a memory-backed filesystem (tmpfs). Many programs create a file in a well-known tmpfs like /dev/shm then delete it, but memfd_create is a more sensible solution if you don't want to share memory via the filesystem. memfd_create eliminates concerns over naming and collisions.

It lets you execute files even with no writeable filesystem , which could be useful exploiting a locked down system. It is also an argument that locking down programs with no writeable fs is not sufficient for security.

The memfd api has lots of other uses, the fact you can exec is just a byproduct.

> It is also an argument that locking down programs with no writeable fs is not sufficient for security.

It is in fact silly since write and execute are orthogonal. You can mount a filesystem as noexec. Although memfd does put a bullet into W^X aspirations.

There are other ways to do this that don't involve tmpfs or write() - see https://github.com/dimkr/papaw and https://github.com/dimkr/Mirai-Source-Code
But the readme says:

The payload executable is extracted to a temporary file. When running as root, this is done by mounting a tmpfs file system and lazily unmounting it before the extraction.

When not running as root, it doesn't use a tmpfs. Also, papaw replaces /proc/self/exe with an empty file. And it has some basic anti-debugging, like locking of the payload to RAM so it cannot be recovered by reading it from a swap partition.
A bit off-topic, but their perl code is buggy. fork() returns undef on error, not -1.

And you can use '$FH->autoflush(1)' instead of the eye-watering 'select((select($FH), $|=1)[0])'

I was going to say that you need to use the IO::Handle module to be able to use the autoflush method, but apparently this no longer necessary with modern Perl.

From https://metacpan.org/pod/perl5120delta#Other-potentially-inc... :

> Filehandles are now always blessed into IO::File.

> The previous behaviour was to bless Filehandles into FileHandle (an empty proxy class) if it was loaded into memory and otherwise to bless them into IO::Handle.

Given that Linux now has both file descriptors, and process memory-page ranges, pointing to anonymous allocated memory regions, are they interchangeable?

What I mean is:

- to go from a memfd to anonymous memory, you can call mmap(2) on the fd

- to go from anonymous memory to a memfd, you...?

Semantically this should do the job: anonymous memory -> vmsplice(gift) -> pipe -> splice(move) -> memfd. Then remap the pages from memfd to the original address ranges so you're allowed to use them again. But I don't know whether this is supported on memfds. And of course it's not atomic.