FORTIFY_SOURCE



0. Introduction

2004年9月,RedHat的几位软件工程师提交了一个针对GCC和GLibc的新补丁,其作用是为内存和字符串函数提供一种轻量级的缓冲区溢出保护机制。它可以通过定义\(\_FORTIFY\_SOURCE\)标志来配置,因此常被称为\(FORTIFY\_SOURCE\),在目前主流的linux操作系统中都能够见到他的身影,包括Ubuntu,Feroda,Redhat,Centos等等……并且\(FORTIFY\_SOURCE\)在安卓平台有着更为广泛的应用,这可能与安卓特殊的管理机制有关

在该补丁的官方描述中,我们能够得到作者对这个patch的部分描述:

The intent of this patch is to add some checks that have no or non-measurable runtime overhead, so something that can be enabled for all programs and libraries in an operating system.

The patch certainly doesn’t prevent all buffer overflows, but should prevent many common ones. It works by computing a constant (conservative) number of bytes remaining to the end of object(s) each destination pointer passed to memory and string functions, if possible checking for overflows at compile time, if not possible passing that constant size to special checking alternatives of the memory/stringfunctions.

when the above GCC 4.0+ and -D_FORTIFY_SOURCE=1 is used at optimization level 1 and above, security measures that shouldn’t change behaviour of conforming programs are taken. With -D_FORTIFY_SOURCE=2 some more checking is added, but some conforming programs might fail.

基本总结如下:

  1. FORTIFY_SOURCE是一种对缓冲区溢出以及格式化字符串的轻量级的保护机制 ,但并不是所有类型的缓冲区溢出都可以用这个它来检测
  2. 它保护C和C ++代码
  3. 没有大量的运行时的开销,不会影响到系统的性能
  4. 这种保护机制并不仅仅够应用于glibc,只要将相应的头文件string.h,stdio.h打上补丁,也能够实现这种保护,在gcc中由于它使用了-D_FORTIFY_SOURCE来开启保护,因此得名

1. Glibc’s FORTIFY_SOURCE

Patch

FORTIFY_SOURCE 目前能够保护的函数有:

可见它保护的函数基本分成两大类,一类是对cpy的内存拷贝类型的函数,另一类是格式化字符串FORTIFY_SOURCE打过补丁的代码基本储存在glibc/debug/ 的目录下

MemCopy

对内存拷贝型的strcpy,memcpy等函数,在用户调用过程中,基本会出现以下几种使用情况,FORTIFY_SOURCE将对以下几种行为进行判定:
<1>

定义信息信息明确,认为安全,不需要在编译或者运行时进行越界检查

<2>

类似此类内存拷贝,不一定正确,并且需要在运行时对长度进行检查,防止Attacker越界读写
FORTIFY_SOURCE 将此类不安全的函数替换为相对安全的__memcpy_chk or strcpy_chk函数,检测如果发生缓冲区溢出,就会调用内置的chk_fail ()函数,并且进程Abort

<3>

认为安全,编译器会在编译的过程中对缓冲区溢出进行检查,并且由于有长度限制,Attacker无法肆意溢出缓冲区。

<4>

在此类调用的方式下,编译出无从得知缓冲区的长度,在编译时不做检查,运行时无法检查,这种调用往往可能导致缓冲区的溢出。
在gcc的选项中,-D_FORTIFY_SOURCE=2与-D_FORTIFY_SOURCE=1是两种不同强度的开启FORTIFY_SOURCE的方式,前者认为其为BOF,并报错,而后者认为安全。

上述四类是FORTIFY_SOURCE对内存拷贝类函数的判断标准,大量的Patch都打在__*_chk函数中
以memcpy函数为例:

在进入到memcpy函数之前就压上内存拷贝的安全值size与拷贝的长度size,判断如果失败,则进入__chk_fail()中,打印,程序crash
这样对于不太安全的函数加入了一个轻量级的缓冲区溢出检测

Printf

