导语

pillar的pwn知识点总结

格式化字符串漏洞

printf特性

首先我们要理解一下printf的一些特性。

printf第一个参数是fmt,即格式化字符串,格式化字符串中可以包含格式化占位符,其语法是

%[ parameter ][ flags ][ field width ][. precision ][ length ]type

1677145835400

1677209898069

1677209916094

1677209931303

会出现格式化字符串漏斗的原因在于,printf不对格式化占位符和之后的参数数量做校验,在fmt里遇到格式化占位符,就直接在栈里通过偏移进行间接寻址,所以就能导致直接任意读取栈内数据

下面是printf的函数栈结构

1677148427755

任意地址读

如果我们可以让 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。

1677148809771

调用函数从右往左入栈,上图里,右边为低地址,为栈顶,在本例子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://zh.wikipedia.org/wiki/%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2#:~:text=%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%EF%BC%88%E8%8B%B1%E8%AA%9E,%E4%B8%80%E7%A8%AE%E5%BD%A2%E5%BC%8F%E8%BC%B8%E5%87%BA%E7%9A%84%E5%87%BD%E6%95%B0%E3%80%82

https://www.anquanke.com/post/id/180009

https://wizardforcel.gitbooks.io/syracuse-sec-lecture-notes/content/7.html

https://www.cnblogs.com/0xJDchen/p/5904816.html