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

目标:加载.so或经过validation的可执行文件(或通过任意algorithm进行validation)。

我想能够validation.so /可执行文件,然后加载/执行.so /可执行文件与dlopen / …

在这个扳手是,似乎没有编程方式来检查然后加载。 人们可以手动检查文件,然后加载它..但是有一个机会窗口,在这个窗口中有人可以换出另一个文件。

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

由于文件系统锁在Linux中是build议性的,所以它们对于这个目的不是那么有用…(好吧,这里有mount -o mand …但是这是用户级而不是根用户的)。

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

最好的办法是确保只有你运行的用户可以写入文件,然后检查它,然后dlopen()。 你的用户(或者root)仍然可以隐藏不同的代码,但是具有这些权限的进程可能只是用ptrace()来做出他们的出价。

许多动态连接器(包括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编译这个cc -shared -fPIC -o test.so test.c或者类似的。

您可以查看glibc/elf/tst-auditmod1.c或latrace获取更多示例,或阅读“ 链接程序和库指南” 。


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
 $ cat> ac
 #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() (你不能这样做),这是不可能的,你的图书馆的部分。

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

DigSig目前提供:

  • ELF二进制文件和共享库的运行时间签名验证。
  • 支持文件的签名撤销。
  • 签名缓存机制,以提高性能。