格式化字符串漏洞
导语
pillar的pwn知识点总结
格式化字符串漏洞
printf特性
首先我们要理解一下printf的一些特性。
printf第一个参数是fmt,即格式化字符串,格式化字符串中可以包含格式化占位符,其语法是
%[ parameter ][ flags ][ field width ][. precision ][ length ]type |
会出现格式化字符串漏斗的原因在于,printf不对格式化占位符和之后的参数数量做校验,在fmt里遇到格式化占位符,就直接在栈里通过偏移进行间接寻址,所以就能导致直接任意读取栈内数据
下面是printf的函数栈结构
任意地址读
如果我们可以让 printf
从格式化字符串获取地址(也位于栈上),我们就可以控制该地址。
例如
printf ("\x10\x01\x48\x08 %x %x %x %x %s"); |
\x10\x01\x48\x08
是目标地址的四个字节。在 C 语言中,\x10
让编译器将十六进制值 0x10 放入当前位置。这个值只占一个字节。如果我们不使用 \x
,直接将 10 放入字符串,就会储存 ASCII 值 1 和 0。它们的 ASCII 值是 49 和 48。
调用函数从右往左入栈,上图里,右边为低地址,为栈顶,在本例子call printf的时候,只有一个参数,也就是 "\x10\x01\x48\x08 %x %x %x %x %s"
,作为一个字符串,他的首地址被压入栈中,在printf函数栈栈顶
左边彩色的部分是格式化字符串在printf的caller函数栈空间的实参,实际上可能好几个%x会挤在一个内存单元里,这里只是方便理解
右边白色的部分是printf的函数栈。address of user_input指针(fmt指针)和user_input数组(fmt本身)之间间隔了一些call过程中压入栈的内容,包括caller函数esp,返回地址等等。
通过printf函数的特点,第一个%x会与其函数栈中指针下面的那个内存单元想匹配,就是图中的1st%x所指的内存单元,而不会去检测printf是否真的输入了这么多的参数。因此,利用无脑%x读取栈内存,可以得到fmt本身的位置与栈顶指向fmt的指针的地址之间间隔的内存单元数,再通过填充相应数量的%x即可将%s与0x10014808匹配,进而输出0x10014808地址的内容
当然我们也可以通过X$的方式(X为数),来避免这么多的%x
任意地址写
接下来介绍一下任意位置写,也就是%n,和任意地址读非常相似
%n的作用是将已打印的字符个数输入到和%n所对应的参数所指向地址,遵循printf格式化占位符的寻址方式。
格式就是%ax%b$n,指的是向格式化字符串指针所在内存偏移b的内存里的内容的地址写a
也可以直接利用 pwntools 的 fmtstr_payload 函数即可生成相应的 payload
格式为fmtstr_payload(offset,{addr:value}),其中offset为我们之前确定的格式化字符串参数的偏移量,addr为我们想要修改的地址,value为我们想要将覆盖至地址上的值。
参考
https://www.anquanke.com/post/id/180009
https://wizardforcel.gitbooks.io/syracuse-sec-lecture-notes/content/7.html