溢出攻击

缓冲区

介绍

缓冲区溢出是指当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量,溢出的数据覆盖在合法数据上。

缓冲区:是一块连续的计算机内存区域,可以保存相同数据类型的多个实例。

堆 栈:是一个在计算机科学中经常使用的抽象数据类型。后进先出。PUSH操作在堆栈的顶部加入一个元素。POP操作相反,在堆栈顶部移去一个 元素,并将堆栈的大小减一。

CPU的ESP寄存器存放当前线程的栈顶指针

 

EBP寄存器中保存当前线程的栈底指针。

CPU的EIP寄存器存放下一个CPU指令存放的内存地址,当CPU执行完当前的指令后,从EIP寄存器中读取下一条指令的内存地址,然后继续执行。

shellcode,实际上是十六进制形式的机器语言,大家知道机器语言是二进制的,CPU只认识二进制,因为要被直接注入到内存中,没办法编译了,所以希望CPU可以执行,那就只能用机器代码了,一般用汇编语言写出程序,在从目标代码中提取出。十六进制和二进制是和容易转换的。

为什么叫shellcode,我想可能是因为它可以启动一个shell(命令解释程序)。也就是可以与system交互了,可以操作系统了。

 

 

危害

缓冲区溢出中,最为危险的是堆栈溢出,因为入侵者可以利用堆栈溢出,在函数返回时改变返回程序的地址,让其跳转到任意地址,带来的危害一种是程序崩溃导致拒绝服务,另外一种就是跳转并且执行一段恶意代码,比如得到shell,然后为所欲为。

我们将一段恶意代码通过漏洞插入到程序正常的代码当中,由于代码长度是固定的,这段代码会有一部分正常代码被溢出,也就是说我们的恶意代码代替了正常的程序代码。在程序要调用这段代码的时候,它会将我们插入的恶意代码当作正常代码调用,这时溢出攻击就完成了。这段恶意代码可实现的作用是任意的,例如提升用户权限、造成程序崩溃等等。

攻击

通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,使程序转而执行其它指令,以达到攻击的目的。造成缓冲区溢出的原因是程序中没有仔细检查用户输入的参数。例如下面程序:

  void function(char *str) {

  char buffer[16]; strcpy(buffer,str);

  }

上面的strcpy()将直接把str中的内容copy到buffer中。这样只要str的长度大于16,就会造成buffer的溢出,使程序运行出错。存在像strcpy这样的问题的标准函数还有strcat()、sprintf()、vsprintf()、gets()、scanf()等。

最常见的手段是通过制造缓冲区溢出使程序运行一个用户shell,再通过shell执行其它命令。如果该程序属于root且有suid权限的话,攻击者就获得了一个有root权限的shell,可以对系统进行任意操作了。

攻击者必须达到如下的两个目标:

1. 在程序的地址空间里安排适当的代码。
2. 通过适当的初始化寄存器和内存,让程序跳转到入侵者安排的地址空间执行。

 

 

有两种在被攻击程序地址空间里安排攻击代码的方法:

1、植入法

攻击者向被攻击的程序输入一个字符串,程序会把这个字符串放到缓冲区里。这个字符串包含的资料是可以在这个被攻击的硬件平台上运行的指令序列。在这里,攻击者用被攻击程序的缓冲区来存放攻击代码。缓冲区可以设在任何地方:堆栈(stack,自动变量)、堆(heap,动态分配的内存区)和静态资料区

2、利用已经存在的代码

有时,攻击者想要的代码已经在被攻击的程序中了,攻击者所要做的只是对代码传递一些参数。比如,攻击代码要求执行“exec (bin/sh)”,而在libc库中的代码执行“exec (arg)”,其中arg使一个指向一个字符串的指针参数,那么攻击者只要把传入的参数指针改向指向“/bin/sh”
 
控制程序转移到攻击代码的方法:
 
所有的这些方法都是在寻求改变程序的执行流程,使之跳转到攻击代码。最基本的就是溢出一个没有边界检查或者其它弱点的缓冲区,这样就扰乱了程序的正常的执行顺序。许多的缓冲区溢出是用暴力的方法来寻求改变程序指针的。
 
1、活动纪录(Activation Records)
每当一个函数调用发生时,调用者会在堆栈中留下一个活动纪录,它包含了函数结束时返回的地址。攻击者通过溢出堆栈中的自动变量,使返回地址指向攻击代码。通过改变程序的返回地址,当函数调用结束时,程序就跳转到攻击者设定的地址,而不是原先的地址。
 
