通过binfmt_misc优化CGI调试流程
通过binfmt_misc优化CGI调试流程
binfmt_misc 是 Linux 内核提供的一种机制,允许内核根据文件格式(如 ELF 头)将特定程序交由用户态解释器处理。例如,在启用 qemu-user 的情况下,ARM64 架构的 ELF 程序可以在 x86-64 Linux 系统上直接通过 execve() 调用成功运行。
可以通过命令 cat /proc/sys/fs/binfmt_misc/status 检查 binfmt_misc 是否已启用。通常情况下,默认是开启的,并且会自动注册与已安装的 qemu 相关的配置文件。
注册新的配置文件
可以使用以下格式注册新的配置文件:
echo ":name:type:offset:magic:mask:interpreter:flags" > /proc/sys/fs/binfmt_misc/register
参数说明
- type: 可以是
E或M类型。 - 如果为
E类型,则可执行文件格式由文件扩展名识别,此时忽略offset和mask参数。 - 如果为
M类型,则通过文件中指定偏移位置的魔数(magic number)来识别。 - flags: 控制解析器行为的标志位。
O: 当多个规则冲突时,优先匹配具有此标志的解析器。E: 表示该解析器用于执行可执行文件。F: 每次访问register文件时重新加载解析器配置。C: 解析器配置仅在系统启动时加载一次。B: 允许使用特权解析器执行文件。M: 强制验证魔数。
注意:offset + size(magic) 必须小于 128,解释器字符串不得超过 127 个字符。
示例实验
即使目标文件 ./xxx 不是合法的可执行程序,只要创建适当的配置文件并指定 type 和 interpreter,满足 interpreter ./xxx 是合法命令即可运行。
取消注册配置文件可通过以下命令实现:
echo -1 > /proc/sys/fs/binfmt_misc/xxx
也可以基于文件后缀进行匹配。
系统级调试
如果手中有真实设备,可以直接使用远程调试工具 gdbserver 进行调试。但有时可能会遇到断点无效的问题,常见的解决方法是对程序进行修补,插入一个无限循环 while(1)。然而这种方法较为繁琐,而通过 binfmt_misc 机制可以在第一次运行目标程序时,自动将其挂载到 gdbserver 上进行远程调试。
配置注册
首先注册一个新的规则:
echo ":cgi_test:E::cgi::/home/zikh/Desktop/ggs/binfmt_misc/debug:O" > /proc/sys/fs/binfmt_misc/register
自定义调试程序代码
以下是一个简单的调试脚本示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
char buf[100];
char tmp_exec[200];
const char *gdb_path = "/usr/bin/gdbserver";
const char *target_config = "test2.cgi";
// 分配内存用于存储新的参数列表
char **new_argv = calloc(argc + 10, sizeof(char *));
if (!new_argv) {
perror("calloc failed");
exit(EXIT_FAILURE);
}
// 提取目标程序名称
const char *name = strrchr(argv[1], '/');
if (name) {
name++;
} else {
name = argv[1];
}
// 创建临时文件路径
snprintf(tmp_exec, sizeof(tmp_exec), "/tmp/%s.tmp", name);
snprintf(buf, sizeof(buf), "cp %s %s", argv[1], tmp_exec);
system(buf);
// 判断是否为目标 CGI 文件
if (argc > 1 && strstr(argv[1], target_config)) {
new_argv[0] = gdb_path;
new_argv[1] = "0.0.0.0:1234"; // GDB Server 地址和端口
new_argv[2] = tmp_exec;
// 添加原始参数
for (int i = 2; i < argc; i++) {
new_argv[i + 1] = argv[i];
}
// 使用 GDB Server 启动目标程序
execv(gdb_path, new_argv);
} else {
new_argv[0] = tmp_exec;
// 直接运行目标程序
for (int i = 2; i < argc; i++) {
new_argv[i - 1] = argv[i];
}
execv(tmp_exec, new_argv);
}
perror("execv failed");
free(new_argv);
return EXIT_FAILURE;
}
qemu-user 模拟下的调试
尝试在 qemu-user 模拟环境下调试 CGI 程序时,发现链接到 busybox 的命令无法正常工作。问题可能出在 binfmt_misc 拦截 ARM ELF 文件后传递给调试程序时,参数构造不正确,导致动态链接的 ELF 文件启动失败。
错误信息如下:
测试发现,恢复 /proc/sys/fs/binfmt_misc/qemu-arm 的默认配置后可以正常工作。尝试调整 flags 参数也未解决问题。
修改后的规则示例:
echo ':qemu-arm:M:0:\x7f\x45\x4c\x46\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/qemu_debug:CF' > /proc/sys/fs/binfmt_misc/register