介绍Propolice怎样保护stack-smashing的攻击

               

整理:XUNDI

        低级            ...                         ...
                    |-----------------------------------|
                    | 如果你觉得这中文乱七八糟,JMP 0xAA|
                    |-----------------------------------|
                    | ....................              |
                    |-----------------------------------|
0xAA ------>        |请看文章最后提供原文,             |
                    |如果你觉得还是看不懂原文,JMP 0xBB |
                    |-----------------------------------|
                    |............                       |
                    |-----------------------------------|
0xBB------->        | 请去请教backend,warning3,scz等他们|
                    |如果你请教以后还是不懂, JMP 0xCC  |
                    |-----------------------------------|
                    |..............                     |
                    |-----------------------------------|
0xCC------->        |JMP 0x黄河                         |
                    |-----------------------------------|
          高级         ...                         ...


stack-smashing是一种引起设备停止并且导致允许攻击者侵入系统的攻击方法,一般它被使用在叫缓冲溢出的应用程序漏洞上。stack-smashing攻击是在UNIX安全新闻报道上最常见最普通的攻击方式。在1999年,IIS4.0的那个由EEYE发现的缓冲溢出,使将近1500000系统遭受此漏洞的侵害。

多数应用程序使用C写的时候使用缓冲区(buffer),这个缓冲区是一个保存了一些同样数据类型的数据,通常是字符数组,在堆栈里临时保存这些字符串操作的临时值。stack-smashing攻击通常是提供一个超过实际BUFFER大小的字节数到缓冲区,这就导致了破坏了BUFFER中的内容,这些内容可能就是调用函数的返回地址和函数指针。

这文章提供了系统的解决缓冲溢出攻击的方法,这文章提供的一种叫ProPolice保护方法能自动在应用程序编译的时候插入保护代码.它的主要特征是在对数字的操作下具有比较少的效率支出,保护多种stack-smashing攻击,支持多种处理器。

下面讲讲攻击说明和它们的类别

缓冲溢出漏洞通常在应用程序需要读取外部信息如字符串时,接收缓冲区相对与实际的输入字符串小而且应用程序又不正确检查这些操作时产生。缓冲区在实时运行时分配空间到堆栈,这时候堆栈中保存了一些可执行函数的信息:如本地变量,参数变量,和返回地址。而超长的字符串改动了这些信息,如攻击者把一系列机器语言命令作为字符串插入到堆栈,并使这些字符串覆盖在堆栈中的返回地址为这些命令的地址(我们通常所说的SHELLCODE),这样到函数返回的时候就执行了这些代码。通常这些方法的最终目标是获得有一定权利的SHELL。

图1描述了在一函数调用的时候典型的堆栈结构,在图的在最底下是堆栈指针在堆栈的顶,C语言程序在堆栈中从堆栈顶(就是内存低端)使用下面的顺序来排列:
本地变量,前栈帧指针 (previous frame pointer--前栈帧指针),这对被调函数而言不可见,也就是被压栈保存的%ebp,返回地址,和被调函数的入口参数。其中下面所示的帧栈指针(frame pointer)定位了本帧以及前栈帧的帧指针存储在调用者的栈帧中。


          高址            ... ...
                     |-----------------------------------|
                     |       传递给函数的参数            | 向上是字符
                     |-----------------------------------| 串增长方向
                     |       函数的返回地址 (RET)        |
                     |-----------------------------------|
帧栈指针-->          |       保存的前栈帧指针            |
(frame pointer)      |      (previous frame pointer)     |
                     |-----------------------------------|
                     |            局部变量               | 向下是堆栈
                     |-----------------------------------| 增加方向
                     |             buffer                |
                     |-----------------------------------|
堆栈指针-->          |-----------------------------------|
                     ... ...
           低址

图 1 -- 堆栈结构

