x86架构下pwn题目libc版本概述 Surager

x86架构下pwn题目libc版本概述

鉴于最近关于 libc 的讨论又开始兴起,在此稍作总结。

关于什么是 libc :GNU C库

libc版本

如何查看

一种方法是直接运行 libc。

$ /lib/x86_64-linux-gnu/libc-2.27.so
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.4) stable release version 2.27.
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 7.5.0.
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.

另一种是 strings 加 grep

$ strings /lib/x86_64-linux-gnu/libc-2.27.so | grep GNU
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.4) stable release version 2.27.
Compiled by GNU CC version 7.5.0.

大版本

在一般情况下,主流的 ubuntu 运行的 libc 大版本如下:(截至2021-05-13)

ubuntu版本libc版本
ubuntu 16.04 LTS2.23-0ubuntu
ubuntu 18.04 LTS2.27-3ubuntu
ubuntu 20.04 LTS2.31-0ubuntu

小版本

pwn 题目的搭建一般使用 docker。大多数出题人会在 Dockerfile 里面这么写:

RUN apt-get update && apt-get -y dist-upgrade

此外还可能安装一些 xinetd 之类的东西。这些操作都有可能将 libc 升级到目前大版本的最高小版本:

例如,截至 2021-05-13 版本如下:

ubuntu16.04 : 2.23-0ubuntu11.2

ubuntu18.04: 2.27-3ubuntu1.4

ubuntu20.04: 2.31-0ubuntu9.2

因此,常用版本一般是这三个。

我们可以在以下几个网站获取二进制文件:

https://launchpad.net/ubuntu

https://pkgs.org/download/libc6

也可以获取源码:

https://ftp.gnu.org/gnu/glibc/

下面对它们的特点进行概述:

2.23-0ubuntu11.2

因为 ubuntu 16.04 已经于 2021-04-30 停止支持,此 libc 成为 ubuntu16 libc-2.23.so 的最后一个版本,也是 ubuntu系统 heap 中没有 tcache 的最后一个版本。可利用的漏洞较后两个版本来说更多一些。

2.27-3ubuntu1.4

加入了 tcache。由于 tcache double free 的严重性,2.27-3ubuntu1.3 中加入了 key 机制。本版本保留了 2.27-3ubuntu1.3 加入的 key 机制。

所以想体验原生 tcache double free 的同学可以找到 1.2(版本名称 ubuntu 后面的数字,下同) 以及之前的 libc 进行体验。

这里给出一种不怎么好但是简单的解决方案:

sudo apt install libc6=2.27-3ubuntu1.2
sudo apt install libc-dev-bin=2.27-3ubuntu1.2
sudo apt install libc6-dev=2.27-3ubuntu1.2
sudo apt install libc6-dbg=2.27-3ubuntu1.2

小tips:buuoj 的 ubuntu18 靶机中使用的 libc 版本是 2.27-3ubuntu1

另外,2.27 大版本下可能用到的其他小版本,有2.27-3ubuntu12.27-3ubuntu1.22.27-3ubuntu1.3

2.31-0ubuntu9.2

在 tcache_entry 中 加入了 key。对 unsorted bin 检查巨多。极大地限制了堆块的利用。另外还能在 gcc 编译选项里面加入 cet 或者 🐏️ switch。很蛋疼。

cet(ida 7.5以下版本 PLT 解析失败)解决指路:M4tsuri/ida_fxxk_cet

另外,2.31 大版本下可能用到的其他小版本,有2.31-0ubuntu92.31-0ubuntu9.1

所以,需要准备的 libc 并不多,准备个 8 个左右就够应付比赛了。

libc加载

当一个动态链接的程序运行起来之后,libc 就会通过链接器 (ld) 加载到内存中,此时用户编写的二进制程序便能够调用其中的函数。在本地调试 pwn 题目时,默认加载的 libc 是本地的 libc。32 位 libc 目录为: /lib/i386-linux-gnu/libc.so.6 或者 /lib32/libc.so.6 ,64 位 libc 目录为 /lib/x86_64-linux-gnu/libc.so.6。而 libc.so.6 是一个指向 ./libc-xxx.so 的一个软链接。

lrwxrwxrwx 1 root root   12 Dec  8 00:38 libc.so.6 -> libc-2.27.so

所以

即使本地 exp 中写了

libc = ELF("./libc-xxx.so")

也是默认使用本机的 libc。验证如下(使用 libc = ELF("./libc-xxx.so") 写脚本,之后进行 gdb attach):

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    ...
    0x562ccc0dc000     0x562ccc0fd000 rw-p    21000 0      [heap]
    0x7f6ee40a3000     0x7f6ee428a000 r-xp   1e7000 0      /lib/x86_64-linux-gnu/libc-2.27.so
    0x7f6ee428a000     0x7f6ee448a000 ---p   200000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7f6ee448a000     0x7f6ee448e000 r--p     4000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7f6ee448e000     0x7f6ee4490000 rw-p     2000 1eb000 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7f6ee4490000     0x7f6ee4494000 rw-p     4000 0
    0x7f6ee4494000     0x7f6ee44bd000 r-xp    29000 0      /lib/x86_64-linux-gnu/ld-2.27.so
    0x7f6ee46a0000     0x7f6ee46a2000 rw-p     2000 0
    0x7f6ee46bd000     0x7f6ee46be000 r--p     1000 29000  /lib/x86_64-linux-gnu/ld-2.27.so
    0x7f6ee46be000     0x7f6ee46bf000 rw-p     1000 2a000  /lib/x86_64-linux-gnu/ld-2.27.so
    ...

原因一是 ELF 只是获取了目标 libc 的信息,并未进行加载。再者如下:

$ ldd pwn
	linux-vdso.so.1 (0x00007ffe7cf8a000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f74a57b5000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f74a5daa000)

libc 和 ld 的路径已经默认写死在程序中了。

除非使用 ld 来运行程序

p = process(['./ld-xxx.so','./pwn'],env={"LD_PRELOAD":"./libc-xxx.so"})

但是笔者和 某dai 亲测 ld-2.23.so 并不好使。原因未知,推测是 2.23 版本有问题。

或者使用 patchelf 来将写死的 libc 换成目标 libc,这里网上教程很多,不再赘述。

(未解决)如何判断远程 libc

如果不给libc,全靠猜。

猜可以根据

  1. 泄露一个 libc 地址,然后查一下
  2. double free 一下看报错
  3. chunk 的个数限制
  4. 题目的难度和白给程度
  5. seccomp 设了多少
  6. 搁这挨个试