首页
论坛
课程
招聘
雪    币: 66
活跃值: 活跃值 (27)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝

[原创]关于不同版本 glibc 更换的一些问题

2019-10-4 18:24 11735

[原创]关于不同版本 glibc 更换的一些问题

2019-10-4 18:24
11735

关于不同版本 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.so 和 libc.so 不匹配的原因,所以直接设置 LD_PRELOAD 可能会炸,就如下所示...

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

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

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

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

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。

$ 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.so 和 ld.so。根据上面 ldd 的结果,可以知道 ELF 中的 libc 的路径为 "libc.so.6",所以替换 libc 时所使用的第 2 个参数为 "libc.so.6"

$ 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 都修改成功了。

$ 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。

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,这里我将其封装成了命令,在程序运行起来后通过修改下面的命令即可加载。

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 文件

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



[看雪官方培训]《安卓高级研修班(网课)》9月班开始招生!顶尖技术、挑战极限、工资翻倍!

最后于 2020-3-19 01:45 被富强民主和谐编辑 ,原因: 丰富细节,增加说明
最新回复 (3)
雪    币: 0
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jackeyQ 活跃值 2019-12-25 14:45
2
0
这么好的文章居然没人留言。。。
雪    币: 0
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jackeyQ 活跃值 2019-12-25 14:48
3
0
推荐个工具是pwn_debug  https://github.com/ray-cp/pwn_debug
雪    币: 237
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
留恋恋恋恋 活跃值 2020-3-18 21:19
4
0
好文章,支持一下!
游客
登录 | 注册 方可回帖
返回