【问题标题】:How do call _start of a binary from another executable?如何从另一个可执行文件调用二进制文件的_start?
【发布时间】:2021-09-13 23:27:01
【问题描述】:

我有一个 elf64 可执行文件 foo,我想“手动”加载和启动它,并能够从中调用其他函数。如何将其加载到内存中,然后设置指令指针以使用它运行。

foo 不是一个共享对象库,它是一个可执行文件,具有导出的某些功能,就好像它是一个 SO。

所以,有几个问题:

  1. 我在哪里将二进制文件加载到内存中以便可执行?堆?堆?
  2. 如何设置指令指针以从我的程序更改为 foo 的入口点?

例如,我有以下,但它的段错误:

#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;

} 

由于各种原因,不能使用普通方法启动 foo(),如 system() 或其他标准 OS 工具。我需要能够调用 _start 来启动它,并在它开始运行后调用“foo_bar()”。我曾尝试使用 dlopen/dlsym,但它不起作用,因为它是可执行文件而不是共享库

【问题讨论】:

  • 例如,我有以下内容,但它有段错误: 您是在尝试启动静态还是动态链接的可执行文件?如果它是动态链接的,那么在调用_start() 之前,您还没有完成动态链接器所做的事情:加载可执行文件所需的所有共享库。加载共享对象涉及很多...
  • 另见loading ELF file in C in user space,它是 32 位的,但可能是相关的。另请注意,如果您跳转到_start,您将永远无法获得控制权。
  • 它通常的工作方式是你有一个线程来执行一个简单的任务,比如通过函数指针的地址调用函数。由于 ASLR,地址必须在运行时解析。通过首先在远程进程中分配内存,然后以执行/写入权限写入分配的内存,您将那段代码/线程注入远程地址空间(进程)。然后,您使用一些线程初始化方法来启动线程并调用您的远程函数。线程如何初始化通常是比较棘手的部分,但有几种技术。
  • 根据您的具体目标,您最好让操作系统像往常一样启动一个进程并使用调试功能来实现您的目标。
  • 将你想要的函数的机器代码提取到共享库中可能更有意义,或者将objcopy整个FOO提取到.so共享对象中,你可以dlopen。 (或者如果它是一个 PIE 可执行文件,它已经一个你可以 dlopen 的 ELF 共享对象)。或者只是将您想要的功能分解为 asm 源代码并将其构建+链接为新可执行文件的一部分。但如果它使用一堆静态数据,你也需要找到它,以及它的运行时初始化程序(如果有的话)。

标签: c assembly elf


【解决方案1】:

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

这是一个谎言:您将可执行文件加载到 heap,而不是 stack

我在哪里将二进制文件加载到内存中以便可执行?堆?堆?

堆和栈都不合适;使用mmap 将二进制文件放入内存。可能需要多个mmap 调用(目标二进制文件中每个PT_LOAD 段调用一个)。

如何设置指令指针以从我的程序更改为 foo 的入口点?

更改指令指针是最少您的问题(您已经编码的jmp 将起作用,或者您可以将地址转换为函数指针,然后简单地调用它——不需要内联汇编)。


您要实现的目标非常重要。查看 UPX 资源以了解其中涉及的内容。

如果二进制文件是非 PIE 可执行文件,那么您无法将其加载到任意内存位置——它已被链接以在 特定 地址加载,如果出现以下情况,将无法正确运行加载到任何其他地址。

如果二进制文件是 PIE 可执行文件,它可以在任意地址运行(尽管它仍然有对齐要求,malloc 不太可能满足)。

在旧版本的 GLIBC 中,可以dlopen() PIE 可执行文件,但新版本不允许这样做。

在 PIE 可执行文件可以运行之前,它必须被重新定位,这也是非常重要的任务。

TL;DR:你可能真的不想这样做(另请参阅http://xyproblem.info),但如果你真的这样做,肯定会比仅仅read将二进制文件放入缓冲区需要更多的努力并跳转到其中的_start 符号。

【讨论】:

  • 是的,用错词了。我一次尝试了几件事,并且在我的示例中使用了错误的词(stack v heap)。我会在周末更多地考虑这个和你的 cmets,XY 问题可能适用。我可能需要发布一个更具体的示例来说明我正在尝试做的事情。
猜你喜欢
  • 2014-07-03
  • 1970-01-01
  • 2018-11-04
  • 2019-02-20
  • 1970-01-01
  • 2012-04-01
  • 2013-12-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多