【问题标题】:Generating functions at runtime in C在 C 中运行时生成函数
【发布时间】:2013-02-12 05:26:59
【问题描述】:

我想在运行时在 C 中生成一个函数。我的意思是我本质上想分配一些内存,指向它并通过函数指针执行它。我意识到这是一个非常复杂的话题,我的问题很幼稚。我也意识到有一些非常强大的库可以做到这一点(例如nanojit)。

但我想从基础开始学习这项技术。有知识的人可以给我一个非常简单的 C 示例吗?

编辑: 下面的答案很好,但这里是 Windows 的相同示例:

#include <Windows.h>

#define MEMSIZE 100*1024*1024
typedef void (*func_t)(void);

int main() {

    HANDLE proc = GetCurrentProcess();
    LPVOID p = VirtualAlloc(
        NULL,
        MEMSIZE,
        MEM_RESERVE|MEM_COMMIT,
        PAGE_EXECUTE_READWRITE);

    func_t func = (func_t)p;
    PDWORD code = (PDWORD)p;
    code[0] = 0xC3; // ret

    if(FlushInstructionCache(
        proc,
        NULL,
        0))
    {
        func();
    }

    CloseHandle(proc);
    VirtualFree(p, 0, MEM_RELEASE);
    return 0;
}

【问题讨论】:

  • 这有一个普遍的问题,即 C89/90 标准明确规定将任意指针转换为函数指针是未定义的行为(G.2: i> 指向函数的指针被转换为指向对象的指针,或者指向对象的指针被转换为指向函数的指针。)我会很惊讶(但很感兴趣) !)如果你真的能以一种可移植的、符合标准的方式做到这一点。
  • 请注意,在运行时生成代码存在重大的安全缺陷。攻击者通常对流经您的程序的数据有一定程度的控制。如果您有攻击者控制的数据成为攻击者控制的代码的途径,那么攻击者就有途径在您/您客户的机器上安装恶意软件。混合数据和代码后果自负!
  • 谢谢马特,我知道它的风险和复杂性。我的理解是,这通常是 JIT 工具使用的技术,本质上是编译器。如果我错了,请纠正我。
  • 从 C11 开始(尚未检查 C99),将函数指针转换为对象指针不再是未定义行为:它现在被视为实现提供的扩展 (J.5.7)。即它是允许的。

标签: c function function-pointers jit dynamically-generated


【解决方案1】:

正如其他发帖人之前所说,您需要非常了解您的平台。

忽略将对象指针转换为函数指针的问题,从技术上讲,UB,这是一个适用于 x86/x64 OS X(也可能是 Linux)的示例。生成的代码所做的只是返回给调用者。

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

typedef void (*func_t)(void);

int main() {
    /*
     * Get a RWX bit of memory.
     * We can't just use malloc because the memory it returns might not
     * be executable.
     */
    unsigned char *code = mmap(NULL, getpagesize(),
            PROT_READ|PROT_EXEC|PROT_WRITE,
            MAP_SHARED|MAP_ANON, 0, 0);

    /* Technically undefined behaviour */
    func_t func = (func_t) code;

    code[0] = 0xC3; /* x86 'ret' instruction */

    func();

    return 0;
}

显然,这在不同平台上会有所不同,但它概述了所需的基本知识:获取内存的可执行部分、写入指令、执行指令。

【讨论】:

  • 据记录,Windows 中的相应函数似乎是带有 PAGE_EXECUTE_READWRITE 标志的 VirtualAlloc。
【解决方案2】:

这需要您了解您的平台。例如,您平台上的 C 调用约定是什么?参数存储在哪里?哪个寄存器保存返回值?哪些寄存器必须保存和恢复?一旦你知道这一点,你基本上可以编写一些 C 代码,将代码组装到一块内存中,然后将该内存转换为函数指针(尽管这在 ANSI C 中技术上是禁止的,并且取决于你的平台是否标记了某些页面,这将不起作用内存作为不可执行的 aka NX 位)。

解决这个问题的简单方法是编写一些代码,编译它,然后反汇编它并查看哪些字节对应于哪些指令。您可以编写一些 C 代码,用该字节集合填充分配的内存,然后将其转换为适当类型的函数指针并执行。

最好先阅读calling conventions 以了解您的架构和编译器。然后学习编写可以从 C 调用的程序集(即遵循调用约定)。

【讨论】:

    【解决方案3】:

    如果您有工具,它们可以帮助您更轻松地完成一些事情。例如,与其尝试设计正确的函数序言/尾声,我可以直接在 C 中编写代码:

    int  foo(void* Data)
        {
        return (Data != 0);
        }
    

    然后(Windows 下的 MicrosoftC)将其提供给“cl /Fa /c foo.c”。然后我可以看看“foo.asm”:

    _Data$ = 8
    ; Line 2
            push    ebp
            mov     ebp, esp
    ; Line 3
            xor     eax, eax
            cmp     DWORD PTR _Data$[ebp], 0
            setne   al
    ; Line 4
            pop     ebp
            ret     0
    

    我还可以使用“dumpbin /all foo.obj”来查看函数的确切字节是:

    00000000: 55 8B EC 33 C0 83 7D 08 00 0F 95 C0 5D C3
    

    只是节省了我一些时间让字节完全正确......

    【讨论】:

      猜你喜欢
      • 2014-05-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-12
      • 1970-01-01
      相关资源
      最近更新 更多