8

目标:加载 .so 或已验证签名(或针对任意算法验证)的可执行文件。

我希望能够验证 .so/executable,然后使用 dlopen/... 加载/执行该 .so/executable

关键在于似乎没有程序化的方式来检查然后加载。可以手动检查文件,然后在之后加载它..但是有一个机会窗口,有人可以在其中将该文件换成另一个文件。

我能想到的一种可能的解决方案是加载二进制文件,检查签名,然后 dlopen/execvt /proc/$PID/fd.... 但是我不知道这是否是一个可行的解决方案。

由于文件系统锁在 Linux 中是建议性的,因此它们对于此目的不是那么有用……(嗯,有mount -o mand……但这是用户级别的东西,而不是 root 使用)。

4

4 回答 4

9

许多动态链接器(包括 Glibc 的)支持将LD_AUDIT环境变量设置为以冒号分隔的共享库列表。这些库被允许挂接到动态库加载过程中的不同位置。

#define _GNU_SOURCE
#include <dlfcn.h>
#include <link.h>
unsigned int la_version(unsigned int v) { return v; }
unsigned int la_objopen(struct link_map *l, Lmid_t lmid, uintptr_t *cookie) {
    if (!some_custom_check_on_name_and_contents(l->l_name, l->l_addr))
        abort();
    return 0;
}

cc -shared -fPIC -o test.so test.c用或类似的方式编译它。

您可以查看glibc/elf/tst-auditmod1.clatrace获取更多示例,或阅读链接器和库指南


对 Glibc 的内部非常非常具体,但您仍然可以libdl在运行时挂钩。

#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>

extern struct dlfcn_hook {
    void *(*dlopen)(const char *, int, void *);
    int (*dlclose)(void *);
    void *(*dlsym)(void *, const char *, void *);
    void *(*dlvsym)(void *, const char *, const char *, void *);
    char *(*dlerror)(void);
    int (*dladdr)(const void *, Dl_info *);
    int (*dladdr1)(const void *, Dl_info *, void **, int);
    int (*dlinfo)(void *, int, void *, void *);
    void *(*dlmopen)(Lmid_t, const char *, int, void *);
    void *pad[4];
} *_dlfcn_hook;
static struct dlfcn_hook *old_dlfcn_hook, my_dlfcn_hook;

static int depth;
static void enter(void) { if (!depth++) _dlfcn_hook = old_dlfcn_hook; }
static void leave(void) { if (!--depth) _dlfcn_hook = &my_dlfcn_hook; }

void *my_dlopen(const char *file, int mode, void *dl_caller) {
    void *result;
    fprintf(stderr, "%s(%s, %d, %p)\n", __func__, file, mode, dl_caller);
    enter();
    result = dlopen(file, mode);
    leave();
    return result;
}

int my_dlclose(void *handle) {
    int result;
    fprintf(stderr, "%s(%p)\n", __func__, handle);
    enter();
    result = dlclose(handle);
    leave();
    return result;
}

void *my_dlsym(void *handle, const char *name, void *dl_caller) {
    void *result;
    fprintf(stderr, "%s(%p, %s, %p)\n", __func__, handle, name, dl_caller);
    enter();
    result = dlsym(handle, name);
    leave();
    return result;
}

void *my_dlvsym(void *handle, const char *name, const char *version, void *dl_caller) {
    void *result;
    fprintf(stderr, "%s(%p, %s, %s, %p)\n", __func__, handle, name, version, dl_caller);
    enter();
    result = dlvsym(handle, name, version);
    leave();
    return result;
}

char *my_dlerror(void) {
    char *result;
    fprintf(stderr, "%s()\n", __func__);
    enter();
    result = dlerror();
    leave();
    return result;
}

int my_dladdr(const void *address, Dl_info *info) {
    int result;
    fprintf(stderr, "%s(%p, %p)\n", __func__, address, info);
    enter();
    result = dladdr(address, info);
    leave();
    return result;
}

