在缺少高版本glibc的机器上通过修改ELF引用使之成功运行测试实例

问题分析:

本篇文档是在《更改引用高版本glibc的程序到引用低版本的glibc》之后的补充文档,如果以后遇到相同问题,首先看我之前原创的《更改文件引用的高版本glibc到低版本glibc》这篇,然后再来看本篇。

本篇文档将详细记录一个在低版本glibc机器上运行由a.cpp文件编译之后的a文件,由最初的缺少GLIBC_2.14错误提示到最终成功运行的一系列步骤。

1我们在45.154机器上查看GLIBC版本,如下:

这是a.cpp文件:

使用g++ a.cpp命令编译之后生成了a.out文件,我们在45.154机器上,也就是在本机运行a.out文件,结果如下:

可以发现a.out文件可以成功运行。

 

2现在我们将a.out文件传输到45.152机器上,这个机器上的GLIBC版本如下:

可以发现该机器上的GLIBC版本比较低,我们通过scp命令将a.out文件从45.154上传输到45.152上,具体命令如下:

执行之后,输入命令,即可成功传输到45.152机器上。

 

3:我们在45.152机器上执行./a.out命令,运行结果如下:

可以发现,此时由于152机器上没有所需的GLIBC_2.14,因此程序执行报错了,那么我们首先需要确定这个a.out程序引用了GLIBC_2.14的哪个函数,再查看GLIBC_2.12中有没有这个函数,如果有,那么只需要将该引用修改为引用低版本的GLIBC即可。

首先我们可以检查一下程序使用了新版本 glibc 的哪些符号,使用 objdump 命令可以查看 ELF 文件的动态符号信息:

从上面的输出可以看到程序使用了 glibc 2.14 版本的 memcpy 函数,而这个常用的函数按说应该是 glibc 很早就已经支持了的,我们可以确认一下当前系统 glibc 提供的符号版本:

这里可以看出glibc 库提供的 memcpy 实现是 2.2.5 版本的,看过这里就基本明白了,第三方程序的开发者是在自带新版本 glibc 的 Linux 系统上编译的,memcpy 的实现默认使用了该系统上 glibc 所提供的最新版本,这样在低版本 glibc 系统中就无法正常运行。

 

解决办法:

虽然我们无法重新编译第三方程序,但如果可以修改 ELF 文件强制让 LD 库加载程序时使用老版本的 memcpy实现,应该就可以避免升级 glibc。

分析 ELF

首先用 readelf 命令查看 ELF 的符号表,由于该命令输出非常多,这里只贴出我们关心的信息:

我们可以在 ELF 的 .dynsym 动态符号表中看到程序用于动态链接的所有导入导出符号,memcpy 后面括号里的数字就是十进制的版本号(为 3 ),而我们需要格外关注的是下面的 .gnu.version 和 .gnu.version_r 符号版本信息段。

.gnu.version 表包含所有动态符号的版本信息,.dynsym 动态符号表中的每个符号都可以在 .gnu.version 中看到对应的条目。

下面关键的 .gnu.version_r 表示二进制程序实际依赖的库文件版本,从输出中也能看到 .gnu.version_r 表是按照不同的库文件进行分段显示的,每个条目占用 0x0010 也就是 16 个字节,该表是从 0x000450 偏移量开始,我们看看 GLIBC_2.14 也就是 0x000460 处的十六进制数据:

.gnu.version_r 表中每个条目是 16 个字节的 Elfxx_Vernaux 结构体,其声明如下(Elfxx_Half 占用 2 个字节,Elfxx_Word 占用 4 个字节):

 

typedef struct {

    Elfxx_Word    vna_hash;

    Elfxx_Half    vna_flags;

    Elfxx_Half    vna_other;

    Elfxx_Word    vna_name;

    Elfxx_Word    vna_next;

} Elfxx_Vernaux;

vna_hash 为 4 个字节的库名称(也就是上面的 GLIBC_2.14 字符串)的 hash 值,vna_other 为对应的 .gnu.version 表中符号的版本值,vna_name 指向库名称字符串的偏移量(也可以在 ELF 头中找到),vna_next 为下一个条目的位置(一般固定为 0x00000010)。

由上面的输出我们可以看到 GLIBC_2.14 对应的 0x000460 处的开始的 4 个字节 vna_hash hash 值为 94919606,而 vna_other 的值 0300(输出里的 Version: 3)也与 .gnu.version 符号的值一致。

 

修改 ELF 符号表

由于 Linux 系统中的 LD 库(也就是 /lib64/ld-linux-x86-64.so.2 库)加载 ELF 时检查 .gnu.version_r 表中的符号,我们可以使用任何一款十六进制编辑器来修改 .gnu.version_r 表中的符号值来强制使用老版本的函数实现。

 

我们现在需要修改0x000460 这行数字中的vna_hash; vna_other; vna_name;这三项和下面的0x000470保持一致, 0x000470是对GLIBC_2.12的引用。

 

这三项所在的字节分别是1~4字节,7~8字节,9~12字节。

修改之后内容如下:

首先在命令模式,输入%!xxd –r转换为二进制,再使用:wq保存即可。否则在执行./a.out的时候会报错。

 

此时,修改保存之后的 ELF 文件再使用 readelf 命令检查就能看到变化了(只列出了修改的 .gnu.version-r 表):

可以发现,原来上面那行引用的GLIBC_2.14变成了现在的GLIBC_2.2.5,此时再使用./a.out命令执行得到正确结果: