-
-
简单的缓冲区溢出攻击
-
发表于:
2015-7-14 15:12
7105
-
一、关于shellcode摘自百度百科:
Shellcode实际是一段代码(也可以是填充数据),是用来发送到服务器利用特定漏洞的代码,一般可以获取权限。另外,Shellcode一般是作为数据发送给受攻击服务器的。 Shellcode是溢出程序和蠕虫病毒的核心,提到它自然就会和漏洞联想在一起,毕竟Shellcode只对没有打补丁的主机有用武之地。网络上数以万计带着漏洞顽强运行着的服务器给hacker和Vxer丰盛的晚餐。漏洞利用中最关键的是Shellcode的编写。由于漏洞发现者在漏洞发现之初并不会给出完整Shellcode,因此掌握Shellcode编写技术就显得尤为重要。
二:为什么会有缓冲区溢出攻击?
1.因为在计算机存储内存中数据跟代码都是以二进制形式存储的,所以直接从内存的二进制形式上是无法区分哪些是数据哪些是代码的,这也为缓冲区溢出攻击提供了可能。
这是最开始的理解,但是之后的学习过程中发现这个想法是错误的。2.根据函数在计算机内部的调用机制,利用栈来完成函数的调用实现,在这个过程中栈内部保存有函数当前地址以及下一步函数调用地址。如果在程序跟用户进行交互的过程中精心构造一部分shellcode代码将函数的执行流程更改就会造成缓冲区溢出攻击。
缓冲区溢出不只是栈溢出,还包括堆溢出以及其他方式的溢出。
在理解堆栈溢出之前首先要了解的是堆栈的概念:
堆区:进程可以在堆区动态的申请一定大小的内存,并在用完之后还给堆区。动态分配和回收是堆的特点。
栈区:动态的存储函数之间的调用关系,以保证被调用函数在返回时恢复到母函数中继续执行。
缓冲区溢出原理:
图1是进程地址空间分布的简单表示。代码存储了用户程序的所有可执行代码,在程序正常执行的情况下,程序计数器(PC指针)只会在代码段和操作系统地址空间(内核态)内寻址。数据段内存储了用户程序的全局变量,文字池等。栈空间存储了用户程序的函数栈帧(包括参数、局部数据等),实现函数调用机制,它的数据增长方向是低地址方向。堆空间存储了程序运行时动态申请的内存数据等,数据增长方向是高地址方向。除了代码段和受操作系统保护的数据区域,其他的内存区域都可能作为缓冲区,因此缓冲区溢出的位置可能在数据段,也可能在堆、栈段。如果程序的代码有软件漏洞,恶意程序会“教唆”程序计数器从上述缓冲区内取指,执行恶意程序提供的数据代码!
2、函数栈帧
栈的主要功能是实现函数的调用。因此在介绍栈溢出原理之前,需要弄清函数调用时栈空间发生了怎样的变化。每次函数调用时,系统会把函数的返回地址(函数调用指令后紧跟指令的地址),一些关
由于栈是低地址方向增长的,因此局部数组buffer的指针在缓冲区的下方。当把data的数据拷贝到buffer内时,超过缓冲区区域的高地址部分数据会“淹没”原本的其他栈帧数据,根据淹没数据的内容不同,可能会有产生以下情况:
1、淹没了其他的局部变量。如果被淹没的局部变量是条件变量,那么可能会改变函数原本的执行流程。这种方式可以用于破解简单的软件验证。
2、淹没了ebp的值。修改了函数执行结束后要恢复的栈指针,将会导致栈帧失去平衡。
3、淹没了返回地址。这是栈溢出原理的核心所在,通过淹没的方式修改函数的返回地址,使程序代码执行“意外”的流程!
4、淹没参数变量。修改函数的参数变量也可能改变当前函数的执行结果和流程。
5、淹没上级函数的栈帧
情况与上述4点类似,只不过影响的是上级函数的执行。当然这里的前提是保证函数能正常返回,即函数地址不能被随意修改(这可能很麻烦!)。
实验一:
操作系统:win7
编译器:vc6.0
编译选项:默认
build版本:debug
实验方法:改邻接变量
实验代码:第一个实验代码,将strcmp函数写到函数内部。
#include <stdio.h>
#include <string.h>
#define P "12345678"
void fun(char str[8])
{
int n=0;
char name[8];
n=strcmp(str,P);
strcpy(name,str);
if(n)
printf("%s\n",str);
else
printf("猜对了!");
}
int main()
{
char str[8];
printf("请输入不超8字节:");
scanf("%s",str);
fun(str);
return 0;
}
//前两天分析失误。
0040131A |. 0AE4 |or ah,ah
0040131C |.^ 75 D2 \jnz short 234.004012F0
0040131E |. 8BFF mov edi,edi //这是判断相等的时候置为0;
00401320 |> 33C0 xor eax,eax
00401322 |. C3 retn
00401323 | 90 nop
00401324 |> 1BC0 sbb eax,eax //这是不等的时候,sbb的操作结果是eax=eax-eax-cf,所以会跟strcmp的返回值相关,strcmp的返回值保存在cf标志位中当输入的字符串ASIC码值大于密码时的情况CF=0,当ASICC小于密码时CF=1;所以可以修改邻接变量来改变程序执行情况。
00401326 |. D1E0 shl eax,1
00401328 |. 40 inc eax
#include <stdio.h>
#include <string.h>
#define P "12345678"
int fun(char str[13])
{
int n;
char name[8];
n=strcmp(str,P);
strcpy(name,str);
return n;
}
int main()
{
char str[8];
int flag=0;
printf("请输入不超8字节:");
scanf("%s",str);
flag=fun(str);
if(flag)
printf("%s\n",str);
else
printf("猜对了!");
return 0;
}
这个实验,是将比较结果作为返回值,之后进行判断。这个程序,当所输入字符串比当前字符串多一位且十六进制值大时flag标志位原本为1被覆盖后为0.当输入字符串比当前字符串小时就不能修改邻接变量值来进行 溢出修改flag标志位。
总结:通过两个小实验证明修改邻接变量的方法破解程序对环境要求很苛刻,《第一个将strcmp函数比较结果直接在函数内部进行判断就不能再用修改邻接变量的方式来破解程序》括号中为错误结论,具体原因上面已经分析出来。
第二种方式:
通用更强大的缓冲区溢出改写的目标不是某一个变量,而是瞄准栈侦最下方的EBP和函数返回地址等栈侦状态值。
通过检测EBP可以找到并修改EBP之后的函数返回地址。
验证程序如下
#include <stdio.h>
#include <string.h>
#include <windows.h>
#define P "12345678"
int fun(char *str)
{
int n=0;
char name[8]; //此处只开辟八个字节大小的空间
n=strcmp(str,P);
strcpy(name,str); //在这里进行拷贝时,所开辟空间比要拷贝的字符串小时就会把函数返回参数n覆盖。具体情况如下
return n;
:在堆栈中分配空间如下
8个字节 name
4Bit n strcmp返回值
4个字节 EBP
4个字节 retn 返回地址
所以如果字符串足够大时会将函数返回值覆盖,如果这时精心构造读取的字符串,就可以将return 返回地址修改为自己想要的地址。
}
int main()
{
int flag=0;
char psw[1024];
FILE *fp;
LoadLibrary("user32.dll");
if(!(fp=fopen("D:\\VS2013\\1.txt","rw+")))
{
printf("error!\n");
}
fscanf(fp,"%s",psw);
flag=fun(psw);
if(flag)
{
printf("incorrect passward!\n");
}
else
{
printf("congratulation!\n");
}
return 0;
}
通过动态调试找到输出正确匹配的语句地址为00401139,所以只要构造一个20字节的文本,其中最后一个字节是401139就可以达到目的。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课