PWN学习之格式化字符串漏洞

格式化输出函数

可变参数:https://blog.csdn.net/smstong/article/details/50751121 (C语言可变参函数的实现)

首先我们了解格式化字符串漏洞前,需要对格式化输出的函数进行一个了解,在C中格式化输出函数一共有如下:

fprintf()  "按照格式字符串将输出写入流中。三个参数分别是流、格式字符串和变参列表。"
printf() "等同于fprintf(),但是它的输出流为stdout。"
sprintf() "等同于fprintf(),但是它的输出不是写入流而是写入数组。在写入的字符串末尾必须添加一个空字符。"
snprintf() "等同于sprintf(),但是它指定了可写入字符的最大值size。超过第size-1的部分会被舍弃,并且会在写入数组的字符串末尾添加一个空字符。"
dprintf() "等同于fprintf(),但是它的输出不是写入流而是一个文件描述符fd。" "分别与上面的函数对应,但是它们将变参列表换成了va_list类型的参数。"
vfprint()、vprintf()、vsprintf()、vsnprintf()、vdprintf()

格式化字符串漏洞

格式化字符串漏洞从2000年左右开始流行起来,几乎在各种软件中都能见到它的身影,随着技术的发展,软件安全性的提升,如今它在桌面端已经比较少见了,但在物联网设备 IoT上依然层出不穷。

#include <stdio.h>
void main()
{
printf("%s %d %s %x %x %x %3$s","Hello World!",233,"\n");
}

我们输入的参数只有三个,但是格式化字符串中还有3个%x和一个%3$s,其中3个%x由于没有参数他会泄漏出栈的地址

接下来继续来看一个例子,其中fgets来接受用户输入的字符串,但是如果是hack他就会输入控制字符串来泄漏出栈地址,这点感觉和Web中的xss有点像,程序员没有过滤敏感字符导致被攻击。由此可以总结出,格式字符串漏洞发生的条件就是格式字符串要求的参数和实际提供的参数不匹配

#include <stdio.h>
void main()
{
char buf[50];
if(fgets(buf,sizeof buf,stdin) == NULL)
return;
printf(buf);
}

漏洞利用

对于格式化字符串漏洞的利用主要有:

  • 使程序崩溃
  • 栈数据泄露
  • 任意地址内存泄露
  • 栈数据覆盖
  • 任意地址内存覆盖

使程序崩溃

造成程序崩溃原因:printf需要在栈中取一个数字视为地址,然后打印出地址所指向的内存,知道出现空白字符;获取的某个数字可能并不是一个地址;获得的数组确实是一个地址,但改地址受保护。

在Linux中,存取无效的指针会使进程收到SIGSEGV信号。

printf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s")

栈数据泄露

#include <stdio.h>
void main()
{
char format[128];
int arg1 = 0x00123456,arg2 = 0x11111111,arg3 = 0x22222222;
char arg4[4] = "ABCD";
scanf("%s",format);
printf(format,arg1,arg2,arg3,arg4);
printf("\n");
}
输入 %p-%p-%p-%p-%p

可以根据泄漏出来的栈的数据,然后挨个的计算出参数的位置,因为栈中的数据一般都是挨着的,可以看到在0xffffd484的下个数据就是字符串ABCD字符串的地址。

现在我们已经知道了如何按顺序泄露栈数据,那么如果想直接泄露指定的某个数据,则可以使用与下面类似的格式字符串,这里的n表示位于格式字符串后的第n个数据%n$p

%<arg#>$<format>
%n$x 分别获取arg3、arg1、arg2、arg2、arg4 以及栈上经跟参数的两个值
"%3$x-%1$p-%2$p-%2$p-%4$p-%5$p-%6$p"

任意地址内存泄漏

攻击者使用类似%s的格式规范就可以泄露出参数(指针)所指向内存的数据,程序会将它作为一个ASCII字符串处理,直到遇到一个空字符。所以,如果攻击者能够操纵这个参数的值,那么就可以泄露任意地址的内容。

