20169219《Linux内核原理及分析》第十二周作业
格式化字符串漏洞实验
格式化字符串攻击原理是利用格式化函数(如printf())的沿着堆栈指针向下打印的特性,通过只提供格式化字符串但不提供对应的变量,读取栈内空间的内容。
更进一步,通过将某个要攻击的目标地址放入栈中,就可以利用格式化字符串读写里面的值。
因此,它的攻击分为两步:
(1)第一步,将目标地址放入栈中;
(2)第二步,设计格式化字符串,读写目标地址里的值。
格式化字符串漏洞是由像 printf(user_input) 这样的代码引起的,其中 user_input 是用户输入的数据,具有 Set-UID root 权限的这类程序在运行的时候,printf 语句将会变得非常危险。
-
实验一:
找出 secret[1]的值
输入命令:
ls
vi miao.c
gcc -z execstack -fno-stack-protector -o miao miao.c
其中miao.c的代码为
#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;
}
编译后进行执行
并定位 int_input 的位置,这样就确认了%s 在格式字符串中的位置
找到输出中和输入相同的数字,这就是int_input在栈中存储的位置。
下面把secret[1]的地址填进去,并打印相应地址中的内容。(记得做进制转换)同时在格式字符串中加入%s,
结果如图
得到输出结果为U,这正是ASCII码中0x55的对应字母
从打印信息中可以发现secret[0]和secret[1]位于堆,即实际地址在堆中。第
一个secret(即变量secret的值)的地址可以在栈中找到,因为变量secret位于栈中。换句话说,如果想重写secret[0],它的地址已经在栈中,格式化字符串可以利用这一信息。然而,
尽管secret[0]就在secret[1]后面, 但在栈中它的地址无效。
修改 secret[1]的值
修改secret[1]的方法非常简单,仅需把前面的%s替换为%n即可,它的功能是将%n之前printf已经打印的字符个数赋值给传入的指针。通过%n我们就可以修改内存中的值了。和%sleak内存一样,只要栈中有我们需要修改的内存的地址就可以使用格式化字符串的漏洞修改它。
修改的结果是,secret[1]综合那个存储前面输出的字符数。
修改 secret[1]为期望值
为了修改secret[1]为期望值,我们只需要控制前面输出的字符数量。对于每个%x,我们可以修改为%0?x来填充前置0,或使用%.?d来控制小数点后的位数,使其总长度达到特定位数。
实验二
现在让我们把第一个 scanf 语句去掉,并去掉与 int_input 变量相关的所有语句,同时设置关闭地址随机化选项。操作如下:
程序将一个格式化字符串写入了一个叫 mystring 的文件,前 4 个字节由任意你想放 入格式化字符串的数字构成,接下来的字节由键盘输入。
#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 = 0x113222580;
/* 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");
}
}
修改 secret[0]的值
修改 test.c 后编译 test.c 与 write_string.c 然后通过 write_string 程序将内容输入进 mystring 文件中,文件内容包括代码中加入的头四个字节和你之后输入的内容。实验过程如下:
0x31=49=5*8+5个逗号+开头4个字节。