加载前检查Linux共享对象的签名

加载前检查Linux共享对象的签名,linux,dll,digital-signature,Linux,Dll,Digital Signature,目标:加载已验证为签名(或根据任意算法验证)的.so或可执行文件 我希望能够验证.so/可执行文件,然后使用dlopen/…加载/执行该.so/可执行文件 这方面的问题在于,似乎没有编程方式来检查然后加载。可以手动检查文件,然后在..之后加载。。然而,有一个机会之窗,在这个机会之窗中,有人可以将该文件换成另一个文件 我能想到的一个可能的解决方案是加载二进制文件,检查签名,然后dlopen/execvt/proc/$PID/fd。。。。不过,我不知道这是否一个可行的解决办法 因为文件系统锁在Lin

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

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

这方面的问题在于,似乎没有编程方式来检查然后加载。可以手动检查文件,然后在..之后加载。。然而,有一个机会之窗,在这个机会之窗中,有人可以将该文件换成另一个文件

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

因为文件系统锁在Linux中是建议性的,所以它们在这方面并不是很有用。。。(嗯,有
mount-o命令
…但这是针对用户级别的,而不是根用户使用的)。

应该在内核级别解决这个问题

DigSig目前提供:

  • ELF二进制文件和共享库的运行时签名验证
  • 支持文件的签名撤销
  • 增强性能的签名缓存机制
许多动态链接器(包括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 hook.so hook.c $cat>a.c #包括 int main(){dlopen(“./hook.so”,RTLD_LAZY);dlopen(“libm.so,RTLD_LAZY);} ^D $cc-低密度脂蛋白a.c 美元/年 my_dlopen(libm.so,1,0x80484bd)
不幸的是,我的调查使我得出结论,即使你能钩住glibc/elf/dl load.c:open_verify()(你不能),也不可能让这场竞赛不受在你的库段上写东西的人的影响。

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


最好的办法是确保只有您正在运行的用户才能写入该文件,然后检查它,然后dlopen()删除它。您的用户(或root用户)仍然可以潜入不同的代码,但具有这些权限的进程可以让您以任何方式进行竞价。

我建议以下解决方案,该解决方案不需要库*)

int memfd=memfd_create(“用于调试.library.so”,MFD_CLOEXEC | MFD_允许密封);
断言(memfd!=-1);
//使用任何方式从磁盘读取库并将内容复制到memfd中
//例如写入(2)或ftruncate(2)和mmap(2)
//重要!如果使用mmap,则必须在下一步之前取消映射
//如果存在可写映射,fcntl(,F_SEAL_WRITE)将失败
int SEAL_to_set=F_SEAL_shresk | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL;
int sealing\u err=fcntl(memfd、F\u添加\u密封、密封到\u集合);
断言(sealing_err==0);
//现在才验证加载文件的内容
//然后您可以安全地*)dlopen(“/proc/self/fd/”;

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

Sweet,这种看起来正是我想要的。。。除了它是一个仅在启动时检查的环境变量外:-/需要此功能的项目通常作为插件加载到另一个产品中。。。然而,LD_AUDIT在我们的受控应用程序中使用时看起来很有用……太棒了!我会给这个最有用的信息之一,并将其标记为答案,除了有人在理论上直接戳了一个洞的情况。嗯,
mmap(,,MAP\u COPY,,)
会给出一个映射,它不会受到磁盘上文件的进一步更改的影响,但它没有得到广泛的实现。在Linux和大多数其他系统上,
mmap(,,MAP\u PRIVATE,,)
被使用;POSIX没有指明文件的更改是否会更改映射,但通常情况下会更改映射,除非页面已被写下复制。啊,这一点很好。。。ptrace使这一切变得毫无用处,不是吗:-/使DigSig看起来像是唯一的选项。。。或者是一个文件系统,它提供对自身经过签名验证的数据的只读访问…将此标记为“答案”,因为似乎没有其他方法可以阻止根用户/当前用户处理问题。UNIX中的安全边界本质上是在UID之间。这是一个公平的赌注,大多数/Ur/bin /*都没有被审计,允许用户执行任意代码作为他们自己的“攻击”。考虑<代码> int MEFD=MeMdfyCube(…)<代码>,然后您可以密封,结合<代码> DLOPEN(“/PRO/FI/FS/”)。。这将使其他进程无法写入它。如果没有内核级的干预,整个问题似乎是无法解决的:-/Segments可以在验证后被覆盖…ptrace可以彻底改变工作方式…等待任何可能产生“神奇”效果的答案…看起来无法解决在没有根级别权限和禁用外部调试的情况下执行此操作。我认为这里的所有内容在很大程度上取决于您在用例场景中希望实现的具体目标。例如,如果您希望向用户发送签名共享库,您可以相对安全地假设不存在机会窗口问题-这台机器上肯定已经安装了一些恶意软件,在这种情况下你无能为力。编辑:2009年的问题?我的问题!谈谈threadomancy!
#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 $ cat > a.c #include <dlfcn.h> int main() { dlopen("./hook.so", RTLD_LAZY); dlopen("libm.so", RTLD_LAZY); } ^D $ cc -ldl a.c $ ./a.out my_dlopen(libm.so, 1, 0x80484bd)
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>");