在 C 中将数据作为代码执行
Executing data as code in C
使用 this answer (and this follow-up) 作为灵感,我正在寻找用 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 成功运行了第二个示例 giving the correct answer i: 100
.
什么时候可以从字符串执行?有没有办法让它(相对)一致?
(我可以合理地猜测这是未定义的行为,我知道我将通过使用它来增加全球失业率。)
当然(合法)undefined behavior,实际上它是特定于实现的。
你需要做几件事才能成功执行。
- 首先,您需要文字字符串中的机器代码是正确的。这显然是处理器和 ABI 特定的。但我相信你。
- 然后,您依赖于用于调用函数指针的协议,即依赖于 ABI 规范。
- 最后,在多个处理器(尤其是 x86-64)上,您需要机器代码位于某些 可执行文件 段中。我想通常情况并非如此(但这可能是特定于操作系统的)。阅读更多关于 NX bit and ASLR (and also PIC) 的信息。有时这可以被规避,例如通过适当地
mmap
-ing 一些具有执行权限的段并在那里复制机器代码。
顺便说一句,您可能会对 JIT compilation techniques and libraries (libjit, lightning, asmjit, LLVM ...)
感兴趣
作为DCoder commented, read more about shellcode & more generally code injection
一种更便携的方法可能是(就像我在 MELT 中所做的那样)动态生成一些 C(或 C++)代码,将该代码的编译分支到一个共享对象中,然后 dlopen
-ing 共享对象(& dlsym
-ing 适当)。
一般来说,Linux和OSX中字符串字面量的内容存储在一个只读段中,该段恰好也是可执行的([=28中可能不一定如此) =] 或其他平台)。这就是为什么你可以做
(L"\xfeeb")();
在 x86 和 x86_64 Linux 和 OSX 上,不会出现编译器错误。但是,如果您放入字符串文字中的机器语言指令不符合根据您的操作系统和硬件平台构建函数的方式的要求,您可能会遇到段错误。适用于 Linux Aarch64 的可执行字符串文字可能不适用于 x86_64 上的 OSX,反之亦然。
如果您想探索程序化 可执行机器代码的生成,您可以(在POSIX 上)使用mmap()
分配一个可执行内存区域功能,将您的代码放在那里并进行实验。
在某些时候,您可能会发现 disassemble <addr>,+<range>
在 gdb
中有用,而 disassemble --start-address <addr> --end-address <addr>
在 lldb
中有用。
使用 this answer (and this follow-up) 作为灵感,我正在寻找用 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 成功运行了第二个示例 giving the correct answer i: 100
.
什么时候可以从字符串执行?有没有办法让它(相对)一致?
(我可以合理地猜测这是未定义的行为,我知道我将通过使用它来增加全球失业率。)
当然(合法)undefined behavior,实际上它是特定于实现的。
你需要做几件事才能成功执行。
- 首先,您需要文字字符串中的机器代码是正确的。这显然是处理器和 ABI 特定的。但我相信你。
- 然后,您依赖于用于调用函数指针的协议,即依赖于 ABI 规范。
- 最后,在多个处理器(尤其是 x86-64)上,您需要机器代码位于某些 可执行文件 段中。我想通常情况并非如此(但这可能是特定于操作系统的)。阅读更多关于 NX bit and ASLR (and also PIC) 的信息。有时这可以被规避,例如通过适当地
mmap
-ing 一些具有执行权限的段并在那里复制机器代码。
顺便说一句,您可能会对 JIT compilation techniques and libraries (libjit, lightning, asmjit, LLVM ...)
感兴趣作为DCoder commented, read more about shellcode & more generally code injection
一种更便携的方法可能是(就像我在 MELT 中所做的那样)动态生成一些 C(或 C++)代码,将该代码的编译分支到一个共享对象中,然后 dlopen
-ing 共享对象(& dlsym
-ing 适当)。
一般来说,Linux和OSX中字符串字面量的内容存储在一个只读段中,该段恰好也是可执行的([=28中可能不一定如此) =] 或其他平台)。这就是为什么你可以做
(L"\xfeeb")();
在 x86 和 x86_64 Linux 和 OSX 上,不会出现编译器错误。但是,如果您放入字符串文字中的机器语言指令不符合根据您的操作系统和硬件平台构建函数的方式的要求,您可能会遇到段错误。适用于 Linux Aarch64 的可执行字符串文字可能不适用于 x86_64 上的 OSX,反之亦然。
如果您想探索程序化 可执行机器代码的生成,您可以(在POSIX 上)使用mmap()
分配一个可执行内存区域功能,将您的代码放在那里并进行实验。
在某些时候,您可能会发现 disassemble <addr>,+<range>
在 gdb
中有用,而 disassemble --start-address <addr> --end-address <addr>
在 lldb
中有用。