通过 fopen 调用时拦截 C 中的 open 系统调用

问题描述 投票:0回答:1

我正在尝试通过 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)。

c linux system-calls fopen libc
1个回答
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)呼叫站点)。

© www.soinside.com 2019 - 2024. All rights reserved.