这可能是期末复习周之前有关pwn的最后一篇了
无奈的博主因为期末考的408科目太多了只能去更新计算机组成原理和计算机网络了。废话不多说我们直接进去正题。
格式化字符串漏洞
格式化字符串函数可以将接受可变数量的参数,并将第一个参数作为格式化字符串,然后依据此来解析之后的参数。通俗的说,格式化字符串函数就是将计算机内存试中表示的数据转化为人类可读的字符串格式。在利用时分为三个部分:
·格式化字符串函数、格式化字符串以及后续的参数。
以大家都接触过的函数printf函数为例。
input:
printf(“Name %s, ID %d,score %4.2f”,“K1rit0”,123123,3.14);
output:
Name k1rit0,ID 123123,score 3.14
通俗的格式基本如下:
%[parameter][flags][field width][.precision][length]type
每一种pattern的含义参考自wiki百科的格式化字符串。
parameter
n$,获取格式化字符串中的指定参数
flag
field width
输出的最小宽度
precision
输出的最大长度
length,输出的长度
hh,输出一个字节
h,输出一个双字节
type
d/i,有符号整数
u,无符号整数
x/X,16 进制 unsigned int 。x 使用小写字母;X 使用大写字母。如果指定了精度,则输出的数字不足时在左侧补 0。默认精度为 1。精度为 0 且值为 0,则输出为空。
o,8 进制 unsigned int 。如果指定了精度,则输出的数字不足时在左侧补 0。默认精度为 1。精度为 0 且值为 0,则输出为空。
s,如果没有用 l 标志,输出 null 结尾字符串直到精度规定的上限;如果没有指定精度,则输出所有字节。如果用了 l 标志,则对应函数参数指向 wchar_t 型的数组,输出时把每个宽字符转化为多字节字符,相当于调用 wcrtomb 函数。
c,如果没有用 l 标志,把 int 参数转为 unsigned char 型输出;如果用了 l 标志,把 wint_t 参数转为包含两个元素的 wchart_t 数组,其中第一个元素包含要输出的字符,第二个元素为 null 宽字符。
p, void * 型,输出对应变量的值。printf("%p",a) 用地址的格式打印变量 a 的值,printf("%p", &a) 打印变量 a 所在的地址。
n,不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。
%, '%'字面值,不接受任何 flags, width。
好,在了解什么是格式化字符串函数后,我们来学习一下
格式化字符串漏洞原理
以上面的例子进行介绍:
在进入printf函数之前,栈上的布局如下:
Unknow value
3.14
123123
addr of “k1rit0”
addr of format string :Name %s …
##我们假设3.14上面的值是位置的某个值
然后在进入printf函数后,函数首先获取第一个参数,一个一个读取其字符,分以下两种情况
-
当前字符为%。继续读取下一个字符
1. 如果接下来没有字符,报错
2. 如果接下来的字符是%,则输出%
3. 如果以上两点都不成立,则根据相应的字符,获取响应的参数,对其进行解析并输出。 -
当前字符不为%,直接按相应标准输出。
假设我们在编写程序的时候,写成了下面这样:
printf(“Name %s, ID %d,score %4.2f”);
那么根据上面所讲的规则,我们这里printf函数在运行后读取到第一个%后,由于我们没有提供参数,则函数会将栈上存储格式化字符串地址上面的三个变量分别解析为
- 解析其地址对应的字符串(对应%s)
- 解析其地址对应的整形(对应%d)
- 解析其地址对应的浮点值(对应%f)
后两点其实无伤大雅,但是对于第一个来说,由于printf函数解析的原理是根据第一个参数的格式来进行解析的所以如果我们对第一个提供了一个不可访问的地址,如0,则程序就会崩溃。
以上为格式化字符串漏洞的基本原理。
利用
在上一部分我们了解到了格式化字符串漏洞的原理,其实不难看出其中包含的利用手段
- 查看进程内容,根据%d,%f输出栈上的内容。
- 让程序崩溃,因为%s对应的参数地址不合法的概率较大。
我们先来看一下程序崩溃:
程序崩溃
通常来说,利用漏洞使得程序崩溃是最简单的利用方式,我们只需要输入多个%s,让printf函数一直读取%,直到其读取到不合法的地址为止。
如:`%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s
因为栈上不可能每个值都对应了合法的地址,所以我们按道理说之啊哟一值写下去,总会有某个地址可以使得程序崩溃,但是这一利用特点攻击者是很难用于自身利用的,但是我们可以利用这个来造成程序不可用。比如说,在远程的服务器上存在格式化字符串漏洞,那么我们就可以利用这一点,使服务崩溃,进而使得用户不能访问,典型的我拿不到的东西你也别想拿!!!`
泄露内存
利用漏洞还可以获取我们想要输出的内容
一般来说分以下几点
-
泄露栈内存
- 获取某个变量的值
- 获取某个变量对应的地址的内存
-
泄露任意地址内存
举个例子:泄露栈内存
给定以下程序:
#include<stdio.h>
int main(){
char s[100];
int a=1,b=0x22222222,c=-1;
scanf("%s",s);
printf("%08x.%08x.%08x.%s\n",a,b,c,s);
printf(s);
return 0;
}
然后我们在Ubuntu下编译:
giantbranch@ubuntu:~/Desktop$ gcc -m32 -fno-stack-protector -no-pie -o scan5 scan5.c
scan5.c: In function ‘main’:
scan5.c:7:8: warning: format not a string literal and no format arguments [-Wformat-security]
printf(s);
^
可以看到编译器指出了程序没有给出参数的问题
然后我们来看一下如何利用这一点来获取对应的栈内存。
根据C语言的函数调用规则,格式化字符串函数会根据格式化字符串直接使用栈上自顶向上的变量作为其参数(32位,64位的规则先不介绍)
我们运行程序:
可以看到输出了一个奇怪的东西:
我们用gdb来设置断点来看一下细节:
--------------------------