AAAA-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p
AAAA代表地址

栈数据覆盖

#include <stdio.h>
void main()
{
int i;
char str[] = "hello";
printf("%s %n\n",str,&i);
printf("%d\n",i);
}

这个例子i被赋值成了6,因为遇到转换指示符之前一共写入了6个字符("hello"加上一个空格)。在没有长度修饰符时,默认写入一个int类型的值。有关详细内容可以参考:https://blog.csdn.net/FollowGodSteps/article/details/74115138

以下例子参考文章:c语言中对字段宽度的理解?

/*************************************************************************
> File Name: printf.c
> Author: Mr.Yang
> Purpose:演示printf的用法
> Created Time: 2017年05月21日 星期日 10时07分44秒
************************************************************************/ #include <stdio.h>
#include <stdlib.h> int main(void)
{
float i = 10000.123; printf("%5f\n",i);
printf("%10f\n",i);
printf("%15f\n",i);
printf("%20f\n",i);
printf("%25f\n",i); return 0;
}

输出内容:

10000.123047
10000.123047
10000.123047
10000.123047
10000.123047

回到一开始的程序,我们尝试将arg2的值更改为任意值(例如0x00000020,十进制32),于是构造格式字符串\x28\xcd\xff\xff%08x%08x%012d%13$n,其中\x28\xcd\xff\xff是arg2的地址,占4字节,“%08x%08x”表示两个8字符宽的十六进制数,占16字节,“%012d”占12字节,三个部分加起来共占4+16+12=32字节,也就是把arg2赋值为0x00000020。格式字符串最后一部分“%13$n”是最重要的一部分,表示格式字符串的第13个参数,即写入0xffffcd28的地方(0xffffcd58),printf()通过该地址找到被覆盖数据。

对比printf()执行前后的栈,可以看到其首先解析“%13$n”,从0xffffcd58找到地址0xffffcd28,然后将其数据覆盖为“0x00000020”。

任意地址内存覆盖

也许已经有人发现了问题,使用上面的方法,值最小只能是4,因为光地址就占去了4个字节,那么怎样覆盖比4小的值呢?利用整数溢出是一个方法,但是在实践中这样做很难成功。再想一下,前面的输入中,地址都位于格式字符串之前,这样做真的有必要吗,能否将地址放在中间呢?我们来试一下,使用格式字符串“AA%15\(nA"+"\x38\xd5\xff\xff”,开头的“AA”占2个字节,即将地址赋值为2,中间“%15\)n”占5个字节(这里不是%13$n,因为地址被放在了后面),是第15个参数,后面跟上一个“A”占用1个字节。于是前半部分总共占用2+5+1=8个字节,刚好是两个参数的宽度,这里的8字节对齐十分重要。最后,输入我们要覆盖的地址“\x38\xd5\xff\xff”,如下所示。

