[PWN]——格式化字符串漏洞(包含几种解题方法)

格式化字符串漏洞原理:

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格式化字符串类来解决格式化字符串漏洞比较方便做题。

如果有什么错误的地方请师傅们告诉我一下。

本文制作不易,盗版必究