0

we are trying to copy a binary/elf file into a shared-memory region of our system and then execute it thereafter. We don't want to call our "client"-program directly, since we need to execute it from the memory itself for our purpose.

While we know that our approach (described below) won't really work, we are (obviously) trying to get it to work. How would it be possible to copy a binary/elf/etc. file directly into the (shared)-memory and execute it thereafter? Maybe we just compiled it in the wrong way? Or something else was done wrong?

We also don't want to convert it into hex/shell-code, we already did that. We are looking for an easier and more practical solution.

Is anyone able to help? Would be much appreciated!

Two programs:

  1. "Host"-Program (copy & execute client-program in shared memory)
  2. "Client"-Program (basically a hello-world echo)

"Client"-Program:

#include <stdio.h>
int main()
{
    printf("Hello, World!\n");
    return 0;
}

Compiled with gcc -o binfile clientprogram.c -static.

"Host"-Program:

#include <string.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
  FILE *fp; //filepointer
  size_t size; //filesize
  unsigned char *buffer; //buffer

  fp = fopen("binfile","rb"); 
  fseek(fp, 0, SEEK_END); 
  size = ftell(fp);
  fseek(fp, 0, SEEK_SET); 
  buffer = (unsigned char *) malloc(size);
  if (fp == NULL){ //file empty?
      printf("Error: There was an Error reading the file %s \n", "binfile");           
      exit(1);
  }
  else if (fread(buffer, sizeof *buffer, size, fp) != size){ 
      printf("Error: There was an Error reading the file %s\n", "binfile");
      exit(1);
  }else{
    int i;
    // for(i=0; i<size;i++){       
    //    printf("%02x", buffer[i]);
    // }
  }


  void *mem = mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
  memcpy(mem, buffer, size);
  mprotect(mem, size, PROT_READ|PROT_WRITE|PROT_EXEC);

  void (*func)();
  func = (void (*)()) buffer;
  func();  

  munmap(mem, size);

  fclose(fp);
  free(buffer);
  return 0;
}

Compiled with gcc hostprogram.c.

Marvin
  • 133
  • 1
  • 9
  • 1
    This approach is naive and will of course fail. Loading and executing a binary is far more complicated than just reading it into memory and calling the load address. This is probably an [XY Problem](http://xyproblem.info/). What _exactly_ are you trying to achieve? – Jabberwocky Jun 14 '17 at 11:55
  • While the `main` function is the official entry-point into your program, there is a lot of code being executed *before* the `main` function is called. For example initialization of global variables is handled before the `main` function, but also other things like setting up the standard file streams `stdout`, `stdin` and `stderr`. Doing that should only happen once. Not to mention that an [ELF file](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) doesn't start with *code* that can be called. – Some programmer dude Jun 14 '17 at 11:59
  • 2
    As for your question, *why* do you want to do this? What problem is it supposed to solve? – Some programmer dude Jun 14 '17 at 12:00
  • @Marvin Have a look at [this](https://stackoverflow.com/questions/2019923/executing-machine-code-in-memory). – Gaurav Pathak Jun 14 '17 at 12:08
  • Thanks for the input, MichaelWalz, Someprogrammerdude and GauravPathak! @Someprogrammerdude We want to be able to copy any (of our own) programs into a shared-memory region and then execute it from there. The approach above was just the first thing that came to our minds, we know its naive ;) Does anyone have any ideas how to do this with another approach? – Marvin Jun 14 '17 at 12:48
  • 1
    But why do you need to use shared memory? Why can't you use the method described in [the answer by PSkocik](https://stackoverflow.com/a/44544383/440558)? If you're worried about loading multiple copies of the program into memory, then don't worry. The operating system should only load a shared object (ELF file) once, and simply map it into the different processes that uses the same shared object. – Some programmer dude Jun 14 '17 at 12:54

2 Answers2

3

Build the client as a PIE, with -rdynamic. Then you'll be able to dlopen() it and dlsym() its main symbol (dlopen() will do the mmaping and mprotecting for you, as you'll be able to see if you strace the program), after which you'll be able to run its main from within the address space of the host.

Example:

#!/bin/sh
cat > client.c <<EOF
#include <stdio.h>
#include <unistd.h>
int main()
{
    printf("Hello, World!: from %ld\n", (long)getpid());
    return 0;
}
EOF

gcc -fpic -c client.c
gcc -pie -rdynamic -o client client.o

cat > host.c <<EOF
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
    printf("Hello, I'm your host: %ld\n", (long)getpid());  ;

    void *client_hndl;
    typedef int main_t(int, char**);
    main_t *client_main;
    client_hndl = dlopen("./client",  RTLD_LAZY);
    if (!client_hndl){
        fprintf(stderr, "%s\n", dlerror());
        exit(1);
    }
    client_main = (main_t*)dlsym(client_hndl, "main");
    if (!client_main){
        fprintf(stderr, "%s\n", dlerror());
        exit(2);
    }
    return client_main(1, (char*[]){"client", 0});
}
EOF
gcc host.c -ldl
./client
echo =============
./a.out

Example output:

Hello, World!: from 14520
=============
Hello, I'm your host: 14521
Hello, World!: from 14521
Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • Thanks, @PSkocik! This kinda works! But we need to use a shared-memory region. So the `client_hndl pointer`just points to the program? Can we copy this into a shared memory region that was created by us?a – Marvin Jun 14 '17 at 12:51
0

You are looking for a solution to this GLIBC feature request.

This feature request is 7 years old, and it's somewhat unlikely that anything will happen with it any time soon.

Your best bet is probably to do roughly what you are already doing (building a fully-static binary).

Your approach doesn't work because the executable you built requires to be loaded at the address it was linked at (visible in readelf -l binfile as the address of the first PT_LOAD segment. You would need to mmap your binfile there with MAP_FIXED, no other address will do.

You also need to read and decode the Elf{32,64}_Ehdr that is found at the beginning of the file to find entry point to jump to. You currently are jumping to the ELF header itself, but that header is not where the execution should start.

Employed Russian
  • 199,314
  • 34
  • 295
  • 362