格式化字符串——以printf为代表

Posted on Nov 14, 2020

span

首先我想说,格式化字符串漏洞,会出现再形如printf(&s);这样的语句中,如果输出的参数等与输入有关,那么,就可能会自然的使用这样的方法,然鹅另一方面,漏洞本身有极高的危险性。

盲打

即便没有二进制文件,如果确定目标程序中存在格式字符串漏洞,也可以利用此漏洞 leak 出整个程序。不过为了 leak 需要一个进程的基址,如果没开启 pie 的话,很可能就在 0x40000 或者 0x8048000 等位置上,通过 %s 即可一句句 leak 出来。

小技巧

  • 有些时候,我们需要写入的不是一个较小的值,相反可能非常的大,比如写入地址,这个时候我们可以使用%n$hhn(写一个字节),%n$hn(写两个字节)来减小输出的字符串长度。当然可供我们输入的字符串可能比较短,这个时候就需要进行取舍。

hijack got...

通过利用格式化字符串漏洞,我们可以劫持许多东西为我们所用,比如劫持got和fini_array

格式化占位符

格式化字符串中,非常重要的组成部分就是格式化占位符,一般来说我们用它可以像控制台方便地输出变量。

格式化占位符地语法:%[parameter][flags][field width][.precision][length]type

当然一般来讲许多的pattern都会被忽略(以前打OI的时候)比如输出一个长整型,就是%lld,但是在我们利用这个漏洞的时候,许多pattern就会有自己的特殊用途了,接下来我列举一下他们。

[parameter]:

说明符描述
n$n来表示这个格式化占位符应该输出第几个参数,这样一个参数可以被
输出多次,而不需要多次提供。这属于POSIX拓展,不属于ISO C
简单的例子,printf("%2$d,%1$d",a,b);输出的就应该是b,a

[flag]

说明符描述
+总是输出有符号数的'+','-'号,如果不加,则默认省略正数的'+'
空格若有符号数在输出的时候,若没有正负号或没有输出,则前缀
一个空格,如果+说明符存在,则空格说明符将被忽略
-改为左对齐,默认为右对齐
#对于'g'与'G',不删除尾部0以表示精度。对于'f', 'F', 'e', 'E', 'g',
'G', 总是输出小数点。对于'o', 'x', 'X', 在非0数值前分别输出前
缀0, 0x, and 0X表示数制。
0如果width选项前缀以0,则在左侧用0填充直至达到宽度要求。
例如printf("%2d", 3)输出" 3",而printf("%02d", 3)输出
"03"。如果0-均出现,则0被忽略,即左对齐依然用空格填充。

[field width]

输出的最小宽度,如果实际输出没达到此值,则会进行填充

[Precision]

输出的最大长度,依赖于特定的格式化类型。对于d、i、u、x、o的整型数值,是指最小数字位数,不足的位要在左侧补0,如果超过也不截断,缺省值为1。对于a,A,e,E,f,F的浮点数值,是指小数点右边显示的数字位数,必要时四舍五入或补0;缺省值为6。对于g,G的浮点数值,是指有效数字的最大位数;缺省值为6。对于s的字符串类型,是指输出的字节的上限,超出限制的其它字符将被截断。如果域宽为*,则由对应的函数参数的值为当前域宽。如果仅给出了小数点,则域宽为0。

[length]

输出的长度

字符描述
hh对于整数类型,printf期待一个从char提升的int尺寸的整型参数。
h对于整数类型,printf期待一个从short提升的int尺寸的整型参数。
l对于整数类型,printf期待一个long尺寸的整型参数。对于浮点类型,printf期待一个double尺寸的整型参数。对于字符串s类型,printf期待一个wchar_t指针参数。对于字符c类型,printf期待一个wint_t型的参数。
ll对于整数类型,printf期待一个long long尺寸的整型参数。Microsoft也可以使用I64
L对于浮点类型,printf期待一个long double尺寸的整型参数。
z对于整数类型,printf期待一个size_t尺寸的整型参数。
j对于整数类型,printf期待一个intmax_t尺寸的整型参数。
t对于整数类型,printf期待一个ptrdiff_t尺寸的整型参数。

以下为平台相关的选项

字符描述
I对于有符号整数类型,printf期待一个ptrdiff_t尺寸的整型参数。对于无符号整数类型,printf期待一个size_t尺寸的整型参数。常见于Win32/Win64平台。
I32对于整数类型,printf期待一个32位(双字)的整型参数。常见于Win32/Win64平台。
I64对于整数类型,printf期待一个64位(四字)的整型参数。常见于Win32/Win64平台。
q对于整数类型,printf期待一个64位(四字)的整型参数。常见于BSD平台。

