3

I have an elf64 executable, foo, that I want to load and start “by hand” and be able to call other functions from. How do I load it into memory and then set the instruction pointer to run with it.

foo is NOT a shared object library, it is an executable that has certain functions exported as if it were an SO.

So, a few questions:

  1. Where do I load the binary into memory so that is executable? Stack? Heap?
  2. How do I setup the instruction pointer to change from my program to the entry point of foo?

For example, I have the following, but it segfaults:

#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <elf.h>

#define ELF_SIZE 10000

int main(int argc, char **argv)
{

    FILE *fp;
    void *   entry_point ;
    Elf64_Ehdr *elfHdr;
    uint8_t *buffer = malloc(ELF_SIZE);

    fp = fopen("./foo", "rb");

    int read_size = fread(buffer, 1, ELF_SIZE, fp);
    if (read_size == ELF_SIZE)
    {
        printf("loaded ELF onto heap
\n");
    } else
    {
        printf("read failed: %d\n", read_size);
        return 0;
    }
    printf("elf loaded at %x\n", buffer);

    elfHdr = (Elf64_Ehdr*) buffer;

    printf("entry point at %x\n", elfHdr->e_entry);

    entry_point = elfHdr->e_entry + buffer;

    printf("trying to jump to: %x\n", entry_point
            );
        int a;
        __asm__ ("jmp %1;"
                : "=r" (a)
                : "r" (entry_point));

    return 0;

} 

Using normal methods to start foo() like system(), or other standard OS tool isn't an option for various reasons. I need to be able to call _start to kick it off and and "foo_bar()" once it has started running. I have tried using dlopen/dlsym but it doesn't work since its an executable and not a shared library

charlesw
  • 572
  • 6
  • 25
  • 2
    *For example, I have the following, but it segfaults:* Are you trying to start a static or dynamically-linked executable? If it's dynamically linked, you haven't done what the dynamic linker does before calling `_start()`: load all the shared libraries needed by the executable. And there's a ***lot*** involved in loading a shared object... – Andrew Henle Jul 01 '21 at 23:30
  • 2
    See also [loading ELF file in C in user space](https://stackoverflow.com/a/13915982/547981) which is 32 bit but might be relevant. Also note if you jump to `_start` you will never get control back. – Jester Jul 01 '21 at 23:38
  • 1
    The way it typically works is you you have a thread which performs a simple task like calling a function by an address through a function pointer. The address has to be resolved at runtime because of ASLR. You inject that piece of code/thread into the remote address space(process) by first allocating memory in the remote process, then writing to that allocated memory with execution/write privileges. You then use some thread initialisation method to launch the thread and call your remote function. How the thread is initialised is usually the trickier part but there are several techniques. – Irelia Jul 01 '21 at 23:58
  • I assume you know that an execve system call would replace this thread with a new process running `_start` from that executable. – Peter Cordes Jul 02 '21 at 00:07
  • 2
    Depending on what exactly you want to do, you might be better off actually letting the OS launch a process as usual and use debugging features to achieve your goal. – Jester Jul 02 '21 at 00:14
  • This seems like a great big XY problem. Whatever it is you're trying to accomplish by doing this, I strongly suspect there is an easier and more reliable method. – Nate Eldredge Jul 02 '21 at 01:40
  • The reason I am trying to do this is that FOO has exported a certain function of it that I want to be called while it is running, but it is not called by anything within FOO itself. I don't have, and cant get, the source for FOO. I was hoping if I could launch it by hand, i'd be able to start it on one thread, and then call the exported function I want to run in another. – charlesw Jul 02 '21 at 02:12
  • 1
    Probably makes a lot more sense to extract the machine code for the function you want into a shared library, or `objcopy` the whole FOO into a `.so` shared object you can `dlopen`. (Or if it's a PIE executable, it already *is* an ELF shared object you can dlopen). Or just disassemble the function you want into asm source code and build+link it as part of your new executable. But if it uses a bunch of static data, you'd need to find that too, along with run-time initializers for it if any. – Peter Cordes Jul 02 '21 at 02:19
  • 1
    So `file FOO` says "ELF ... executable", not "ELF ... pie executable"? Too bad, a pie executable *is* a shared object, but a traditional executable isn't, and only works if its segments are mapped at their specified base addresses because code can hard-code absolute addresses without relocation info. (PIE was adopted to allow ASLR). – Peter Cordes Jul 02 '21 at 02:32
  • Anyway, depending on what static data the function you want actually references, you might still be able to `objcopy` it out into a shared object, or into something you can statically include in your new program. – Peter Cordes Jul 02 '21 at 02:34

1 Answers1

5

printf("loaded ELF onto stack \n");

That is a lie: you loaded the executable into heap, not stack.

Where do I load the binary into memory so that is executable? Stack? Heap?

Neither heap nor stack are appropriate; use mmap to bring the binary into memory. Several mmap calls are likely required (one for each PT_LOAD segment in the target binary).

How do I setup the instruction pointer to change from my program to the entry point of foo?

Changing the instruction pointer is the least of your problems (the jmp you've already coded will work, or you can cast the address to a function pointer and then simply call it -- no need for inline assembly).


What you are trying to achieve is exceedingly non-trivial. Look at UPX sources to see what's involved.

If the binary is non-PIE executable, then you can't load it into arbitrary memory location -- it has been linked to be loaded at a specific address, and will not run correctly if loaded at any other address.

If the binary is a PIE executable, it can run at arbitrary address (though it will still have alignment requirements, which malloc is unlikely to satisfy).

In older versions of GLIBC it was possible to dlopen() PIE executables, but newer versions disallow that.

Before a PIE executable can run, it must be relocated, which is again highly non-trivial task.

TL;DR: you probably don't really want to do this (see also http://xyproblem.info), but if you really do, it would certainly take a lot more effort than merely reading the binary into a buffer and jumping to the _start symbol in it.

Employed Russian
  • 199,314
  • 34
  • 295
  • 362
  • Yep, had the wrong word. I was trying several things at once and had the wrong word in my example (stack v heap). I'll think more about this and your comments over the weekend, XY problem may be applicable. I might need to post a more specific example of what I'm trying to do. – charlesw Jul 02 '21 at 04:47