是否可以在 C 中执行任意代码?
Is it possible to excecute arbitrary code in C?
我想知道是否可以执行根本不在主程序中的代码,如果可能的话,您将如何执行?如果不能在c中完成,是否可以在c++中完成?
我认为如果将二进制文件导入堆,将指针移动到寄存器 int,然后尝试预测寄存器并调用指针,这可能是可能的,但我真的不知道有一台备用电脑来尝试这个,以防出现问题。
(更新:我可能只见树木不见森林,所以我将首先介绍更明智的动态链接方法。请注意,在本次编辑之前答案已被接受,因此请参阅下面的分隔符以获取原始内容。)
如果您想在主程序之外使用代码,操作系统通常会为此提供工具,作为其动态链接器的一部分。您通常可以让它在 运行 时间加载 .so
或 .dll
文件。
在 Linux 上,您可以使用 dlopen
(加载 .so 文件)和 dlsym
(从中获取函数)来实现此目的。这是一个虚构插件系统的示例,您将 load_plugin
传递给 .so
文件的路径,它会尝试从该文件 运行 foo_plugin_init
:
#include <stdio.h>
#include <dlfcn.h>
int load_plugin(const char* filename) {
void *handle = dlopen(filename, RTLD_LAZY); // Try to load the .so file
dlerror(); // Clear any error a previous dlsym might have reported
void (*plugin_init)() = dlsym(handle, "foo_plugin_init"); // Load the "foo_plugin_init" function
const char* error = dlerror(); // Check for errors reported by dlsym
if (error) {
fprintf(stderr, "Could not load plugin %s: %s\n", filename, error);
return 0;
}
plugin_init(); // Call the newly loaded function
return 1;
}
在 Windows 上,您可以使用 LoadLibrary
(加载 .dll 文件)和 GetProcAddress
(从中获取函数)执行类似的操作。用法大致相同,除了错误报告舞蹈有点复杂,使用 GetLastError
和 FormatMessage
来检索结果字符串。我的 Windows 程序很生疏,所以我不能轻易演示。
只要你有一个可执行的内存区域,你就可以简单地将它转换为一个函数指针:
void execute_memory(const void* mem) {
void (*func)() = mem;
func();
}
您可能需要先使内存可执行,但执行此操作的方法因平台而异。大多数操作系统为此使用 mprotect
,尽管 Windows 使用 VirtualProtect
.
请记住,尽管 JIT 编译器中经常使用这种技术,但 非常 很容易意外编写出具有严重安全漏洞的软件,因此我会尽量避免使用它可能的。它还违反了可能存在的各种安全策略(例如 DEP 或 grsecurity 的 W^X 策略)。因此,如果你打算在实践中使用它,你应该提供一个不依赖于执行 运行time-generated 代码的回退。
我想知道是否可以执行根本不在主程序中的代码,如果可能的话,您将如何执行?如果不能在c中完成,是否可以在c++中完成?
我认为如果将二进制文件导入堆,将指针移动到寄存器 int,然后尝试预测寄存器并调用指针,这可能是可能的,但我真的不知道有一台备用电脑来尝试这个,以防出现问题。
(更新:我可能只见树木不见森林,所以我将首先介绍更明智的动态链接方法。请注意,在本次编辑之前答案已被接受,因此请参阅下面的分隔符以获取原始内容。)
如果您想在主程序之外使用代码,操作系统通常会为此提供工具,作为其动态链接器的一部分。您通常可以让它在 运行 时间加载 .so
或 .dll
文件。
在 Linux 上,您可以使用 dlopen
(加载 .so 文件)和 dlsym
(从中获取函数)来实现此目的。这是一个虚构插件系统的示例,您将 load_plugin
传递给 .so
文件的路径,它会尝试从该文件 运行 foo_plugin_init
:
#include <stdio.h>
#include <dlfcn.h>
int load_plugin(const char* filename) {
void *handle = dlopen(filename, RTLD_LAZY); // Try to load the .so file
dlerror(); // Clear any error a previous dlsym might have reported
void (*plugin_init)() = dlsym(handle, "foo_plugin_init"); // Load the "foo_plugin_init" function
const char* error = dlerror(); // Check for errors reported by dlsym
if (error) {
fprintf(stderr, "Could not load plugin %s: %s\n", filename, error);
return 0;
}
plugin_init(); // Call the newly loaded function
return 1;
}
在 Windows 上,您可以使用 LoadLibrary
(加载 .dll 文件)和 GetProcAddress
(从中获取函数)执行类似的操作。用法大致相同,除了错误报告舞蹈有点复杂,使用 GetLastError
和 FormatMessage
来检索结果字符串。我的 Windows 程序很生疏,所以我不能轻易演示。
只要你有一个可执行的内存区域,你就可以简单地将它转换为一个函数指针:
void execute_memory(const void* mem) {
void (*func)() = mem;
func();
}
您可能需要先使内存可执行,但执行此操作的方法因平台而异。大多数操作系统为此使用 mprotect
,尽管 Windows 使用 VirtualProtect
.
请记住,尽管 JIT 编译器中经常使用这种技术,但 非常 很容易意外编写出具有严重安全漏洞的软件,因此我会尽量避免使用它可能的。它还违反了可能存在的各种安全策略(例如 DEP 或 grsecurity 的 W^X 策略)。因此,如果你打算在实践中使用它,你应该提供一个不依赖于执行 运行time-generated 代码的回退。