Skip to content

标题: 查看/修改ELF的动态链接器

创建: 2019-07-04 16:03 更新: 2019-07-05 09:27 链接: https://scz.617.cn/unix/201907041603.txt

不想看全文的,就一句话,用patchelf查看、修改ELF的动态链接器。

关于动态链接器,Linux用户可以看ld.so(8)。

动态链接版ELF从内核态获得执行时控制权首先交给ELF的动态链接器,而不是e_entry, 更不是main()。

除非在编译链接时给ld(1)指定了-static选项,否则Linux程序必需动态链接器。

有两种动态链接器,ld.so用于处理a.out格式(很古老的一种格式),ld-linux.so*用 于处理ELF格式。

Debian的ld-linux.so*是可以单独执行的,但FreeBSD 6.1的/libexec/ld-elf.so.1 就不行。可以单独执行的动态链接器在某些特定场景有意义,比如:

《动态链接器符号链接被破坏后的灾难恢复》 https://scz.617.cn/unix/201809191202.txt

本文对ELF的动态链接器本身不多解释,假设读者对ELF是了解的。说一个真实案例。

受组织之托逆向分析一个设备,千辛万苦之后确认这个设备是基于x86/FreeBSD 6.1 改的。

TK推荐过:

https://www.osboxes.org/freebsd/

这里有现成的FreeBSD 10.3/12.0的VMware映像。没有6.1,只好在VMware中自己装 x86/FreeBSD 6.1,这样可以自己编译某些静态版本的工具,比如xxd、lrzsz、gdb等 等。

有一天想弄个lsof到目标设备上。多说一句,如果只是想知道端口与进程的映射关系, 或许有替代方案,参看:

《x64/FreeBSD 10.3/12.0中lsof的替代方案》 https://scz.617.cn/unix/201906131429.txt

在FreeBSD 6.1中用ports自编译lsof,TK帮我找到了精确匹配版本:

http://ftp-archive.freebsd.org/pub/FreeBSD-Archive/ports/distfiles/lsof_4.77D.freebsd.tar.bz2

这个链接居然没有被Google爬到,简直了。

在目标设备上执行时报错:

$ /tmp/lsof -lnPR -i4 ELF interpreter /libexec/ld-elf.so.1 not found Abort

从错误提示看出,lsof所用动态链接器是:

/libexec/ld-elf.so.1

在目标设备上找了一圏,只有:

$ ls -l /libexec/ld-elf32.so.1 lrw-r--r-- 1 root wheel 24 Mar 3 2015 /libexec/ld-elf32.so.1 -> /usr/libexec/ld-elf.so.1

受制于只读文件系统,无法通过创建符号链接解决:

$ ln -s /libexec/ld-elf32.so.1 /libexec/ld-elf.so.1 ln: /libexec/ld-elf.so.1: Read-only file system

至此,产生了两个需求:

a) 查看指定ELF的动态链接器 b) 修改指定ELF的动态链接器

FreeBSD 6.1的ELF工具不够强大,在Debian中检查自编译得到的lsof:

$ readelf -p .interp lsof

String dump of section '.interp': [ 0] /libexec/ld-elf.so.1

$ readelf -x .interp lsof $ readelf -x1 lsof

Hex dump of section '.interp': 0x080480d4 2f6c6962 65786563 2f6c642d 656c662e /libexec/ld-elf. 0x080480e4 736f2e31 00 so.1.

$ objdump -j .interp -hs lsof

lsof: file format elf32-i386

Sections: Idx Name Size VMA LMA File off Algn 0 .interp 00000015 080480d4 080480d4 000000d4 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA Contents of section .interp: 80480d4 2f6c6962 65786563 2f6c642d 656c662e /libexec/ld-elf. 80480e4 736f2e31 00 so.1.

$ readelf -Wl lsof ... Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align ... INTERP 0x0000d4 0x080480d4 0x080480d4 0x00015 0x00015 R 0x1 [Requesting program interpreter: /libexec/ld-elf.so.1] ...

$ patchelf --print-interpreter lsof /libexec/ld-elf.so.1

$ file -b lsof ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD), dynamically linked, interpreter /libexec/ld-elf.so.1, stripped

前面这些命令都可以查看指定ELF的动态链接器。Debian上"file -b "是最简查 看动态链接器的办法,有些系统的file命令不会显示动态链接器,比如FreeBSD 6.1 的:

$ file -b lsof ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD), dynamically linked (uses shared libs), stripped

FreeBSD 10.3/12.0的file命令会显示动态链接器,FreeBSD 6.1实在太老了。

此外,Linux的"ldd "输出中包含动态链接器,但FreeBSD的如下命令输出中并 不包含动态链接器:

$ ldd $ LD_TRACE_LOADED_OBJECTS=1

在Debian上检查目标设备中的id命令,确认其动态链接器是:

/usr/libexec/ld-elf.so.1

我们需要将自编译得到的lsof的动态链接器改成上面这个。

有两种办法,一种是用patchelf直接修改ELF,另一种是在编译链接阶段指定动态链 接器。

$ cp lsof lsof-test $ patchelf --set-interpreter "/usr/libexec/ld-elf.so.1" lsof-test

$ cd /usr/ports/sysutils/lsof $ make CC=gcc

注意到最后一条命令是:

gcc -o lsof -fno-strict-aliasing -pipe -DHASEFFNLINK=i_effnlink -DHASF_VNODE \ -DHASCPUMASK_T -DHASSBSTATE -DHAS_KVM_VNODE -DHAS_UFS1_2 -DHAS_NO_SI_UDEV \ -DHAS_SI_PRIV -DHAS_SYS_SX_H -DFREEBSDV=6010 -DHASFDESCFS=2 -DHASPSEUDOFS \ -DHASNULLFS -DHAS9660FS -DHAS_NO_ISO_DEV -DHASIPv6 -DLSOF_VSTR=\"6.1-RELEASE\" \ -I/usr/src/sys -O2 dmnt.o dnode.o dnode1.o dproc.o dsock.o dstore.o arg.o \ main.o misc.o node.o print.o proc.o store.o usage.o -L./lib -llsof -lkvm

$ cd work/lsof_4.77D.freebsd/ $ gcc -Wl,-dynamic-linker,/usr/libexec/ld-elf.so.1 -o lsof-test \ -fno-strict-aliasing -pipe -DHASEFFNLINK=i_effnlink -DHASF_VNODE \ -DHASCPUMASK_T -DHASSBSTATE -DHAS_KVM_VNODE -DHAS_UFS1_2 -DHAS_NO_SI_UDEV \ -DHAS_SI_PRIV -DHAS_SYS_SX_H -DFREEBSDV=6010 -DHASFDESCFS=2 -DHASPSEUDOFS \ -DHASNULLFS -DHAS9660FS -DHAS_NO_ISO_DEV -DHASIPv6 -DLSOF_VSTR=\"6.1-RELEASE\" \ -I/usr/src/sys -O2 dmnt.o dnode.o dnode1.o dproc.o dsock.o dstore.o arg.o \ main.o misc.o node.o print.o proc.o store.o usage.o -L./lib -llsof -lkvm

两种办法得到的lsof-test在目标设备上均可执行。由于FreeBSD 6.1上存在:

/usr/libexec/ld-elf.so.1

于是lsof-test既可以在目标设备上运行,也可以在FreeBSD 6.1虚拟机上运行。

一般来说,尽可能向目标设备上传静态链接版本ELF,可以避免很多麻烦,但我没有找 到编译静态链接版本lsof的办法。

前面演示的patchelf还可以修改ELF的其他内容:

--set-interpreter INTERPRETER --print-interpreter

--print-soname --set-soname SONAME

--set-rpath RPATH --remove-rpath --shrink-rpath --allowed-rpath-prefixes PREFIXES --print-rpath --force-rpath

--add-needed LIBRARY --replace-needed LIB_ORIG LIB_NEW --remove-needed LIBRARY

--no-default-lib

Debian中可以这样安装patchelf:

$ aptitude install patchelf