图2的函数foo是一个有漏洞的函数,它产生的堆栈结构就如图1所示。这函数读取了环境变量'HOME'的内容到大小为128字节的'buffer'中,但是由于strcpy函数没有正确检查输入的大小,它就能拷贝超过128字节的数据到"buffer"中,想象下如果"HOME"变量有这样的字符串:128字节的41,1, 1, 1, 1, 2, 2, 2,2, 3, 3, 3, 3。这就分配了128个A字符,和0x01010101, 0x02020202, 和0x03030303分别到"buffer","lvar"和前栈帧指针(previous frame pointer)及返回地址(ret)中.(我们假定使用了默认的32位变量和C语言符号.)当foo函数完成它的操作并返回调用foo函数的函数返回地址,也就是覆盖了刚才堆栈中的返回地址,而变为了0x03030303地址,这样如果有恶意代码存放在上面这个0x03030303地址中的话,这恶意代码将以此函数原来的运行级别运行。

void foo()
{
long *lvar;
char buffer[128];
........
strcpy (buffer,getenv("HOME"));
........
}
图 2:一个简单的有缓冲溢出的示例

我们下面介绍下一种典型的攻击方式,攻击者怎样获得应用程序的控制。在第一类中攻击的目标在堆栈中,下面列出了存储在堆栈中的数据并描述攻击模式的使用:

--返回地址
通过改变返回地址的值到恶意代码地址是最流行的攻击方式。

--本地变量

--入口参数变量

这个函数的变量是另一个需要控制的目标,把入口参数或者本地变量指定为攻击代码也是攻击的一种方式,在这种情况下,漏洞一般是通过检查源程序发现的。

第三种函数指针重定向(Function Pointers)
“void (* foo)()”声明了一个返回值为void函数指针的变量foo。函数指针可以用来定位任何地址空间,所以攻击者只需在任何空间内的函数指针附近找到一个能够溢出的缓冲区,然后溢出这个缓冲区来改变函数指针。在某一时刻,当程序通过函数指针调用函数时,程序的流程就按攻击者的意图实现了!它的一个攻击范例就是在Linux系统下的superprobe程序。

改变指针变量而不是指向函数的指针也是有可能获得应用程序的控制,在这种情况下,如图2函数foo,指针变量'lvar'可以改变它指向返回地址的地址。如果在'strcpy'声明(statement)后有一个声明(statement)通过'lvar'修改值的指向,就存在了改变其值到返回地址的可能。

--前栈帧指针(previous frame pointer)

对于对前栈帧指针(previous frame pointer)的攻击也可以获得对应用程序的控制,前栈帧指针(previous frame pointer)和返回地址的联系是基于下面的条件下:
--返回地址的位置由栈帧指针(the frame pointer)决定
--在函数返回时候栈帧指针(the frame pointer)指派了前栈帧指针
(previous frame pointer)的值。
这样一个攻击者可以建立一个含有指向攻击代码的返回地址的伪造帧。他也可以改变前栈帧指针(previous frame pointer)的值而指向伪造帧地址。当函数返回到调用函数时,栈帧指针(frame pointer)会根据上面所述的第二个条件而指向攻击者伪造的帧。也就是说攻击者改变了返回地址。

一些目前存在的相关技术

目前对于缓冲问题的保护已经有不少方案(project)发表,一种方法是下面参考中的[10]:从源程序中检查排除有漏洞代码并从发现的问题中帮助应用程序变的更安全。如在参考[10]中所示地址中有安全审核工具来自动检查源码,它主要是检查和排除一些危险的函数:如strcpy,gets等等,但工具的受限之处是它不检查指针变量的边界。

另一个方案是提供对程序代码潜在的漏洞进行保护的方式,我们可以根据他们怎样保护动机分为四个类别:

1,避免数组的溢出问题
参考中的[7]是对数组边界进行检查方法和参考中的[6]是对内存访问进行检查的方法,这两种方法防止了对分配给数组边界之外访问。因此,这些方法是很安全的一种方法,但是,这种保护的开销比不保护的代码昂贵的很,经过这样优化的代码相对于普通的代码速度慢。

