glibc 2.26开始, 引入了 tcache 技术. 使得对 UAF 漏洞的利用非常困难. (更: 听 M4x 师傅说其实安全性下降了? thread local caching in glibc malloc)

然后 Arch 的 glibc 已经到了 2.27…. 再加上 pwndbg 的作者与堆相关的一系列命令只对 Ubuntu 做了适配…

pwn 的世界对 Archer 真是充满了恶意 = =

放弃 Arch 是不可能放弃的, 这辈子都不可能放弃的. 所幸有大佬提出, 使用带Debug Symbols的glibc可以成功运行heap等命令, 试了一下果然如此. 可是把默认的 glibc 换成带调试符号的总觉得不好. 而且这样也没有解决 tcache 的问题.

一番尝试之后, 我得到了如下的解决方案, 记录一下以免忘记. 因为已经忘了一次

编译glibc-2.23

第一步

获取 glibc-2.23

/tmp> wget https://ftp.gnu.org/gnu/glibc/glibc-2.23.tar.xz /tmp> x glibc-2.23.tar.xz

第二步

修改源码, 防止一些奇怪的问题 glibc: add patch fixing the build with binutils 2.29

简要地讲就是把 misc/regexp.c 中的

1
2
3
4
5
char *loc1;
char *loc2;
...
...
char *loc;

换成

1
2
3
4
5
char *loc1 __attribute__ ((nocommon));
char *loc2 __attribute__ ((nocommon));
...
...
char *locs __attribute__ ((nocommon));

PS. 原链接中还多了一个 compat_symbol, 但是我没加也编译过了….就不管了

第三步

建立编译文件夹

/tmp>md glibc-build-32 /tmp>md glibc-build-64

然后随便cd进一个

32位使用如下命令配置

1
../glibc-2.23/configure --disable-werror --prefix=/home/aloxaf/.local/lib/glibc-2.23_x86 --host=i686-linux-gnu --build=i686-linux-gnu CC="gcc -m32" CXX="g++ -m32" CFLAGS="-g -O2 -march=i686 -fno-stack-protector" CXXFLAGS="-g -O2 -march=i686 -fno-stack-protector" --enable-debug=yes

64位使用如下命令配置

1
../glibc-2.23/configure --disable-werror --prefix=/home/aloxaf/.local/lib/glibc-2.23 --enable-debug=yes CFLAGS="-O2 -g" CPPFLAGS="-O2 -g"

Note:

  • --prefix请自行更改, 简要地讲就是你打算把这个glibc装到哪个地方
  • 32位不加 -fno-stack-protector 会出现奇怪的link问题. 反正这个只是用来调试的, 就这样粗暴地解决吧.

配置完以后分别 make -j4 && make install 就安装完成了

配置运行环境

大多数 elf 编译的时候都写死了ld的路径 /usr/lib/ld.so, 这就比较尴尬. 就算强行让 2.26 的 ld.so 加载 2.23 的libc, 也指不定会出啥奇怪问题.

幸运的是看雪大佬已经给出了方案: 关于不同版本glibc强行加载的方法(附上代码) 虽然很暴力, 但是有效啊.

不过每次都把ld.so复制到当前文件夹下 && 使用 LD_PRELOAD 指定libc好麻烦的样子, 于是结合自己的经验, 修改了一下脚本.

 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
54
55
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# https://bbs.pediy.com/thread-225849.htm

from tempfile import gettempprefix
from pwn import *
import os
import sys


def change_ld(binary: str)->str:
    """
    更改ELF文件加载的libc版本
    :param binary: ELF路径
    :param ld:   ld.so路径
    :param libc: lib.so路径
    :return: 新的ELF文件路径
    """

    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()

            ld = '/opt/ctf/ld.so' if binary.arch == 'amd64' else '/opt/ctf/ld32.so'

            if size <= len(ld):
                raise Exception(
                    "Failed to change PT_INTERP from {} to {}".format(data, ld))
                return None

            binary.write(addr, ld.ljust(size, '\0').encode())

            if not os.access('/tmp/easypwn', os.F_OK):
                os.mkdir('/tmp/easypwn')
            path = '/tmp/easypwn/{}_debug'.format(
                os.path.basename(binary.path))

            if os.access(path, os.F_OK):
                os.remove(path)
                # print("Removing exist file {}".format(path))
            binary.save(path)
            os.chmod(path, 0b111000000)  # rwx------

    # print("PT_INTERP has changed from {} to {}. Using temp file {}".format(data, ld, path))
    return ELF(path)


if __name__ == '__main__':
    change_ld(sys.argv[1]).save(sys.argv[2])
    os.chmod(sys.argv[2], 0b111000000)

其中 /opt/ctf/ld.so/home/aloxaf/.local/lib/glibc-2.23/lib/ld-2.23.so 的硬链接, 32位版以此类推.

建立硬链接主要是为了缩短路径长度, 保证能够替换掉原来的路径.

然后给这个.py加个硬链接到 ~/.local/bin, 就可以直接 change_ld test test-debug 让这个 elf 默认加载我们自己编译的glibc了.

愉快地Debug 吧少年, Arch Linux 大法好折腾