0%

运行调试binary时更换不同版本libc

今天发现一篇好文章,转载收藏一下。
以下全文转载自关于不同版本 glibc 更换的一些问题

关于不同版本 glibc 更换的一些问题

在做 pwn 题时,更换 ELF 文件的 libc 版本一直让人头疼,所以写文记录关于 glibc 的下载,替换,调试的一些问题。

如何获取不同版本的 glibc

手动下载

通过镜像源可以下载到常见版本的 glibc 及其符号表。

通过 Ubuntu 的 old-releases 镜像站 或者 清华的镜像站 可以下载到不同版本的 glibc。然后通过 dpkg -x *.deb 来解压 deb 包得到 libc。

当然也可以通过 Debian 等发行版的镜像源来下载 glibc。

自动化工具

有两个项目可以实现自动下载 libc:https://github.com/niklasb/libc-databasehttps://github.com/matrix1001/glibc-all-in-one ,前者不会下载符号表,而后者会将符号表存入对应 libc 的 “.debug” 文件夹中。

让程序强行加载特定版本 glibc

通过 LD_LIBRARY_PATH 或者 LD_PRELOAD

因为 ld.solibc.so 不匹配的原因,所以直接设置 LD_PRELOAD 可能会炸,就如下所示…

1
2
$ LD_PRELOAD=./libc.so.6 ./baby_tcache
段错误 (核心已转储)

可以将配套的 ld 和 libc 一起使用即可实现动态加载 libc。只需将下面代码中 LD_PRELOAD 后面的 “/path/to/libc.so.6” (要加载的 libc 的路径)和第二行的 “/path/to/ld.so” (要加载的 ld 的路径)替换成相应文件的路径就行了。

1
2
$ LD_PRELOAD=/path/to/libc.so.6;
$ /path/to/ld.so ./test

在 pwntools 启动程序时,可以按照下面的代码进行设置。

1
2
p = process(["/path/to/ld.so", "./test"],
env={"LD_PRELOAD":"/path/to/libc.so.6"})

通过 patchelf 修改 ELF 文件

在 github 上有一个项目叫 patchelf,通过这个项目可以实现修改 ELF 中硬编码的 libc 和 ld 的路径。

一般 ELF 文件的 lddfile 结果与下面类似,可以看到 libc 等动态库的路径被写死在文件中,而 libc.so.6 是一个符号链接,所指向的文件是真正的 libc。

1
2
3
4
5
6
7
8
9
10
11
$ ldd /usr/local/bin/patchelf
linux-vdso.so.1 (0x00007ffc497e5000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f2d46aee000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f2d4696b000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f2d46951000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2d46790000)
/lib64/ld-linux-x86-64.so.2 (0x00007f2d46caf000)
$ file /usr/local/bin/patchelf
/usr/local/bin/patchelf: ELF 64-bit LSB pie executable, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=cfa182b059312c4c03e401efc8efe47a373d348c, with debug_info, not stripped
$ file /lib/x86_64-linux-gnu/libc.so.6
/lib/x86_64-linux-gnu/libc.so.6: symbolic link to libc-2.28.so

我们通过 patchelf 修改 ELF 文件达到加载指定版本 libc。我们先用 “–set-interpreter” 这个选项来将旧的 ld.so 替换为要加载的 ld.so,然后使用 “–replace-needed” 这个选项将旧的 libc.so 替换成要加载的 libc.so。在使用 “–replace-needed” 时,第 2 个参数是程序原本的动态库的路径,可以由 ldd $目标文件 得到,第 3 个参数是新的动态库的路径,第 4 个参数为要修改文件的路径。

这里我们修改 “./patchelf” 这个文件的的 libc.sold.so。根据上面 ldd 的结果,可以知道 ELF 中的 libc 的路径为 “libc.so.6”,所以替换 libc 时所使用的第 2 个参数为 “libc.so.6”

1
2
$ patchelf --set-interpreter /opt/libs/2.27-3ubuntu1_amd64/ld-2.27.so ./patchelf
$ patchelf --replace-needed libc.so.6 /opt/libs/2.27-3ubuntu1_amd64/libc-2.27.so ./patchelf

然后再用 ldd 和 file 命令查看程序,可以看到 libc 和 ld 都修改成功了。

1
2
3
4
5
6
7
8
9
$ ldd ./patchelf
linux-vdso.so.1 (0x00007fff785a0000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fb408672000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fb4084ef000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fb4084d5000)
/opt/libs/2.27-3ubuntu1_amd64/libc-2.27.so (0x00007fb4080e4000)
/opt/libs/2.27-3ubuntu1_amd64/ld-2.27.so => /lib64/ld-linux-x86-64.so.2 (0x00007fb408a67000)
$ file ./patchelf
./patchelf: ELF 64-bit LSB pie executable, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /opt/libs/2.27-3ubuntu1_amd64/ld-2.27.so, for GNU/Linux 3.2.0, BuildID[sha1]=cfa182b059312c4c03e401efc8efe47a373d348c, with debug_info, not stripped

