我能欺骗libc (GLIBC_2.13)加载它没有的符号(来自GLIBC_2.15)吗?

17
在尝试在Debian上运行"Steam for Linux"时,我遇到了一个问题。 libcef(Chromium嵌入式框架)与GLIBC_2.13(Debian测试版中的eglibc可以提供)配合良好,但需要从GLIBC_2.15中获得一个麻烦的额外函数(这是eglibc无法提供的):
$ readelf -s libcef.so | grep -E "@GLIBC_2\.1[4567]"
1037: 00000000     0 FUNC    GLOBAL DEFAULT  UND __fdelt_chk@GLIBC_2.15 (49)
2733: 00000000     0 FUNC    GLOBAL DEFAULT  UND __fdelt_chk@@GLIBC_2.15

我的攻击计划是LD_PRELOAD一个提供这些函数的Shim库,但这似乎不起作用。我真的想避免安装GLIBC_2.17(因为它在Debian experimental中;即使是Debian sid仍然有GLIBC_2.13)。


这是我尝试过的东西。

fdelt_chk.c基本上是从GNU C库偷来的:

#include <sys/select.h>

# define strong_alias(name, aliasname) _strong_alias(name, aliasname)
# define _strong_alias(name, aliasname) \
  extern __typeof (name) aliasname __attribute__ ((alias (#name)));

unsigned long int
__fdelt_chk (unsigned long int d)
{
  if (d >= FD_SETSIZE)
    __chk_fail ();

  return d / __NFDBITS;
}
strong_alias (__fdelt_chk, __fdelt_warn)

我的 Versions 脚本如下所示:

GLIBC_2.15 {
    __fdelt_chk; __fdelt_warn;
};

然后我按照以下方式构建库:

$ gcc -m32 -c -fPIC fdelt_chk.c -o fdelt_chk.o
$ gcc -m32 -shared -nostartfiles -Wl,-s -Wl,--version-script Versions -o fdelt_chk.so fdelt_chk.o

不过,如果我再运行Steam(需要添加许多额外内容才能使其正常工作),加载器仍然无法找到该符号:

% LD_LIBRARY_PATH="/home/tinctorius/.local/share/Steam/ubuntu12_32" LD_PRELOAD=./fdelt_chk.so:./steamui.so ./steam 
./steam: /lib/i386-linux-gnu/i686/cmov/libc.so.6: version `GLIBC_2.15' not found (required by /home/tinctorius/.local/share/Steam/ubuntu12_32/libcef.so)    

然而,版本符号也由我刚刚构建的.so提供:

% readelf -s fdelt_chk.so

Symbol table '.dynsym' contains 8 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND __chk_fail@GLIBC_2.3.4 (3)
     2: 0000146c     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
     3: 0000146c     0 NOTYPE  GLOBAL DEFAULT  ABS _end
     4: 00000310    44 FUNC    GLOBAL DEFAULT   11 __fdelt_warn@@GLIBC_2.15
     5: 00000310    44 FUNC    GLOBAL DEFAULT   11 __fdelt_chk@@GLIBC_2.15
     6: 00000000     0 OBJECT  GLOBAL DEFAULT  ABS GLIBC_2.15
     7: 0000146c     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start

此时,我不知道我能做什么来欺骗加载器(是谁?)选择我的符号。我是否完全朝着正确的方向前进?


1
尝试启用LD_DEBUG=all并检查rtld(ld-linux.so)如何搜索__fdelt@@GLIBC_2.15 - osgx
太多的日志垃圾。看起来它只查看/lib/i386-linux-gnu/i686/cmov/libc.so.6并立即放弃。也许我需要为fdelt_chk.so选择一个合适的soname... - user824425
设置-Wl,-soname,libc.so.6会导致一切崩溃,因为现在它不会查找除了我的库之外的其他库。是否可能通过绝对soname导入实际的libc,并重新导出所有其他符号? - user824425
为什么不将Ubuntu中的glibc添加到Steam库路径中呢? - sherpya
2个回答

10
我遇到了同样的问题,尽管不是在Steam上。我想运行的程序需要2.15fdelt_chk,而我的系统只有2.14。我找到了一个解决方案,适用于像我们这样简单的情况,我们可以轻松地为缺失的功能提供自己的实现。
我从您尝试实现功能并使用LD_PRELOAD的解决方案开始。使用LD_DEBUG=all(osgxs建议)显示链接器仍在寻找2.15,因此仅具有正确的符号是不够的,并且还有其他版本控制机制。我注意到objdump -preadelf -V都显示了对2.15的引用,因此我查阅了关于ELF的文档,并发现了版本要求信息
所以我的新目标是将对2.15的引用转换为对其他内容的引用。我觉得只需用指向某个较低版本(如2.1)的结构覆盖引用2.15的结构就可以了。最终,在一些试验和错误之后,我发现只需编辑.gnu.version_r中正确的Elfxx_Vernaux即可,但要注意,这可能存在风险。 .gnu.version_r部分是一个16字节的Elfxx_Verneed列表和16字节的Elfxx_Vernaux列表。每个Elfxx_Verneed条目后面跟着相应的Elfxx_Vernaux。据我所知,vn_file实际上是与之相关的Elfxx_Vernaux数量,尽管文档中说它是“与之关联的verneed数组条目数”。不过这可能只是我的误解。

那么,为了开始进行编辑,让我们看一下readelf -V的一些信息。我剪掉了我们不关心的部分。

$ readelf -V mybinary
<snip stuff before .gnu.version_r>
Version needs section '.gnu.version_r' contains 5 entries:
 Addr: 0x00000000000021ac  Offset: 0x0021ac  Link: 4 (.dynstr)
<snip libraries that don't refer to GLIBC_2.15>
  0x00c0: Version: 1  File: libc.so.6  Cnt: 10
  0x00d0:   Name: GLIBC_2.3  Flags: none  Version: 19
  0x00e0:   Name: GLIBC_2.7  Flags: none  Version: 16
  0x00f0:   Name: GLIBC_2.2  Flags: none  Version: 15
  0x0100:   Name: GLIBC_2.2.4  Flags: none  Version: 14
  0x0110:   Name: GLIBC_2.1.3  Flags: none  Version: 13
  0x0120:   Name: GLIBC_2.15  Flags: none  Version: 12
  0x0130:   Name: GLIBC_2.4  Flags: none  Version: 10
  0x0140:   Name: GLIBC_2.1  Flags: none  Version: 9
  0x0150:   Name: GLIBC_2.3.4  Flags: none  Version: 4
  0x0160:   Name: GLIBC_2.0  Flags: none  Version: 2

从这里我们可以看到,段在0x21ac处开始。每个列出的文件都有一个Elfxx_Verneed,后面跟着每个子条目的Elfxx_Vernaux(比如GLIBC_2.3)。我假设输出中的信息顺序将始终与文件中的顺序相匹配,因为readelf只是转储结构。这是我的整个.gnu.version_r部分。
000021A0                                          01 00 02 00
000021B0   A3 0C 00 00  10 00 00 00  30 00 00 00  11 69 69 0D
000021C0   00 00 11 00  32 0D 00 00  10 00 00 00  10 69 69 0D
000021D0   00 00 0B 00  3C 0D 00 00  00 00 00 00  01 00 02 00
000021E0   BE 0C 00 00  10 00 00 00  30 00 00 00  13 69 69 0D
000021F0   00 00 08 00  46 0D 00 00  10 00 00 00  10 69 69 0D
00002200   00 00 07 00  3C 0D 00 00  00 00 00 00  01 00 02 00
00002210   99 0C 00 00  10 00 00 00  30 00 00 00  11 69 69 0D
00002220   00 00 06 00  32 0D 00 00  10 00 00 00  10 69 69 0D
00002230   00 00 05 00  3C 0D 00 00  00 00 00 00  01 00 02 00
00002240   AE 0C 00 00  10 00 00 00  30 00 00 00  11 69 69 0D
00002250   00 00 12 00  32 0D 00 00  10 00 00 00  10 69 69 0D
00002260   00 00 03 00  3C 0D 00 00  00 00 00 00  01 00 0A 00
00002270   FF 0C 00 00  10 00 00 00  00 00 00 00  13 69 69 0D
00002280   00 00 13 00  46 0D 00 00  10 00 00 00  17 69 69 0D
00002290   00 00 10 00  50 0D 00 00  10 00 00 00  12 69 69 0D
000022A0   00 00 0F 00  5A 0D 00 00  10 00 00 00  74 1A 69 09
000022B0   00 00 0E 00  64 0D 00 00  10 00 00 00  73 1F 69 09
000022C0   00 00 0D 00  70 0D 00 00  10 00 00 00  95 91 96 06
000022D0   00 00 0C 00  7C 0D 00 00  10 00 00 00  14 69 69 0D
000022E0   00 00 0A 00  87 0D 00 00  10 00 00 00  11 69 69 0D
000022F0   00 00 09 00  32 0D 00 00  10 00 00 00  74 19 69 09
00002300   00 00 04 00  91 0D 00 00  10 00 00 00  10 69 69 0D
00002310   00 00 02 00  3C 0D 00 00  00 00 00 00

简单介绍一下这里的结构,它以一个 Elfxx_Verneed 开始。根据文档,我们可以看到会有两个 Elfxx_Vernaux,一个偏移量为16字节,下一个 Elfxx_Verneed 的偏移量为48字节。这些偏移量是从当前结构的开头算起的。从技术上讲,似乎相关的 Elfxx_Vernaux 并不一定紧邻在当前的 Elfxx_Verneed 之后,但在我查看的所有文件中实际上都是这样的。

从中我们可以以几种不同的方式找到我们想要的文件(libc.so.6)。可以交叉引用字符串(我不会深入讲解),找到计数为0A 00(10,与上面的readelf输出匹配)的Elfxx_Verneed,或者找到最后一个Elfxx_Verneed,因为它是readelf输出的最后一个。无论哪种情况,对于我的文件来说,正确的文件位于0x226C。它的第一个Elfxx_Vernaux0x227C开始。
我们希望找到版本为0C 00(与上面的readelf输出匹配)的Elfxx_Vernaux。我们看到匹配的Elfxx_Vernaux位于0x22CC,整个结构是95 91 96 06 00 00 0C 00 7C 0D 00 00 10 00 00 00。我们将覆盖前12个字节,以保留偏移量不变。毕竟,我们只修改数据,而不是移动结构。

为了选择要覆盖的数据,我们只需从另一个可以满足的glibc版本的Elfxx_Vernaux中复制它。我选择了一个2.1的版本,在我的文件中位于0x22EC,数据为11 69 69 0D 00 00 09 00 32 0D 00 00 10 00 00 00。因此,将此数据的前12个字节复制到上面的前12个字节进行覆盖,这就完成了十六进制编辑。

当然,你可能需要处理多个引用。你的程序可能有多个二进制文件需要编辑。 此时,我们的程序仍无法运行。但是,它不会像报告“找不到GLIBC_2.15”那样,而是会抱怨缺少“__fdelt_chk”。现在,我们进行shim和LD_PRELOAD,就像问题中描述的那样,但是我们使用在十六进制编辑中选择的版本,而不是将我们的实现版本化为“2.15”。此时,程序应该可以运行。 这种方法取决于能够提供缺失内容的实现。我们的“__fdelt_chk”非常简单,但我毫不怀疑,在某些情况下,提供实现可能比仅升级系统的libc更加困难。

谢谢提供链接!你的解决方案听起来像是一个hack。你能否展示十六进制数据的前后,并详细说明如何找到偏移量? - osgx
2
扩展了我的回答。这绝对是一个hack,呵呵。 - Fyren
我接受了这个答案,不是因为它完全符合我的要求,而是因为它详细地描述了最直接的解决方案,并且我觉得没有更好的方法可以做到。谢谢 :) - user824425

8

就目前而言,__fdelt_chk函数与在glibc 2.15中添加的FORTIFY_SOURCE功能相关。它使得编译时和运行时检查缓冲区溢出成为可能。

如果您能够使用以下CFLAGS重新编译,它将构建一个向后兼容的二进制文件,没有额外的检查:

-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0

1
不幸的是,-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 的建议并没有移除 __fdelt_chk 符号。我还链接到 pthreads,也许它正在使用它。 - hookenz
就我个人而言,设置标志后替换readline,然后参考此链接https://forums.gentoo.org/viewtopic-p-7807512.html?sid=72aae1be7e1c8994989b783fe1462ee8解决了我的问题。 - Michael Schubert
适用于我,已删除__fdelt_chk@GLIBC_2.15__longjmp_chk@GLIBC_2.11 - zhaorufei

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接