安全漏洞--字符串格式化(FSV)漏洞分析

来源:互联网 发布:软件测试工程师教学 编辑:程序博客网 时间:2024/05/30 04:18

一 漏洞简介



  格式化字符串漏洞(format string vulnerability),也是一种比较常见的漏洞类型。常出现于c语言格式化字符串一系列函数。比如printfsprintf,fprintf等一系列家族函数。由于此函数对参数类型和个数过滤不严格,导致用户可以构造

任意数据,实现读取写入内存数据,从而实现代码执行。


二 原理分析



至于为什么会产生字符串溢出漏洞呢,我们来看看产生溢出的关键函数。比如说


_Check_return_opt_ _CRTIMP int __cdecl printf(_In_z_ _Printf_format_string_ const char *_Format, ...);


这个输出控制字符串函数。解释一下参数:

Format是个格式化控制字符串,里面控制这个字符串输出的格式。

常见格式控制如下列表




...省略号代表是用户可以控制的参数类型和列表。

关键就在于这个位置,参数类型和数量的不确定性,如果允许用户自己定义这两个参数。

那么就可以经过精心够早实现对程序任意位置的数据读取和写入,那么就能构造出漏洞

利用程序,从而控制我们的程序流程。


涉及的关键控制符

%n功能

是将%n之前printf已经打印的字符个数赋值给传入的指针。通过%n我们就可以修改内存中

的值了。例如: printf("aaaaaaa%n\n",&a);

printf("%d\n",a);

可以发现a的值被printf函数修改为了7。这就是%n的功效了。这是一个不常用到的参数.

它的功能是将%n之前printf已经打印的字符个数赋值给传入的指针。通过%n我们就可以

修改内存中的值了。和%sleak内存一样,只要栈中有我们需要修改的内存的地址就可以

使用格式化字符串的漏洞修改它。当然,如果需要修改的数据是相当大的数值时,我们

可以使用%02001d或者%2001x这种形式。

%p功能

格式控制符“%p”中的p是pointer(指针)的缩写。指针的值是语言实现(编译程序)

相关的,但几乎所有实现中,指针的值都是一个表示地址空间中某个存储器单元的整数。

printf函数族中对于%p一般以十六进制整数方式读取输出指针的值,附加前缀0x。

%$功能

格式化字符串的“$”操作符,其允许我们从格式化字符串中选取一个作为特定的参数。

例如:printf("%3$s", 1, "b", "c", 4);
最终会显示结果“c”。这是因为格式化字符串“%3$s”,它告诉计算机“把格式化字

符串后面第三个参数读取告诉我,然后将参数解释为字符串”。所以,也可以这样做
printf("AAAA%3$n");
printf函数将值“4”(输入的A的数量)写入第三个参数指向的地址。

由此可见格式化字符串漏洞主要是:


1.读取任意地址的值,泄露内存

2.任意地址写入数据,修改内存


主要实现方式是利用格式化串本身也处于栈中,去用直接参数访问找到这个栈中的格式化串。

这个格式化串可以使用一个要写入的内存地址。也就是直接参数访问+%n格式符+长度表示=

向任意地址写入值,这个写入是不能一次写入4字节的,所以可以分两次写入2字节和分四次

写入1字节。其中hhn是写一个字节,hn是写两个字节。n是写四个字节。


三 漏洞测试


windows系统示例


int _tmain(int argc, _TCHAR* argv[]){char str_test1[100];char str_test2[100];scanf("%s",str_test1);printf("%s\n",str_test1);scanf("%s",str_test2);printf(str_test2);return 0;}



编译完成之后,命令行下面测试。发现同样的输入参数,却输出不同的结果。

而且输出了一部分我们不知道的参数和内存地址。

注意不同地方:

1 主要使用了%P参数控制,泄露了内存地址。

2 printf("%s\n",str_test1);//代码采用格式化进行了过滤,所以并没有输出内存数据。

printf(str_test2); //而这行代码却泄露了我们的内存数据。



Linux系统示例

此处的代码里面有三个整数变量,对应的三个值参数分别为10,20,30第四个没有参数。

但是格式化控制符却有参数控制,因此这个函数将会输出栈地址的30后面的数据也就

是栈的下方数据。


#include<stdio.h>int key=30;int main(int argc, char** argv) {int value=10;int num=20; printf("%d,%d,%d,%p\n",value,num,key);    return 0;}



注意看此函数的参数,只有三个参数,格式化控制符却又四个控制,那么第四个%P输出的

是什么呢。



我们来跟进去看看printf函数调用之前的栈布局和数据



同样的原理,如果printf函数的所有参数可以被我们控制,自定义。

这样的话,我们就可以自己控制堆栈数据了。

比如代码这样编写。


#include<stdio.h>int main(int argc, char** argv){    printf(argv[1]);    return 0;}


岂不很危险了。


四 漏洞利用



下面我们来做个例题吧,来熟悉练习一下这个漏洞原理吧。

先正向给大家看一下这道题目的源代码:


/* * format.c *  Created on: Apr 12, 2017 *      Author: 5t4rk */#include<stdio.h>#include<stdlib.h>int nsecret = 0x110;void give_flag(){system("cat /down/key.txt"); }int main(int argc, char** argv){int * pKey = &nsecret;printf(argv[1]);if (nsecret == 2017){give_flag();}return 1;}


阅读分析此源代码,发现有一个函数give_flag,如果实现调用此函数那么就可以直接获

取到机器的shell执行代码权限。但是此处的逻辑必须要满足nsecret==2017这个条件。

才能进入我们的flag获取函数里面,所以此处的关键是如何让nsecret=2017。然后并

没有我们用户直接赋值个nsecret变量的输入和接口,因此直接修改不可能。但是发现

printf函数的直接调用,argv[1]参数。

大家还记前面讲的漏洞函数,看我们能否利用此函数参数进行修改nsecret变量的值为

2017,基于栈的构造原理,如果成功,那么就会直接进入这个函数。先编译此代码:



得到format这个可执行程序。




然后测试。



测试发现nsecret的变量地址是0x804a024

在对栈上的变量是第七个,于是我们知道只要修改第七个地址数据0x110为

2017就能实现我们的结果。



于是我们想到了前面学习的%n可以实现修改内存地址数据。

结合我们找到的位置,顺带采用%$实现准确覆盖,修改数据。

“%02017u%7$n”

在打印数值右侧用0补齐不足位数的方式来补齐足。

./format "$(python -c 'import sys;sys.stdout.write("%02017x%7$hn")')"




0 0
原创粉丝点击