2,禁止攻击代码的执行
``Solar Designer'' 开发的LINUX补丁就是使堆栈区域的不可执行性,因此存储在堆栈中的攻击代码就不能执行了。Solar Designer提供的kernel security patch中是通过减少代码段的长度,来区分堆栈段和代码段的,由于堆栈段的增长方向是从高地址到低地址的,因此堆栈段和代码段地址范围通常是不会重叠的。这样可以有效的避免在堆栈中安排溢出代码,并返回到堆栈中执行的攻击手段。这种方法的好处就是没有额外的开销,不需要应用程序源程序。

这种方法的缺点就是依赖特别的操作系统和处理器。而且特别是把堆栈段标识为不可执行,它不能保护每个区域不可执行,通过把攻击代码放置到别的某处,仍旧有可能让攻击者获得对程序的控制,如把攻击代码静态的插入到已经分配的缓冲区并改变起返回地址,让其指向攻击代码来实现。

参考[5]中-Janus通过限制程序访问操作系统的方法设计了应用程序的安全环境。它保护了一些特权操作,如对于攻击代码不在堆栈里的情况下,但在其他静态区域里怎样保护在特权模式下执行/bin/sh等。它也是依靠操作系统的某些特点,如必须提供一些如strace的调试工具(注:对于strace的用法你可以使用man,但在THC的LKM文章里也解释了不少用法,具体文章可以看http://focus.silversand.net里面的文章)。

3,保护执行控制不传递到已经被攻击的代码

Snarskii已经开发了一个关于FreeBSD的补丁来实现堆栈完整性检查来探测缓冲溢出,见参考[9]。这种方法是采用libc嵌入的方法,不具可移植性。

StackGuard--参考[2], StackShield--参考[11], 和libsafe参考[1]提供可移植的,通用的保护方式.Libsafe提供的方法是基于在软件中层中截获所有一些有漏洞的库函数调用,如gets,strcpy等等。

StackGuard是探测是否有攻击者对返回地址的攻击,它将一个"canary"值(一个单字)放到返回地址的前面,如果当函数返回时,发现这个canary的值被改变了,就证明可能有人正在试图进行缓冲区溢出攻击,程序会立刻响应,发送一则入侵警告消息给syslogd,然后停止工作。

StackShield是做法是创建一个特别的堆栈--(不能缓冲溢出的地方)用来储存函数返回地址的一份拷贝。它在受保护的函数的开头和结尾分别增加一段代码,开头处的代码用来将函数返回地址拷贝到一个特殊的表中,而结尾处的代码用来将返回地址从表中拷贝回堆栈。因此函数执行流程不会改变,将总是正确返回到主调函数中。由于没有比较堆栈中的返回地址与保存的是否相同,因此并不能得知是否发生了堆栈溢出。

但这三种方法都有其破坏的地方,有关资料可以参考warning3的'绕过StackGuard和StackShield保护',至于Libsafe,就象上面描述的,光光对函数的检查是远远不能很好保护的。

在下面的图7中会对Propolice和这些方法做一次比较。

堆栈保护模式

按照上面所说的,针对stack-smashing攻击我们要有四个区域需要保护:本地入口参数,返回地址,前栈帧指针(previous frame pointer)和本地变量。

下面介绍的guard变量是为了防止改变前三个区域,这个技术源自StackGuard方案--参考[2]的设计,它设计在返回地址之后立即插入guard变量,在ProPolice里采用的方法与Stackguard不同的是guard的位置和函数指针的保护,ProPolice中guard将被插入到next to the previous frame pointer 并且居于攻击者能开始破坏堆栈之前的数组。

这里将使用源代码的方式简明的阐述,提供一个函数的源代码,一个预处理步骤会把下面的代码片段插入到适当的位置:本地变量声明部分,入口点,和退出点,如:

--本地变量声明部分(declaration part of local variables )

volatile int guard;

--入口点(the entry point )

guard = guard_value;

--退出点(the exit point )

if (guard != guard_value) {
/* output error log */
/* halt execution */
}

修改后的源代码程序如图3所示,注意guard变量的位置是在字符串buffer之前,入口点存储了一个攻击者不知道的值,并在退出的时候校验它,如果不相等就会报警到系统并记录信息到数据库,还会停止代码的执行。

void foo()
{
volatile int guard;
char buf[128]

guard=guard_value;
................
if (guard!=guard_value) {
/*output error log*/
/*halt execution*/
}
}
图3:增加了保护代码后的程序

上面简单的解释了用源码转化进行保护,但在事实上的实现比较困难,因为函数通常有很多退出点和guard必须定位在堆栈中字符缓冲之前。而ProPolice的实现是采用了GCC编译器的intermediate code translator
(这个我查了不少地方,感觉还是模糊,所以请大家赐教!),根据intermediate language的说明:对于每个函数只有一个退出点并且每个变量的位置已经确定。

这里的guard值是一个不能让攻击者知道的值,如果他知道这个值,攻击者就可以在buffer和前栈帧指针(previous frame pointer)之间填充此值,就导致对guard值检验正确,接着就修改返回地址。

ProPolice的实现方法是为guard值挑选一个随机数,这个随机数是由应用程序初始化时计算而得,而且不能用普通用户发现。这个数字必须是一个不能预测的随机数,LINUX有一个随机数发生器,它的实现是采用一个叫/dev/urandom和/dev/random的设备,这个设备使用环境噪音来生成数字,所以可以提供一个不能预测的随机数。

安全函数模型

下面是图4介绍的一个安全函数模型,它能采用下面的方式处理堆栈中的使用限制:

--位置(A) 没有数组和指针变量
--位置(B) 有数组或者包含结构的数组
--位置(C) 没有数组


                  高址       ...                 ...
                     |-----------------------------------|
                     |          传递给函数的参数 (A)     |
                     |-----------------------------------|
                     |          函数的返回地址 (RET)     |
                     |-----------------------------------|
         帧栈指针--> |          保存的前栈帧指针         |
      (frame pointer)|       (previous frame pointer)    |
                     |-----------------------------------|
                     |             guard                 | 向下是堆栈
                     |-----------------------------------| 增加方向
                     |            数组(B)                |
                     |-----------------------------------|
                     |          本地变量(C)              |
         堆栈指针--> |-----------------------------------|
                  ...                        ...
                  低址

图4:安全帧结构

这个模型具有下面的特性:

1,当函数返回的时候函数帧之外的内存区域不会被破坏。

只有位置(B)是攻击者可以开始破坏堆栈的有漏洞的位置,对于超过帧函数后的区域(如想破坏返回地址),都将在校验guard值时被检测到,如果出现这种破坏,程序的执行将停止。

2,一个在函数帧之外的指针变量上的攻击将不会成功。

攻击能成功的话就需要下面的条件:1)攻击者改变了函数指针的值; 2)他使用函数指针调用一个函数,为了获得这第二个条件,函数指针必须是可见的,但我们的假设是说这个信息是超越函数的范围之外的,所以,第二个条件不能成立,所以攻击往往失败。