我写了个简单脚本,使用 “patchelf” 给 ELF 文件 Patch 上 “glibc all in one” 下载的 libc。将下面脚本以 “chlibc.sh” 保存后,只需要使用 ./chlibc.sh $libc所在目录 $目标文件 就能修改 libc 了。例如 ./chlibc.sh /opt/libs/2.23-0ubuntu10_i386 echo3 就是将 “echo3” 这个 ELF 文件的 libc 和 ld 换成 /opt/libs/2.23-0ubuntu10_i386 这个目录下的 libc 和 ld。

1
2
3
4
5
6
7
8
9
10
11
set -x
libc_path=$1
elf_path=$2
patchelf_bin_path="/path/to/patchelf"
if [ -f ${libc_path}/ld-[2].[0-9][0-9].so ]; then
$patchelf_bin_path --set-interpreter $libc_path/ld-[2].[0-9][0-9].so $elf_path
fi
if [ -f $libc_path/libc-[2].[0-9][0-9].so ]; then
$patchelf_bin_path --replace-needed libc.so.6 $libc_path/libc-[2].[0-9][0-9].so $elf_path
fi
set +x

需要注意的是 patchelf 可能会有 Bug,所以使用从 Github 上的代码进行编译得到的 patchelf 能避开一些 Bug。

使用 LIEF

在看 LIEF 文档时发现了一篇老外的博文,讲的是通过 LIEF 来使用不是系统自带的 libc。

在 gdb 中加载 debug 文件/符号表

如果只是直接 Patch 了 ELF 文件的 ld 和 libc,而没有放置符号表的话,那么在使用 GDB 调试的时候就不容易看到 libc 中的各种结构。所以我们需要加载符号表来方便调试。

Ubuntu 的软件维护者在编译对应的 ELF 文件时,会将符号表与 ELF 文件分离,将符号表命名为 “*-dbg.deb”。这样我们就可以通过手动下载符号表来方便调试。

如果是使用 “glibc all in one” 下载的 libc,会在 libc 等库放置的位置使用 “.debug” 文件夹存放好了 libc 的符号表,使用 gdb 可以自动加载。但是碰到 “glibc all in one” 没有的 libc 版本时就需要手动下载 debug 文件,并手动加载了。

将 debug 文件放入 “.debug” 文件夹

在放置 libc 的目录下新建 ".debug"文件夹,将 debug 文件放入其中即可。原理见参考文章 2,glibc 的官方文档。

通过 gdb 命令 set debug-file-directory directories

在参考文章1可以看到关于 gdb 如何加载分离的 debug 文件,我们只要将 libc 等库的 debug 文件放入对应文件夹,并通过 set debug-file-directory $directories 命令将文件夹设为分离的 debug 文件目录,就可以让 gdb 加载 debug 文件。

通过 gdb python api 的 add_separate_debug_file() 加载

gdb 的 python api 中有一个Objfile.add_separate_debug_file (file)来让对应的二进制文件加载分离的 debug file,这里我将其封装成了命令,在程序运行起来后通过修改下面的命令即可加载。

1
2
3
4
pwndbg> source '/path/to/loadsym.py'
pwndbg> loadsym '/path/to/usr/lib/debug/lib/x86_64-linux-gnu/libc-2.27.so'
[*] symbol file path: /path/to/usr/lib/debug/lib/x86_64-linux-gnu/libc-2.27.so
[+] load debug file success!

这是 loadsym.py 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import gdb


class loadsym(gdb.Command):
"""
load symbol file to glibc
Usage: loadsym {symbol file}
Example:
(gdb) loadsym '/path/to/usr/lib/debug/lib/x86_64-linux-gnu/libc-2.27.so'
"""

def __init__(self):
'''
register command in constructer function
'''

super(self.__class__, self).__init__("loadsym", gdb.COMMAND_USER)

def invoke(self, args, from_tty):
'''
in invoke method, we add command's features
'''

# using string_to_argv to convert args to list
argv = gdb.string_to_argv(args)
if len(argv) != 1:
raise gdb.GdbError(
'Fail to execute command, use "help loadsym" for help')
print('[*] symbol file path: {}'.format(argv[0]))

# traverse objfiles to find libc
for i in gdb.objfiles():
if 'libc' in i.filename:
self.add_debug_file(i, argv[0])
return
print('[-] fail to find libc!')

def add_debug_file(self, objfile, debugfile_path):
'''
add debug file and check debug file's status
'''

objfile.add_separate_debug_file(debugfile_path)
# check symbol file is loading
if gdb.lookup_symbol('main_arena') == None:
print('[-] load debug file fail!')
return False
else:
print('[+] load debug file success!')
return True

if __name__ == "__main__":
loadsym()

参考文章

https://wiki.ubuntu.com/Debug%20Symbol%20Packages

https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html

http://man7.org/linux/man-pages/man8/ld.so.8.html

《0day安全 软件漏洞分析技术(第二版)》第三次再版印刷预售开始!

以上全文转载自关于不同版本 glibc 更换的一些问题

咖啡,亦我所需也