堆栈构造

text segment存放代码;
data segment存放初始化了的静态变量;
BSS segment存放未初始化的静态变量。

Heap存放需要长期保存的变量;
Stack存放临时变量。

调用函数时,如f(int a,int b):
则b放在大的正地址如ebp+12,a放在小的正地址如ebp+8,临时变量放在负地址如ebp-4。旧的ebp放在ebp+0,返回地址放在ebp+4。
这些数据构成一个栈帧

内存分配

内存

攻击方式

攻击

攻击原理:栈溢出时,能把以前的数据覆盖。

-攻击步骤1:在return address和攻击代码之间可写NOP;如果return address域指向的地址高于其自身的地址,则程序会沿着NOP走到攻击代码。(攻击代码足够短的话,也能放在return address之前)
-攻击步骤2:确定return address到栈底的偏移量,在这里将存放新的返回地址。
-攻击步骤3:在堆栈空间内找到存放攻击代码(操作寄存器的汇编代码)的地址。

注意:字符串中的0会将字符串截断。解决:寄存器与寄存器自身异或获得数据0。
例子:
shell
execve()的三个参数分别为”/bin/sh”、”/bin/sh NULL”、NULL,分别存在ebx,ecx,edx中,execve()函数本身的代码放在eax中。
上述代码激活时,栈会随代码运行而发生变化,当寄存器内的值都准备妥当时,通过汇编指令int 0x80启动execve()函数。
code

对策

开放商方法

使用安全的函数

OS方法

ASLR:地址随机化(每次启动程序时,栈和堆的地址不固定。可高度随机化,也可以几种情况轮换)。这时,ebp(定位return address)和malicious code的位置难以确定。
应对方法:重复运行攻击代码

编译器方法

stack-Guard:编译器使用一些guard值插入栈的内部,自动检测栈数据有没有被溢出数据覆盖
guard

shell方法

当检测到真实的id(用户id)跳入到有效id时,自动降回更低的id
应对方法:运行攻击部分之前,将自己的real id设置为0

硬件方法

Non-Executable Stack:禁止在栈内运行代码
应对方法:return-to-libc攻击

return-to-libc攻击 考试重点

原理:利用已有的系统函数(比如system),结合缓冲区的溢出。

-攻击步骤1:找到system()和exit()函数的位置。
-攻击步骤2:找到字符串“bin/sh”的位置,作为参数(将“bin/sh”设置为环境变量:这会导致“bin/sh”被压入栈中)。
-攻击步骤3:为system()设置好栈结构(准备好参数,返回地址:这里返回地址写的是exit()函数的地址)。
详细见return-to-libc攻击(压栈时,eip等也会压入)

更优的方法:

return-oriented programing攻击

原理:将系统中的多个片段指令,以一定顺序组织在一起。
例子:
需执行r1->r2->r3三段代码,他们的地址分别为a1、a2、a3,则栈内从下到上为a1、a2、a3、正常返回地址,并且三个程序的最后一句为ret指令。

jmp-oriented programing攻击

例子:
以上个攻击为例,栈内从下到上为a1、a2、a3、正常返回地址,三个程序的最后一句为相对跳转(jmp [dx])指令跳到调度器
JOP攻击需要结合调度器,来实现“dx=dx+4;jmp[dx]”的控制。
如何初始化dx:设置好esp的位置后,执行pop dx