3,在函数帧中对于指针变量的攻击将不会成功。

位置(B)是唯一可以遭受stack-smashing攻击的位置,文章说位置(C)是安全的位置,我的理解是,位置(B)这个数组假如缓冲溢出,字符增长是在堆栈中向高地址的走向,所以是安全的。

指针保护

下面图5示例的是另一个有漏洞的函数,攻击者可以通过替代函数指针为 攻击代码的地址而获得对应用程序的控制:

void bar( void (*func1)() )
{
void (*func2)();
char buf[128];
.........
strcpy(buf,getenv("HOME"));
(*func1)();(*func2)();
}
图5:会被攻击函数指针方法攻击的示例

为了保护对函数指针的stack-smashing攻击,我们改变每个变量在堆栈中的位置来建立安全的函数模型。

C语言中没有限制本地变量的顺序,但它不允许改变入口参数的位置,对入口参数的限制可以通过建立一个新的本地变量声明,拷贝参数"func1"给这新的变量,使用新的本地变量来改变"func1"的参考,图6显示了转化的结果:

void bar( void (*tmpfunc1)() )
{
char buf[128];
void (*func2)();
void (*func1); func1=tmpfunc1;
.........
strcpy(buf,getenv("HOME"));
(*func1)();(*func2)();
}
图5:能保护攻击函数指针方法的示例

