各种 house of

House of Orange

概述

top chunk进行利用,在没有free存在的情况下创造freeunsortedbin中的空闲块,从而得到unsortedbin地址,在进行后续操作即可获得main_arena地址等

原理

当当前堆的top chunk尺寸不足以满足申请的分配大小的时候,原来的top chunk会被释放并且放入unsortedbin中,接着再映射或者扩展一个新top chunk出来

glibc2.23:

当其他bin都满足不了,topchunk也无法满足时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  /* Record incoming configuration of top */

old_top = av->top;
old_size = chunksize (old_top);
old_end = (char *) (chunk_at_offset (old_top, old_size));

brk = snd_brk = (char *) (MORECORE_FAILURE);

/*
If not the first time through, we require old_size to be
at least MINSIZE and to have prev_inuse set.
*/
//下面都是需要通过的检查
assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));

/* Precondition: not enough current space to satisfy nb request */
assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));

条件:

  • 申请的size不能大于mmap分配阈值,默认128k
  • 伪造size对齐的内存页
  • 伪造size要大于MINSIZE(0x10)
  • 伪造size要小于申请的chunk size+MINSIZE(0x10)
  • 伪造size的prev inuse为必须为1

过程:

  • 修改top chunk的size
  • 申请一个大于top chunk的size
    • scanf函数会申请一个0x1000的块,如果调用scanf的话就不用我们自己申请了,scanf调用完top chunk就会进入unsortedbin

House of Einherjar

概述

利用堆块的向后合并机制强制使得malloc返回一个几乎任意地址的chunk

原理

向后合并

1
2
3
4
5
6
7
8
/* consolidate backward */
if (!prev_inuse(p))
{
prevsize = p->prev_size;
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}

要有溢出的漏洞,比如off-by-one等

过程:(设有三堆块分别是0,1,2。2块用来隔开top chunk,还有A块为目标地址)

  • 利用1块漏洞修改2块pre_inuse为0
  • 利用1块漏洞修改2块pre_size为A块和2块之间距离,让2块释放时去找A块,也就是利用第6行的代码
  • 在A块附近构造数据,以通过unlink检查
  • A块size位为chunksize(A)

unlink检查:(2.26)

1
2
3
4
5
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      \
malloc_printerr (check_action, "corrupted size vs. prev_size", P, AV);
.....
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV);
  • A块下一个块的prev_size字段要=A块大小,就是 &(A+chunksize(A))=chunksize(A)
  • A块的fd,bk都指向A块的地址

2.23只有第二个检查

  • 最后unlink将其放入了unsortedbin后需要将fd,bk改写为main_arena+88

最后malloc就得到目标地址的堆块了

House of Force

概述

利用篡改top chunk的size,来实现任意地址分配

2.23-2.27

原理

top chunk的分割机制(2.23)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
victim = av->top;//获取top chunk
size = chunksize (victim);//获取top的大小

if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))//top chunk足够大
{
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;//返回分配的内存地址
}

要可以控制top chunk的size域

要可以分配任意大小的堆块

过程:

  • 首先溢出将top chunk的size变成-1,因为比较时会把size看成无符号数,这样-1就是一个极大数,可以通过所有检查
  • 然后分配一块空间,利用这块空间将top chunk推到目标地址附近,如果要推到高地址,就malloc(+),推到低地址就malloc(-),这块空间大小为malloc_size = target_addr - top_chunk_addr - 2*sizeof(size_t);
  • 然后再malloc一次就可以得到目标地址的堆块了

House of Lore

概述

利用篡改smallbins中free的块的bk来实现任意地址分配

<=2.26可以使用

2.27加入tcache,需要手动填满

原理

smallbins中

  • 原理bins的堆块是更前面的堆块,所以fd指向远离bins的
  • 靠近bins的堆块是更后面的堆块,所以bk指向靠近bins的

smallbins的分配中

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
 if (in_smallbin_range (nb))//检查是否属于smallbin范围内
{
idx = smallbin_index (nb);//计算smallbin索引
bin = bin_at (av, idx);//获取smallbin指针

if ((victim = last (bin)) != bin)//尝试从smallbin中分配内存
{
if (victim == 0) /* initialization check(初始化检查) */
malloc_consolidate (av);//如果分配区还没初始化就初始化,这是初始化函数
else
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))//检查双向链表完整性
{
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
set_inuse_bit_at_offset (victim, nb);//标记内存块已使用和设置内存块大小
//接下来两行是更新双向链表指针
bin->bk = bck;
bck->fd = bin;

if (av != &main_arena)//如果不属于主分配区,则进入if,标记为非主分配区
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb);//检查内存块
void *p = chunk2mem (victim);//转换
alloc_perturb (p, bytes);
return p;
}
}
}

