BetaMao

堆利用

字数统计: 2.7k阅读时长: 11 min
2018/01/10 Share

堆里面涉及到很多指针操作,一出错将很容易危及整个内存区域,堆比较复杂,所以堆漏洞利用方式也十分灵活,这里记录的堆漏洞大致分为两种,一种是通过点射直接修改小块内存,一种是通过将期待控制的区域加入堆管理系统中使分配后可控。。。
2018-2-22更新,玩了20几天了,学习学习学习!!!!!

编译libc

这样能够获得带调试信息的libc,方便调试呀,下载对应版本libc:http://mirrors.ustc.edu.cn/gnu/libc/ 然后开始编译

1
2
3
4
cd glibc && mkdir build && cd build
CFLAGS="-g -g3 -ggdb -gdwarf-4 -Og"
CXXFLAGS="-g -g3 -ggdb -gdwarf-4 -Og"
../configure --prefix=/path/to/install
1
2
3
4
5
cd glibc && mkdir build32 && cd build32
CC="gcc -m32" CXX="g++ -m32" \
CFLAGS="-g -g3 -ggdb -gdwarf-4 -Og" \
CXXFLAGS="-g -g3 -ggdb -gdwarf-4 -Og" \
../configure --prefix=/path/to/install --host=i686-linux-gnu