PWN学习之格式化字符串漏洞的更多相关文章

  1. [二进制漏洞]PWN学习之格式化字符串漏洞 Linux篇

    目录 [二进制漏洞]PWN学习之格式化字符串漏洞 Linux篇 格式化输出函数 printf函数族功能介绍 printf参数 type(类型) flags(标志) number(宽度) precisi ...

  2. Linux pwn入门教程——格式化字符串漏洞

    本文作者:Tangerine@SAINTSEC 原文来自:https://bbs.ichunqiu.com/thread-42943-1-1.html 0×00 printf函数中的漏洞printf函 ...

  3. CTF必备技能丨Linux Pwn入门教程——格式化字符串漏洞

    Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程. 教程仅针对i386/am ...

  4. 格式化字符串漏洞 format string exploit(一)

    本文系原创,转载请说明出处 本文为基于CTF WIKI的PWN学习 0x00 格式化字符串原理 先附一张经典的图,如下 其栈上布局如下: some value 3.14 123456 addr of ...

  5. Linux pwn入门教程(6)——格式化字符串漏洞

    作者:Tangerine@SAINTSEC 0x00 printf函数中的漏洞 printf函数族是一个在C编程中比较常用的函数族.通常来说,我们会使用printf([格式化字符串],参数)的形式来进 ...

  6. 格式化字符串漏洞利用实战之 0ctf-easyprintf

    前言 这是 0ctf 的一道比较简单的格式化串的题目. 正文 逻辑非常简单 do_read 可以打印内存地址的数据,可用来 泄露 got. leave 格式化字符串漏洞. printf(s) 直接调用 ...

  7. 格式化字符串漏洞利用实战之 njctf-decoder

    前言 格式化字符串漏洞也是一种比较常见的漏洞利用技术.ctf 中也经常出现. 本文以 njctf 线下赛的一道题为例进行实战. 题目链接:https://gitee.com/hac425/blog_d ...

  8. Linux下的格式化字符串漏洞利用姿势

    linux最早的漏洞防护机制nx-stack刚刚出现后就有人想出了突破方法.那就是只有栈是不可执行,而除了栈以外的其他地方还是可以执行的,只要把返回地址执行别的地方就可以. 一.格式化字符串漏洞 格式 ...

  9. 通过格式化字符串漏洞绕过canary

    1.1    canary内存保护机制 1.1.1    canary工作原理 canary保护机制类似于/GS保护机制,是Linux下gcc编译器的安全保护机制之一,在栈中的结构如下图所示: 在函数 ...

随机推荐

  1. php laravel v5.1 消息队列

    * install https://laravel.com/docs/5.1#installationcomposer create-project laravel/laravel msgq &quo ...

  2. P7324-[WC2021]表达式求值【dp】

    正题 题目链接:https://www.luogu.com.cn/problem/P7324 题目大意 给一个只包含\(m\)个值的表达式,\(<\)表前后取最小值,\(>\)表前后取最大 ...

  3. P4292-[WC2010]重建计划【长链剖分,线段树,0/1分数规划】

    正题 题目链接:https://www.luogu.com.cn/problem/P4292 题目大意 给出\(n\)个点的一棵树,然后求长度在\([L,U]\)之间的一条路径的平均权值最大. 解题思 ...

  4. YbtOJ#943-平方约数【莫比乌斯反演,平衡规划】

    正题 题目链接:http://www.ybtoj.com.cn/contest/122/problem/3 题目大意 \(S(i)\)表示\(i\)的约数个数,\(Q\)次询问给出\(n,m\)求 \ ...

  5. 熬夜整理小白入门与提升分布式版本管理软件:Git,图文并茂(建议收藏)

    @ 目录 什么是Git SVN VS Git 什么是版本控制 安装Git 谁在操作? Git本地仓库 本地仓库构造 重点 Git常用基本操作 git add git commit git diff g ...

  6. 【Golang】三个点(...)用法

    众所周知,Go语言是严格类型语言,而开发的时候又遇到传入参数不定的情况,怎么办? 这里的三个点(-),就给我们编程人员带来很大的灵活性,具体如下 在Golang中,三个点一共会用在四个地方(话说三个点 ...

  7. 解决 Asp.Net5 在视频文件下载预览时无法快进的问题

    前情提要 https://www.cnblogs.com/puzhiwei/p/15265005.html 在解决.Net5 如何修改Content-Disposition实现在线预览的功能后,我又遇 ...

  8. Rclone使用教程 - 挂载Onedrive和谷歌网盘

    1. 介绍 Rclone 是一个用于多个云平台之间同步文件和目录的命令行工具,其支持多种运营商网盘. 官网网址:https://rclone.org 开源地址:https://github.com/n ...

  9. Android应用开发特色

    目录 Android应用开发特色 四大组件 Activity Service Broadcastreceiver Contentprovider 丰富的系统控件 Sqlite数据库 强大的多媒体 An ...

  10. C语言对"不定长"字符串数组的遍历

    一般来说,c语言的数组的初始化可以通过三种方式: {0},在声明时使用,如 int a[10]={0} 使用memset, memset(array,0,sizeof(array)) 用for循环赋值 ...