一些优化

下面是讨论了为减少使用guard产生的额外支出效率的优化方法。下面的假设介绍知识为了这个优化目的,当碰到下面这些假设的时候,一些保护代码可以安全的去掉。

假设 1

源代码正确的类型声明及遵循类型转换规则。

如:一个整数变量永远是存储整数值,不能存储字符值。不正确的操作将在程序执行的时候出现内存保护错误。

假设 2

只有字符数组会引起缓冲溢出

缓冲溢出出现在对数组进行分配操作的时候没有进行边界检查。分配操作通常时候终止符来检查边界问题。多数终止符仅在字符函数中使用,如在 gets()函数中使用null字符或者换行字符。

如果除设想1之外碰到设想2,除了函数声明字符缓冲为本地变量或者参数, guard的保护就会忽略。这是为了减少运行的开销,就想最开头说的,尤其对数字处理更有利,这是因为这些函数通常使用数字数组而且会在短时间内多次调用。

局限性

堆栈保护的方法是通过程序转化获得的主要是转化一些有漏洞的函数为没有漏洞的函数。但这些转化在某些情况下不是永远有效。

如果一个结构包含了指针变量和字符数组,指针就得不到保护,因为改变结构成员的顺序是被禁止的。

其他还有对保护指针变量的局限性。当参数声明为变量参数,即函数参数数目可变和类型可变。这样的用法指针变量在编译时候就不能被判断,而是在执行阶段来判断。

各种技术的比较


图7显示了各种保护技术的比较:
---------------------------------------------------------------------------------
|Description              |None | ProPolice | libsafe | StackGuard | StackShield |
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
|Protection Effectiveness                                                        |
---------------------------------------------------------------------------------
|return address           | NO  | Yes       | Yes     | Yes        | Yes1        |
|pre. frame pointer       | NO  | Yes       | Yes     | No         | Yes1        |
|argument                 | NO  | Yes       | Yes     | No         | No          |
|local variable           | NO  | Yes2      | No      | No         | No          |
|string operation coverage| NO  | ALL       |Not all  | All        | All         |
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
|Implementation characteristics                                                  |
---------------------------------------------------------------------------------
|OS independence          | --  | Yes       | No3     | Maybe4     | Maybe4      |
|Processor independence   | --  | Yes       | Yes     | No         | No          |
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
|Other characteristics                                                           |
---------------------------------------------------------------------------------
|Performance overhead     | none| Very low |Very low  | Low        | Low         |
|source code needed       | --  | Yes      | No       | Yes        | Yes         |
---------------------------------------------------------------------------------

1,Protects only the function within a limited depth of function calls
2,It cannot protect a pointer variable in a structure that contains a vulnerable string
3,Needs dynamic link library
4,Intel processor only

图7:各种技术的特点比较

protection effectiveness 描述了对堆栈中所保护的区域的覆盖的保护(有点拗口),及针对字符函数引起的缓冲溢出的覆盖。保护区域其中标为"No"的意思是有些特别的区域不能被保护,如针对于SuperProbe2.11的EXPLOIT程序攻击是从参数变量中攻击函数指针变量的指向,因此STACKGUARD就不能很好的阻止攻击而导致获得ROOT SHELL。

