【问题标题】:using memfd_create and fexecve to run ELF from memory使用 memfd_create 和 fexecve 从内存运行 ELF
【发布时间】:2020-11-22 06:35:06
【问题描述】:

我知道其他人用不同的语言做过,但我根本找不到 C 代码示例,最常见的是在 Perl 中,这真的很混乱,因为我不懂 Perl 我只想将一个二进制文件(从磁盘)加载到内存中然后执行它 https://magisterquis.github.io/2018/03/31/in-memory-only-elf-execution.html

【问题讨论】:

  • 你做memfd_create,你把二进制的内容写到它,然后你在它上面调用fexecve。真的没有什么复杂的。如果您尝试过但没有成功,请发布您尝试过的代码并准确描述发生的情况。
  • > 我只想将二进制文件(从磁盘)加载到内存中,然后执行它——字面意思是单行的:从磁盘部分加载将自动为您完成,您只需致电execve。您实际上想要达到什么目的?

标签: c binary elf


【解决方案1】:

给你一个用 C 编写的示例(在 linux 5.4 上编译并按预期运行):

#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L

#include <sys/types.h>
#include <sys/mman.h>

#include <unistd.h>

#include <err.h>
#include <errno.h>

size_t min(size_t x, size_t y)
{
    return x > y ? y : x;
}

/**
 * @param len != 0
 */
void fdput(int fd, const char *str, size_t len)
{
    size_t cnt = 0;
    do {
        ssize_t result = write(fd, str + cnt, min(len - cnt, 0x7ffff000));
        if (result == -1) {
            if (errno == EINTR)
                continue;
            err(1, "%s failed", "write");
        }
        cnt += result;
    } while (cnt != len);
}
#define fdputc(fd, constant_str) fdput((fd), (constant_str), sizeof(constant_str) - 1)

int main(int argc, char* argv[])
{
    int fd = memfd_create("script", 0);
    if (fd == -1)
        err(1, "%s failed", "memfd_create");

    fdputc(fd, "#!/bin/bash\necho Hello, world!");

    {
        const char * const argv[] = {"script", NULL};
        const char * const envp[] = {NULL};
        fexecve(fd, (char * const *) argv, (char * const *) envp);
    }

    err(1, "%s failed", "fexecve");
}

我还测试了在fexecve 之前调用fork(),它也可以按预期工作。

这是代码(与上面提供的大部分相同):

#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L

#include <sys/types.h>
#include <sys/mman.h>
#include <sys/wait.h>

#include <unistd.h>

#include <err.h>
#include <errno.h>

size_t min(size_t x, size_t y)
{
    return x > y ? y : x;
}

/**
 * @param len != 0
 */
void fdput(int fd, const char *str, size_t len)
{
    size_t cnt = 0;
    do {
        ssize_t result = write(fd, str + cnt, min(len - cnt, 0x7ffff000));
        if (result == -1) {
            if (errno == EINTR)
                continue;
            err(1, "%s failed", "write");
        }
        cnt += result;
    } while (cnt != len);
}
#define fdputc(fd, constant_str) fdput((fd), (constant_str), sizeof(constant_str) - 1)

int main(int argc, char* argv[])
{
    int fd = memfd_create("script", 0);
    if (fd == -1)
        err(1, "%s failed", "memfd_create");

    fdputc(fd, "#!/bin/bash\necho Hello, world!");

    pid_t pid = fork();
    if (pid == 0) {
        const char * const argv[] = {"script", NULL};
        const char * const envp[] = {NULL};
        fexecve(fd, (char * const *) argv, (char * const *) envp);

        err(1, "%s failed", "fexecve");
    } else if (pid == -1)
        err(1, "%s failed", "fork");

    wait(NULL);

    return 0;
}

【讨论】:

  • 我认为fdputc 宏和fpdut 函数没用,因为memfd 上的所有I/O 操作都应该是阻塞的。除此之外,答案非常好
  • 我写 fdput 是因为 write 的 API 说 writen 的字节数可以小于请求的字节数,并且这个系统调用可以通过信号来解释。我知道在这样的小程序中可能不会发生这种情况,但我只是想确保它写得正确并且它的代码没有误导。
  • 是的,我明白了。尽管如此,我希望在memfd 中写入的所有操作都可以工作或失败,这意味着不应该有部分写入支持,因为内存要么可以分配,要么不能分配。当然,在假设它是这样之前,这应该记录在某个地方。
  • 不幸的是,memfd 只有通用的写/读支持,AFAIK linux 没有为读/写中的任何 fd 提供任何特殊语义以具有稳定的 API 并与 posix 兼容。因此,安全的方法是像检查任何其他代码一样检查 m。
猜你喜欢
  • 2022-01-06
  • 2018-09-09
  • 1970-01-01
  • 1970-01-01
  • 2018-01-23
  • 2018-11-13
  • 2020-12-12
  • 2016-03-21
  • 2017-11-16
相关资源
最近更新 更多