简介
格式化字符串漏洞发现于2000年左右,利用printf()
等的输出造成内存的泄露与内存的读写。本文章主要讨论printf()
函数
格式化字符串常见的语法
- %d 打印 signed int
- %u 打印 unsigned int
- %s 打印参数地址所指向的字符串
- %x 打印十六进制整数
- %p 打印参数地址
- %c 打印一字节字符
%
长度的控制
- %d 4字节
- %hd 2字节
- %hhd 1字节
- %ld 大于4字节
关于格式化字符串中的漏洞
关于漏洞的原理
以下为printf()
正常用法
1 |
|
运行后该程序会输出hello,world
并换行
但是,以下写法也能正常输出
1 |
|
此处的输出结果依旧为hello,world
并换行
因为此处printf(str)
和printf("hello,world\n")
等效
这就产生一个漏洞,如果str是可以被控制内容的,那么是不是就可以让printf()
打印出他不该打印的东西
1 |
|
输入%p
会发现printf()
并没有打印%p而是打印出了一段地址,造成了地址的泄露
因为此时printf(str)
和printf("%p")
等效
关于%n
- %n 将当前已经打印字符的个数写入参数地址处(4字节)
- %hn 2字节
- %hhn 1字节
1 |
|
在第一个printf中已经打印了 A
(九个空格加一个A)共十个字符后又把10这个值付给了int型指针a(4字节)第二个printf对a解引用发现a指向的值为10
因此完整的输出结果为
A10
关于$符号
$ :指定占位符
- %<整数n>$x 指定输出第n给参数
1 |
|
如果输入%d,%d,%d
则会输出1,2,3
,同样的,如果输入%d
则只会输出1
可是,如果输入%2$d
则会直接输出第二个参数b也就是2
关于参数不足
关于printf()
我们都知道,在一般情况下,前面的百分号与后面的参数一一对应。
如果参数不足会怎么样?
printf()
会在寄存器和栈上一直找,直到找到能对应输出的数,这样,就会把一些隐藏的信息给打印出来。
%d %u %x %p会打印地址
%s 则会打印这个地址指向的位置
因此$
符号就是制造参数不足的情况
eg: %100$x
就是对应第100个参数,参数肯定不足,除非你真的给printf传了100个参数…….
注:x86和x64架构下参数不足时泄露的东西位置有差别
scanf的格式化字符串漏洞
可以直接在指定地址内写入
若出现scanf(buf)
buf可以为格式化字符串(%x$s)+指针地址,这样就可以直接向指针所指向的地址处写入数据
若指针为stdout,则可以构造stdout泄露指定位置的地址
若指针为malloc_hook,free_hook等函数,可以直接往里写,不需要伪造堆
实操
题目:**[第五空间2019 决赛]PWN5**
来源:[buuctf-PWN](BUUCTF在线评测 (buuoj.cn))
分析文件
32位架构开启了canary和nx保护
代码就是从一个文件中读取4字节的随机数存放到地址0x804C044
中
之后让你输入name 存放到buf中
之后在输出一些字符串,以及buf 后让你输入passwd 存放到nptr中
最后检查nptr的值和4字节的随机数是否相等
利用漏洞
由于printf()
直接输出buf,因此可以利用格式化字符串漏洞
首先输入几个a
,在一直输入%p
看能不能找到把a存到栈上的哪里了
由于a的ascii码为0x61,因此不难发现,第十个%p的位置就是输入的那几个a
因此可以直接利用%10$定位到这个位置
1 | addr1=0x804C044 |
从0x804c044
往后直接利用%n修改那4字节随机数的值
前面输出的地址总的字节数加起来为4+4+4+4=16 十六进制为0x10
因此每个字节都是0x10
所以最后的passwd直接就是0x10101010四个16连在一块
所以第二次提交数据,记得要转化成字符串形式
1 | r.sendline(str(0x010101010)) |
最终得到shell
exp
1 | from pwn import* |