在windows下,printf等格式化字符串类的函数默认时不能够使用%n参数的,然而glibc中默认可以(-D_FORTIFY_SOURCE=1),并且在glibc中能够任意指定格式化串的参数,例如%4$p,在FORTIFY_SOURCE=2的编译条件下,glibc启动了FORTIFY_SOURCE的相关保护
<1>glibc在调用printf时,更改为调用printf_chk函数

并且将第一个参数flag置为1

在参数置为1后,___printf_chk函数将stdout文件结构中的 IOFLAGS2_置为_IO_FLAGS2_FORTIFY=4,启动FORTIFY检查

<2>
IOFLAGS2_FORTIFY bit标志置位后,在vfprintf函数(几乎所有的格式化串类的底层函数)中将开启两个保护
第一个:限制%n,如果%n写入一个拥有写权限的内存空间,如栈,bss段,就会终止报错

<3>
第二个在vfprintf的补丁

首先在glibc中获取到格式化串的最大参数为nargs,在 IOFLAGS2_FORTIFY开启的情况下
利用alloc函数在栈中动态分配内存并将内存中的值初始化为-1,用于表示相应位置的参数在printf中使用情况
如果被相应参数使用,堆栈中相应位置就不再是-1
通过检查最大的nargs之前的栈中的值,检测他们是否是-1
如果是-1,说明该位置的参数没有用到过,报错
如果不是,正常
大致说来就是如果你想用%4$x就必须用 \%3$x,\%2$x,\%1$x,不能够单独使用\%4$x

如此以来,linux下格式化字符串漏洞,就很难施展拳脚了

Example

在gcc的编译选项中,-D_FORTIFY_SOURCE=1 and -D_FORTIFY_SOURCE=2开启该保护,我们的example程序如下:

在example中,有stcpy代表memcopy函数与printf代表格式化字符串函数
我们编译出三个binary:

我们分别用checksec来查看三个binary的保护情况:
fortify_test0

-D_FORTIFY_SOURCE=0没有开启相关保护,strcpy与printf函数都是未保护状态
在chencksec的脚本也可见,它一共检测libc中的78个函数,该binary用到了47个函数,可以被FORTIFY_SOURCE的有4个函数
fortify_test1

-D_FORTIFY_SOURCE=1部分开启,strcpy被替换成__strcpy_chk函数
fortify_test2

-D_FORTIFY_SOURCE=2保护全部开启,strcpy与printf函数都被替换成相应的保护函数
应用程序默认时不会开启FORTIFY_SOURCE保护的,我们使用gcc默认选项编译fortify_test

我们尝试使得strcpy溢出,运行fortify_test1,程序dump

我们尝试使用%n,运行fortify_test2,程序同样dump

2. Bypassing FORTIFY_SOURCE

首先,遇到FORTIFY_SOURCE保护的第一反应是应该绕过被保护的函数,从其他方面考虑进行漏洞利用,但是这并并不代表FORTIFY_SOURCE无法绕过

CVE-2012-0809

绕过FORTIFY_SOURCE中的格式化字符串保护部分,利用vfprintf中的一个Interger Overflow
其中参考A Eulogy for Format Strings
当FORTIFY_SOURCE=2时开启了两个保护
其中一个是不允许使用%n,另一个限制最大的参数

Bypass的基本思路:

_IO_FLAGS2_FORTIFY=0

利用vfprintf中存在一个任意的4bit写,巧妙使用,将FORTIFY_SOURCE的flag覆盖为0,从而关闭FORTIFY_SOURCE
首先_IO_FLAGS2_FORTIFY 4在IO_FILE的结构中,而这个结构位于栈上

其次,vfprintf中存在任意4bit写的漏洞

简单解释这段vfprintf 4bit写的含义为: args_type[ ATTACKER_OFFSET ] = 0x00000000;
其中ATTACKER_OFFSET 是格式化字符串中的width_arg
举例来说:
如果我们输入的格式化串是:其中格式化字符串的width argument是269096872
\[
\%1$*269096872$x
\]
那么vfprintf就会将