在自己编译glibc时可能会遇到一些神奇的错误,直接把错误放谷歌上就能找到解决办法,一般都是一些bug,例如我编译2.24时遇到的错误,64位都通过这篇文章解决啦,而32位,忽略警告,将其不作为错误处理即可(目前还没发现潜在错误,逃

fastbin

chunk size <= get_max_fast()的chunk,会被放在一系列称为fastbin的bin里

  • 64bit是128bytes,32bit是64bytes
  • global_max_fast一开始是0

注:单链表先进后出,其他先进先出

安全检查

  1. malloc:chunk size要正确(malloc不检查地址对齐)
  2. free: next chunk的size要对,且检查bin里第一个chunk是不是和将要释放的chunk一样。

use after free

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
#include<cstdio>
#include<cstdlib>
#include<cstring>

class A{
public:
virtual void print(){
puts("class A");
}
}
class B: public A{
public:
void print(){
puts(class B);
}
}
void sh(){
system("/bin/sh");
}
char buf[1024];

int main(){
setvbuf(stdout,0,_IONBF,0);
A *p = new B(); //一个虚表指针
delete p;
fgets(buf,sizeof(buf),stdin);
char *q = strdup(buf); //内部调用malloc
p->print();
}

这里B里存放了虚表指针,将其改为一个地址,那个地址处存放了sh()的地址即可,只是需要注意坏字符。

fastbin corruption

让fastbin linked list指向任意地址,之后malloc时就会将该为地址作为chunk拿出来,这里需要注意的就是指向的任意地址后面必须跟正确的数字表示大小。

double free

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
#include<stdlib.h>
#include<stdio.h>
#include<string.h>

void sh(char *cmd){
system(cmd);
}
int main(){
setvbuf(stdout,0,_IONBF,0);
int cmd,idx,sz;
char* ptr[10];
memset(ptr,0,sizeof(ptr));
puts("1. malloc + gets\n2. free\n3. puts");
while(1){
printf("> ");
scanf("%d %d",&cmd,&idx);
idx %=10;
if(cmd == 1){
scanf("%d%*c",&sz);
ptr[idx] = malloc(sz);
fgets(ptr[idx],sz,stdin);
}else if(cmd == 2){
free(ptr[idx]);
}else if(cmd==3){
puts(ptr[idx]);
}else{
exit(0);
}
}
}

这里就可以使用double free,将指针改为指向GOT的项(为了满足size大小,最好申请0x38byte,这样就能找到0x40的地址了,因为大部分情况,在got未被改写的情况存在0x40,这样+2-8为chunk的地址即可。)

overflow

覆盖掉已经释放的chunk,很像上面那种啦。

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
#include<stdlib.h>
#include<stdio.h>
#include<string.h>

void sh(char *cmd){
system(cmd);
}
int main(){
setvbuf(stdout,0,_IONBF,0);
int cmd,idx,sz;
char* ptr[10];
memset(ptr,0,sizeof(ptr));
puts("1. malloc + gets\n2. free\n3. puts");
while(1){
printf("> ");
scanf("%d %d",&cmd,&idx);
idx %=10;
if(cmd == 1){
scanf("%d%*c",&sz);
ptr[idx] = malloc(sz);
gets(ptr[idx]);
}else if(cmd == 2){
free(ptr[idx]);
ptr[idx]=0;
}else if(cmd==3){
puts(ptr[idx]);
}else{
exit(0);
}
}
}

利用思路:这里没有double free了,但是存在溢出,先申请两个chunk再倒序释放,再申请一同个,这时溢出覆盖到下一个chunk的fd(注意保持size等不改变),就可以在下次申请时获取到指定位置,例如此时申请GOT并且覆写puts,再讲第一个的字符串设置为sh输出第二个即可获取shell。

house of spirit

改掉free的参数,free掉一个想要控制的地方,然后再malloc,这里chunk不能太大也不能太小,地址要对齐,并且会检查下一个chunk的大小,不能太小也不能太大:


总结下条件:

  1. 能够控制free的参数,例如通过缓冲区溢出改掉malloc返回的指针,再触发free。
  2. 伪造一个位置已知的chunk,这个chunk在想要控制的那片内存区域。
  3. 这个chunk的大小要合适并且地址对齐,且下一个chunk的大小也要合适。
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
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>
char buf[128];
char ptr[8];
char* cmd;
int size;
int n = 0;

void sh(char *c){
system(c);
}
int main(){
setvbuf(stdout,0,_IONBF,0);
memset(ptr,0,sizeof(ptr));
cmd = buf;

while(1){
fgets(cmd,sizeof(buf),stdin);
if(!strncmp(cmd,"push",4)){
if(n<8){
scanf("%d%*c",&size);
ptr[n] = malloc(size);
fgets(ptr[n],size,stdin);
n++;
}else{
puts("stack is full");
}
}else if(!strncmp(cmd,"pop",3)){
if(n>=0){ //漏洞处
n--;
puts(ptr[n]);
free(ptr[n]);
ptr[n]=0;
}else{
puts("stack is empty");
}
}else{
puts("unknown command");
}
}
return 0;
}

利用思路:指针下溢,于是buf的最后8字节写入的数据可以被当做地址,这里没有写入,但是可以直接free掉在malloc就可以写入啦,由于要绕过限制,需要buf[120]处写上buf[n]的地址,这样这个伪造的chunk的size位就是可控的,并且下一个chunk的size位也可控,这样就可以改写之间的cmd,接着就可以任意地址写啦。

smallbin

在释放时会合并freed chunks:

其中unlink在郎朗耳钩可以这样用:

1
2
FD = p->fd = free@got.plt - 0x18
BK = p->bk = shellcodeAddr

当时现在的unlink宏如下:

在理为smallbin,会有两个检查:

  1. chunksize(P) == next_chunk(P)->prev_size
  2. FD->bk == P && BK->fd == P

这里先忽略检查一,它是新增的,先看看检查二,正常情况下,对于双链表,两个块之间是会存在那种关系的,但是若随便传入一个P那么这种关系是不成立的,但是,若有满足下面条件的指针,依然能够使以上关系成立并可利用:

  1. 一个指向heap的指针p
  2. 存放该指针的地址可知,即&p可知
  3. 可以对改地址进行多次写入,即可以对p多次写入

例如,这里p是一个全局变量,并且地址已知,p里的内容是一个地址,指向了堆,那么在堆里面构造一个伪造的chunk,此chunk满足:

1
2
p->fd = &p - 0x18
p->bd = &p - 0x10

那么它将绕过检查,因为:

1
2
3
4
5
FD = p->fd = &p - 0x18
BK = fd->bk = &p - 0x10
//则
FD->bk => *(&p-0x18+0x18) == p
BK->fd => *(&p-0x10+0x10) == p

这样在unlink完成以后:

1
2
FD->bk = Bk <===> p = &p - 0x10
BK->fd = FD <===> p = &p - 0x18

这样的结果就是,原本指向heap的chunk现在指向了global段,一种场景,分配两个chunk,前一个覆盖后一个的presize和inuse位(只需要溢出1位),释放后者则会合并前面的,前面的伪造的chunk就会被unlink,这样p就会被改写到一个指向自己附近的地址(之前0x18位置),此时若再对p进行写操作就又能够改写p,而且此时可以将p改写为任意位置啦,即获取一个任意位置指针。

现在继续记录,对于检查一:

1
chunksize(P) != next_chunk(P)->prev_size

即当前要unlink的chunk后面的chunk里的pre_size是有效的而且应该与当前chunk相等,而这个检查是单方面的,完全依靠伪造的数据,那么可以伪造size为0、8等轻松绕过。

2.26新特性绕过:它增加了tcache,使每个线程默认有64个大小递增的bins,每个bin是一个单链表,默认最多包含七个chunk,这里的chunk不会被合并,那么我们这里的smallbin在合并时触发unlink将不会发生,也就不会触发漏洞,解决方法是连续分配多个chunk再释放它们,将其填满再次释放就不会进入tcache了。

top chunk

house of force

libc不会检查top chunk的大小,当修改top chunk到过大值时,当再次分配一个大的值将会产生溢出,新的top chunk将会指向一个小的内存区域,即我们希望控制的内存区域,再次从top chunk分配内存,将能够控制此区域,条件:

  1. 能够控制top块的大小,例如前一个chunk溢出到top chunk的size位。
  2. 能够控制malloc的大小。
  3. 用户的输入能够写入malloc分配的空间中。

mmap

当malloc size超过约0x21000时,会改用mmap直接映射,mmap得到的地址是连续的,得到的chunk会接在上一次mmap之前,通常最后一次mmap会是tls段,另外mmap是由高到低分配的,那么第一使用mmap分配的区域后面就是tls区,这样若是能够覆盖tls就能做一些事,例如stack canary,stack address存在于tls中 ,malloc用到的arena等也在tls中:

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
48
49
50
51
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>
void sh(char *c){
system(c);
}

char cmd[1024];

int main(){
char ptr[8];
char magic[32];
int size,n;

setvbuf(stdout,0,_IONBF,0);
memset(ptr,0,sizeof(ptr));
gets(magic);

while(1){
fgets(cmd,sizeof(cmd),stdin);
if(!strncmp(cmd,"add",3)){
printf("Index: ");
scanf("%d",&n);
if(n>=0 && n<8){
printf("Size: ");
scanf("%d%*c",&size);
ptr[n] = malloc(size);
printf("Data: ");
gets(ptr[n]);
}else{
puts("out of bound");
}
}else if(!strncmp(cmd,"printf",5)){
printf("Index: ");
scanf("%d",&n);
if(n>=0 && n<8 && ptr[n]){
printf("Size: ");
scanf("%d%*c",&size);
write(1,ptr[n],size);
}else{
puts("nothing here");
}
}else if(!strncmp(cmd,"exit",4)){
break;
}else{
puts("unknown command");
}
}
return 0;
}

利用思路:此处可以先malloc(0x21000)然后多输出一些内容获取stack addr,stack canary,main_arena,然后用到前两者,可以得到magic的地址,然后再次malloc(0x21000)去覆写main_arena,改为cmd的地址,并且构造cmd为一个fake arena(只需要构造前面需要的部分),然后将fastbin的合适位置写为magic的地址,magic需要提前构造好,主要是size部分和那几个标志位要正确,这样就可以malloc得到它,进行rop啦,这里需要注意,查看malloc源码,可以看到,在libc_malloc里还会对分配的chunk进行断言,绕过的方法是,设置main_arena位或者设置mmap位:

arena

house of mind

参考

[1]winesap-《stcs 2016 week10-13》
[2]ctf-all-in-one 《Linux 堆利用》
[3]Hcamael-

CATALOG
  1. 1. 编译libc
  2. 2. fastbin
    1. 2.1. 安全检查
    2. 2.2. use after free
    3. 2.3. fastbin corruption
      1. 2.3.1. double free
    4. 2.4. overflow
    5. 2.5. house of spirit
  3. 3. smallbin
    1. 3.1. unlink
  4. 4. top chunk
    1. 4.1. house of force
  5. 5. mmap
  6. 6. arena
    1. 6.1. house of mind
  7. 7. 参考