我正在尝试通过 libc/syscall 拦截来编写(有限的)内存中文件系统重定向,并为虚拟化的文件返回
memfd_create
生成的文件描述符。作为准备步骤,我有以下 test.c
文件:
#define _GNU_SOURCE
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <dlfcn.h>
#include <sys/stat.h>
/*
FILE* fopen(const char *path, const char *mode)
{
typedef FILE* (*orig_fopen_func_type)(const char *path, const char *mode);
fprintf(stderr, "log_file_access_preload: fopen(\"%s\", \"%s\")\n", path, mode);
orig_fopen_func_type orig_func = (orig_fopen_func_type)dlsym(RTLD_NEXT, "fopen");
return orig_func(path, mode);
}
*/
int open(const char *path, int flags)
{
typedef int (*orig_func_type)(const char *pathname, int flags);
fprintf(stderr, "log_file_access_preload: open(\"%s\", %d)\n", path, flags);
orig_func_type orig_func = (orig_func_type)dlsym(RTLD_NEXT, "open");
return orig_func(path, flags);
}
int open64(const char *path, int flags)
{
typedef int (*orig_func_type)(const char *pathname, int flags);
fprintf(stderr, "log_file_access_preload: open64(\"%s\", %d)\n", path, flags);
orig_func_type orig_func = (orig_func_type)dlsym(RTLD_NEXT, "open64");
return orig_func(path, flags);
}
//TODO: int openat(int dirfd, const char *path, int flags, mode_t mode)
int openat(int dirfd, const char *path, int flags)
{
typedef int (*orig_func_type)(int dirfd, const char *pathname, int flags);
fprintf(stderr, "log_file_access_preload: openat(%d, \"%s\", %d)\n", dirfd, path, flags);
orig_func_type orig_func = (orig_func_type)dlsym(RTLD_NEXT, "openat");
return orig_func(dirfd, path, flags);
}
int main()
{
(void)fopen("test.txt", "r");
}
编译为
gcc test.c
(在 Ubuntu 22.04 上)并将其调用为 ./a.out
不会打印任何内容(如果我取消注释 fopen
拦截,那么它可以工作,但不幸的是 fmemopen
不允许创建相应的 fd
/ fileno
使用它,而不是 shm_open
和 memfd_create
,所以我更喜欢只覆盖 open
调用)
如果我在
LD_PRELOAD
ed 共享库中放置类似的拦截,它仍然不会被调用 open
调用(同样,如果我拦截 fopen
,它会起作用)。
如果我
strace -f ./a.out
,那么我确实会在输出上得到 openat(AT_FDCWD, "test.txt", O_RDONLY) = -1 ENOENT (No such file or directory)
,所以 open
会被调用吗?
execve("./a.out", ["./a.out"], 0x7ffff9b89518 /* 22 vars */) = 0
brk(NULL) = 0x7fffde75e000
arch_prctl(0x3001 /* ARCH_??? */, 0x7fffe5547b50) = -1 EINVAL (Invalid argument)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcee5410000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=103195, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 103195, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fcee53b6000
close(3) = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\237\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0 \0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0"..., 48, 848) = 48
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\244;\374\204(\337f#\315I\214\234\f\256\271\32"..., 68, 896) = 68
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=2216304, ...}, AT_EMPTY_PATH) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
mmap(NULL, 2260560, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fcee5180000
mmap(0x7fcee51a8000, 1658880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7fcee51a8000
mmap(0x7fcee533d000, 360448, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bd000) = 0x7fcee533d000
mmap(0x7fcee5395000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x214000) = 0x7fcee5395000
mmap(0x7fcee539b000, 52816, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fcee539b000
close(3) = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcee53b0000
arch_prctl(ARCH_SET_FS, 0x7fcee53b0740) = 0
set_tid_address(0x7fcee53b0a10) = 5270
set_robust_list(0x7fcee53b0a20, 24) = 0
rseq(0x7fcee53b10e0, 0x20, 0, 0x53053053) = -1 ENOSYS (Function not implemented)
mprotect(0x7fcee5395000, 16384, PROT_READ) = 0
mprotect(0x7fcee5419000, 4096, PROT_READ) = 0
mprotect(0x7fcee5408000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=8192*1024}) = 0
munmap(0x7fcee53b6000, 103195) = 0
getrandom("\x05\x04\x5c\x57\xcc\x25\x9c\x8e", 8, GRND_NONBLOCK) = 8
brk(NULL) = 0x7fffde75e000
brk(0x7fffde77f000) = 0x7fffde77f000
openat(AT_FDCWD, "test.txt", O_RDONLY) = -1 ENOENT (No such file or directory)
exit_group(0) = ?
+++ exited with 0 +++
为什么
strace
可以看到这个调用而我的拦截却看不到?有没有办法让这个拦截起作用?
谢谢!
附注在一天结束时,我希望为内存缓冲区创建一些
fd
(也许通过 memfd_create
然后写入我的缓冲区,然后 seek
为 0)。
为什么
可以看到这个通话而我的拦截看不到?strace
strace
看不到openat
中对libc
的调用,它通过使用完全不同的机制看到实际的系统调用(ptrace
)。
相反,您要做的是使用动态链接器符号解析将调用插入到
libc.so
中。
这个程序根本不调用libc,但是
strace
仍然会在里面显示openat
:
section .text
global _start
_start:
mov rax,257
mov rdi,0
mov rsi,0
syscall
mov rax,231
mov rdi,0
syscall
nasm -f elf64 x.s && ld x.o && strace ./a.out
execve("./a.out", ["./a.out"], 0x7fff89dee840 /* 30 vars */) = 0
openat(0, NULL, O_RDONLY) = -1 EFAULT (Bad address)
exit_group(0) = ?
+++ exited with 0 +++
有没有办法让这个拦截起作用?
首先,让我们看看
openat
是如何实际调用的。在 Fedora 40 上,使用 int main() { fopen("test.txt", "r"); }
,运行到 main
并在 GDB 中使用 catch syscall openat
:
Catchpoint 1 (call to syscall openat), 0x00007ffff7ed4403 in __libc_open64 (file=0x402012 "test.txt", oflag=0)
at ../sysdeps/unix/sysv/linux/open64.c:41
Downloading source file /usr/src/debug/glibc-2.39-15.fc40.x86_64/io/../sysdeps/unix/sysv/linux/open64.c
41 return SYSCALL_CANCEL (openat, AT_FDCWD, file, oflag | O_LARGEFILE,
(gdb) bt
#0 0x00007ffff7ed4403 in __libc_open64 (file=0x402012 "test.txt", oflag=0)
at ../sysdeps/unix/sysv/linux/open64.c:41
#1 0x00007ffff7e56adf in __GI__IO_file_open (fp=fp@entry=0x4052a0, filename=<optimized out>,
posix_mode=<optimized out>, prot=prot@entry=438, read_write=8, is32not64=<optimized out>) at fileops.c:188
#2 0x00007ffff7e56c95 in _IO_new_file_fopen (fp=fp@entry=0x4052a0,
filename=filename@entry=0x402012 "test.txt", mode=<optimized out>, mode@entry=0x402010 "r",
is32not64=is32not64@entry=1) at fileops.c:281
#3 0x00007ffff7e4b046 in __fopen_internal (filename=0x402012 "test.txt", mode=0x402010 "r", is32=1)
at iofopen.c:75
#4 0x0000000000401139 in main ()
这里可以看到
__libc_open64
是执行系统调用的地方,是从__GI__IO_file_open
调用的。调用站点的反汇编是什么样的?
(gdb) x/i $pc-5
0x7ffff7e56ada <__GI__IO_file_open+42>: call 0x7ffff7ed43b0 <__libc_open64>
可以看到这个调用没有经过PLT,因此不参与动态符号解析。
那么对此可以做什么呢?
如果你控制目标环境,你可以修改 GLIBC 源来调用
open
而不是 __open
中的 fileops.c
并重建 libc.so.6
。
如果您需要使用现有的
libc.so.6
来完成这项工作,我知道的唯一选择是(非常hacky)运行时修补。
基本上,您必须扫描
__IO_file_open
中的指令,找到对 __open64
的调用,然后对其进行修补以调用您的插入器代码(另一个复杂之处是您的插入器与插入器的距离不能超过 2GiB)呼叫站点)。