How printf works——浅谈格式化串漏洞

来源:互联网 发布:软件开发实例java 编辑:程序博客网 时间:2024/05/22 00:30

以后的文章可能都会发在github博客上了,现在暂时还懒得弄域名和服务器。地址:http://jameeeees.github.io/

How printf works——浅谈格式化串漏洞

0x00 简介

格式化字符串漏洞由来已久,其主要原因是程序员在使用printf时未将格式化串的个数
和变量的个数相匹配,给了黑客们可乘之机。
我们可以先来看看printf函数的声明

int printf (const char *__format, ...)

很奇怪吧,printf的argument中有一个...,这表示printf的argument的个数
是没有限制的,写多少个都没事。

比如下面这段代码是可以成功运行的,用gcc编译不会出现任何错误。

int main(){    char * s = "hello";    printf(s,s,s);}

0x01 原理

printf函数和一般函数不太相同,因为它的argument(包括格式化串和变量)可以是任意
个,那么当程序被编译成二进制代码之后,CPU会知道格式化串和变量的分界线吗?肯定是
不知道的。假如程序员在写程序时使用了printf(something)这种写法的话,相
当于没有限定自己的格式化串,那么我们就可以自己输入格式化串,此时CPU就会被误导,
以为自己要往高地址方向读很多个变量,从而我们就可以读出栈上原本我们看不到的数据。

int id = 1000,age = 25;char * name = "Bob Smith";printf("ID: %d, Name %s, Age %d\n",id,name,age);

在这段代码里,我们可以看到格式化串共有三个,分别是%d,%s和%d,在printf
函数执行的过程中,会有一个指针依次遍历这三个格式化串。当指针指向第一个%d的同时
另一个指针指向id,也就是图中的1000;当指针指向%s时,另一个指针指向图中的0x5000
这里我们将0x5000假设为字符串Bob Smith的首地址。

可以看出,格式化串和变量的个数应该是一一对应的,但是假如格式化串的个数多于变量
的个数呢,那么栈中的指针会继续往内存的高地址方向指,甚至一直读到这个函数的返回
地址。
比如我们定义的是

printf("ID: %d, Name %s, Age %d\n",id,name);

那么当程序运行时,它就会读出与name相邻的数据,而在正常情况下,这个数据我们是读
不出来的。

0x02 应用

1.读内存

我们可以通过一个小例子来实践一下:

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

编译并运行:

a.exe %p,%p,%p,%p00000000,7FFDE000,00400080,0029FF1C

可以看到,我们成功地打印出了栈中原本我们看不到的信息。

2.写内存

在printf函数中,有一个鲜为人知的控制符%n,它可以将输出的内容写回到一个变量中去。
具体例子:

int main(){    int b = 0;    printf("hello%n",&b);    printf("\n%d\n",b);}

这段代码执行之后,可以看到b的值被修改成了5,这是因为%n控制符会将字符串hello的
长度写回到变量b中。
可以看出,格式化串漏洞可以改写内存中的任意数值,这对于修改函数返回地址,劫持
进程,执行shellcode等都是具有很大的帮助的。
但是,由于字符串漏洞的原因比较单一,而且容易被静态检查,在编译器不断优化的今天,
如vs就默认禁用了printf中的%n控制符,所以格式化串漏洞的应用相对较少。

参考资料

1.SEED Project

2.《0day安全:软件漏洞分析技术》

0 0