House of Force

原理

Glibc的很多版本中(2.23和2.27),并没有对Top Chunk的Size字段进行完整性检查.

利用

1.先通过溢出到top chunk的size字段,伪造top chunk的大小。
2.然后malloc申请堆块并写入数据,实现任意地址任意写

详述

tip:
1.详述示例参考hacking everyday
2.用抽象的思维看此示例,不用过度纠结比如malloc对齐等细节,着重看如何实现House of Heap

分析程序

1.程序运行前输出了puts地址和heap开始的地址
2.给出菜单
示例
3.选择malloc,申请一个0x18大小的堆块
(实际上分配了0x20大小)
malloc
在pwndbg里,用vis命令查看现在的堆
可以看到由于溢出了7个字节的b和1个字节的\x0a,可以覆盖top chunk的size
heap
4.选择第二个选项,会输出变量target的值
target
x/gx &target指令(或者dp &target等其他指令)查看变量target地址附近的值
可以看到target变量地址比堆的起始地址要小。
而malloc函数只能继续向高地址申请空间,没办法向target变量靠拢。
check

漏洞利用

前置知识

程序执行malloc函数,先从bins中寻找满足需求的堆块.如果没有,再从top chunk中切下一块内存返回给malloc
malloc源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
victim = av->top;//获取当前top chunk的地址
size = chunksize (victim);//计算top chunk的大小

if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
//MINSIZE就是堆块的最小size,32位程序为0x10,64位程序为0x20
//如果top chunk的大小大于nb(程序执行malloc需要分配的内存大小)
//加上MINSIZE的大小,就从top chunk中来切一块内存
//之所以要加上MINSIZE是要保证切割后剩余的内存要是一个完整的堆块
{
remainder_size = size - nb;//remainder_size为切割后的剩余大小
remainder = chunk_at_offset (victim, nb);
//remainder为切割前top chunk+nb的值,也就是切割后top chunk的地址
av->top = remainder;//更新top chunk
//下面两个set_head给切割出去的堆块以及切割后的top chunk设置新的size
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);

check_malloced_chunk (av, victim, nb);//调试用的,这里没用
void *p = chunk2mem (victim);//返回用户指针
alloc_perturb (p, bytes);
return p;
}

1.首先申请0x18的空间,并输入b’a’*24 + b’\xff’*8,由此可以把top chunk的size字段覆盖为’ffffffffffffffff’

问:为什么要填充成0xffffffffffffffff?
答:
源码中有如下代码:

1
2
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
(size是切割前top_chunk的大小,nb是用户申请的大小)

其中

1
2
1.nb = request + chunk_header   
2.由下面的讲解可知:request_size = target_addr - 2 * chunk_header-victim

由于request_size一般为负数,所以nb也大概率为负数.
负数强制转化为无符号数–即求负数的补码–除符号位,原码取反+1.
所以比较式右边很大.
因此要想比较式成立,我们要将top chunk的size改为-1,如此一来,对size强制转化为无符号数之后,会变成0xffffffffffffffff,一定满足size>nb+MINSIZE

问:那为什么不能直接覆盖为-1,而是要覆盖为0xffffffffffffffff?
答:
如下为chunk的源码

1
2
3
4
5
6
7
8
struct malloc_chunk {

INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */

struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
};

其中size的数据类型为无符号整型

2.然后,申请(0xffffffff-0x603000)+0x602010-0x20-0x20大小的chunk

计算分配大小的原理

源码中有如下代码:

1
2
remainder = chunk_at_offset (victim, nb)
//victim(切割前top_chunk的地址) + nb (要申请的内存大小) = top_chunk (切割后top_chunk的地址)

如果我们改变nb的值,就可以让切割后top chunk的地址变为目标地址
其中:

1
2
3
1.nb = request_size + chunk_header    
2.top_chunk + chunk_header = target_addr
(因为是通过往top_chunk的usr data区里输入数据,来达到修改target_addr的效果)

整合得:

request_size = target_addr - 2 * chunk_header-victim         
(request_size为负数) 

tip

1.House of Force可以在tcache存在的libc中使用
2.