这个偏移处地址的值写为0,如果我们计算好此时stack与_IO_FLAGS2_FORTIFY的偏移,就可以顺利将FORTIFY的标志改成0,这样我们可以使用%n进行任意地址写
但是,在vfpintf的代码中

可见*args_type = alloca (nargs * sizeof (int));*时根据当前输入的格式化字符串的最大参数nargs分配在栈上的空间,当我们输入的nargs很大,栈空间会发生移位,下一步的 memset就会造成程序crash
绕过它的方式是利用一个integer overflow
由于发生整形溢出nargs * sizeof (int)=0,栈空间不会移位,并且memset的长度是0,相当于没有执行
我们可以用一个’$‘来结束它
这样一个整形溢出的方式使得我们能够正确修改_IO_FLAGS2_FORTIFY=0,从而使用%n

nargs=0

下一步我们需要绕过第二个Patch

在第二个Patch中,如果堆栈正确对齐,则该循环的终止会自动发生。 循环将超出alloca创建的缓冲区的边界,并在以下情况下自动终止:nargs:设置为0,前提是:nargs:由编译器存储在堆栈上。 如果没有这样做,将会触发一个assert()statemet,以防止利用。
所以我们再次利用上面的任意4bit写成0,确定与它在栈上的偏移,再将它写为0,绕过该Patch

  1. Turn off the IO_FORTIFY_SOURCE bit to allow %n from a writeable address
  2. Set nargs=0 to skip the value-filling loop.

整个过程与上半部分基本类似

综上,我们绕过FORTIFY_SOURCE分成了以下步骤:

  1. 确定_IO_FLAGS2_FORTIFY 在与当前栈的偏移
  2. 利用vfprintf中的整形溢出任意地址写位0,绕过FORTIFY_SOURCE第一层保护
  3. 确定nargs在栈中的位置
  4. 再次利用vfprintf中的任意地址写,绕过FORTIFY_SOURCE第二层保护

3. Exploitation

在Nelbula level18中的题目中就可以利用Bypass FORTIFY_SOURCE实现getshell
首先level18的源代码中存在一个格式化字符串漏洞:

binary开启了FORTIFY_SOURCE,所以我们需要想办法将其绕过,并利用printf函数进行getshell
关闭ASLR后
我们首先要定位_IO_FLAGS2_FORTIFY的偏移

首先,填入大整数 width argument 对程序进行测试

IOFLAGS2_FORTIFY的地址为0xbfffecf8c,栈地址在edx寄存中,可计算出 width argument=2848

成功修改后,程序会因为Assertion报错从而abort

现在我们需要寻找到偏移修改 nargs
我们找到nargs在内存的地址在0xbfffca88,并计算出能够覆盖到它的偏移

为了正确利用,由于栈地址在高端地址,部分操作会造成越界访问,所以我们设置一个很大的环境变量,让栈的地址下落,这样就不会出现该情况

如上图,我们顺利地绕过了FORTIFY_SOURCE,输入了我们想要的格式化串,程序没有报错crash
实现了在FORTIFY_SOURCE=2情况下格式化字符串的漏洞利用

4. Afterword

FORTIFY_SOURCE是一种非常”便宜”的保护,它是一种轻量级的保护机制,能够保护大多数的函数不被攻击者攻击,并且Bypass是比较困难的
A Eulogy for Format Strings一文中,作者利用了vfprintf的一个整形溢出,两步将FORTIFY_SOURCE=2绕过,整个过程非常精彩,值得我们学习与思考
但同样我们要意识到该方式是受到了很大的系统环境的限制的,很多情形下不可能成功
综合来说,FORTIFY_SOURCE很有效果,并且在一般我们遇到该保护时,最好的方式就是避过它所保护的所有函数,寻找其他可以进行漏洞利用的结构,相对明智一些

关于Bypass FORTIFY_SOURCE,资料较少,CVE-2012-0809是一个典型,大家可以深入学习

5.Reference

A Eulogy for Format Strings
Exploiting Sudo format string vunerability
Exploit Exercise – Format String FORTIFY_SOURCE Bypass

发表评论