主要利用

1
2
3
4
5
6
7
8
9
10
             bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))//检查双向链表完整性
{
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
set_inuse_bit_at_offset (victim, nb);//标记内存块已使用和设置内存块大小
//接下来两行是更新双向链表指针
bin->bk = bck;
bck->fd = bin;

要可以修改 smallbin 中 chunk 的 bk 和 fake_chunk 的fd

过程

  • 修改 smallbins 中的 chunk 的 bk为 &fake_chunk
  • 修改f ake_chunk 的fd为 smallbins 中的 chunk 地址来通过检查
  • 还需要另一个 fake_chunk 来使第二次分配时通过检查
    • 需要伪造 fake_chunk->bk=&fake_chunk2
    • 需要 fake_chunk2->fd=&fake_chunk
  • 分配两次得到 fake_chunk

House of Rabbit

概述

利用 fastbin 和 malloc_consolidate 机制实现任意地址分配或读写

原理

利用 malloc_consolidate 的时候对 fastbin 中的堆块的 size 没有检查从而可以伪造一个 fake_chunk

  • 可以修改 fastbin 的 fd 或 size

  • 可以触发 malloc consolidate

利用过程

  • 修改size
    • 分配两个 chunk,fastbin 范围内,再来一个隔开 top chunk
    • free 前两个 chunk
    • 修改第一个 chunksize 为 0xa1
    • 接着 malloc(0x1000) 触发 malloc_consolidate
    • 然后这两个快就被分别放到了对应的 smallbin 中
  • 修改 fd
    • 分配1个 chunk ,fastbin范围内,再来一个隔开top
    • free 掉第一个
    • 修改第一个 fd 为 fake_chunk
    • 接着 malloc(0x1000) 触发 malloc_consolidate
    • 然后这个fake_chunk就合法了,可以对其进行其他操作

House of Roman

概述

利用fastbins attack和unsortedbin attack来 绕过PIE

要有溢出写和UAF类似的漏洞

2.23-2.29

原理

  1. 分配四个chunk(A,B,C,D),chunkB大小为0x70
  2. free(B),B进入fastbin[0x70]
  3. 利用A来将B->size修改为unsortedbin范围内
  4. 再次释放B,B进入unsortedbin,此时fd,bk都是main_arena+88,修改fd为_malloc_hook-0x23
  5. 利用A再次修改B的size,使其符合fastbin[0x70]
  6. 接着利用unsortedbin attack将_malloc_hook处写成main_arena+88
  7. 然后利用部分写将该地址改成one_gadgets或system完成攻击

House of banana

条件

  • 程序调用exit函数

  • 程序通过libc_start_main启动的主函数,且主函数可以结束

  • glibc-2.23及以上

  • 程序能够实现largebinattach

原理

程序退出时会有以下调用,也就是说如果可以控制 array[i] 为ogg,就可以getshell

1
rtld_global -> _ns_loaded -> link_map -> ((fini_t) array[i]) ()

而 rtld_global

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
struct rtld_global
{
#endif
/* Don't change the order of the following elements. 'dl_loaded'
must remain the first element. Forever. */

/* Non-shared code has no support for multiple namespaces. */
#ifdef SHARED
# define DL_NNS 16
#else
# define DL_NNS 1
#endif
EXTERN struct link_namespaces
{
/* A pointer to the map for the main map. */
struct link_map *_ns_loaded;//指向link_map结构体指针
/* Number of object in the _dl_loaded list. */
unsigned int _ns_nloaded;
/* Direct pointer to the searchlist of the main object. */
struct r_scope_elem *_ns_main_searchlist;
/* This is zero at program start to signal that the global scope map is
allocated by rtld. Later it keeps the size of the map. It might be
reset if in _dl_close if the last global object is removed. */
unsigned int _ns_global_scope_alloc;

/* During dlopen, this is the number of objects that still need to
be added to the global scope map. It has to be taken into
account when resizing the map, for future map additions after
recursive dlopen calls from ELF constructors. */
unsigned int _ns_global_scope_pending_adds;

/* Once libc.so has been loaded into the namespace, this points to
its link map. */
struct link_map *libc_map;

/* Search table for unique objects. */
struct unique_sym_table
{
__rtld_lock_define_recursive (, lock)
struct unique_sym
{
uint32_t hashval;
const char *name;
const ElfW(Sym) *sym;
const struct link_map *map;
} *entries;
size_t size;
size_t n_elements;
void (*free) (void *);
} _ns_unique_sym_table;
/* Keep track of changes to each namespace' list. */
struct r_debug _ns_debug;
} _dl_ns[DL_NNS];
/* One higher than index of last used namespace. */
EXTERN size_t _dl_nns;
.................................................................................
};

该结构体里有多个 _dl_ns 结构体,存储的是 elf 各段的符号结构体

1
2
3
4
5
6
7
pwndbg> p _rtld_global
$1 = {
_dl_ns = {{
_ns_loaded = 0x7ffff7ffe2e0, //link_map 头指针
_ns_nloaded = 4, //有几个
_ns_main_searchlist = 0x7ffff7ffe5a0,

而 link_map 在 gdb 里

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
pwndbg> p *(struct link_map *) 0x7ffff7ffe2e0
$2 = {
l_addr = 93824992231424,
l_name = 0x7ffff7ffe888 "",
l_ld = 0x555555557dc8,
l_next = 0x7ffff7ffe890,
l_prev = 0x0,
l_real = 0x7ffff7ffe2e0,
l_ns = 0,
l_libname = 0x7ffff7ffe870,
l_info = {0x0, 0x555555557dc8, 0x555555557ea8, 0x555555557e98, 0x0, 0x555555557e48, 0x555555557e58, 0x555555557ed8, 0x555555557ee8, 0x555555557ef8, 0x555555557e68, 0x555555557e78, 0x555555557dd8, 0x555555557de8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x555555557eb8, 0x555555557e88, 0x0, 0x555555557ec8, 0x555555557f18, 0x555555557df8, 0x555555557e18, 0x555555557e08, 0x555555557e28, 0x0, 0x555555557f08, 0x0, 0x0, 0x0, 0x0, 0x555555557f38, 0x555555557f28, 0x0, 0x0, 0x555555557f18, 0x0, 0x555555557f58, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x555555557f48, 0x0 <repeats 25 times>, 0x555555557e38},
l_phdr = 0x555555554040,
l_entry = 93824992235616,
l_phnum = 13,
l_ldnum = 0,
l_searchlist = {
r_list = 0x7ffff7fbb690,
r_nlist = 3
},
l_symbolic_searchlist = {
r_list = 0x7ffff7ffe868,
r_nlist = 0
},
l_loader = 0x0,
l_versions = 0x7ffff7fbb6b0,
l_nversions = 4,
l_nbuckets = 2,
l_gnu_bitmask_idxbits = 0,
l_gnu_shift = 6,
l_gnu_bitmask = 0x5555555543c0,
{
l_gnu_buckets = 0x5555555543c8,
l_chain = 0x5555555543c8
},
{
l_gnu_chain_zero = 0x5555555543b8,
l_buckets = 0x5555555543b8
},
l_direct_opencount = 1,
l_type = lt_executable,
l_relocated = 1,
l_init_called = 1,
l_global = 1,
l_reserved = 0,
l_main_map = 0,
l_visited = 1,
l_map_used = 0,
l_map_done = 0,
l_phdr_allocated = 0,
l_soname_added = 0,
l_faked = 0,
l_need_tls_init = 0,
l_auditing = 0,
l_audit_any_plt = 0,
l_removed = 0,
l_contiguous = 1,
l_symbolic_in_local_scope = 0,
l_free_initfini = 0,
l_ld_readonly = 0,
l_find_object_processed = 0,
l_nodelete_active = false,
l_nodelete_pending = false,
l_property = lc_property_valid,
l_x86_feature_1_and = 3,
l_x86_isa_1_needed = 1,
l_1_needed = 0,
l_rpath_dirs = {
dirs = 0xffffffffffffffff,
malloced = 0
},
l_reloc_result = 0x0,
l_versyms = 0x555555554510,
l_origin = 0x0,
l_map_start = 93824992231424,
l_map_end = 93824992247832,
l_text_end = 93824992235897,
l_scope_mem = {0x7ffff7ffe5a0, 0x0, 0x0, 0x0},
l_scope_max = 4,
l_scope = 0x7ffff7ffe650,
l_local_scope = {0x7ffff7ffe5a0, 0x0},
l_file_id = {
dev = 0,
ino = 0
},
l_runpath_dirs = {
dirs = 0xffffffffffffffff,
malloced = 0
},
l_initfini = 0x7ffff7fbb670,
l_reldeps = 0x0,
l_reldepsmax = 0,
l_used = 1,
l_feature_1 = 0,
l_flags_1 = 134217729,
l_flags = 8,
l_idx = 0,
l_mach = {
plt = 0,
gotplt = 0,
tlsdesc_table = 0x0
},
l_lookup_cache = {
sym = 0x555555554420,
type_class = 1,
value = 0x7ffff7fbb160,
ret = 0x7ffff7c15cc0
},
l_tls_initimage = 0x0,
l_tls_initimage_size = 0,
l_tls_blocksize = 0,
l_tls_align = 0,
l_tls_firstbyte_offset = 0,
l_tls_offset = 0,
l_tls_modid = 0,
l_tls_dtor_count = 0,
l_relro_addr = 15800,
l_relro_size = 584,
l_serial = 0
}

当执行到 _dl_fini 函数时

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
void
_dl_fini (void)
{
...
struct link_map *maps[nloaded];

unsigned int i;
struct link_map *l;
assert (nloaded != 0 || GL(dl_ns)[ns]._ns_loaded == NULL);
for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next)
/* Do not handle ld.so in secondary namespaces. */
if (l == l->l_real) //检查1,需要伪造
{
assert (i < nloaded);

maps[i] = l;
l->l_idx = i;
++i;

/* Bump l_direct_opencount of all objects so that they
are not dlclose()ed from underneath us. */
++l->l_direct_opencount;
}
assert (ns != LM_ID_BASE || i == nloaded);
assert (ns == LM_ID_BASE || i == nloaded || i == nloaded - 1);
unsigned int nmaps = i;

_dl_sort_maps (maps + (ns == LM_ID_BASE), nmaps - (ns == LM_ID_BASE),
NULL, true);

__rtld_lock_unlock_recursive (GL(dl_load_lock));56

for (i = 0; i < nmaps; ++i)
{
struct link_map *l = maps[i];

if (l->l_init_called) //检查2
{
l->l_init_called = 0;

/* Is there a destructor function? */
if (l->l_info[DT_FINI_ARRAY] != NULL
|| (ELF_INITFINI && l->l_info[DT_FINI] != NULL))
{
/* When debugging print a message first. */
if (__builtin_expect (GLRO(dl_debug_mask)
& DL_DEBUG_IMPCALLS, 0))//检查3
_dl_debug_printf ("\ncalling fini: %s [%lu]\n\n",
DSO_FILENAME (l->l_name),
ns);

/* First see whether an array is given. */
if (l->l_info[DT_FINI_ARRAY] != NULL)//检查4
{
ElfW(Addr) *array =
(ElfW(Addr) *) (l->l_addr
+ l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
/ sizeof (ElfW(Addr)));
while (i-- > 0)
((fini_t) array[i]) (); //这里就是最后要执行的
}

....
}

这里的 l 指的就是 link_map 结构体

控制 array 的

1
2
3
ElfW(Addr) *array =
(ElfW(Addr) *) (l->l_addr
+ l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);

DT_FINI_ARRAY 是宏,为 26

而 d_un 是一个联合体

1
2
3
4
5
union
{
Elf32_Word d_val; /* Integer value */
Elf32_Addr d_ptr; /* Address value */
} d_un;

所以我们如果将 l->l_info[26] 设为 l->l_info[26] 的地址 ,l->l_info[27] 就是 array 的的值

控制 i 的

1
2
unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
/ sizeof (ElfW(Addr)));

DT_FINI_ARRAYSZ 宏,是 28

同理,如果这里将 l->l_info[28] 设为其地址,那么 i=l->l_[29]/8

这样同时控制了 array 和 i ,就可以在后面的流程里控制程序流了

参考

House-of-Banana攻击 - 能打八个攻城狮

house of banana-安全KER - 安全资讯平台

关于house of banana的学习总结 | ZIKH26’s Blog


各种 house of
http://yyyffff.github.io/2025/06/07/各种 house of/
作者
yyyffff
发布于
2025年6月7日
许可协议