今天发现一篇好文章,转载收藏一下。
以下全文转载自关于不同版本 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-database 和https://github.com/matrix1001/glibc-all-in-one ,前者不会下载符号表,而后者会将符号表存入对应 libc 的 “.debug” 文件夹中。
让程序强行加载特定版本 glibc
通过 LD_LIBRARY_PATH 或者 LD_PRELOAD
因为 ld.so 和 libc.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 文件的 ldd
和 file
结果与下面类似,可以看到 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.so 和 ld.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 -xlibc_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 ''' 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 ])) 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) 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 更换的一些问题