在软件开发和维护的过程中,经常会遇到需要在不中断服务的情况下修复程序中的问题的情况。这种需求催生了“热补丁”技术,即在程序运行时动态地替换或修改程序代码。本文将介绍如何使用Intel Pin工具来实现这一目标。Intel Pin是一个动态二进制插桩工具,它允许在不停止程序的情况下,对程序的执行过程进行监控和修改。
首先,需要下载并安装Intel Pin。这个工具可以在多种操作系统上运行,包括Linux和Windows。本文以Ubuntu x86_64系统为例进行介绍。Intel Pin的下载和安装过程相对简单,只需要将其解压到工作目录中即可。
在实际应用中,热补丁技术对于提供远程服务的服务器端点非常有用。例如,如果一个服务对某些输入存在漏洞,那么第一个提交特殊请求的攻击者可能会利用这个漏洞。在这种情况下,可能不希望关闭服务、编译、部署并启动新实例,而是希望使用热补丁技术来解决问题,直到新版本准备就绪。
Intel Pin通过执行在工具中定义的动作来对目标二进制文件或进程进行操作。例如,可以创建一个工具,每次找到RET指令时就增加一个计数器的值,然后将这个工具附加到可执行文件上,并在某个时间点获取计数器的值。
Intel Pin提供了一个包含示例工具的目录,可以在pin/source/tools/下找到这些示例。为了避免更新makefile依赖,将在这个目录下工作。首先,创建一个新的目录(例如命名为Hotpatch),这是编写代码的地方。然后,将makefile复制到新目录中,如果不想自己编写makefile的话:
cp ../SimpleExamples/makefile .
接下来,使用以下内容作为makefile.rules文件:
TEST_TOOL_ROOTS := hotpatch
for hotpatch.cpp SANITY_SUBSET := $(TEST_TOOL_ROOTS) $(TEST_ROOTS)
最后,创建一个名为hotpatch.cpp的文件,并在其中编写一些示例代码,然后运行make命令。如果一切顺利,应该得到一个类似于以下结构的目录:
整个热补丁程序的核心是注册一个回调函数,该函数在二进制文件加载映像时被调用(参见IMG_AddInstrumentFunction())。由于方法是在运行程序中定义的,对进程加载自己的映像时感兴趣。在这个回调中,寻找想要热补丁(替换)的方法——在示例中,它是read_input()。
可以使用以下命令列出二进制文件中存在的函数:
nm targeted_binary_name
替换函数(RTN_ReplaceSignatureProbed())的过程基于探针——正如名字所示,根据Intel的说法,这可以确保更少的开销并且更不侵入。在底层,Intel Pin会用一个指向替换函数的JMP指令覆盖原始函数的指令。如果需要,需要调用原始函数。
以下是最终得到的代码:
#include "pin.H"
#include
#include
char target_routine_name[] = "read_input";
// 替换例程的代码(即修补后的read_input)
void read_input_patched(void *original_routine_ptr, int *return_address) {
printf("Tell me your name:\n");
// 5星级的stdin读取方法
char name[12] = {0}, c;
fgets(name, sizeof(name), stdin);
name[strcspn(name, "\r\n")] = 0;
// 丢弃stdin中的其余数据
while ((c = getchar()) != '\n' && c != EOF);
printf("Hello, %s!\n\n", name);
}
void loaded_image_callback(IMG current_image, void *v) {
// 在加载的映像中查找例程
RTN current_routine = RTN_FindByName(current_image, target_routine_name);
// 如果在该映像中没有找到例程,则停止
if (!RTN_Valid(current_routine))
return;
// 跳过不安全的替换例程
if (!RTN_IsSafeForProbedReplacement(current_routine)) {
std::cerr << "Skipping unsafe routine " << target_routine_name << " in image " << IMG_Name(current_image) << std::endl;
return;
}
// 替换例程的原型:返回void,默认调用标准,名称,不接受参数
PROTO replacement_prototype = PROTO_Allocate(PIN_PARG(void), CALLINGSTD_DEFAULT, target_routine_name, PIN_PARG_END());
// 用跳转到新例程的指令替换原始例程
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);
}
// 注册一个回调函数用于“加载映像”操作
IMG_AddInstrumentFunction(loaded_image_callback, 0);
// 以探针模式运行程序
PIN_StartProgramProbed();
return EXIT_SUCCESS;
}
运行make后,使用以下命令将Intel Pin附加到目标进程的运行实例:
sudo ../../../pin -pid $(pidof targeted_binary_name) -t obj-intel64/hotpatch.so