Pwn题目本地调试加载libc版本 Surager

接上文

上文,我有幸和Blue-Whale的前辈们参加XCTF高校战“疫”网络安全分享赛。赛后回顾学习,用AiDai的exp本地调easyheap的时候出现了一点小插曲,让我陷入了深深的沉思。

原因在于题目使用的libc为2.23,而我的ubuntu18.04使用的libc为2.27。因此申请fastbin的时候变成了tcache,导致fd的指向不同。俺还得把padding去掉才能打得通。我想不能还原比赛环境有点遗憾,于是就搞了搞libc加载的配置,记录踩坑。

原理

ELF文件在生成之后会把动态链接器和libc写死到ELF文件中。我们只要把ld改掉就可以将ELF文件链接到其他的libc,进而加载不同的libc。

坑1——gcc和g++切换

一会儿我们要进行编译glibc,而降版本的glibc最好用降版本的gcc编译,所以要事先配置好gcc和g++。

在ubuntu18.04上加上16的镜像源。sudo vim /etc/apt/source.list。配置ubuntu16.04的时候也得放这个源,不然就会被直接更新到18.04.

deb http://mirrors.aliyun.com/ubuntu/ xenial main
deb http://mirrors.aliyun.com/ubuntu/ xenial universe

然后sudo apt update。接下来安装gcc-4和gcc-5

sudo apt-get install gcc-4.8
sudo apt-get install gcc-4.8-multilib
sudo apt-get install g++-4.8
sudo apt-get install g++-4.8-multilib
sudo apt-get install gcc-5
sudo apt-get install gcc-5-multilib
sudo apt-get install g++-5
sudo apt-get install g++-5-multilib

然后加到update-alternatives中:

sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 40 --slave /usr/bin/g++ g++ /usr/bin/g++-4.8
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 50 --slave /usr/bin/g++ g++ /usr/bin/g++-5
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 70 --slave /usr/bin/g++ g++ /usr/bin/g++-7

用这个命令切换gcc版本。

sudo update-alternatives --config gcc

坑2——下载编译个版本glibc

glibc-all-in-one里面有各个版本的glibc,可以用一下。

git clone https://github.com/matrix1001/glibc-all-in-one

cd glibc-all-in-one

chmod 777 build download extract

sudo ./build 2.29 amd64     #编译glibc
sudo ./build 2.27 amd64
sudo ./build 2.23 amd64

后来发现2.23不能正常编译,俺也不知道啥原因。换了gcc也不行,只好手动编译。如果可以编译就直接忽略。

tar -zxvf glibc-2.23.tar.gz
cd glibc-2.23
mkdir build
cd build
sudo ../configure --profix=/glibc/2.23
sudo make
sudo make install

2.27和2.29的ld和libc在/glibc/2.2?/amd64中,2.23在/glibc/2.23/lib/中。

指定libc

两种方法。一种方法是用patchelf改变elf的ld链接器和libc加载。另一种是跑了个脚本,其实原理都差不多。

先把libc和ld链接器cp到pwn题目录,用绝对路径不嫌麻烦也行。

patchelf:

patchelf --set-interpreter ./ld.so.2 ./elfname
patchelf --set-rpath ./libc-2.2?.so ./elfname

脚本:

#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)
elf = change_ld('./easyheapx', 'ld-2.23.so')

gdb调试pwn题

以前我都是gdb.attach(io)之后没加pause,结果脚本直接跑完了gdb显示程序未运行。所以长记性了,调试=下断点+观察,先gdbattach再加一个pause,调试完了在原窗口回车继续跑脚本。