切换glibc版本

看到这题目就知道现在说的是个困扰pwn选手多年的问题,这里提供两种强行解决的方案

方法1

TODO:
1. 自行下载glibc 2.23/2.24/2.25/2.26 源码并编译
2. 将所需要使用到版本的ld.so放在pwn题目录下
3. 跑这个代码
引用自看雪论坛 https://bbs.pediy.com/thread-225849.htm

def change_ld(binary, ld):
    """
    Force to use assigned new ld.so by changing the binary
    """
    if not os.access(ld, os.R_OK): 
        log.failure("Invalid path {} to ld".format(ld))
        return None


    if not isinstance(binary, ELF):
        if not os.access(binary, os.R_OK): 
            log.failure("Invalid path {} to binary".format(binary))
            return None
        binary = ELF(binary)


    for segment in binary.segments:
        if segment.header['p_type'] == 'PT_INTERP':
            size = segment.header['p_memsz']
            addr = segment.header['p_paddr']
            data = segment.data()
            if size <= len(ld):
                log.failure("Failed to change PT_INTERP from {} to {}".format(data, ld))
                return None
            binary.write(addr, ld.ljust(size, '\0'))
            if not os.access('/tmp/pwn', os.F_OK): os.mkdir('/tmp/pwn')
            path = '/tmp/pwn/{}_debug'.format(os.path.basename(binary.path))
            if os.access(path, os.F_OK): 
                os.remove(path)
                info("Removing exist file {}".format(path))
            binary.save(path)    
            os.chmod(path, 0b111000000) #rwx------
    success("PT_INTERP has changed from {} to {}. Using temp file {}".format(data, ld, path)) 
    return ELF(path)
#example
elf = change_ld('./pwn', './ld.so')
p = elf.process(env={'LD_PRELOAD':'./libc.so.6'})

用法很简单,参数第一个可以是ELF,可以是路径,第二个必须是ld.so的路径(最好放在当前文件夹,原因后面讲)。返回值我自己方便用所以是ELF,不喜欢的自己改成path,这个不会改变你原来的文件,只是建立一个修改的副本放在/tmp/pwn里面
4. 不用LD_PRELOAD则加载 源码级 libc(调libc=调源码? 存在的) 或者 使用LD_PRELOAD加载给定的libc

原理:
PT_INTERP这是个神秘的东西,这个是编译的时候就硬编码好的东西,值是’/lib/ld-linux.so.2’ ,其实这个就是万恶的ld了,程序加载的时候,首先进去的不是_start处的代码,而是先加载执行PT_INTERP指向的程序,ld在加载后会给程序加载libc,最后再执行程序的代码。
但是在改的时候,是有长度限制的,代码已经写好了,所以将ld.so放当前文件夹就不会出错。
所以说,利用LD_PRELOAD直接加载不同版本的libc,会由于libc和ld匹配不上而出错。

改进

转载自简书的韦师傅。https://www.jianshu.com/p/ee1ad4044ef7
做pwn题的时候经常会遇到不同的libc, 最简单的解决方法就是LD_PRELOAD了, 不过这个方法有一个局限. 就是如果libc的版本差太多的话就没办法使用了. 毕竟LD_PRELOAD只是告诉ld.so该去哪儿加载libc, 可是ld.so也没有办法去加载不同版本的libc
于是我就只能傻乎乎地配了三个ubuntu的pwn环境, 虽然有docker可以用, 可是感觉在虚拟机里面又装一层docker实在有点蠢…. 总之既浪费时间又浪费空间
通过上面的帖子我才发现其实是可以在一个ubuntu中使用多个版本的libc的, 只要修改程序使得其使用对应版本的ld.so即可

elf文件有个段 PT_INTERP, 其中存储了程序使用的ld.so的路径, 默认是这样

切换glibc版本

默认使用 /lib64/ld-linux-x86-64.so.2

我们通过上面链接里面给出的脚本可以修改程序的这个段的内容, 使得其启动的时候使用我们提供的ld.so, 然后我们就可以修改其为特定版本的ld.so从而达到使用不同版本libc的目的了
下面就是修改为2.24版本的ld.so其使用 libc2.24
我们通过上面链接里面给出的脚本可以修改程序的这个段的内容, 使得其启动的时候使用我们提供的ld.so, 然后我们就可以修改其为特定版本的ld.so从而达到使用不同版本libc的目的了
下面就是修改为2.24版本的ld.so其使用 libc2.24

切换glibc版本

我发现如果使用自己编译的libc的话即使不使用LD_PRELOAD它也会找到对应的libc, 而不是默认的 /lib/x86_64-linux-gnu/libc.so.6, 应该是在ld.so里面记录了libc的路径.

切换glibc版本

而且我们还可以下载libc 2.24的源码从而实现源码级调试的目的. (具体操作参考这个SO)

切换glibc版本

贴一下我自己的脚本, 相比原帖修改了2点:

  1. 将修改后的文件就放在当前文件夹而不是/tmp/pwn/下
  2. 修改后的文件添加一个后缀, 表示使用的是哪个版本的ld.so, 因为现在经常有些题目不给libc…. 可能得试好几个
#coding:utf-8
from pwn import *
import os
def change_ld(binary, ld):
    if not os.access(ld, os.R_OK): 
      log.failure("Invalid path {} to ld".format(ld))
      return None
    if not os.access(binary, os.R_OK): 
      log.failure("Invalid path {} to binary".format(binary))
      return None
    binary = ELF(binary)
    path = './{}_{}'.format(os.path.basename(binary.path), ld.split('.')[-2])
    if os.access(path, os.F_OK):
      os.remove(path)
      print("remove exist file.....")
      return ELF(path)
    for segment in binary.segments:
      if segment.header['p_type'] == 'PT_INTERP':
        size = segment.header['p_memsz']
        addr = segment.header['p_paddr']
        data = segment.data()
        if size <= len(ld):
          log.failure("Failed to change PT_INTERP from {} to {}".
            format(data, ld))
          return None
        binary.write(addr, ld.ljust(size, '\x00'))
        break
    binary.save(path)    
    os.chmod(path, 0b111000000) #rwx------
    success("PT_INTERP has changed from {} to {}. Using temp file {}".format(data, ld, path)) 
    return ELF(path)

这个仓库里面有编译好的各个版本的libc.so和ld.so

不过我还是建议自己编译libc, 一个版本也就20分钟不到就编译好了. 之后调试的时候会很方便

这里提供一些安装博客,里面提到的问题绝大多数情况大家都会遇到。
编译 Libc 2.23
在 Arch Linux 下使用 glibc 2.23 调试程序&使用 pwndbg
Pwn题目本地调试加载libc版本
编译带debug_info的glibc
Using a non-system glibc

使用工具

另一种方案是使用patchelf工具的–set-interpreter和–set-rpath命令替换ld.so和libc.so

要是实在弄不好,就直接多安一个虚拟机,或者用docker,省的在这自闭好几天都没进展

本文章首发在 网安wangan.com 网站上。

上一篇 下一篇
讨论数量: 0
只看当前版本


暂无话题~