一.什么是格式化字符串
格式化字符串就是使用Format函数将指定字符串转换为想要输出的格式
1.格式化字符串函数
①常见的有格式化字符串函数有
- 输入
- scanf
- 输出
函数 | 基本介绍 |
printf | 输出到stdout |
printf | 输出到 stdout |
fprintf | 输出到指定 FILE 流 |
vprintf | 根据参数列表格式化输出到 stdout |
vfprintf | 根据参数列表格式化输出到指定 FILE 流 |
sprintf | 输出到字符串 |
snprintf | 输出指定字节数到字符串 |
vsprintf | 根据参数列表格式化输出到字符串 |
vsnprintf | 根据参数列表格式化输出指定字节到字符串 |
setproctitle | 设置 argv |
syslog | 输出日志 |
err, verr, warn, vwarn 等 | 。。。 |
2.格式化字符串
①常见的格式化字符串
字符串 | 类型 | 表示 |
d | 4-byte | 整数 |
u | 4-byte | 无符号整数 |
x | 4-byte | 十六进制数 |
s | 4-byte ptr | 字符串 |
c 1-byte | 单个字符 | |
hh | 1-byte | 限定输出格式为8位 |
h | 2byte | 限定输出格式为16位 |
I | 4byte | 限定输出格式为32位 |
II | 8-byte | 限定输出格式为64位 |
②常见格式化字符串格式(摘自CTF Wiki)
%[parameter][flags][field width][.precision][length]type
- parameter
- n$,获取格式化字符串中的指定参数
- flags(可为0个或多个)
- field width
- 输出的最小宽度
- precision
- 输出的最大长度
- length,输出的长度
- hh,输出一个字节
- h,输出一个双字节
- type
- d/i,有符号整数
- u,无符号整数
- x/X,16 进制 unsigned int 。x 使用小写字母;X 使用大写字母。如果指定了精度,则输出的数字不足时在左侧补 0。默认精度为 1。精度为 0 且值为 0,则输出为空。
- o,8 进制 unsigned int 。如果指定了精度,则输出的数字不足时在左侧补 0。默认精度为 1。精度为 0 且值为 0,则输出为空。
- s,如果没有用 l 标志,输出 null 结尾字符串直到精度规定的上限;如果没有指定精度,则输出所有字节。如果用了 l 标志,则对应函数参数指向 wchar_t 型的数组,输出时把每个宽字符转化为多字节字符,相当于调用 wcrtomb 函数。
- c,如果没有用 l 标志,把 int 参数转为 unsigned char 型输出;如果用了 l 标志,把 wint_t 参数转为包含两个元素的 wchart_t 数组,其中第一个元素包含要输出的字符,第二个元素为 null 宽字符。
- p, void * 型,输出对应变量的值。printf("%p",a) 用地址的格式打印变量 a 的值,printf("%p", &a) 打印变量 a 所在的地址。
- n,不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。
- %, '%'字面值,不接受任何 flags, width。
二.格式化字符串漏洞
1.原理
错误的使用,直接将使用者的输入作为了fmt使用
例如,以printf为例:
我们正常使用printf时,是这样的:printf("%s", str,n)
但是由于一些人马虎或者偷懒,可能会写成printf(str)
,这种情况下,程序虽然会报错,但是依旧是编译成功的。但是,此时str会被当做是一个format参数。众所周知,format参数中的字符串是可以被输出的,但是如果这串字符串中有基本的格式化字符串参数(如%s, %n, %x, %p等),那么这些内容就会被当做基本的格式化字符串参数来处理,这样就出现了可利用的漏洞。
2.常用格式化字符串参数
参数 | 基本介绍 |
%c | 输出字符 |
%d | 输出十进制整数 |
%x | 输出16进制数据 |
%p | 输出16进制数据(与%x基本相同,只是附加前缀0x) |
%s | 输出字符串(偏移处指针指向的字符串) |
%n | 将%n之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置 |
- 注:%n是通过格式化字符串漏洞改变程序流程的关键方式,而其他格式化字符串参数可用于读取信息或配合%n写数据。
三.格式化字符串漏洞利用
由于存在格式化字符串漏洞,所以,格式化字符串可以被攻击者的输入任意控制,但是printf本身是检查后面有几个参数。所以,我们可以利用%x来造成栈上信息泄露,利用$来控制信息泄露的位置,从而达到对任意地址数据的控制。
1.对任意内存地址处数据的读取
2.对任意内存地址处数据的写入
3.格式化字符串的综合利用
①got劫持