17

在我尝试让“Steam for Linux”在 Debian 上运行时,我遇到了一个问题。libcef(Chromium Embedded Framework)可以很好地使用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 实验中;即使 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

在这一点上,我不知道我能做些什么来欺骗加载程序(谁?)选择我的符号。我是否朝着正确的方向前进?

4

2 回答 2

9

我遇到了同样的问题,尽管 Steam 没有。当我2.15fdelt_chk系统有2.14. 我为像我们这样的简单案例找到了一个解决方案,我们可以轻松地为缺少的功能提供我们自己的实现。

我从您尝试实现功能并LD_PRELOAD对其进行编程的解决方案开始。使用LD_DEBUG=all(如 osgx 所建议的那样)表明链接器仍在寻找2.15,因此仅具有正确的符号是不够的,并且在某处还有一些其他版本控制机制。我注意到这两个objdump -preadelf -V显示了对的引用2.15,因此我查找了 ELF 的文档并找到了有关版本要求的信息。

所以我的新目标是将引用转换2.15为对其他事物的引用。我可以用引用一些较低版本的结构覆盖引用2.15的结构,例如2.1. 最后,经过一些试验和错误,我发现只需编辑正确的Elfxx_Vernaux(es?).gnu.version_r就足够了,但我猜要警告黑客。

.gnu.version_r部分是 16-byteElfxx_Verneed和 16-byte的列表Elfxx_Vernaux。每个Elfxx_Verneed条目后跟相关的Elfxx_Vernauxes。据我所知,vn_file实际上有多少相关联Elfxx_Vernaux的 es,即使文档说number of associated verneed array entries. 不过,这可能只是我的一个误解。

因此,要开始进行编辑,让我们看一下来自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. 根据文档,我们可以看到会有 2 个Elfxx_Vernauxes,一个偏移 16 字节,下一个Elfxx_Verneed偏移 48 字节。这些偏移量来自当前结构的开头。从技术上讲,关联的Elfxx_Vernauxes 在当前之后可能并不相邻,Elfxx_Verneed但实际上在我浏览的所有文件中都是如此。

从这里我们可以通过几种不同的方式找到我们想要的文件(libc.so.6)。交叉引用字符串(我不会进入),找到Elfxx_Verneed计数为0A 00(10,与我们上面的输出匹配readelf),或者找到最后一个,Elfxx_Verneed因为它是最后一个readelf输出。无论如何,我的文件的正确位置是0x226C. 它的第一个Elfxx_Vernaux开始于0x227C.

我们想要找到Elfxx_Vernaux版本为0C 00(12,再次匹配我们readelf上面的输出)的 。我们看到Elfxx_Vernaux匹配的是 at0x22CC并且整个结构是95 91 96 06 00 00 0C 00 7C 0D 00 00 10 00 00 00。我们将覆盖前 12 个字节,以便单独保留偏移量。毕竟,我们只是在修改数据,而不是在结构中移动。

要选择要覆盖的数据,我们只需从不同Elfxx_Vernaux的 glibc 版本中复制它即可满足我们的要求。我选择了一个 for 2.1,它0x22EC在我的文件中,带有 data 11 69 69 0D 00 00 09 00 32 0D 00 00 10 00 00 00。因此,从中取出前 12 个字节并覆盖上面的前 12 个字节,这就是十六进制编辑。

当然,您可能需要处理多个引用。您的程序可能有多个要编辑的二进制文件。

此时,我们的程序仍然无法运行。但与其被告知类似的事情GLIBC_2.15 not found,不如抱怨失踪__fdelt_chk。现在我们执行问题中描述的 shim 和LD_PRELOADing,除了不是将我们的实现版本化为2.15,我们使用我们在十六进制编辑时选择的版本。此时程序应该运行。

此方法取决于能够为缺少的任何内容提供实现。我们__fdelt_chk非常简单,但我不怀疑在某些情况下提供实现可能比仅仅升级系统的 libc 更困难。

于 2013-03-03T04:02:45.227 回答
8

值得一提的是,__fdelt_chk 函数与 glibc 2.15 中添加的 FORTIFY_SOURCE 功能有关。它启用了缓冲区溢出的编译时和运行时检查。

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

-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0
于 2014-01-06T15:20:16.953 回答