使用 Intel Pin 热补丁 C/C++ 函数





5.00/5 (5投票s)
本文讨论了使用 Intel Pin 热补丁 C/C++ 函数以消除已知漏洞的想法。
五年前,我在一篇文章中说过,我有一天会带着一种在 实时进程中热补丁函数的方法回归;所以…我想就是今天了。
我们在这里要实现的目标是 从外部替换运行中的 可执行文件中的函数,而不停止/冻结进程(或使其崩溃…)。
在我看来,如果 从头开始实现 热补丁,这是一项相当艰巨的任务,因为
- 它需要访问不同进程的内存(大多数操作系统都倾向于 进程隔离)
- 存在软件兼容性限制(Windows 二进制文件 vs Linux 二进制文件)
- 存在架构兼容性限制(32 位 vs 64 位)
- 它涉及到处理机器码,并带来一些问题
- 它仅具有教学目的 — probably no one would actually use a ‘from-scratch’ method since there are tools that do this better(大概没有人会真正使用“从头开始”的方法,因为有更好的工具可以做到这一点)
考虑到这些,我认为最好使用真正为此任务编写的东西,而不是手动编写。因此,我们将通过 Intel Pin 来实现这一点。我在做一个完全不同的项目时偶然发现了这个工具,但它似乎非常通用。基本上,它被描述为一个 动态二进制插桩工具,然而我们将使用它来促进向另一个进程的内存写入代码的过程。
1. 初始准备
首先 下载 Intel Pin 并将其解压到您的工作区中的某个位置。
免责声明:我将在 Ubuntu x86_64 上进行此教程。您可能会看到一些 bash 命令。
现在,我想象一下,这对于为客户端提供远程服务的端点很有用 — 例如:服务器接收某种输入并期望也返回一些东西。比方说,有人发现某个服务容易受到特定输入的攻击 — 那么第一个提交特制请求的攻击者就可以将其攻陷。我们认为,停止服务、编译、部署和启动新实例不是一个理想的解决方案,因此在准备好新版本之前,就需要进行热补丁。
我将使用以下 示威 C 程序来说明上述模型 — 为了简单起见,我从 stdin
读取输入(而不是 TCP 流/网络)。
#include <stdio.h>
// TODO: hot patch this method
void read_input()
{
printf("Tell me your name:\n");
char name[11];
scanf("%s", name); // this looks bad
printf("Hello, %s!\n\n", name);
}
int main()
{
// not gonna end too soon
while(1 == 1)
read_input();
return 0;
}
你们中的一些人可能已经注意到 read_input()
函数写得不太好,因为它使用 scanf("%s", name);
来读取输入,从而使攻击者能够利用 缓冲区溢出来劫持程序的执行。
我们的目标是通过“替换”有漏洞的读取函数 (read_input()
) 为我们知道实际上是安全的函数来修补此漏洞。我在这里使用引号来表达事实,它更像是一个重定向过程 — 原始(有漏洞)函数的代码仍然在进程的内存中,但所有调用都将被转发到新的(已修补的)方法。
希望现在大家都能明白。
2. 项目结构
Intel Pin 的工作方式是执行 工具中指示的操作,针对 二进制文件或 进程。例如,您可能有一个工具,它会说“每次找到 RET 指令时增加计数器”,您可以将其附加到可执行文件并获取计数器在特定时间的值。
它提供了一个包含 工具示例的目录,位于:pin/source/tools/。为了避免更新 makefile 依赖项,我们将在这里工作,所以请继续创建一个新目录(我将其命名为 Hotpatch)— 这里就是编写代码的地方。
另外,如果您不想自己写一个 makefile,请复制一个到您的新目录。
cp ../SimpleExamples/makefile .
并使用以下内容作为您的 makefile.rules 文件
TEST_TOOL_ROOTS := hotpatch # for hotpatch.cpp
SANITY_SUBSET := $(TEST_TOOL_ROOTS) $(TEST_ROOTS)
最后,创建一个名为 hotpatch.cpp 的文件,其中包含一些示威代码,然后运行 make
命令。如果一切正常,您应该会看到类似这样的结果…
3. 编写热补丁
整个想法围绕着注册一个 回调,该回调在二进制加载图像时被调用(参见 IMG_AddInstrumentFunction()
)。由于方法定义在运行的程序中,我们对进程加载自身图像时感兴趣。在此回调中,我们查找要 热补丁(替换)的方法 — 在我的示例中,它是 read_input()
。
您可以使用以下方法列出二进制文件中存在的函数
nm targeted_binary_name
替换函数的过程(RTN_ReplaceSignatureProbed()
)基于 探针 — 如名称所示,根据 Intel 的说法,探针可确保较低的开销且侵入性较小。在底层,Intel Pin 将用一个指向替换函数的 JMP
指令覆盖原始函数的指令。如果您需要,可以调用原始函数。
话不多说,我最终的代码
#include "pin.H"
#include <iostream>
#include <stdio.h>
char target_routine_name[] = "read_input";
// replacement routine's code (i.e. patched read_input)
void read_input_patched(void *original_routine_ptr, int *return_address)
{
printf("Tell me your name:\n");
// 5 stars stdin reading method
char name[12] = {0}, c;
fgets(name, sizeof(name), stdin);
name[strcspn(name, "\r\n")] = 0;
// discard rest of the data from stdin
while((c = getchar()) != '\n' && c != EOF);
printf("Hello, %s!\n\n", name);
}
void loaded_image_callback(IMG current_image, void *v)
{
// look for the routine in the loaded image
RTN current_routine = RTN_FindByName(current_image, target_routine_name);
// stop if the routine was not found in this image
if (!RTN_Valid(current_routine))
return;
// skip routines which are unsafe for replacement
if (!RTN_IsSafeForProbedReplacement(current_routine))
{
std::cerr << "Skipping unsafe routine " << target_routine_name <<
" in image " << IMG_Name(current_image) << std::endl;
return;
}
// replacement routine's prototype: returns void, default calling standard,
// name, takes no arugments
PROTO replacement_prototype = PROTO_Allocate(PIN_PARG(void),
CALLINGSTD_DEFAULT, target_routine_name, PIN_PARG_END());
// replaces the original routine with a jump to the new one
RTN_ReplaceSignatureProbed(current_routine,
AFUNPTR(read_input_patched),
IARG_PROTOTYPE,
replacement_prototype,
IARG_ORIG_FUNCPTR,
IARG_FUNCARG_ENTRYPOINT_VALUE, 0,
IARG_RETURN_IP,
IARG_END);
PROTO_Free(replacement_prototype);
std::cout << "Successfully replaced " << target_routine_name <<
" from image " << IMG_Name(current_image) << std::endl;
}
int main(int argc, char *argv[])
{
PIN_InitSymbols();
if (PIN_Init(argc, argv))
{
std::cerr << "Failed to initialize PIN." << std::endl;
exit(EXIT_FAILURE);
}
// registers a callback for the "load image" action
IMG_AddInstrumentFunction(loaded_image_callback, 0);
// runs the program in probe mode
PIN_StartProgramProbed();
return EXIT_SUCCESS;
}
在运行 make
后,使用类似以下的命令将 Intel Pin 附加到目标进程的正在运行的实例。
sudo ../../../pin -pid $(pidof targeted_binary_name) -t obj-intel64/hotpatch.so
4. 结果和结论
嗯,好像起作用了
总而言之,我非常确定 Intel Pin 比我在这里展示的能够完成更复杂的工作 — 我认为这些只是示例级别的(实际上,这是受示例启发)。对我来说,它没有成为一个更受欢迎的工具似乎相当奇怪 — 而且,我不是 Intel paid to endorse it(Intel 付费让我为其代言)。
然而,我希望本文能够为那些正在寻找 热补丁方法并且像我一样以前从未听说过 Intel Pin 的人提供支持和解决方案/想法。