ISO C99的头文件inttypes.h包含了许多宏,用于平台独立的printf编码。例如:

定义
PRId32典型地等效于I32d (Win32/Win64)或d
PRId64典型地等效于I64d (Win32/Win64), lld (32位平台)或ld (64位平台)
PRIi32典型地等效于I32i (Win32/Win64)或i
PRIi64典型地等效于I64i (Win32/Win64), lli (32位平台)或li (64位平台)
PRIu32典型地等效于I32u (Win32/Win64)或u
PRIu64典型地等效于I64u (Win32/Win64), llu (32位平台)或lu (64位平台)
PRIx32典型地等效于I32x (Win32/Win64)或x
PRIx64典型地等效于I64x (Win32/Win64), llx (32位平台)或lx (64位平台)

TYPE

Type,也称转换说明(conversion specification/specifier),可以是:

字符描述
d, i有符号十进制数值int。'%d'与'%i'对于输出是同义;但对于scanf()输入二者不同,其中%i在输入值有前缀0x或0时,分别表示16进制或8进制的值。如果指定了精度,则输出的数字不足时在左侧补0。默认精度为1。精度为0且值为0,则输出为空。
u十进制unsigned int。如果指定了精度,则输出的数字不足时在左侧补0。默认精度为1。精度为0且值为0,则输出为空。
f, Fdouble型输出10进制定点表示。'f'与'F'差异是表示无穷与NaN时,'f'输出'inf', 'infinity'与'nan';'F'输出'INF', 'INFINITY'与'NAN'。小数点后的数字位数等于精度,最后一位数字四舍五入。精度默认为6。如果精度为0且没有#标记,则不出现小数点。小数点左侧至少一位数字。
e, Edouble值,输出形式为10进制的([-]d.ddd e[+/-]ddd). E版本使用的指数符号为E(而不是e)。指数部分至少包含2位数字,如果值为0,则指数部分为00。Windows系统,指数部分至少为3位数字,例如1.5e002,也可用Microsoft版的运行时函数_set_output_format 修改。小数点前存在1位数字。小数点后的数字位数等于精度。精度默认为6。如果精度为0且没有#标记,则不出现小数点。
g, Gdouble型数值,精度定义为全部有效数字位数。当指数部分在闭区间 [-4,5] 内,输出为定点形式;否则输出为指数浮点形式。'g'使用小写字母,'G'使用大写字母。小数点右侧的尾数0不被显示;显示小数点仅当输出的小数部分不为0。
x, X16进制unsigned int。'x'使用小写字母;'X'使用大写字母。如果指定了精度,则输出的数字不足时在左侧补0。默认精度为1。精度为0且值为0,则输出为空。
o8进制unsigned int。如果指定了精度,则输出的数字不足时在左侧补0。默认精度为1。精度为0且值为0,则输出为空。
s如果没有用l标志,输出null结尾字符串直到精度规定的上限;如果没有指定精度,则输出所有字节。如果用了l标志,则对应函数参数指向wchar_t型的数组,输出时把每个宽字符转化为多字节字符,相当于调用wcrtomb函数。
c如果没有用l标志,把int参数转为unsigned char型输出;如果用了l标志,把wint_t参数转为包含两个元素的wchart_t数组,其中第一个元素包含要输出的字符,第二个元素为null宽字符。
pvoid *
a, Adouble型的16进制表示,"[−]0xh.hhhh p±d"。其中指数部分为10进制表示的形式。例如:1025.010输出为0x1.004000p+10。'a'使用小写字母,'A'使用大写字母。[2][3] (C++11流使用hexfloat输出16进制浮点数)
n不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。
%'%'字面值,不接受任何flags, width, precision or length。

宽度与精度格式化参数可以忽略,或者直接指定,或者用星号"*"表示取对应函数参数的值。例如printf("%*d", 5, 10)输出" 10";printf("%.*s", 3, "abcdef") 输出"abc"。

如果函数参数太少,不能匹配所有的格式参数说明符,或者函数参数的类型不匹配,将导致未定义(undefined)行为。过多的函数参数被忽略。许多时候,未定义的行为将导致格式化字符串攻击

某些编译器,如GCC,会静态检查printf这一类函数的格式化参数并编译警告存在的问题(当使用编译标志-Wall-Wformat)。GCC也会对用户自定义的printf风格函数做静态检查,如果在函数定义时使用了非标准的 __attribute__((format(...)))


上面都是从维基百科上摘录的