基础栈溢出复习 之基础
栈与系统栈
栈:一种先进先出的数据结构。常见操作有两种,进栈(PUSH) 和弹栈(POP),用于标识栈的属性有两个,一个是栈顶(TOP),一个是栈底(BASE
程序中的栈:
- 内存中的一块区域,用栈的结构来管理,从高地址向低地址增长
- 寄存器esp代表栈顶(即最低栈地址)
- 栈操作
- 压栈(入栈)push sth-> [esp]=sth,esp=esp-4
- 弹栈(出栈)pop sth-> sth=[esp],esp=esp+4
- 栈用于保存函数调用信息和局部变量
函数调用
如何通过系统栈进行函数的调用和递归函数的分布应当是:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17int fun_b(x,y){
int var_b1,var_b2;
rutrun var_b1 * var_b2 ;
}
int fun_a(a,b){
int var_a;
var_a = fun_b(a*b)
}
int main(int argc,chr **argv,chr **envp)
{
int var_main;
var_main = func_a{5,5};
rutrun var_main;
}
当CPU调用func_A函数,会从main函数对应的机器指令跳转到func_A,取值在执行,执行结束后,需要返回又会进行跳转…….以此类似的跳转过程。
函数调用指令: call ret
大致过程:
- 参数入栈
- 返回地址入栈
- 代码区块跳转
- 栈帧调整:
保存当前栈帧的状态值,为了后面恢复本栈帧时使用(EBP入栈);
将当前的栈帧切换到新栈帧(ESP值装入EBP,更新栈帧底部)
给新栈帧分配空间(ESP减去所需要空间的大小,抬高栈顶)
相关指令: - Call func -> push pc, jmp func
- Leave ->mov esp,ebp, pop ebp
- Ret -> pop pc
函数约定:
- __stdcall,__cdecl,__fastcall,__thiscall,__nakedcall,__pascal
以 __fastcall为例子:1
2
3
4
5
6
7
8
9push 参数 3 #参数由右向左入栈
push 参数 2
push 参数 1
call 函数地址 #push当前指令位置,跳转到所调用函数的入口地址
push ebp #保存旧栈帧的底部
mov ebp,esp #设置新栈帧底部
sub esp ,xxx #设置新栈帧顶部参数传参:取决于调用约定,一般情况下:
- X86 从右向左入栈,X64 优先寄存器,参数过多时才入栈
寄存器
重要的寄存器:rsp/esp, pc, rbp/ebp, rax/eax, rdi, rsi, rdx, rcx
ESP: 栈指针寄存器,内存存放着一个指针,指针指向系统栈最上面一个栈帧的底部
EBP:基址指针寄存器,存放着一个指针,指针指向系统栈最上面的一个栈帧底部
堆栈溢出原理
通俗的讲,栈溢出的原理就是不顾堆栈中分配的局部数据块大小,向该数据快写入了过多的数据,导致数据越界,结果覆盖来看老的堆栈数据。
栈溢出的保护机制
栈上的数据无法被当作指令来执行
- 数据执行保护(NX/DEP)
- 绕过方法ROP
难以找到想要找的地址
- 地址空间布局随机化(ASLR)
- 绕过方法:infoleak 、retdlresolve 、ROP
检测栈数据是否被修改
- Stack Canary/ Cookie
- 绕过方法: infoleak
- 如今 计算机保护 基本上都是NX+Stack Canary +ASLR
CTF 常用套路: 栈溢出的利用方法
- 现代栈溢出利用技术基础:ROP
- 利用signal机制的ROP技术:SROP
- 没有binary怎么办:BROP 、dump bin
- 劫持栈指针:stack pivot
- 利用动态链接绕过ASLR:ret2dl resolve、fake linkmap
- 利用地址低12bit绕过ASLR:Partial Overwrite
- 绕过stack canary:改写指针与局部变量、leak canary、overwrite canary
- 溢出位数不够怎么办:覆盖ebp,Partial Overwrite
现代栈溢出利用技术基础:ROP
讲道理学习ROP ,看蒸米的文章是最实在的。蒸米的一步一步学ROP简直是经典篇目。
ROP的基础学习可以看我翻译的一篇文章