在C语言中执行数据作为代码

3

使用这个答案(和这个后续)作为灵感,我正在寻找在C语言中实现一些函数式编程的方法(对此网站上已经有了很多有趣的讨论)。我想知道的是如何以及何时可以使用链接代码中的方法,将字符串转换为函数指针并执行它。例如,在我的机器(OSX 10.10、Darwin 14.0.0、GCC 4.8.3)上,我可以编译并运行。

int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();

(一直返回0,如果程序不做其他事情,这是我所期望的)但是
#include <stdio.h>

int main() {
  const char* lol = "\x8b\x5c\x24\x4\x3d\xe8\x3\x0\x0\x7e\x2\x31\xc0\x83\xf8\x64\x7d\x6\x40\x53\xff\xd3\x5b\xc3\xc3 <- Recursively calls the function at address lol.";
  int i = ((int(*)())(lol))(lol);
  printf("i: %d\n",i);
  return 0;
}

程序崩溃。另一方面,Codepad成功运行了第二个例子,给出了正确的答案i: 100

什么情况下可以从字符串中执行代码?是否有方法使其(相对)一致?

(我可以合理猜测这是未定义的行为,我知道我将通过使用它增加全球失业率。)


2
相关搜索词:[标签:shellcode]。 - DCoder
假设语言和编译器允许,另一个重要的前提条件是操作系统允许数据执行。出于安全考虑,许多操作系统默认禁用此功能。可能由于微妙的安全策略,OSX允许您执行前面的代码但不允许执行后面的代码,但我对此只是猜测。 - Jeff Hammond
谢谢@DCoder,知道某个东西的名称可以带来多大的不同真是太神奇了。 - user328062
2个回答

4
这肯定是(在法律上)未定义行为,而实际上它取决于具体实现。
要成功执行此操作,您需要几个要素。
  • 首先,您需要确保字面字符串中的机器码是正确的。这显然是处理器和ABI特定的。但我相信您可以做到。
  • 然后,您依赖于用于调用函数指针的协议,即ABI规范。
  • 最后,在几个处理器(尤其是x86-64)上,您需要将机器码放在一些可执行段中。我想通常情况下不是这样的(但可能是操作系统特定的)。了解更多关于NX bitASLR(以及PIC)的信息。有时可以通过适当地mmap某个具有执行权限的段并将机器码复制到那里来绕过这个问题。

顺便提一下,您可能会对JIT编译技术和库(libjitlightningasmjitLLVM等)感兴趣。

正如DCoder所评论的那样,了解更多关于shellcode和更普遍的代码注入

一种更加便携的方法可能是(如我在MELT中所做的),动态生成一些 C(或 C++)代码,分叉编译该代码为共享对象,然后使用dlopen打开该共享对象(并适当地使用dlsym)。


即使将一组位模式放置在内存中,这些模式可以成为有效的可执行代码,并且具有运行所需的所有权限,并且查阅ABI文档以创建指向它的函数指针,仍然不能保证编译器不会决定,因为标准不要求编译器在尝试执行数据时表现出任何特定的功能,所以它没有义务生成您的代码否则会暗示的机器指令。 - supercat

0

一般来说,在Linux和OSX中,字符串字面量的内容存储在一个只读段中,该段也可以执行(在Windows或其他平台上可能并非如此)。这就是为什么你可以做像这样的事情:

(L"\xfeeb")();

在 x86 和 x86_64 的 Linux 和 OSX 上,你可以不出现编译器错误地创建可执行字符串文字。但是,如果你放入字符串文字中的机器语言指令不符合操作系统和硬件平台规定函数结构的要求,你很可能会遇到段错误。一个能够在 Linux Aarch64 上工作的可执行字符串文字可能无法在 x86_64 上的 OSX 上工作,反之亦然。

如果你想探索可执行机器代码的程序化生成,你可以 (在 POSIX 上)使用 mmap() 函数分配一块可执行内存区域,将你的代码放在那里并进行实验。

在某些时候,你可能会发现gdb 中的 disassemble <addr>,+<range> 以及 lldb 中的 disassemble --start-address <addr> --end-address <addr> 是有用的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接