Libsafe不能保护所有字符函数,也不能保护字符操作直接使用指针,如针对于``xtermdistributed with RedHat5.2''的EXPLOIT程序使用了libtermcap库中的tgetent()函数的一个缓冲溢出。

implementation characteristics 表示了ProPolice进行独立于多个系统和处理器的保护机制。

测试评价

图8显示了攻击的测试结果,其中前三个是通过攻击返回地址获得ROOT SHELL而最后一个是攻击指向带函数指针的结构参数获得ROOT SHELL的,从下面的表中可以看到,这些被保护后都有效的终止执行。

虽然没有提供更多更全面的EXPLOIT列表,但足可以校验保护方式工作正常,受保护后都回显示"a stack-smashing attack had been detected"的信息,并且没有获得ROOT SHELL。

-----------------------------------------------------------------------------
| EXPLOIT程序     | 描述                         | 攻击结果       |保护后结果|
-----------------------------------------------------------------------------
| xlockkmore3.10  |Lock an X windows display     | ROOT SHELL     | 终止     |
-----------------------------------------------------------------------------
| Elm 2.003       |ELM mail user agent           | ROOT SHELL     | 终止     |
-----------------------------------------------------------------------------
| Perl 5.003      |Perl script language          | ROOT SHELL     | 终止     |
-----------------------------------------------------------------------------
| SuperProbe 2.11 |Probes video hardware         | ROOT SHELL     | 终止     |
-----------------------------------------------------------------------------

图8:测试评价


下面是文章对效率额外支出的评估,时间的关系就没有进行整理。有兴趣朋友就去看原文-参考[12]

参考:

1 A. Baratloo, N. Singh, and T. Tsai.
Transparent Run-Time Defense Against Stack Smashing Attacks.
In Proceedings of the USENIX Annual Technical Conference, June 2000.
to be appeard.

2 C. Cowan, C. Pu, D. Maier, H. Hinton, J. Walpole, P. Bakke, A. G. Steve Beattie, P. Wagle, and Q. Zhang.
StackGuard: Automatic Adaptive Detection and Prevention of Buffer-Overflow Attacks.
In Proceedings in the 7th USENIX Security Symposium, January 1998.

3 S. Designer".
Non-executable user stack.
http://www.false.com/security/linux/.

4 "eEye-Digital Security Team".
Iis4.0 remote exploit.
http://www.eeye.com/, 1999.

5 I. Goldberg, D. Wagner, R. Thomas, and E. A. Brewer.
A secure environment for untrusted helper applications.
In In Proceedings of the 6th USENIX Security Symposium, 1996.

6 R. Hastings and B. Joyce.
Purify: Fast Detection of Memory Leaks and Access Errors.
In Proceedings of the Winter USENIX Conference. 1992.

7 R. Jones and P. Kelly.
Bounds Checking for C.
http://www-ala.doc.ic.ac.uk/ phjk/BoundsChecking.html, July 1995.

8 Perlbench.
http://www.metacard.com/perlbench.html.

9 A. Snarskii.
FreeBSD stack integrity patch.
ftp://ftp.lucky.net/pub/unix/local/libc-letter, 1997.

10 "The Software Security Group.
Its4: Open source software security tool.
http://www.rstcorp.com/its4/.

11
"Vendicator".
Stack shield: A "stack smashing" technique protection tool for linux.
http://www.angelfire.com/sk/stackshield/.

12 Hiroaki Etoh and Kunikazu Yoda
IBM Research Division, Tokyo Research Laboratory,
1623-14 Shimotsuruma, Yamato, Kanagawa 242-8502, Japan
{etoh,yoda}@jp.ibm.com
http://www.trl.ibm.co.jp/projects/security/propolice/

13 关于缓冲溢出的中文资料请看http://www.nsfocus.com中的资料

最后语:很奇怪我没有找到Propolice这个project,不知道是不是要EMAIL想作者要,或者
是我没有找到.:( 有人获得的时候不要忘了MAIL我一份。xundi1@21cn.com

                                                                 

 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值