update memory

This commit is contained in:
firmianay 2017-08-11 23:38:12 +08:00
parent 321ae62e58
commit 6e61a3f025
5 changed files with 265 additions and 4 deletions

View File

@ -6,6 +6,7 @@
- [权限设置](#权限设置)
- [字节序](#字节序)
- [输入输出](#输入输出)
- [文件描述符](#文件描述符)
## 常用基础命令
@ -134,3 +135,17 @@ Big-endian 规定 MSB 在存储时放在低地址,在传输时放在流的开
- ```$ your_command_here > filename```
- 使用文件作为输入
- ```$ ./vulnerable < filename```
## 文件描述符
在 Linux 系统中一切皆可以看成是文件文件又分为普通文件、目录文件、链接文件和设备文件。文件描述符file descriptor是内核管理已被打开的文件所创建的索引使用一个非负整数来指代被打开的文件。
标准文件描述符如下:
文件描述符 | 用途 | stdio 流
--- | --- | ---
0 | 标准输入 | stdin
1 | 标准输出 | stdout
2 | 标准错误 | stderr
当一个程序使用 `fork()` 生成一个子进程后,子进程会继承父进程所打开的文件表,此时,父子进程使用同一个文件表,这可能导致一些安全问题。如果使用 `vfork()`,子进程虽然运行于父进程的空间,但拥有自己的进程表项。

View File

@ -1,6 +1,8 @@
# C 语言基础
- [从源代码到可执行文件](#从源代码到可执行文件)
- [整数表示](#整数表示)
## 从源代码到可执行文件
我们以经典著作《The C Programming Language》中的第一个程序 “Hello World” 为例,讲解 Linux 下 GCC 的编译过程。
@ -23,7 +25,7 @@ hello world
![](../pic/1.5.1_compile.png)
### 预编译
#### 预编译
```text
$gcc -E hello.c -o hello.i
```
@ -36,13 +38,13 @@ $gcc -E hello.c -o hello.i
- 添加行号和文件名标号。
- 保留所有的 #pragma 编译器指令。
### 编译
#### 编译
```text
$gcc -S hello.c -o hello.s
```
编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件。
### 汇编
#### 汇编
```text
$gcc -c hello.s -o hello.o
或者
@ -50,5 +52,95 @@ $gcc -c hello.c -o hello.o
```
汇编器将汇编代码转变成机器可以执行的指令。
### 链接
#### 链接
目标文件需要链接一大堆文件才能得到最终的可执行文件。链接过程主要包括地址和空间分配Address and Storage Allocation、符号决议Symbol Resolution和重定向Relocation等。
## 整数表示
默认情况下C 语言中的数字是有符号数,下面我们声明一个有符号整数和无符号整数:
```c
int var1 = 0;
unsigned int var2 = 0;
```
- 有符号整数
- 可以表示为正数或负数
- `int` 的范围:`-2,147,483,648 ~ 2,147,483,647`
- 无符号整数
- 只能表示为零或正数
- `unsigned int` 的范围:`0 ~ 4,294,967,295`
`signed` 或者 `unsigned` 取决于整数类型是否可以携带标志 `+/-`
- Signed
- int
- signed int
- long
- Unsigned
- unit
- unsigned int
- unsigned long
`signed int` 中,二进制最高位被称作符号位,符号位被设置为 `1` 时,表示值为负,当设置为 `0` 时,值为非负:
- 0x7FFFFFFF = 2147493647
- 01111111111111111111111111111111
- 0x80000000 = -2147483647
- 10000000000000000000000000000000
- 0xFFFFFFFF = -1
- 11111111111111111111111111111111
二进制补码以一种适合于二进制加法器的方式来表示负数,当一个二进制补码形式表示的负数和与它的绝对值相等的正数相加时,结果为 0。首先以二进制方式写出正数然后对所有位取反最后加 1 就可以得到该数的二进制补码:
```text
eg: 0x00123456
= 1193046
= 00000000000100100011010001010110
~= 11111111111011011100101110101001
+= 11111111111011011100101110101010
= -1193046 (0xFFEDCBAA)
```
编译器需要根据变量类型信息编译成相应的指令:
- 有符号指令
- IDIV带符号除法指令
- IMUL带符号乘法指令
- SAL算术左移指令保留符号
- SAR右移右移指令保留符号
- MOVSX带符号扩展传送指令
- JL当小于时跳转指令
- JLE当小于或等于时跳转指令
- JG当大于时跳转指令
- JGE当大于或等于时跳转指令
- 无符号指令
- DIV除法指令
- MUL乘法指令
- SHL逻辑左移指令
- SHR逻辑右移指令
- MOVZX无符号扩展传送指令
- JB当小于时跳转指令
- JBE当小于或等于时跳转指令
- JA当大于时跳转指令
- JAE当大于或等于时跳转指令
32 位机器上的整型数据类型,不同的系统可能会有不同:
C 数据类型 | 最小值 | 最大值 | 最小大小
--- | --- | --- | ---
char | -128 | 127 | 8 bits
short | -32 768 | 32 767 | 16 bits
int | -2 147 483 648 | 2 147 483 647 | 16 bits
long | -2 147 483 648 | 2 147 483 647 | 32 bits
long long | -9 223 372 036 854 775 808 | 9 223 372 036 854 775 807 | 64 bits
固定大小的数据类型:
- `int [# of bits]_t`
- int8_t, int16_t, int32_t
- `uint[# of bits]_t`
- uint8_t, uint16_t, uint32_t
更多信息在 `stdint.h``limits.h` 中:
```text
$ man stdint.h
$ cat /usr/include/stdint.h
$ man limits.h
$ cat /usr/include/limits.h
```
了解整数的符号和大小是很有用的,在后面的相关章节中我们会介绍整数溢出的内容。

View File

@ -25,6 +25,149 @@
## 栈与调用约定
#### 栈
栈是一个先入后出First In Last Out(FIFO))的容器。用于存放函数返回地址及参数、临时变量和有关上下文的内容。程序在调用函数时,操作系统会自动通过压栈和弹栈完成保存函数现场等操作,不需要程序员手动干预。
栈由高地址向低地址增长栈保存了一个函数调用所需要的维护信息称为堆栈帧Stack Frame在 x86 体系中,寄存器 `ebp` 指向堆栈帧的底部,`esp` 指向堆栈帧的顶部。压栈时栈顶地址减小,弹栈时栈顶地址增大。
- `PUSH`:用于压栈。将 `esp` 减 4然后将其唯一操作数的内容写入到 `esp` 指向的内存地址
- `POP` :用于弹栈。从 `esp` 指向的内存地址获得数据,将其加载到指令操作数(通常是一个寄存器)中,然后将 `esp` 加 4。
x86 体系下函数的调用总是这样的:
- 把所有或一部分参数压入栈中,如果有其他参数没有入栈,那么使用某些特定的寄存器传递。
- 把当前指令的下一条指令的地址压入栈中。
- 跳转到函数体执行。
其中第 2 步和第 3 步由指令 `call` 一起执行。跳转到函数体之后即开始执行函数,而 x86 函数体的开头是这样的:
- `push ebp`把ebp压入栈中old ebp
- `mov ebp, esp`ebp=esp这时ebp指向栈顶而此时栈顶就是old ebp
- [可选] `sub esp, XXX`:在栈上分配 XXX 字节的临时空间。
- [可选] `push XXX`:保存名为 XXX 的寄存器。
把ebp压入栈中是为了在函数返回时恢复以前的ebp值而压入寄存器的值是为了保持某些寄存器在函数调用前后保存不变。函数返回时的操作与开头正好相反
- [可选] `pop XXX`:恢复保存的寄存器。
- `mov esp, ebp`恢复esp同时回收局部变量空间。
- `pop ebp`恢复保存的ebp的值。
- `ret`:从栈中取得返回地址,并跳转到该位置。
栈帧对应的汇编代码:
```text
PUSH ebp ; 函数开始使用ebp前先把已有值保存到栈中
MOV ebp, esp ; 保存当前esp到ebp中
...   ; 函数体
 ; 无论esp值如何变化ebp都保持不变可以安全访问函数的局部变量、参数
MOV esp, ebp  ; 将函数的其实地址返回到esp中
POP ebp   ; 函数返回前弹出保存在栈中的ebp值
RET ; 函数返回并跳转
```
函数调用后栈的标准布局如下图:
![](../pic/1.5.7_stack.png)
我们来看一个例子:[源码](../src/Others/1.5.7_stack.c)
```c
#include<stdio.h>
int add(int a, int b) {
int x = a, y = b;
return (x + y);
}
int main() {
int a = 1, b = 2;
printf("%d\n", add(a, b));
return 0;
}
```
使用 gdb 查看对应的汇编代码:
```text
gdb-peda$ disassemble main
Dump of assembler code for function main:
0x00000563 <+0>: lea ecx,[esp+0x4]
0x00000567 <+4>: and esp,0xfffffff0
0x0000056a <+7>: push DWORD PTR [ecx-0x4]
0x0000056d <+10>: push ebp
0x0000056e <+11>: mov ebp,esp
0x00000570 <+13>: push ebx
0x00000571 <+14>: push ecx
0x00000572 <+15>: sub esp,0x10
0x00000575 <+18>: call 0x440 <__x86.get_pc_thunk.bx>
0x0000057a <+23>: add ebx,0x1a86
0x00000580 <+29>: mov DWORD PTR [ebp-0x10],0x1
0x00000587 <+36>: mov DWORD PTR [ebp-0xc],0x2
0x0000058e <+43>: push DWORD PTR [ebp-0xc]
0x00000591 <+46>: push DWORD PTR [ebp-0x10]
0x00000594 <+49>: call 0x53d <add>
0x00000599 <+54>: add esp,0x8
0x0000059c <+57>: sub esp,0x8
0x0000059f <+60>: push eax
0x000005a0 <+61>: lea eax,[ebx-0x19b0]
0x000005a6 <+67>: push eax
0x000005a7 <+68>: call 0x3d0 <printf@plt>
0x000005ac <+73>: add esp,0x10
0x000005af <+76>: mov eax,0x0
0x000005b4 <+81>: lea esp,[ebp-0x8]
0x000005b7 <+84>: pop ecx
0x000005b8 <+85>: pop ebx
0x000005b9 <+86>: pop ebp
0x000005ba <+87>: lea esp,[ecx-0x4]
0x000005bd <+90>: ret
End of assembler dump.
gdb-peda$ disassemble add
Dump of assembler code for function add:
0x0000053d <+0>: push ebp
0x0000053e <+1>: mov ebp,esp
0x00000540 <+3>: sub esp,0x10
0x00000543 <+6>: call 0x5be <__x86.get_pc_thunk.ax>
0x00000548 <+11>: add eax,0x1ab8
0x0000054d <+16>: mov eax,DWORD PTR [ebp+0x8]
0x00000550 <+19>: mov DWORD PTR [ebp-0x8],eax
0x00000553 <+22>: mov eax,DWORD PTR [ebp+0xc]
0x00000556 <+25>: mov DWORD PTR [ebp-0x4],eax
0x00000559 <+28>: mov edx,DWORD PTR [ebp-0x8]
0x0000055c <+31>: mov eax,DWORD PTR [ebp-0x4]
0x0000055f <+34>: add eax,edx
0x00000561 <+36>: leave
0x00000562 <+37>: ret
End of assembler dump.
```
这里我们在 Linux 环境下,由于 ELF 文件的入口其实是 `_start` 而不是 `main()`,所以我们还应该关注下面的函数:
```text
gdb-peda$ disassemble _start
Dump of assembler code for function _start:
0x00000400 <+0>: xor ebp,ebp
0x00000402 <+2>: pop esi
0x00000403 <+3>: mov ecx,esp
0x00000405 <+5>: and esp,0xfffffff0
0x00000408 <+8>: push eax
0x00000409 <+9>: push esp
0x0000040a <+10>: push edx
0x0000040b <+11>: call 0x432 <_start+50>
0x00000410 <+16>: add ebx,0x1bf0
0x00000416 <+22>: lea eax,[ebx-0x19d0]
0x0000041c <+28>: push eax
0x0000041d <+29>: lea eax,[ebx-0x1a30]
0x00000423 <+35>: push eax
0x00000424 <+36>: push ecx
0x00000425 <+37>: push esi
0x00000426 <+38>: push DWORD PTR [ebx-0x8]
0x0000042c <+44>: call 0x3e0 <__libc_start_main@plt>
0x00000431 <+49>: hlt
0x00000432 <+50>: mov ebx,DWORD PTR [esp]
0x00000435 <+53>: ret
0x00000436 <+54>: xchg ax,ax
0x00000438 <+56>: xchg ax,ax
0x0000043a <+58>: xchg ax,ax
0x0000043c <+60>: xchg ax,ax
0x0000043e <+62>: xchg ax,ax
End of assembler dump.
```
#### 调用约定
## 堆与内存管理
#### 堆
#### 进程堆管理

BIN
pic/1.5.7_stack.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

11
src/Others/1.5.7_stack.c Normal file
View File

@ -0,0 +1,11 @@
#include<stdio.h>
int add(int a, int b) {
int x = a, y = b;
return (x + y);
}
int main() {
int a = 1, b = 2;
printf("%d\n", add(a, b));
return 0;
}