【问题标题】:Is there a reliable way to know what libraries could be dlopen()ed in an elf binary?有没有一种可靠的方法可以知道可以在 elf 二进制文件中 dlopen() ed 哪些库?
【发布时间】:2015-12-18 17:54:24
【问题描述】:

基本上,我想获取一个二进制文件可能加载的库列表。

我想出的不可靠方法似乎有效(可能会出现误报):

comm -13 <(ldd elf_file | sed 's|\s*\([^ ]*\)\s.*|\1|'| sort -u) <(strings -a elf_file | egrep '^(|.*/)lib[^:/]*\.so(|\.[0-9]+)$' | sort -u)

这不可靠。但它提供了有用的信息,即使二进制文件已被剥离。

是否有可靠的方法来获取此信息而不会出现误报?

编辑:更多上下文。

Firefox 正在从使用 gstreamer 过渡到使用 ffmpeg。 我想知道libavcodec.so 的哪些版本可以使用。 libxul.sodlopen() 用于许多可选功能。 并且库名称是硬编码的。所以,上面的命令有帮助 在这种情况下。

我对包管理和二进制依赖项也很感兴趣。 我知道您可以使用readelf -d 获得直接依赖关系,依赖关系 与ldd 的依赖关系。我想知道可选依赖项,因此问题。

【问题讨论】:

  • 获得dlopen()d 的库的名称不必出现在ldds 输出中。
  • 如果我dlopen(argv[i])怎么办?您将如何发现?
  • 另外,dlopen()ed 模块的名称不必以“lib”开头。甚至,我猜,以“.so”结尾。
  • 这是一个相当可靠的方法:find / |xargs file |grep 'ELF .* shared object'
  • @nwellnhof:如果动态下载共享对象会怎样? :-)

标签: c posix gnu elf


【解决方案1】:

ldd 告诉您二进制文件已链接到的库。这些不是程序可以dlopen打开的。

dlopen 的签名是

void *dlopen(const char *filename, int flag);

所以你可以,仍然不可靠,在二进制文件上运行strings,但如果库名称不是静态字符串,而是在程序执行期间从某个地方构建或读取,这仍然可能失败 - - 最后一种情况意味着您的问题的答案是“不”......不可靠。 (例如,库文件的名称可以从网络、Unix 套接字读取,甚至可以即时解压缩。一切皆有可能!——尽管我自己不推荐任何这些想法......)

编辑:另外,正如 John Bollinger 所提到的,库名称可以从配置文件中读取。

编辑:您也可以尝试用您的系统调用替换dlopen 系统调用(例如,这是由Boehm 垃圾收集器用malloc 完成的),所以它会打开图书馆,但也要在某处记录它的名字。但是如果程序在执行过程中没有打开特定的库,你仍然不会知道。

【讨论】:

  • 更常见和合理的是从配置文件中读取可加载模块名称。我的猜测是dlopen() 模块的名称在二进制文件中显示为文字字符串是最常见的。
  • 感谢您的回答。所以,假设库名称是硬编码的,strings 是我们最好的部分,对吧?
  • 关于您的第二次编辑: 1- 它需要执行哪种方式违背了目的,因为我们无论如何都可以跟踪正在运行的过程。 2- 没有保证我们可以很好地运行所有调用 dlopen() 的代码路径。
  • @不重要:是的,我提到过——所以并不是所有的代码路径都会被覆盖.
  • @Not 重要:strings 是一种可能性。尝试查找配置文件并跟踪程序可能也有很大帮助!
【解决方案2】:

(我专注于 Linux;我想我的大部分答案都适用于每个 POSIX 系统;但在 MacOSX 上 dlopen 想要 .dylib dynamic library 文件,而不是 .so 共享对象)支持>

一个程序甚至可以在某个临时文件/tmp/foo1234.cfork 中发出一些 C 代码,通过一些 gcc -O -shared -fPIC /tmp/foo1234.c -o /tmp/foo1234.so 命令生成并在运行时执行该 /tmp/foo1234.c 到共享库 /tmp/foo1234.so程序-,也许删除/tmp/foo1234.c 文件-因为不再需要它-,和dlopen /tmp/foo1234.so(甚至可能在dlopen 之后删除/tmp/foo1234.so),所有这些都在同一个过程中。我的GCC MELT 插件gcc 正是这样做的,Bigloo 也是如此,GCCJIT 库正在做一些接近的事情。

所以总的来说,你的任务是不可能的,甚至没有意义。

是否有可靠的方法来获取此信息而不会出现误报?

不,没有可靠的方法来获取此类信息而不会出现误报(您可以证明这相当于halting problem,或其他一些undecidable problem)。另见Rice's theorem

实际上,大多数dlopen 发生在某些配置提供的插件上。在配置文件中可能没有完全这样命名(例如,某些Foo 程序可能有一个约定,例如在某些foo.conf 配置文件中名为bar 的插件由foo-bar.so 插件提供)。

但是,您可能会发现一些 启发式 近似值。大多数执行dlopen 的程序都有一些插件convention,在插件中请求一些特定的符号名称。您可以搜索定义这些名称的共享对象。当然你会得到误报。

例如,zsh shell 接受名为 zsh modules 的插件。 example 模块显示 enables_boot_features_ 等...函数在 zsh 模块中是预期的。您可以使用nm -D 查找提供这些文件的*.so 文件(因此发现插件可能 可能可由zsh 加载)

(我不相信这种方法值得;事实上,您通常应该知道哪些插件在您的系统上对哪些应用程序有用)

顺便说一句,您可以在执行某些命令时使用strace(1) 来了解它正在执行的系统调用,从而了解它正在加载的插件。您也可以使用ltrace(1),或pmap(1)(在某些给定的进程上),或者简单地-对于process 1234-使用cat /proc/1234/maps 来了解它的virtual address space,因此它具有的插件已经 已加载。见proc(5)

请注意,straceltracepmap 存在于 Linux 上,但许多 POSIX 系统有类似程序。

此外,程序可以在运行时生成一些机器代码并执行它(SBCL REPL 交互时都会这样做!)。您的程序也可以使用一些JIT 技术(例如使用libjitllvmasmjitGCCJIT 或使用手写代码...)来做同样的事情。因此,在没有 dlopen 的情况下可能会发生类似插件的行为(您可能会通过 mmap 调用和一些 ELF 重定位处理来模仿 dlopen)。


附录:

如果您从其打包版本安装 firefox(例如 Debian 上的 iceweasel 包),它的包可能会处理依赖项

【讨论】:

  • 我相信程序甚至可以在dlopen()-ing 之后删除.so 文件,不是吗?或者 POSIX 是否要求,即使是隐含的,.so 文件在 dlopen() 之后仍然可用?
  • @Jay:你是对的。 .so 文件甚至可以在dlopen 之后删除。但是/proc/1234/maps 会显示一些关于它的信息。
猜你喜欢
  • 2011-01-18
  • 2021-07-25
  • 2021-06-27
  • 1970-01-01
  • 2017-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多