泰晓科技 -- 聚焦 Linux - 追本溯源,见微知著!
网站地址:https://tinylab.org

泰晓Linux实验盘,即刻上手内核与嵌入式开发
请稍侯

一起看看那些经典的 LD_PRELOAD 用法

Wu Zhangjin 创作于 2019/06/24

By Falcon of TinyLab.org Jun 14, 2019

简介

一次偶然的机会,刚编译完 buildroot,里头用到 fakeroot ,又看到自己早期写的 segment fault,里头有个 catchsegv,两者都有共性,那就是都用到了 LD_PRELOAD

从 fakeroot 和 catchsegv,都看到了 ‘LD_PRELOAD’ 的强大功能:

  1. fakeroot

    run a command in an environment faking root privileges for file manipulation

  2. catchsegv

    Catch segmentation faults in programs

fakeroot 允许执行 mknod 这样需要 root 权限的程序而不会失败(虽然没有真正创建设备文件),所以被 buildroot 用于文件系统构建。而 catchsegv 能够捕捉断错误,并打印出 backtrace 从而方便辅助定位软件问题。

fakeroot 通过 libfakeroot-0.so 篡改了 getuid 相关的函数,而 catchsegv 通过 libSegFault.so 捕获 SIGSEGV 信号来获得 backtrace。

它们本质上都是“篡改”了系统共享库文件中的函数。

$ man ld.so

LD_PRELOAD

     A  list of additional, user-specified, ELF shared objects to be loaded before all others.
     This can be used to selectively override functions in other shared objects.

LD_PRELOAD 经典应用

除了 fakeroot 和 catchsegv,还有大量工具利用了 LD_PRELOAD 特性。这篇讨论 就列举了数十种用法。比较经典的有:

  1. stderred: red the output to stderr
  2. Electric Fence: detect boundaries overruns and uses after free of malloced space
  3. InstallWatch: keep track of created and modified files during the installation of a new program
  4. libfiu: Fault injection in userspace
  5. libfaketime: libfaketime modifies the system time for a single application
  6. openonload: a high performance network stack from Solarflare that dramatically reduces latency and cpu utilisation, and increases message rate and bandwidth
  7. libshape: limiting the download rate of programs
  8. libeatmydata: disables all forms of writing data safely to disk. fsync() becomes a NO-OP, O_SYNC is removed etc.
  9. fakechroot: runs a command in an environment were is additional possibility to use chroot(8) command without root privileges.
  10. libtmperamental: intercepts filesystem writes, and causes loud failures when writes are attempted on /tmp/*
  11. libsafe: protect systems against some of the more common buffer overflow attacks

LD_PRELOAD 实例

下面来一个实实在在的例子,那就是强制 ls 输出到文件的结果也带颜色信息。默认情况下,ls 输出到控制台才带颜色信息,输出到文件则不带。

先来看看 ls 输出到控制台和输出到文件的差异:

$ ltrace -o ltrace-ls-stdout.log ls
$ ltrace -o ltrace-ls-file.log ls > file.tmp
$ vimdiff ltrace-ls-stdout.log ltrace-ls-file.log

可以观察到 isatty 的区别:

$ grep isatty -ur ltrace*.log
ltrace-file.log:isatty(1)    = 0
ltrace-stdout.log:isatty(1)  = 1

$ man isatty
isatty - test whether a file descriptor refers to a terminal

int isatty(int fd);

根据上述结果来看,isatty 在控制台下返回了 1,在输出到文件时则返回了 0。如果强制构造一个一直返回 1 的 isatty() 就可以确保输出到文件也显示颜色了吗?

下面试试看,先创建一个共享库,然后用 LD_PRELOAD 指定,从而可以 override glibc 中的 isatty() 实现:

$ echo 'int isatty(int fd) { return 1; }' | gcc -x c -o libisatty.so -shared -
$ LD_PRELOAD=./libisatty.so ls > file.tmp
$ cat file.tmp

这样打开的文件确实能看到输出带颜色了,完整演示视频如下:

LD_PRELOAD 探索

LD_PRELOAD 这种特性有点像是 live patching,也就是说不用修改源代码不用重新编译应用程序,就可以在运行时调整某部分功能。

上面列举的几个经典应用已经充分利用了这种特性,它们有用来做问题诊断、错误注入测试、数据监控、日志加亮、性能优化以及异常检测等,网络上甚至还有人用来做 Android antt-RE

或许还可以:

  1. 用作大型系统应用的在线更新方案,把核心功能用共享库的方式实现,升级时可以发布新的小型库通过 LD_PRELOAD 覆盖老版本中对应的功能即可。
  2. 也可以用于循序渐进地学习某个软件功能,一部分一部分的重写,用 LD_PRELOAD 替换,从而逐步完整实现某个软件库。可参考 overriding malloc
  3. 还可以用于紧急修复某个软件漏洞,比如说,某个软件库有 Bug,等到官方发布新版本会有个漫长的周期,修改源代码重新编译也很费时,或许可以考虑做个临时的库,通过 LD_PRELOAD 即时更新覆盖掉有 Bug 的函数。

当然,还有很多值得探索的空间,欢迎到文末加作者微信进一步交流探讨。



Read Related:

Read Latest: