格式化字符串漏洞的不解
1.偏移量(首地址偏移)与函数参数的调用
在做这类题的时候,经常会有这样的操作:输入AAAA(或AAAAAAAA)-%x-%x-%x-%x-%x-%x-%x-%x-%x…,然后通过程序输出中的AAAA的二次出现,来确定这个“偏移量”。
我对这个“偏移量”的不理解的主要原因是忽略了:原来printf也是个函数!父函数中又调用了printf这个子函数,所以在原来的父函数开辟的栈帧上会在开辟一个新的属于printf函数的栈。而AAAA既做为printf的函数,又作为父函数的输入参数。所以,可以如下拙略的图示:
这里感觉还得给自己强化一下这个函数的传参,以这个为例:
1 | printf("AAAA,%d,%c,%s",a,b,c); |
printf函数的参数应从右往左依次入栈c,b,a依次入栈,虽然printf参数可变,但有一点不变,就是它起码有一个参数,那就是它要打印的字符串,也就是AAAA,但其实printf函数的第一个参数并不是字符串本身,而是该字符串的地址,真正的字符串并不存在栈里。
2.%n的应用(覆写任意地址)
一般利用%n来修改参数里的内容,由此可以将未知变为已知。它的定义是:%n,不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。用法形如下:
1 | ...[overwrite addr]....%[overwrite offset]$n |
其中… 表示我们的填充内容,overwrite addr 表示我们所要覆盖的地址,overwrite offset 地址表示我们所要覆盖的地址存储的位置为输出函数的格式化字符串的第几个参数。一般操作步骤:确定覆盖地址、确定相对偏移、进行覆盖。这里我们所要覆盖的地址就相当于AAAA,而我们以确定其在第n个参数的位置,则只控制n之前的填充内容(包括所要写入的地址)相当于几个字符,就可将几写入该地址。
注:%n是写入目标空间4字节,%hn是写入目标空间2字节,%hhn是写入目标空间1字节,%lln是写入目标空间8字节。
3.pwntools里的FmtStr格式化字符串类
格式化字符串漏洞如果要修改某个内存地址的内容常常需要构造比较长的payload,而pwntools里的FmtStr格式化字符串类可以大大简便该工作
1 | fmtstr_payload(offset, writes, numbwritten=0, write_size='byte') |
第一个参数表示格式化字符串的偏移;
第二个参数表示需要利用%n写入的数据,采用字典形式,如我们要将printf的GOT数据改为system函数地址,就写成
1 | {printfGOT: systemAddress}; |
第三个参数表示已经输出的字符个数;
第四个参数表示写入方式,是按字节(byte)、按双字节(short)还是按四字节(int),对应着hhn、hn和n,默认值是byte,即按hhn写。
其返回值即为payload
其常用的形式是:
1 | fmtstr_payload(offset,{address1:value1}) |
4.格式化字符串绕过canary
计算buf与canary偏移+6即为canary偏移(6为参数寄存器数量,64位程序函数的参数获取方式为寄存器与栈相结合;32位只用栈)。
5.任意地址写
%{number}c表示写入的数,%{index}$n表示偏移index位置的值为地址写入,n写入四字节,hn写入两字节,hhn写入单字节。
%12c%7$hhn这里我们没有另外传入地址,表示将偏移为7的值作为地址,在该地址处写入单字节的内容0xc
p32(0x80480045)+b"%12c%7$hhn"假设偏移为7的地方是字符串的第一个参数,此时的表达式为将0x80480045处的单字节改写为0xc;同样值得注意的是如果是64位程序,应将地址放在格式化表达式的后面,对应的偏移也需要修改。