【问题标题】:Can I trick libc (GLIBC_2.13) into loading a symbol it doesn't have (from GLIBC_2.15)?我可以欺骗 libc (GLIBC_2.13) 加载它没有的符号(来自 GLIBC_2.15)吗?
【发布时间】:2013-01-30 12:56:32
【问题描述】:

在尝试让“Steam for Linux”在 Debian 上运行时,我遇到了一个问题。 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 实验中;甚至 Debian sid 仍然有 GLIBC_2.13)。


这是我尝试过的。

fdelt_chk.c基本被盗from the GNU C library

#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

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

【问题讨论】:

  • 尝试启用LD_DEBUG=all并检查rtld(ld-linux.so)如何搜索__fdelt@@GLIBC_2.15
  • 这么多的垃圾邮件。好像只看了/lib/i386-linux-gnu/i686/cmov/libc.so.6就立马放弃了。也许我需要为 fdelt_chk.so 选择一个合适的 soname...
  • 设置-Wl,-soname,libc.so.6 会杀死一切,因为现在它看起来不会比我自己的库更远。是否可以通过绝对 soname 导入实际的 libc,然后重新导出所有其他符号?
  • 为什么不在steam库路径中添加来自ubuntu的glibc?

标签: ld glibc dynamic-linking fortify-source


【解决方案1】:

我遇到了同样的问题,但 Steam 没有。当我的系统有2.14 时,我试图运行的东西需要2.15 用于fdelt_chk。我为像我们这样的简单案例找到了一个解决方案,我们可以轻松地为缺少的功能提供我们自己的实现。

我从您尝试的实现该功能的解决方案开始,并LD_PRELOADing 它。使用LD_DEBUG=all(由osgx 建议)表明链接器仍在寻找2.15,因此仅使用正确的符号是不够的,并且在某处还有其他一些版本控制机制。我注意到objdump -preadelf -V 都显示了对2.15 的引用,所以我查找了有关ELF 的文档并找到了information on version requirements

所以我的新目标是将对2.15 的引用转换为对其他内容的引用。我可以用引用一些较低版本的结构(如2.1)覆盖引用2.15 的结构,这似乎是合理的。最后,经过反复试验,我发现只需在.gnu.version_r 中编辑正确的Elfxx_Vernaux(es?) 就足够了,但我猜要注意黑客。

.gnu.version_r 部分是 16 字节 Elfxx_Verneeds 和 16 字节 Elfxx_Vernauxes 的列表。每个Elfxx_Verneed 条目后跟关联的Elfxx_Vernauxes。据我所知,vn_file 实际上是有多少关联的Elfxx_Vernauxes,即使文档说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)。交叉引用字符串(我不会进入),找到计数为0A 00Elfxx_Verneed(10,匹配我们上面的readelf 输出),或者找到最后一个Elfxx_Verneed,因为它是最后一个readelf 输出。无论如何,我的文件的正确位置是0x226C。它的第一个 Elfxx_Vernaux0x227C 开始。

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

要选择要覆盖的数据,我们只需从不同的Elfxx_Vernaux 复制它,以获得我们可以满足的 glibc 版本。我为2.1 选择了一个,它位于我的文件中的0x22EC,数据为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 更困难。

【讨论】:

  • 感谢您的链接!你的解决方案听起来像一个黑客。你能显示前后的十六进制数据并详细告诉我们,如何找到偏移量?
  • 扩展了我的答案。这绝对是一个黑客,呵呵。
  • 我接受了这个答案,不是因为它完美契合,而是因为它详细描述了最直接的解决方案,我认为没有其他好的方法可以做到这一点。谢谢:)
【解决方案2】:

不管怎样,__fdelt_chk 函数与 glibc 2.15 中添加的 FORTIFY_SOURCE 特性有关。它可以在编译时和运行时检查缓冲区溢出。

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

-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0

【讨论】:

  • 不幸的是,-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 建议没有删除 __fdelt_chk 符号。我还链接到可能正在使用它的 pthreads。
  • FWIW,它在设置标志后为我解决了问题,然后替换了 readline,然后是:forums.gentoo.org/…
  • 为我工作,删除了 __fdelt_chk@GLIBC_2.15 和 __longjmp_chk@GLIBC_2.11
猜你喜欢
  • 1970-01-01
  • 2018-07-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-15
  • 2015-05-07
  • 2013-05-31
  • 2012-09-26
相关资源
最近更新 更多