int my_dladdr1(const void *address, Dl_info *info, void **extra_info, int flags) {
    int result;
    fprintf(stderr, "%s(%p, %p, %p, %d)\n", __func__, address, info, extra_info, flags);
    enter();
    result = dladdr1(address, info, extra_info, flags);
    leave();
    return result;
}

int my_dlinfo(void *handle, int request, void *arg, void *dl_caller) {
    int result;
    fprintf(stderr, "%s(%p, %d, %p, %p)\n", __func__, handle, request, arg, dl_caller);
    enter();
    result = dlinfo(handle, request, arg);
    leave();
    return result;
}

void *my_dlmopen(Lmid_t nsid, const char *file, int mode, void *dl_caller) {
    void *result;
    fprintf(stderr, "%s(%lu, %s, %d, %p)\n", __func__, nsid, file, mode, dl_caller);
    enter();
    result = dlmopen(nsid, file, mode);
    leave();
    return result;
}

static struct dlfcn_hook my_dlfcn_hook = {
    .dlopen   = my_dlopen,
    .dlclose  = my_dlclose,
    .dlsym    = my_dlsym,
    .dlvsym   = my_dlvsym,
    .dlerror  = my_dlerror,
    .dladdr   = my_dladdr,
    .dlinfo   = my_dlinfo,
    .dlmopen  = my_dlmopen,
    .pad      = {0, 0, 0, 0},
};

__attribute__((constructor))
static void init(void) {
    old_dlfcn_hook = _dlfcn_hook;
    _dlfcn_hook = &my_dlfcn_hook;
}

__attribute__((destructor))
static void fini(void) {
    _dlfcn_hook = old_dlfcn_hook;
}
$ cc -shared -fPIC -o hook.so hook.c
$猫>交流
#include <dlfcn.h>
int main() { dlopen("./hook.so", RTLD_LAZY); dlopen("libm.so", RTLD_LAZY); }
^D
$ cc -ldl ac
$ ./a.out
my_dlopen(libm.so, 1, 0x80484bd)

不幸的是,我的调查使我得出的结论是,即使您可以挂钩glibc/elf/dl-load.c:open_verify()(您不能),也不可能使这种与在您的图书馆的某些部分上写作的人毫无竞争。

于 2009-07-21T19:20:15.467 回答
2

这个项目据说在内核级别解决了这个问题。

DigSig 目前提供:

  • ELF 二进制文件和共享库的运行时签名验证。
  • 支持文件的签名撤销。
  • 签名缓存机制以增强性能。
于 2009-07-21T18:54:10.637 回答
2

这个问题在你给出的形式中基本上是无法解决的,因为共享对象是通过 mmap()ing 加载来处理内存空间的。因此,即使您可以确保 dlopen() 操作的文件是您检查并声明 OK 的文件,任何可以写入该文件的人都可以在您加载后随时修改加载的对象。(这就是为什么您不通过写入来升级正在运行的二进制文件 - 而是删除然后安装,因为写入它们可能会使任何正在运行的实例崩溃)。

您最好的选择是确保只有您正在运行的用户才能写入文件,然后检查它,然后 dlopen() 它。您的用户(或 root)仍然可以潜入不同的代码,但是具有这些权限的进程可能只是 ptrace() 您无论如何都要进行投标。

于 2009-07-22T00:54:25.233 回答
0

我提出以下解决方案,应该在没有库的情况下工作 *)

int memfd = memfd_create("for-debugging.library.so", MFD_CLOEXEC | MFD_ALLOW_SEALING);
assert(memfd != -1);

// Use any way to read the library from disk and copy the content into memfd
// e.g. write(2) or ftruncate(2) and mmap(2)
// Important! if you use mmap, you have to unmap it before the next step
// fcntl( , , F_SEAL_WRITE) will fail if there exists a writeable mapping

int seals_to_set = F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL;
int sealing_err = fcntl(memfd, F_ADD_SEALS, seals_to_set);
assert(sealing_err == 0);

// Only now verify the contents of the loaded file
// then you can safely *) dlopen("/proc/self/fd/<memfd>");

*) 实际上并未针对攻击进行测试。未经进一步调查,请勿在生产中使用。

于 2018-07-24T10:50:52.490 回答