服务器安全攻防技术实验报告实验名称:缓冲区溢出班级:姓名:学号:实验地点:日期:2020/3/11一、实验目的:1.通过实践掌握缓冲区溢出的原理;掌握常用的缓冲区溢出方法;理解缓冲区溢出的危害性;掌握防范和避免缓冲区溢出攻击的方法。
二、基本技能实验内容、要求和环境:实验环境:1.PC机一台,安装有Windows 2003 server;2.Vmware软件一套;3.Codeblockse软件。
实验内容:1、简单陈述缓冲区溢出的原理。
2、(1)陈述程序如何溢出?(2)为什么执行why_here方法?(3)画出栈结构#include <stdio.h>#include <stdlib.h>void why_here(void) //这个函数没有任何地方调用过{printf("why u here !n\n");printf("you are trapped here\n");system("pause");_exit(0);}int main(int argc,char * argv[]){int buffer[1];buffer[4] = why_here;system("pause");return 0;}三、基本技能实验结果与分析:1、简单陈述缓冲区溢出的原理。
答案:缓冲区溢出是指当前计算机向缓冲区内填充数据位数超过了缓冲区本身的容量,溢出的数据覆盖在合法数据上,破坏程序的堆栈,使程序转而执行其他指令,以达到攻击的目的。
2、(1)陈述程序如何溢出?运行程序,返回结果如下:分析:由代码我们可以看出,在我们的main()函数中并没有调用过why_here()函数,只是定义了一个buffer[1]的数组,但是在我们定义了数组之后,我们同时为buffer[4]赋值为why_here()函数的返回值。
我们知道buffer数组并没有buffer[4]这个位置,此时我们为它赋值就会造成该buffer[4]中原来返回地址被覆盖,转去执行why_here(),就会输出以上的语句了。
进入debug模式,查看Selected frame,从中我们可以获取的信息有:EIP为0x401384,在执行main函数之后被保存为0x4010fd,,编码为C语言,涉及到的实参有argc=1, argv=0x1e2e28,局部变量存放在栈的0x60ff08的位置;而目前的frame指针所指示的位置为0x60ff10;保存的寄存器为EBP存放在0x60ff08,EIP保存在0x60ff0c。
debugging windows的watchs窗口中函数中涉及的参数和变量所对应的值。
(2)为什么执行why_here 方法?答案:我们的EBP 所处的位置为0x60ff08,EIP 所处的位置为0x60ff0c,而我们当前的指针(SP )指向了0x60ff10,刚好是buffer[4]所处的地址,那么它就会转去执行why_here()函数,从而就会输出当前的语句。
(3)画出栈结构 答案:栈空间由操作系统自动分配释放,存放函数的参数值,局部变量,EBP,EIP 的值,栈使用的是一级缓存,只是在被调用的时候处于存储空间中,调用完成立即释放。
注意,全局变量和局部静态变量的存储并不会放到内存中的栈区,而是放到静态区,程序执行结束由系统释放。
在Windows 中,栈是线低地址扩展的数据结构,是一块连续的内存区域。
在大多数的C 编译器中,参数是由右向左入栈,然后就是函数中的局部变量。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后是栈顶指针指向函数的返回地址,就是主函数中下一条指令的地址。
程序就会从该点继续执行。
了解栈的相关知识之后,就可以画出以以下栈的结构:局部变量存放在低地址端(0x60ff04)EBP 存放位置(0x60ff08)函数返回的地址,下一条指令地址(0x60ff10)EIP 存放位置(0x60ff0c)四、进阶技能的任务与要求:1、(1)陈述程序如何溢出?(2)画出栈结构(3)补充auth方法,如果输入字符超出正常范围下,提示“输入字符超出范围”;否则,无提示。
#include <stdio.h>#include <string.h>int auth(const char* password){int flag = 0;char pwd[16];strcpy(pwd, password);pwd[0] = 't';if(!strcmp(pwd, "test")){flag = 1;}return flag;}int main(){int flag = 0;char password[128];scanf("%s", password);if(auth(password)){printf("right password\n");}else{printf("wrong password\n");}return 0;}2、设计具有溢出的程序,并解释说明五、进阶技能实验结果与分析:1.首先分析代码块,strcpy()函数用于对字符串进行复制,strcpy(char* strDestination, const char* strSource)。
其中,strDestination代表的是目的字符串,strSource,代表的是源字符串,strcpy()函数会把strSource指向字符串复制到strDestination。
必须要保证strDestination足够大,能够容纳下strSource,否者的话,就会造成溢出错误。
返回值为目的字符串,即strDestination。
而我们的程序中的main()函数中的password定义为128位的字符数组,而main()函数中if语句调用的auth()函数中定义的pwd为16位的字符数组,并且在auth()函数中strcpy()函数将password的值赋值给了pwd。
如果我们输入的password大于16位,这就造成了溢出错误。
1)在我们输入和需要比对的字符串test相同时,就会返回正确的密码;2)如果我们输入的密码大于test,但是并不超过16位,此时不会造成溢出错误,但是会返回“wrong password”。
之所以会返回“wrong password”,是因为在auth()函数中使用strcmp()函数进行比较我们输入的password和“test”的值的大小时,strcmp()函数返回的值大于0,又因为if语句对strcmp()函数我的返回值取非,所以if语句判断的值小于0,就会返回flag=0。
之后将返回的结果交于main()函数中的if语句进行判断,此时,就符合else,就会打印输出“wrong password”。
3)如果我们输入的password的位数大于16位,此时就会造成溢出错误,但是同样会输出“wrong password”。
正常情况下,我们应该对输入的密码进行判断,如果输入的密码的位数大于16位,就不允许进行auth()函数中的strcpy()复制操作,这样就不会造成溢出错误了。
(2)画出栈结构局部变量flag、pwd、passowrd存放的位置EBP存放的位置EIP存放的位置auth()函数的返回值(3)补充auth方法,在auth()函数中,对传递进来的参数password进行长度的判断,示例代码如下:int auth(const char* password){int flag = 0;char pwd[16];if(strlen(password)>16){printf("输入字符串超出范围,请重新输入!\n");}else{strcpy(pwd, password);}pwd[0] = 't';if(!strcmp(pwd, "test")){flag = 1;}return flag;}2、设计具有溢出的程序,并解释说明示例代码如下:/*Author: 孤烟逐云Time: 2020/3/11*/#include <stdio.h>#define PASSWORD "root1233"int verify(char *password) // 定义验证用户输入的密码是否正确的函数{int auth;char buffer[8]; // 定义buffer字符数组的长度为8auth = strcmp(password, PASSWORD); // 比较输入的字符串和定义密码是否相同strcpy(buffer, password); // 将用户输入的密码拷贝至bufferreturn auth;}int main(void){int flag = 0;char pass[30]; // 定义pass字符数组长度为30while(1){ // 永远为True,执行该程序就会提示用户输入密码printf("enter the password:\t");scanf("%s", pass); // 提示用户输入密码flag = verify(pass); // 调用验证用户输入的密码是否正确的函数if(flag==0){ // 判断输入的密码如果相同,就跳出验证printf("congratulation!\n");break;}else{ // 如果用户输入的密码不正确,就提示用户再次输入 printf("password incorrect! \n");}}}执行结果如下:(1)输入密码不正确就会提示用户再次输入,输入的密码正确的的话,就会打印出congratulation,并且跳出if语句。
(2)让输入的密码不正确,并且长度超过定义的最大长度,就处于中断状态。