2、函数指针(Function Pointers)
函数指针可以用来定位任何地址空间。例如:“void (* foo)()”声明了一个返回值为void的函数指针变量foo。所以攻击者只需在任何空间内的函数指针附近找到一个能够溢出的缓冲区,然后溢出这个缓冲区来改变函数指针。在某一时刻,当程序通过函数指针调用函数时,程序的流程就按攻击者的意图实现了。它的一个攻击范例就是在Linux系统下的superprobe程序。
 
3、长跳转缓冲区(Longjmp buffers)
在C语言中包含了一个简单的检验/恢复系统,称为setjmp/longjmp。意思是在检验点设定“setjmp(buffer)”,用“longjmp(buffer)”来恢复检验点。然而,如果攻击者能够进入缓冲区的空间,那么“longjmp(buffer)”实际上是跳转到攻击者的代码。象 函数指针一样,longjmp缓冲区能够指向任何地方,所以攻击者所要做的就是找到一个可供溢出的缓冲区。
 
代码植入和流程控制:
 
攻击者定位一个可供溢出的自动变量,然后向程序传递一个很大的字符串,在引发缓冲区溢出,改变活动纪录的同时植入了代码。 代码植入和缓冲区溢出不一定要在在一次动作内完成。
 
攻击者可以在一个缓冲区内放置代码,这是不能溢出的缓冲区。然后,攻击者通过溢出另外一个缓冲区来转移程序的指针。这种方法一般用来解决可供溢出的缓冲区不够大(不能放下全部的代码)的情况。 这种方法一般用来解决可供溢出的缓冲区不够大(不能放下全部的代码)的情况。
 
如果攻击者试图使用已经常驻的代码而不是从外部植入代码,他们通常必须把代码作为参数调用。举例来说,在libc(几乎所有的C程序都要它来连接)中的部分代码段会执行“exec(something)”,其中somthing就是参数。攻击者然后使用缓冲区溢出改变程序的参数,然后利用另一个缓冲区溢出使程序指针指向libc中的特定的代码段。
 
 
例子:
原理:
void function(int a, int b, int c) {
   char buffer1[5];
   char buffer2[10];
}
void main() {
 function(1,2,3);
}
 
 
调用function:
pushl $3,pushl $2,pushl $1,call function】,call会把指令指针(IP)也压入栈中. 我们把这被保存的IP称为返回地址(RET)。
function中:
pushl %ebp,movl %esp,%ebp,subl $20,%esp】,将帧指针EBP压入栈中. 然后把当前的SP复制到EBP, 使其成为新的帧指针. 我们把这个被保存的FP叫做SFP. 接下来将SP的值减小, 为局部变量保留空间. 我 们必须牢记:内存只能以字为单位寻址. 在这里一个字是4个字节, 32位. 因此5字节的缓冲区会占用8个字节(2个字)的内存空间, 而10个字节的缓冲区会占用12个字节(3个字)的内存空间. 这就是为什么SP要减掉20的原因. 
 
攻击:

溢出攻击_第1张图片

假如我们输入的buffer1超长了,直接覆盖掉后面的sfp和ret,就可以修改该函数的返回地址了。
 
 
具体案例:
一个正常函数foo,把输入的参数strcpy到缓存buf里。
一个target函数bar,我们要传入一个超过缓冲区buf长度的字符串,执行拷贝后,缓冲区溢出,把ret返回地址修改成函数bar的地址,达到调用函数bar的目的。
 
在foo调用时找到ret address【0x08048499】的值, 我们只要输入一个超长的字符串,覆盖掉0x08048499,变成bar的函数地址0x8048419,就达到了调用bar函数的目的。
 
 

堆溢出

堆是内存的一个区域,它 被应用程序利用并在运行时被动态分配。堆内存与堆栈内存的不同在于它在函数之间更持久稳固。这意味着分配给一个函数的内存会持续保持分配直到完全被释放为 止。这说明一个堆溢出可能发生了但却没被注意到,直到该内存段在后面被使用。

溢出攻击_第2张图片

 

 

格式化字符串

这类错误是指使用printf,sprintf,fprint等函数时,没有使用格式化字符串,比如:正确用法是:

printf ( "%s" ,  input ) 

如果直接写成:

printf ( input )

将会出现漏洞,当input输入一些非法制造的字符时,内存将有可能被改写,执行一些非法指令。

如果不提供参数,prinf将从stack里获取值。

 

1. 泄露内存地址:

Below are some format parameters which can be used and their consequences:

•"%x" Read data from the stack (stack里存着memory address),读取stack里的内容,如”0x08047458“

•"%s" Read character strings from the process' memory,如显示address为0x08047458的memory内的内容

•"%n" Write an integer to locations in the process' memory,把前面参数的数量写入位置。如修改address为0x08047458的memory内的内容。

如:printf("hi%n",&x)——————x=2

 

例如:

printf ( “Buffer size is: (%d) \nData input: %s \n” , strlen (buf) , buf ) ;

当输入为Bob时,输出正常: Buffer size is (16) Data input : Bob

当输入为Bob %x %x时,可看成:

printf ( “Buffer size is: (%d) \n Data input: Bob %x %x \n” , strlen (buf) , buf )

输出:Buffer size is (27) Data input : Bob bffff 8740【stack里的值】

If the format string parameter “%x %x” is inserted in the input string, when the format function parses the argument, the output will display the name Bob, but instead of showing the %x string, the application will show the contents of a memory address. 【%x打出stack的内容——memory address】

 

2. DOS

printf (userName);

printf (%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s);

当地址不存在,程序就会崩溃。

The attacker could insert a sequence of format strings, making the program show the memory address where a lot of other data are stored, then, the attacker increases the possibility that the program will read an illegal address, crashing the program and causing its non-availability.

 

https://www.owasp.org/index.php/Format_string_attack

 

3. 改写

printf的%n格式化说明符它允许向后面一个存储单元写入前面输出数据的总长度,那么只 要前面输出数据的长度(这个长度的控制可以利用格式化说明符的特性,比如%.200d,这样我们就可以控制输 出数据长度为200了,想象一下如果我们用%f.呢?嗬嗬,堆栈地址固然很大,但是我们应该可以构造足够的 %f....用来到达我们需要改写的存储单元)等于我们需要程序跳转到的那个地址(通常是shellcode+nop的区域),而%n恰到好处的将这一地址写入适当位置,那么我们就可以按照我们的意愿改变程序流程了.:) 不过这里有一点需要注意,如果格式化字符串攻时覆盖函数的返回地址,那么实际上我们是去覆盖存储 这个函数返回地址的那块存储空间.也就是说我们是间接的覆盖.这一点很重要,不能混淆.回想一下C语言的指 针。

格式化字符串攻击中覆盖函数返回地址通常有两种选择:1.覆盖临近的一个函数(调用该函数的函数,比如main())的 返回地址.2.覆盖*printf()系列函数自身的返回地址.在这个例子中,两种方式都作了简单分析,并且给出覆盖*prinf()系列函数自身返回地址的Exploit,这样当这个函数执行完毕返回的时候,就可以按照我们的意愿改变程序的流程了.

我们需要知道以下几个信息: 
1.堆栈中存储函数的返回地址的那个存储单元的地址. 
2.shellcode的地址 

 

如:

找位置

% ./fmtme "aaaa %x %x" 
buffer (15): aaaa 1 61616161 
x is 1/0x1 (@ 0x804745c)

 

说明stack内第一个是存的x的值,第二个是我们输入的值

【一般要寻找几个%x,这里恰好是第二个】

 

所以利用这一点

% perl -e 'system "./fmtme", "\x58\x74\x04\x08 %d %n"' 
buffer (5): X1 
x is 5/x05 (@ 0x8047458)

 

 "\x58\x74\x04\x08%d%n" (x, 我们写的地址)

将"\x58\x74\x04\x08“存入buf,跳过x,遇到%n指令 。这个指令从栈堆中取出下一个值,将5写入buf里的内容指向的address(buf此时为0x08047458,则将5写入0x08047458的memory,也就是x的所在地)

 

另:

打印出制定location的值

溢出攻击_第3张图片

 

 

改写任意location的值

将上面的%s换成%n

 

防御:

1. 避免使用不安全的字符串处理函数,比如使用安全的函数代替:

不安全的函数

安全函数

strcpy

strncpy

strcat

strncat

sprintf

_snprintf

gets

fgets

 

 

【Format_String.pdf】
 

 

 

 

 

 

你可能感兴趣的:(溢出)