C语言常见漏洞-格式化字符串漏洞

格式化字符串漏洞

1 预备知识

在c语言中的printffprintfsprintfsnprintfprint函数经常会用到类似%形式的一个或者多个说明符。比如printf("my name is %s",panghu);中的%s

说明符如下

说明符注释
%d以十进制整数的格式输出
%s以字符串的的格式输出
%x以十六进制数的格式输出
%c以字符的格式输出
%p以指针的格式输出
%n到目前为止所输出的字符数(把一个int值写到指定的地址去)

%n是一个不常见的格式化字符串,可以将一个int型的值(4字节)写入指定的地址中,这将可以实现栈空间的随意改写。

除了%n,还有%hn%hhn%lln,分别为写入目标空间2字节,1字节,8字节,这里的字节对应的是参数的地址开始的几字节。

以如下程序举例:

#include <stdio.h>

int main() {
    int c = 0;
    printf("the usage of %n", &c);
    printf("c = %d\n", c);
    printf("%p", c);
    return 0;
}

执行结果如下
在这里插入图片描述the usage of 长度为13,而0xd为13 指针形式16进制表示。而想打印c的地址还得用 printf("%p", &c); 来。

2 漏洞产生机理

printf并不是一般的函数,它是C语言中少有的支持可变参数的库函数,所以,在被调用之前,被调用者无法知道函数调用之前有多少个参数被压入栈中。所以printf函数要求传入一个format参数以指定参数的数量和类型,然后printf函数就会严格的按照format参数所规定的格式逐个从栈中取出并输出参数。

比如在 printf("%s %c %d","num",'a',2); 中,format参数有3个格式串。

#include "stdio.h"
#include "string.h"
#include <stdio.h>

int main()
{
    printf("%s %c %d %d %d %d %d %d %d\n","num",'a',2, 1, 2, 3, 4, 5, 6);
    return 0;
}

gcc -o no_format no_format.c -g命令编译后

查看main函数汇编代码,发现printf的前6个参数从左到右分别用rdi, rsi, edx, ecx, r8d, r9d保存,直到第7个才开始压栈。

在这里插入图片描述

再看一个有问题的

#include "stdio.h"
#include "string.h"
#include <stdio.h>

int main()
{
    printf("%s %c %d %d %d %d %d %d %d %x %p\n","num",'a',2, 1, 2, 3, 4, 5, 6);
    return 0;
}

在这里插入图片描述
栈中的数据如下
在这里插入图片描述
其中,CF0为rsp,D10为rep。

执行结果
![在这里插入图片描述](https://img-blog.csdnimg.cn/img_convert/5ef687bb52c282a6157ac

当然,仅仅有问题还不够,还得知道如何利用,用CTF题目改版后的代码来看看

#include "stdio.h"
#include "string.h"
#include <stdio.h>

int main()
{
     int pwnme = 0x11111111;
     char v8[100];
     puts("leave your message please:");
     gets(v8);
     puts("your message is:");
     printf(v8);
     printf("\n");
     if ( pwnme == 8 )
     {
       puts("you pwned me, here is your flag:\n");
     }
     else
     {
       puts("Thank you!\n");
     }
     return 0;
}

gcc -m32 -fno-stack-protector -o middle_format middle_format.c编译,这里用32位编译,不然64位都是8字节有点难搞。

假设能够让pwnme的值为8就算胜利,这里可以利用printf%n参数修改pwnme的值。

用IDA查看堆栈信息,发现v8ebp - 0x70的位置, pwnmeebp - 0xC

在这里插入图片描述

输入v8abcdefgh-%p-%p-%p-%p-%p-%p-%p-%p-%p测试v8printf参数第几个位置
在这里插入图片描述

可以看到从第6和第7个参数的位置分别均为0x646362610x68676665(linux小段存储),即v8从第6个参数开始(4字节一个)。

(代码执行过程中的很多信息都保存在栈上),比如调用printf时堆栈保存的数据为(这里不考虑ebp,esp指向位置,只考虑堆栈内容)

printf函数局部变量等
main函数的 ebp
返回地址
printf参数1(aaaa-%x)等等
main函数局部变量

执行printf时,main函数ebp和printf函数局部变量等位于printf的栈帧中,剩下3个位于main函数的栈帧(不考虑现在用寄存器保存调用参数)。

而需要利用printf泄漏或者修改的为 main函数的一些局部变量(比如局部变量v8,它相对于printf的参数相对位置为6个int变量,而根据IDA信息推测,v8在-0x70,而pwnme在-0xC,中间隔了112-12=100个字节,间隔相对位置为25个,所以修改的为25 + 6 = 31)。

那么怎么修改呢?肯定是用%n$操作符。这里%6$n表示把之前输出的长度赋值为第6个参数,不包括%6$n的长度。而这里修改pwnme肯定是%31$n。payload为aaaaaaaa%31$n

假如输入abcdefgh-%6$p
在这里插入图片描述

可以看到参数区的和main局部变量区的abcd都输出了

现在看看第31个参数位置是什么,输出0x11111111(即pwnme)。现在要修改pwnme的值为8,那么输入即abcdefgh%31$n(随便8个可打印字符都可以)。
在这里插入图片描述
可以触发了保护机制,不知道怎么关闭,希望大神可以指教指教。

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值