update memory

This commit is contained in:
firmianay 2017-08-16 15:07:55 +08:00
parent d167525354
commit 79f10279ca
5 changed files with 245 additions and 6 deletions

View File

@ -185,6 +185,8 @@ fastcall | 函数本身 | 都两个 DWORD4 字节)类型或者占更少字
## 堆与内存管理 ## 堆与内存管理
#### 堆 #### 堆
![](../pic/1.5.7_spacelayout.png)
堆是用于存放除了栈里的东西之外所有其他东西的内存区域有动态内存分配器负责维护。分配器将堆视为一组不同大小的块block的集合来维护每个块就是一个连续的虚拟内存器片chunk。当使用 `malloc()``free()` 时就是在操作堆中的内存。对于堆来说,释放工作由程序员控制,容易产生内存泄露。 堆是用于存放除了栈里的东西之外所有其他东西的内存区域有动态内存分配器负责维护。分配器将堆视为一组不同大小的块block的集合来维护每个块就是一个连续的虚拟内存器片chunk。当使用 `malloc()``free()` 时就是在操作堆中的内存。对于堆来说,释放工作由程序员控制,容易产生内存泄露。
堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。 堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
@ -202,7 +204,132 @@ int brk(void *addr);
void *sbrk(intptr_t increment); void *sbrk(intptr_t increment);
``` ```
参数 `*addr` 是进程数据段的结束地址,`brk()` 通过改变该地址来改变数据段的大小,当结束地址向高地址移动,进程内存空间增大,当结束地址向低地址移动,进程内存空间减小。`brk()`调用成功时返回 0失败时返回 -1。 `sbrk()``brk()` 类似,但是参数 `increment` 表示增量,即增加或减少的空间大小,调用成功时返回增加后减小后数据段的结束地址,失败时返回 -1。 参数 `*addr` 是进程数据段的结束地址,`brk()` 通过改变该地址来改变数据段的大小,当结束地址向高地址移动,进程内存空间增大,当结束地址向低地址移动,进程内存空间减小。`brk()`调用成功时返回 0失败时返回 -1。 `sbrk()``brk()` 类似,但是参数 `increment` 表示增量,即增加或减少的空间大小,调用成功时返回增加后减小前数据段的结束地址,失败时返回 -1。
在上图中我们看到 brk 指示堆结束地址start_brk 指示堆开始地址。BSS segment 和 heap 之间有一段 Random brk offset这是由于 ASLR 的作用,如果关闭了 ASLR则 Random brk offset 为 0堆结束地址和数据段开始地址重合。
例子:[源码](../src/Others/1.5.7_brk.c)
```
#include <stdio.h>
#include <unistd.h>
void main() {
void *curr_brk, *tmp_brk, *pre_brk;
printf("当前进程 PID%d\n", getpid());
tmp_brk = curr_brk = sbrk(0);
printf("初始化后的结束地址:%p\n", curr_brk);
getchar();
brk(curr_brk+4096);
curr_brk = sbrk(0);
printf("brk 之后的结束地址:%p\n", curr_brk);
getchar();
pre_brk = sbrk(4096);
curr_brk = sbrk(0);
printf("sbrk 返回值(即之前的结束地址):%p\n", pre_brk);
printf("sbrk 之后的结束地址:%p\n", curr_brk);
getchar();
brk(tmp_brk);
curr_brk = sbrk(0);
printf("恢复到初始化时的结束地址:%p\n", curr_brk);
getchar();
}
```
开启两个终端,一个用于执行程序,另一个用于观察内存地址。首先我们看关闭了 ASLR 的情况。第一步初始化:
```text
# echo 0 > /proc/sys/kernel/randomize_va_space
```
```text
$ ./a.out
当前进程 PID27759
初始化后的结束地址0x56579000
```
```text
# cat /proc/27759/maps
...
56557000-56558000 rw-p 00001000 08:01 28587506 /home/a.out
56558000-56579000 rw-p 00000000 00:00 0 [heap]
...
```
数据段结束地址和堆开始地址同为 `0x56558000`,堆结束地址为 `0x56579000`
第二步使用 `brk()` 增加堆空间:
```text
$ ./a.out
当前进程 PID27759
初始化后的结束地址0x56579000
brk 之后的结束地址0x5657a000
```
```text
# cat /proc/27759/maps
...
56557000-56558000 rw-p 00001000 08:01 28587506 /home/a.out
56558000-5657a000 rw-p 00000000 00:00 0 [heap]
...
```
堆开始地址不变,结束地址增加为 `0x5657a000`
第三步使用 `sbrk()` 增加堆空间:
```text
$ ./a.out
当前进程 PID27759
初始化后的结束地址0x56579000
brk 之后的结束地址0x5657a000
sbrk 返回值即之前的结束地址0x5657a000
sbrk 之后的结束地址0x5657b000
```
```text
# cat /proc/27759/maps
...
56557000-56558000 rw-p 00001000 08:01 28587506 /home/a.out
56558000-5657b000 rw-p 00000000 00:00 0 [heap]
...
```
第四步减小堆空间:
```text
]$ ./a.out
当前进程 PID27759
初始化后的结束地址0x56579000
brk 之后的结束地址0x5657a000
sbrk 返回值即之前的结束地址0x5657a000
sbrk 之后的结束地址0x5657b000
恢复到初始化时的结束地址0x56579000
```
```text
# cat /proc/27759/maps
...
56557000-56558000 rw-p 00001000 08:01 28587506 /home/a.out
56558000-56579000 rw-p 00000000 00:00 0 [heap]
...
```
再来看一下开启了 ASLR 的情况:
```text
# echo 2 > /proc/sys/kernel/randomize_va_space
```
```text
]$ ./a.out
当前进程 PID28025
初始化后的结束地址0x578ad000
```
```text
# cat /proc/28025/maps
...
5663f000-56640000 rw-p 00001000 08:01 28587506 /home/a.out
5788c000-578ad000 rw-p 00000000 00:00 0 [heap]
...
```
可以看到这时数据段的结束地址 `0x56640000` 不等于堆的开始地址 `0x5788c000`
`mmap()` 的声明如下: `mmap()` 的声明如下:
```c ```c
@ -220,7 +347,73 @@ void *mmap(void *addr, size_t len, int prot, int flags,
int munmap(void *addr, size_t len); int munmap(void *addr, size_t len);
``` ```
C 标准库提供了一个叫做 `malloc` 的分配器,程序通过调用 `malloc()` 函数来从堆中分配块,声明如下: 例子:[源码](../src/Others/1.5.7_mmap.c)
```
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
void main() {
void *curr_brk;
printf("当前进程 PID%d\n", getpid());
printf("初始化后\n");
getchar();
char *addr;
addr = mmap(NULL, (size_t)4096, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
printf("mmap 完成\n");
getchar();
munmap(addr, (size_t)4096);
printf("munmap 完成\n");
getchar();
}
```
第一步初始化:
```text
$ ./a.out
当前进程 PID28652
初始化后
```
```text
# cat /proc/28652/maps
...
f76b2000-f76b5000 rw-p 00000000 00:00 0
f76ef000-f76f1000 rw-p 00000000 00:00 0
...
```
第二步 mmap
```text
]$ ./a.out
当前进程 PID28652
初始化后
mmap 完成
```
```text
# cat /proc/28652/maps
...
f76b2000-f76b5000 rw-p 00000000 00:00 0
f76ee000-f76f1000 rw-p 00000000 00:00 0
...
```
第三步 munmap
```text
$ ./a.out
当前进程 PID28652
初始化后
mmap 完成
munmap 完成
```
```text
# cat /proc/28652/maps
...
f76b2000-f76b5000 rw-p 00000000 00:00 0
f76ef000-f76f1000 rw-p 00000000 00:00 0
...
```
可以看到第二行第一列地址从 `f76ef000`->`f76ee000`->`f76ef000` 变化。`0xf76ee000-0xf76ef000=0x1000=4096`。
通常情况下,我们不会直接使用 `brk()``mmap()` 来分配堆空间C 标准库提供了一个叫做 `malloc` 的分配器,程序通过调用 `malloc()` 函数来从堆中分配块,声明如下:
```c ```c
#include <stdlib.h> #include <stdlib.h>

View File

@ -46,7 +46,7 @@ Partial RELRO Canary found NX enabled PIE enabled No RPATH No RU
No-eXecute表示不可执行其原理是将数据所在的内存页标识为不可执行如果程序产生溢出转入执行 shellcode 时CPU 会抛出异常。其绕过方法是 ret2libc。 No-eXecute表示不可执行其原理是将数据所在的内存页标识为不可执行如果程序产生溢出转入执行 shellcode 时CPU 会抛出异常。其绕过方法是 ret2libc。
#### PIE #### PIE
PIEPosition Independent Executable需要配合 ASLR 来使用以达到可执行文件的加载时地址随机化。简单来说PIE 是编译时随机化由编译器完成ASLR 是加载时随机化,操作系统完成。 PIEPosition Independent Executable需要配合 ASLR 来使用以达到可执行文件的加载时地址随机化。简单来说PIE 是编译时随机化由编译器完成ASLR 是加载时随机化,操作系统完成。
我们通过实际例子来探索一下 PIE 和 ASLR 我们通过实际例子来探索一下 PIE 和 ASLR
```c ```c
@ -146,8 +146,8 @@ Partial RELRO No canary found NX enabled No PIE No RPATH No RU
> >
>完全开启(在部分开启的基础上增加 heap的随机化`# echo > /proc/sys/kernel/randomize_va_space` >完全开启(在部分开启的基础上增加 heap的随机化`# echo > /proc/sys/kernel/randomize_va_space`
#### RELOAD #### RELRO
RELRO 设置符号重定向表为只读或在程序启动时就解析并绑定所有动态符号,从而减少对 GOTGlobal Offset Table的攻击。 RELROReLocation Read-Only设置符号重定向表为只读或在程序启动时就解析并绑定所有动态符号,从而减少对 GOTGlobal Offset Table的攻击。
## 编译参数 ## 编译参数
@ -171,7 +171,7 @@ gcc hello.c -o hello-S -fstack-protector-all -z noexecstack -pie -z now
- FORTIFY - FORTIFY
- `-D_FORTIFY_SOURCE=1`:仅在编译时检测溢出 - `-D_FORTIFY_SOURCE=1`:仅在编译时检测溢出
- `-D_FORTIFY_SOURCE=1`:在编译时和运行时检测溢出 - `-D_FORTIFY_SOURCE=2`:在编译时和运行时检测溢出
## 保护机制检测 ## 保护机制检测

BIN
pic/1.5.7_spacelayout.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

27
src/Others/1.5.7_brk.c Normal file
View File

@ -0,0 +1,27 @@
#include <stdio.h>
#include <unistd.h>
void main() {
void *curr_brk, *tmp_brk, *pre_brk;
printf("当前进程 PID%d\n", getpid());
tmp_brk = curr_brk = sbrk(0);
printf("初始化后的结束地址:%p\n", curr_brk);
getchar();
brk(curr_brk+4096);
curr_brk = sbrk(0);
printf("brk 之后的结束地址:%p\n", curr_brk);
getchar();
pre_brk = sbrk(4096);
curr_brk = sbrk(0);
printf("sbrk 返回值(即之前的结束地址):%p\n", pre_brk);
printf("sbrk 之后的结束地址:%p\n", curr_brk);
getchar();
brk(tmp_brk);
curr_brk = sbrk(0);
printf("恢复到初始化时的结束地址:%p\n", curr_brk);
getchar();
}

19
src/Others/1.5.7_mmap.c Normal file
View File

@ -0,0 +1,19 @@
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
void main() {
void *curr_brk;
printf("当前进程 PID%d\n", getpid());
printf("初始化后\n");
getchar();
char *addr;
addr = mmap(NULL, (size_t)4096, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
printf("mmap 完成\n");
getchar();
munmap(addr, (size_t)4096);
printf("munmap 完成\n");
getchar();
}