diff --git a/doc/1.3_linux_basic.md b/doc/1.3_linux_basic.md index 33f7158..3647431 100644 --- a/doc/1.3_linux_basic.md +++ b/doc/1.3_linux_basic.md @@ -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()`,子进程虽然运行于父进程的空间,但拥有自己的进程表项。 diff --git a/doc/1.5.1_c_basic.md b/doc/1.5.1_c_basic.md index d32b884..ab1b2f7 100644 --- a/doc/1.5.1_c_basic.md +++ b/doc/1.5.1_c_basic.md @@ -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 +``` + +了解整数的符号和大小是很有用的,在后面的相关章节中我们会介绍整数溢出的内容。 diff --git a/doc/1.5.7_memory.md b/doc/1.5.7_memory.md index 6a8fa03..5ed9613 100644 --- a/doc/1.5.7_memory.md +++ b/doc/1.5.7_memory.md @@ -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 +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 + 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 + 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. +``` + +#### 调用约定 ## 堆与内存管理 +#### 堆 + +#### 进程堆管理 diff --git a/pic/1.5.7_stack.png b/pic/1.5.7_stack.png new file mode 100644 index 0000000..f804449 Binary files /dev/null and b/pic/1.5.7_stack.png differ diff --git a/src/Others/1.5.7_stack.c b/src/Others/1.5.7_stack.c new file mode 100644 index 0000000..14f23f2 --- /dev/null +++ b/src/Others/1.5.7_stack.c @@ -0,0 +1,11 @@ +#include +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; +}