『阿男的工程世界』*04 ldd,objdump与patchelf*
2017-01-05 00:00
197 查看
『阿男的工程世界』*04 ldd,objdump与patchelf*
我们可以使用ldd命令来查看一个binary file(二进制可执行文件)的共享库,以httpd为例:
从上面的执行情况来看,ldd的命令不但输出了使用到的共享库,还把调用到的共享库的目录列出来了。根据ldd的man page我们可以看到ldd命令的运行原理:
In the usual case, ldd invokes the standard dynamic linker (see ld.so(8)) with the LD_TRACE_LOADED_OBJECTS environment variable set to 1, which causes the linker to display the library dependencies.
我们因此可以知道ldd是调用ld.so去实际地加载程序,因此在加载过程中也就得知了所调用共享库的路径。此外在ldd的man page中还写道:
Be aware, however, that in some circumstances, some versions of ldd may attempt to obtain the dependency information by directly executing the program. Thus, you should never employ ldd on an untrusted executable, since this may result in the execution of arbitrary code. A safer alternative when dealing with untrusted executables is:
可以看到ldd的man page中建议我们使用更为安全的objdump命令,因此我们可以试试看:
因为objdump并不会加载和执行binary file,只是读取文件的elf头信息,因此安全。此外因为没有实际的加载过程,所以从上面的输出可以看到,并不能获得实际运行时加载的共享库位置信息。
接下来我们来聊一聊patchelf这个工具:首先这个工具的链接在这里^1。patchelf的能力是修改binary file的elf信息。因此,我们可以使用它修改httpd的共享库的默认加载路径。在elf中,定义默认加载路径的参数叫做RPATH。我们来使用patchelf来设置httpd的RPATH,下面是使用方法:
我们设置httpd查找共享库的默认位置为httpd所在目录。$ORIGIN就是指代binary file所在的当前目录,具体定义^2如下:
接下来我们可以使用objdump命令来验证RPATH被正确设置了:
我们还可以使用readelf命令达到同样的读取目的:
有趣的一点是,我们使用rpath设置的是RPATH,但显示出来设置的是RUNPATH。这个在patchelf的文档中有说明:
也就是说patchelf默认会设置RUNPATH,因为RPATH已经“过时”了。RPATH和RUNPATH的区别在于配置顺序。在这篇文档^3中,有关于共享库加载路径的配置顺序的说明:
也就是说RPATH的设置在LD_LIBRARY_PATH之前,而RUNPATH在之后。RUNPATH的好处是,用户在运行代码时,可以自己设置LD_LIBRARY_PATH变量来override掉RUNPATH的默认配置,使得整个配置更为灵活。
最后,让我们用ldd命令来验证RUNPATH的作用:
我们把libm.so.6这个文件copy到与httpd同目录:
因为我们设置了RUNPATH为$ORIGIN,所以我们再次使用ldd验证:
可以看到RUNPATH生效了。最后我们再用LD_LIBRARY_PATH设置override掉RUNPATH的设置:
可以看到结果与我们预期的一样。
我们可以使用ldd命令来查看一个binary file(二进制可执行文件)的共享库,以httpd为例:
[weli@fedora sbin]$ ldd httpd linux-vdso.so.1 (0x00007fffe38da000) libm.so.6 => /lib64/libm.so.6 (0x00007f236dbfc000) libpcre.so.1 => /lib64/libpcre.so.1 (0x00007f236d98c000) libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f236d768000) libaprutil-1.so.0 => not found libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007f236d532000) libexpat.so.1 => /lib64/libexpat.so.1 (0x00007f236d307000) libdb-5.3.so => /lib64/libdb-5.3.so (0x00007f236cf45000) libdl.so.2 => /lib64/libdl.so.2 (0x00007f236cd41000) libapr-1.so.0 => not found libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f236cb23000) libc.so.6 => /lib64/libc.so.6 (0x00007f236c762000) /lib64/ld-linux-x86-64.so.2 (0x000055754302b000) libfreebl3.so => /lib64/libfreebl3.so (0x00007f236c55e000)
从上面的执行情况来看,ldd的命令不但输出了使用到的共享库,还把调用到的共享库的目录列出来了。根据ldd的man page我们可以看到ldd命令的运行原理:
In the usual case, ldd invokes the standard dynamic linker (see ld.so(8)) with the LD_TRACE_LOADED_OBJECTS environment variable set to 1, which causes the linker to display the library dependencies.
我们因此可以知道ldd是调用ld.so去实际地加载程序,因此在加载过程中也就得知了所调用共享库的路径。此外在ldd的man page中还写道:
Be aware, however, that in some circumstances, some versions of ldd may attempt to obtain the dependency information by directly executing the program. Thus, you should never employ ldd on an untrusted executable, since this may result in the execution of arbitrary code. A safer alternative when dealing with untrusted executables is:
$ objdump -p /path/to/program | grep NEEDED
可以看到ldd的man page中建议我们使用更为安全的objdump命令,因此我们可以试试看:
[weli@fedora sbin]$ objdump -p httpd | grep NEEDED NEEDED libm.so.6 NEEDED libpcre.so.1 NEEDED libselinux.so.1 NEEDED libaprutil-1.so.0 NEEDED libcrypt.so.1 NEEDED libexpat.so.1 NEEDED libdb-5.3.so NEEDED libdl.so.2 NEEDED libapr-1.so.0 NEEDED libpthread.so.0 NEEDED libc.so.6
因为objdump并不会加载和执行binary file,只是读取文件的elf头信息,因此安全。此外因为没有实际的加载过程,所以从上面的输出可以看到,并不能获得实际运行时加载的共享库位置信息。
接下来我们来聊一聊patchelf这个工具:首先这个工具的链接在这里^1。patchelf的能力是修改binary file的elf信息。因此,我们可以使用它修改httpd的共享库的默认加载路径。在elf中,定义默认加载路径的参数叫做RPATH。我们来使用patchelf来设置httpd的RPATH,下面是使用方法:
[weli@fedora sbin]$ ./patchelf --set-rpath '$ORIGIN/' httpd
我们设置httpd查找共享库的默认位置为httpd所在目录。$ORIGIN就是指代binary file所在的当前目录,具体定义^2如下:
$ORIGIN is a special variable that means ‘this executable’, and it means the actual executable filename, as readlink would see it, so symlinks are followed. In other words, $ORIGIN is special and resolves to wherever the binary is at runtime.
接下来我们可以使用objdump命令来验证RPATH被正确设置了:
[weli@fedora sbin]$ objdump -p httpd | grep PATH RUNPATH $ORIGIN/
我们还可以使用readelf命令达到同样的读取目的:
[weli@fedora sbin]$ readelf --dynamic httpd | grep PATH 0x000000000000001d (RUNPATH) Library runpath: [$ORIGIN/]
有趣的一点是,我们使用rpath设置的是RPATH,但显示出来设置的是RUNPATH。这个在patchelf的文档中有说明:
* `--set-rpath', `--shrink-rpath' and `--print-rpath' now prefer DT_RUNPATH over DT_RPATH, which is obsolete. When updating, if both are present, both are updated. If only DT_RPATH is present, it is converted to DT_RUNPATH unless `--force-rpath' is specified. If neither is present, a DT_RUNPATH is added unless `--force-rpath' is specified, in which case a DT_RPATH is added.
也就是说patchelf默认会设置RUNPATH,因为RPATH已经“过时”了。RPATH和RUNPATH的区别在于配置顺序。在这篇文档^3中,有关于共享库加载路径的配置顺序的说明:
1. the RPATH binary header (set at build-time) of the library causing the lookup (if any) 2. the RPATH binary header (set at build-time) of the executable 3. the LD_LIBRARY_PATH environment variable (set at run-time) 4. the RUNPATH binary header (set at build-time) of the executable 5. /etc/ld.so.cache 6. base library directories (/lib and /usr/lib)
也就是说RPATH的设置在LD_LIBRARY_PATH之前,而RUNPATH在之后。RUNPATH的好处是,用户在运行代码时,可以自己设置LD_LIBRARY_PATH变量来override掉RUNPATH的默认配置,使得整个配置更为灵活。
最后,让我们用ldd命令来验证RUNPATH的作用:
[weli@fedora sbin]$ ldd httpd …… libm.so.6 => /lib64/libm.so.6 (0x00007fe457ce4000) ……
我们把libm.so.6这个文件copy到与httpd同目录:
[weli@fedora sbin]$ cp /usr/lib64/libm.so.6 .
因为我们设置了RUNPATH为$ORIGIN,所以我们再次使用ldd验证:
[weli@fedora sbin]$ ldd httpd …… libm.so.6 => /home/weli/rpms/jboss-ews-2.1/httpd/sbin/./ ……
可以看到RUNPATH生效了。最后我们再用LD_LIBRARY_PATH设置override掉RUNPATH的设置:
[weli@fedora sbin]$ LD_LIBRARY_PATH=/usr/lib64 ldd httpd …… libm.so.6 => /usr/lib64/libm.so.6 (0x00007f8fb6a19000) ……
可以看到结果与我们预期的一样。
相关文章推荐
- 『阿男的工程世界』*03 POSIX AIO vs. Native AIO*
- 『阿男的工程世界』*让我们简单聊一下IO(01)*
- 『阿男的技术日志』*02 处理Git项目当中既有Windows文件又有Linux/Unix/MacOS文件的问题*
- Properties的load方法
- iOS微信第三方登录
- 统计每个月兔子的总数
- 整数与IP地址间的转换
- 求小球落地5次后所经历的路程和第5次反弹的高度
- 输入一行字符,分别统计出包含英文字母、空格、数字和其它字符的个数
- 名字的漂亮度
- 找出字符串中第一个只出现一次的字符
- 输出单向链表中倒数第k个结点
- Qt持久性对象进行序列化
- Xamarin+Prism开发详解六:DependencyService与IPlatformInitializer的关系
- 完全数计算
- 挑7
- 杨辉三角的变形
- Win10系统下安装centos7并存的双系统
- 手动实现单向链表跟双向链表的一个简单demo
- Hive SQL 数据倾斜总结