1

Pwn-tip | Format String(格式化字符串)漏洞

 2 years ago
source link: https://jianmou.github.io/jm-miniproject-pwn-1/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Pwn-tip | Format String(格式化字符串)漏洞

Format String(格式化字符串)漏洞

格式化信息有某种字符串来描述,称为格式化字符串。格式化字符串漏洞的根源在于未对用户提供的输入进行验证。

格式化信息有某种字符串来描述,称为格式化字符串。(格式化字符串是用一种功能有限的数据处理语言来描述,以便于描述输出的格式)

格式化字符串漏洞的根源在于未对用户提供的输入进行验证。参数个数不定的常见函数集合是printf函数族:printf,sprintf,snprintf,fprintf,vprintf等。

C 库函数 - printf():C 库函数 int printf(const char format, …) 发送格式化输出到标准输出 stdout。
printf()函数的调用格式为:printf(“<格式化字符串>”, <参量表>);
int printf(const char
format, …)
format – 这是字符串,包含了要被写入到标准输出 stdout 的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。format 标签属性是 %[flags][width][.precision][length]specifier,具体讲解如下:
Alt textAlt text

具体原理:当printf在输出格式化字符串的时候,会维护一个内部指针,当printf逐步将格式化字符串的字符打印到屏幕,当遇到%的时候,printf会期望它后面跟着一个格式字符串,因此会递增内部字符串以抓取格式控制符的输入值。这就是问题所在,printf无法知道栈上是否放置了正确数量的变量供它操作,如果没有足够的变量可供操作,而指针按正常情况下递增,就会产生越界访问。甚至由于%n的问题,可导致任意地址读写。

案例 - 编译一道简单的Pwn的题目(X64编译)

源代码如下:
Alt textAlt text
使用gcc编译一下,生成pwn.out文件:
Alt textAlt text
虽然爆出了一个警告,但是仍然可以使用。好像是因为gcc版本高一点的会在编译的时候检查里面的语法,好像也可以关闭这种检查…有强迫症的可以自行查一下0.0.

-fstack-protector:启用堆栈保护,不过只为局部变量中含有char数组的函数插入保护代码; -fstack-protector-all:启用堆栈保护,为所有函数插入保护代码; -fno-stack-protector:禁用堆栈保护;

顺便提一下我编译的环境:
Alt textAlt text
运行一下:
Alt textAlt text
运行没有问题,我们发现输入的数据都被解释成了格式化字符串,这个时候就要测试一下有没有Format String漏洞,我们静态看一下:
Alt textAlt text
看汇编不明显,我们用堆栈图表示:
Alt textAlt text
这里我们还原一下c语言的关键点:
int a int* p(p是指向int型变量的指针) a = 0 p = &a(p指向a变量的int型指针,修改*p就相当于修改a)
在这里我们看到一个比较(cmp),如果a=2000,就能拿到flag。前面提到了修改p就相当于修改a,我们没有办法直接修改a的值(原因:)所以我们就只能修改p(p地址里面的数据),我们算一下我们需要多少个参数才可以到这个位置:
(112-8)/8=13
通常利用格式化字符串漏洞写入内存的说明符是%n,它可以向给定变量的地址写入当前已写入的字符个数,作为相应的参数,所以我们的利用代码应该是:
%2000x%13$n
但是最后的效果不是很好。。
Alt textAlt text
Segmentation fault (core dumped)多为内存不当(空指针、野指针的读写操作,数组越界访问,破坏常量等)操作造成。
想想不知道哪里出问题了,就只能一个一个来打印一下试试:
Alt textAlt text
(%x:从栈中读取数据,每次四个字节,%s:打印参数指向地址里面的内容。%p:不仅显示栈,还会显示程序是32位还是64位,打印出参数的内容)
看不出来什么情况…动态调试一下,当我们调试到:
Alt textAlt text
我们看到现在寄存器的值如下:
Alt textAlt text
最终运行的结果如下:
Alt textAlt text
0x1 0x7fe4c61e9790 0xa (nil) 0x7fe4c6409700 0x7025702570257025 0x702570257025
这个时候才想起来,在X86-64中,所有寄存器都是64位,%rdi,%rsi,%rdx,%rcx,%r8,%r9 用作函数参数,依次对应第1参数,第2参数…如果函数的参数个数超过6个,则超过的参数直接使用栈来传递。所以应该是13+6=19个参数,所以利用的代码应该是:%2000x%19$n,我们试验一下:
Alt textAlt text
嗯,到此为止,已经可以了。

网上出现的都是X86编译的,X64编译的有一点坑需要踩…
补救措施也很简单:
printf(“s%”, a)
PS:尽量不要使用printf族的函数
最后给一个来源于《软件安全24宗罪》的提示:

要使用固定的格式化字符串,或者来自可信源的格式化字符串。
要检查并限定locale的请求为有效值。
要注意编译器给出的警告和错误(我前面在编译的时候出现过那个警告,虽然也编译成功了,但是很多不注重安全,只注重效果的大佬们可能赶项目,只注重功能的开发)
不要将用户输入直接作为格式化字符串传给格式化函数。
考虑使用受此漏洞影响较小的高级语言。

分享到

评论


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK