C语⾔常见漏洞-格式化字符串漏洞
格式化字符串漏洞
1 预备知识
在c语⾔中的printf,fprintf,sprintf,snprintf等print函数经常会⽤到类似%形式的⼀个或者多个说明符。⽐如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);
return0;
}
执⾏结果如下
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);
return0;
}
⽤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);
return0;
}
栈中的数据如下
其中,CF0为rsp,D10为rep。
当然,仅仅有问题还不够,还得知道如何利⽤,⽤CTF题⽬改版后的代码来看看
#include"stdio.h"
printf怎么输出字符#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");
}
return0;
}
⽤gcc -m32 -fno-stack-protector -o middle_format middle_format.c编译,这⾥⽤32位编译,不然64位都是8字节有点难搞。假设能够让pwnme的值为8就算胜利,这⾥可以利⽤printf的%n参数修改pwnme的值。
⽤IDA查看堆栈信息,发现v8在ebp - 0x70的位置, pwnme为 ebp - 0xC。
输⼊v8为abcdefgh-%p-%p-%p-%p-%p-%p-%p-%p-%p测试v8在printf参数第⼏个位置
可以看到从第6和第7个参数的位置分别均为0x64636261和0x68676665(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个可打印字符都可以)。
可以触发了保护机制,不知道怎么关闭,希望⼤神可以指教指教。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论