在mmap中执行代码以生成可执行代码段错误

时间:2018-05-04 17:09:00

标签: c linux assembly function-pointers currying

我正在尝试编写一个复制函数的函数(并最终修改其程序集)并返回它。这适用于一个级别的间接,但在两个我得到段错误。

这是一个最小(非)工作示例:

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

#define BODY_SIZE 100

int f(void) { return 42; }
int (*G(void))(void) { return f; }
int (*(*H(void))(void))(void) { return G; }

int (*g(void))(void) {
    void *r = mmap(0, BODY_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    memcpy(r, f, BODY_SIZE);
    return r;
}

int (*(*h(void))(void))(void) {
    void *r = mmap(0, BODY_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    memcpy(r, g, BODY_SIZE);
    return r;
}

int main() {
    printf("%d\n", f());
    printf("%d\n", G()());
    printf("%d\n", g()());
    printf("%d\n", H()()());
    printf("%d\n", h()()()); // This one fails - why?

    return 0;
}

我可以记忆到一个mmap的区域,创建一个可以调用的有效函数(g()())。但是,如果我再次尝试应用它(h()()()),则会出现段错误。我已经确认它正确创建了g的复制版本,但是当我执行该版本时,我得到了一个段错误。

我有什么理由不能在另一个mmap的区域中的一个mmap区域中执行代码吗?从使用x/i检查的探索性gdb开始,我似乎可以成功调用,但是当我返回我来自的函数时已被删除并替换为0。

如何让这种行为发挥作用?它甚至可能吗?

BIG EDIT:

很多人都问我的理由,因为我在这里显然是在做XY问题。这是真实而有意的。你看,一个月之前有一点this问题发布在代码高尔夫堆栈交换上。对于C / Assembly解决方案,它也有一个很好的bounty。我对这个问题做了一些空洞的思考,并意识到通过复制一个函数体同时删除一个具有一些唯一值的地址,我可以在其内存中搜索该值并将其替换为有效的地址,从而允许我有效地创建lambda函数将单个指针作为参数。使用这个我可以得到单一的currying工作,但我需要更一般的currying。因此,我当前的部分解决方案与here相关联。这是展示我试图避免的段错误的完整代码。虽然这几乎是一个坏主意的定义,但我觉得它很有趣,并且想知道我的方法是否可行。我唯一缺少的是运行从函数创建的函数的能力,但我无法让它工作。

3 个答案:

答案 0 :(得分:5)

代码使用相对调用来调用mmapmemcpy,因此复制的代码最终会调用无效位置。

您可以通过指针调用它们,例如:

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

#define BODY_SIZE 100

void* (*mmap_ptr)(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset) = mmap;
void* (*memcpy_ptr)(void *dest, const void *src, size_t n) = memcpy;

int f(void) { return 42; }
int (*G(void))(void) { return f; }
int (*(*H(void))(void))(void) { return G; }

int (*g(void))(void) {
    void *r = mmap_ptr(0, BODY_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    memcpy_ptr(r, f, BODY_SIZE);
    return r;
}

int (*(*h(void))(void))(void) {
    void *r = mmap_ptr(0, BODY_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    memcpy_ptr(r, g, BODY_SIZE);
    return r;
}

int main() {
    printf("%d\n", f());
    printf("%d\n", G()());
    printf("%d\n", g()());
    printf("%d\n", H()()());
    printf("%d\n", h()()()); // This one fails - why?

    return 0;
}

答案 1 :(得分:3)

  

我正在尝试编写一个复制函数的函数

我认为这不是正确的方法,除非你非常了解你的平台的机器代码(然后你不会问这个问题)。请注意position independent code(有用,因为一般mmap(2)会使用ASLR并在地址中提供一些“随机性”。 BTW,真正的self-modifying machine code(即更改某些现有有效机器代码的某些字节)今天cachebranch-predictor不友好,应该在实践中避免使用。

我建议采用两种相关方法(选择其中一种方法)。

  • 生成一些临时 C文件(另请参阅this),例如在/tmp/generated.c中,然后将使用gcc -Wall -g -O -fPIC /tmp/generated.c -shared -o /tmp/generated.so的汇编分为plugin,然后dlopen(3)dynamic loading} /tmp/generated.so {{3插件(并且可能使用shared object来查找其中的函数指针...)。有关共享对象的更多信息,请阅读Drepper的dlsym(3)论文。今天,你可以dlopen成千上万的这样的共享库(参见我的How To Write Shared Libraries示例)和C编译器(比如最近的manydl.c)足够快,可以编译几千行代码在与交互相容的时间内(例如,小于十分之一秒)。生成C代码是GCC实践。实际上,在发出之前,你会在生成的C代码的内存中代表一些widely used

  • 使用一些AST库,例如JIT compilationGCCJIT,或LLVM,或libjit等....会在内存中生成一个函数,执行所需的asmjit,然后给你一些指针。

BTW,您可以考虑使用一些relocations语言实现(例如homoiconic用于Common Lisp,而不是使用C编码,它可以在每次SBCL交互时编译为机器代码,或者任何动态构造的REPL程序表示。)

S-exprclosures的概念值得了解。阅读callbacksSICP(当然还有Lisp In Small Pieces,以了解一般编译器文化)。

答案 2 :(得分:0)

  

此问题发布在代码golf.SE

我更新了the 8086 16-bit code-golf answer关于问题总结的问题以包含评论的反汇编。

您可以在32位代码中使用相同的想法,使用stack-args调用约定来生成机器代码函数的修改副本,该函数适用于push imm32。但它不再是固定大小的,因此您需要更新复制的机器代码中的函数大小。

在普通的通话约定中,第一个arg被推送到最后,因此您无法在固定大小push imm32 / {{之前追加另一个call target 1}} / leave预告片。如果编写纯asm答案,则可以使用备用调用约定,其中args以其他顺序推送。或者你可以有一个固定大小的介绍,然后是一个不断增长的ret +呼叫/离开/返回序列。

currying函数本身可以使用register-arg调用约定,即使你希望目标函数以i386 System V为例(stack args)。

您肯定希望通过不支持超过32位的args来简化,因此没有值的结构,也没有push imm32。 (当然你可以链接多次调用currying函数来构建一个更大的arg。)

考虑到编写新代码 - 高尔夫挑战的方式,我猜你要比较curried args的总数与目标&#34;输入&#34;的args数量。功能需要。

我认为没有机会只用double就可以在纯C中完成这项工作;你必须修改机器代码。