Linux AMD64 从复制的程序集中调用 C 库函数

Posted

技术标签:

【中文标题】Linux AMD64 从复制的程序集中调用 C 库函数【英文标题】:Linux AMD64 call C library functions from copied assembly 【发布时间】:2015-05-30 17:44:14 【问题描述】:

如何从 memcpy 的汇编函数调用 C 库函数?

我正在制作一个示例测试代码,如何在 Linux、AMD64 上分配和更改内存保护以运行从 C 中任意生成的代码。 我所做的是在我的主程序(用 C 编写)旁边编译一个小的 GAS 汇编函数,然后在运行时将汇编二进制 blob 复制到一块可执行内存中并跳转到其中。 这部分工作正常。

但是如果我从复制的程序集 blob 中调用 C 库 puts() 会由于函数地址错误而导致段错误?!我该如何解决?

汇编代码块:

       .text
       .global      _print_hello_size
       .global      _print_hello
       .type        _print_hello,@function
_print_hello:
       push %rbp
       mov %rsp, %rbp
       # puts("Hello World\n")
       mov $_message, %rdi
       call puts    # <-- SEGFAULT
       pop %rbp
       ret
procend: # mark end address of the _print_hello code
       .section .rodata
_message:
       .asciz  "Hello, world\n"
_print_hello_size:
       .long procend - _print_hello

然后在 C main() 我做(伪代码):

// Import assembler function and its size
extern "C" void _print_hello(void);
extern "C" const long _print_hello_size;
int main() 
    // Use special function that allocates Read-Write-Executable memory
    void * memexec = MallocExecutableMemory(1024);
    // Copy the binary asm blob, memexec is aligned to at least 16-bytes
    memcpy(memexec, (void*)_print_hello, _print_hello_size);

    void (*jmpfunc)(void) = (void (*)(void))memexec; 
    jmpfunc(); // Works, jumps into copied assembly func
    return 0;

以后如果这甚至可能的话,甚至不会编译 asm blob,而只是在 unsigned char execblob[] = 0xCC,0xCC,0xC3,.. 中编码示例程序并复制进入可执行区域。本位代码探索如何从 C 开始生成 asm。

【问题讨论】:

能否也提供已编译程序集 blob 的 objdump? blob 期望 puts 位于相对于代码的特定位置,因此如果您移动代码,则不会到达正确的位置。根据具体情况,您可以从 C 端传入指向 puts 的指针。 首先,我怀疑你的意思是reinterpret_cast。其次,我确实认为reinterpret_cast 是一种 C++ 主义,在 C 中无效——但我以前错了。 哎呀,我尝试从 C++ 简化代码,所以 reinterpret_cast 是错误的。这是 objdump -d pastebin.com/bUgjL1SK 【参考方案1】:

也许你可以这样做

push %rbp
mov %rsp, %rbp
# puts("Hello World\n")
mov $_message, %rdi
mov $puts, %eax
call %eax
pop %rbp
ret

从而迫使call 成为一个绝对的。问题是汇编器是否不会出于自己的目的对其进行优化。

【讨论】:

谢谢! mov $puts, *%rax 工作!当链接器随机打乱电话时,我快疯了.. 对不起,我忘了你是在 64 位机器上。是的,链接器有时会让我们的时间变得比我们想要的更有趣。【参考方案2】:

不可能从 memcpy-ed 代码块调用任何 C 标准库函数(或任何链接),就好像链接器决定 puts 函数入口点在它原来的位置之外的任何地方复制的代码块只是格式错误。第一个答案停止工作,第二个二进制 blob 与程序的其余部分不同步。

唯一的解决方法是在运行时修改二进制 blob,并像 C 链接器那样将当前的实际函数地址(来自 C 程序)分配给 blob。

【讨论】:

这是因为 AMD64 使用 RIP 相对地址来处理所谓的(在这种情况下具有讽刺意味)与位置无关的代码。关键是,一个库可以在任何内存地址加载,所以如果没有 RIP-relative,一个想要调用同一个库中的其他函数的函数要么需要在每个调用站点,在动态链接时进行地址修复。 (即对于每个进程启动)。 32 位 x86 必须这样做,这就是为 AMD64 引入 RIP-relative 的原因。

以上是关于Linux AMD64 从复制的程序集中调用 C 库函数的主要内容,如果未能解决你的问题,请参考以下文章

linux / amd64 C与C ++上的abi差异

为啥在 x64 程序集中将复制变量地址移动到寄存器?

在 Windows 上使用 AMD64 构建的 Scipy 调用 scikit-learn 时出错

渗透测试资源大合集

在现代 amd64 CPU 上进行 memset 的最快方法

linux下用c语言写出复制黏贴文件