• 2019-2020-1 20199311《Linux内核原理与分析》第十二周作业


    1.问题描述

    通过这一周的实习,主要学习了格式化字符串的漏洞,同时进行了利用含有该漏洞的程序修改任意一块内存的实验。

    2.解决过程

    2.1 理论知识

    2.1.1 栈与格式化字符串

    printf的格式控制字符串组成如下:

    %[flags][width][.prec][length]type
    

    例如,

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

    上面的这段 C 语言代码运行结果为 The magic number is: 1911 ,字符串 The magic number is: %d 中的格式符 %d被参数(1911)替换。
    下表是常见的格式符

    格式符 含义 传递
    %d 十进制数(int)
    %u 无符号十进制数 (unsigned int)
    %x 十六进制数 (unsigned int)
    %s 字符串 ((const) (unsigned) char *) 引用(指针)
    %n %n 符号以前输入的字符数量 (* int) 引用(指针)

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

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

    如下图所示
    图片描述
    由上图可知,printf函数会从存放字符串的位置开始输出参数到对应位置。

    2.1.2 产生漏洞的原因

    正常的printf用法:

    #include <stdio.h>
    int main()
    {
      char str[100];
      scanf("%s",str);
      printf("%s",str);
      return 0;
    }
    

    错误的printf用法

    #include <stdio.h>
    int main()
    {
      char str[100];
      scanf("%s",str);
      printf(str);
      return 0;
    }
    

    漏洞形成原因:程序将格式化字符串的输入权交给用户,printf函数并不知道参数个数,它的内部有个指针,用来索检格式化字符串。对于特定类型%,就去取相应参数的值,直到索检到格式化字符串结束。所以没有参数,代码也会将format string 后面的内存当做参数以16进制输出。这样就会造成内存泄露。
    例如:

    #include <stdio.h>
    
    int main(void)
    {str
        char str[100];
        scanf("%s",str);
        printf(str);
        return 0;
    }
    

    如果我们输入

    AAAA%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x
    

    输出为

    AAAA61fe4c,61ffcc,76e4d250,70734fbf,fffffffe,76e473da,41414141,252c7825,78252c78,2c78252c,252c7825
    

    这时会从str[]首地址后面的内存地址当做16进制的值输出。

    2.1.3 利用漏洞访问任意位置内存

    我们需要得到一段数据的内存地址,但我们无法修改代码,供我们使用的只有格式字符串。如果我们调用 printf(%s) 时没有指明内存地址, 那么目标地址就可以通过 printf 函数,在栈上的任意位置获取。printf 函数维护一个初始栈指针,所以能够得到所有参数在栈中的位置。
    观察: 格式字符串位于栈上. 如果我们可以把目标地址编码进格式字符串,那样目标地址也会存在于栈上,格式字符串将保存在栈上的缓冲区中。
    示例:

    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;
    }
    

    如果我们让 printf 函数得到格式字符串中的目标内存地址 (该地址也存在于栈上), 我们就可以访问该地址。(注:代码中引号内容为 user_input 数组内容的展开)

    printf ("x10x01x48x08 %x %x %x %x %s");
    

    x10x01x48x08 是目标地址的四个字节, 在 C 语言中, x10 告诉编译器将一个 16 进制数 0x10 放于当前位置(占 1 字节)。%x 导致栈指针向格式字符串的方向移动。下图解释了攻击方式
    图片描述
    如图所示,我们使用四个 %x 来移动 printf 函数的栈指针到我们存储格式字符串的位置,一旦到了目标位置,我们使用 %s 来打印,它会打印位于地址 0x10014808 的内容,因为是将其作为字符串来处理,所以会一直打印到结束符为止。user_input 数组到传给 printf 函数参数的地址之间的栈空间不是为了 printf 函数准备的。但是,因为程序本身存在格式字符串漏洞,所以 printf 会把这段内存当作传入的参数来匹配 %x。
    最关键的问题在于设法找出 printf 函数栈指针(函数取参地址)到 user_input 数组首地址的这一段距离是多少,这段距离决定了你需要在 %s 之前输入多少个 %x。

    2.1.4 利用漏洞在内存中写一个数字

    %n: 该符号前输入的字符数量会被存储到对应的参数中去,例如

    int i;
    printf ("12345%n", &i);
    

    数字 5(%n 前的字符数量)将会被写入 i 中。运用同样的方法在访问任意地址内存的时候,我们可以将一个数字写入指定的内存中。只要将上一小节的 %s 替换成 %n 就能够覆盖 0x10014808 的内容。
    利用这个方法,攻击者可以做以下事情:

    • 重写程序标识控制访问权限
    • 重写栈或者函数等等的返回地址

    2.2 实验步骤

    2.2.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)
    ", &secret);
      printf("The variable secret's value is 0x%8x (on heap)
    ", secret);
      printf("secret[0]'s address is 0x%8x (on heap)
    ", &secret[0]);
      printf("secret[1]'s address is 0x%8x (on heap)
    ", &secret[1]);
    
      printf("Please enter a decimal integer
    ");
      scanf("%d", &int_input);  /* getting an input from user */
      printf("Please enter a string
    ");
      scanf("%s", user_input); /* getting a string from user */
    
      /* Vulnerable place */
      printf(user_input);  
      printf("
    ");
    
      /* Verify whether your attack is successful */
      printf("The original secrets: 0x%x -- 0x%x
    ", SECRET1, SECRET2);
      printf("The new secrets:      0x%x -- 0x%x
    ", secret[0], secret[1]);
      return 0;
    }
    

    程序说明:程序内存中存在两个秘密值,我们想要知道这两个值,但发现无法通过读二进制代码的方式来获取它们(实验中为了简单起见,硬编码这些秘密值为 0x44 和 0x55)。尽管我们不知道它们的值,但要得到它们的内存地址很简单,因为对大多数系统而言,每次运行程序,这些内存地址基本上是不变的。实验假设我们已经知道了这些内存地址,为了达到这个目的,程序特意为我们打出了这些地址。
    有了这些前提以后我们需要达到以下目标:

    • 找出 secret[1] 的值
    • 修改 secret[1] 的值
    • 修改 secret[1] 为期望值

    使用如下命令在/home/shiyanlou目录下下载源代码

    wget http://labfile.oss.aliyuncs.com/courses/228/vul_prog.c
    

    图片描述
    编译并给vul_prog程序赋予可执行权限(编译时可以添加以下参数关掉栈保护)

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

    图片描述
    警告可以忽略

    2. 找出 secret[1]的值

    运行 vul_prog 程序去定位 int_input 的位置,这样就确认了 %s 在格式字符串中的位置。
    图片描述
    12 的十六进制码就是 0x000C ,可以看到输出中 12 的十六进制在第 8 个位置上,这样我们就能确定格式化字符串的位置了。
    输入 secret[1] 的地址,做进制转换,同时在格式字符串中加入 %s 。
    图片描述
    可以看到 secret[1] 的地址是 0x602014 ,转换成十进制就是 6299668。第八个位置上替换成 %s 就能打印出 secret[1] 的值了。而U的ascii 码为55。

    3. 修改 secret[1]的值

    只要求修改secret[1]的值,不要求改成什么。
    图片描述
    修改为0x77。

    4. 修改 secret[1]为期望值

    如果想要将secret[1]的值修改为1000,可以用格式化字符填充
    图片描述
    从图上得知,secret[1] 的地址是 0x602014,换算成十进制就是 6299668。在 %.897u 前有 6 个 64 位地址,每个地址对应 16 个字符,加上逗号一共就是 6*(16+1)=102 ,在 %.897u 后面的地址上还有一个 , 占一个字符。所以一共是 102+1=103 个字符,要改成 1000 的话,还差 1000-103=897 个字符。而根据输出结果,0x3e8的十进制就是1000。

    2.2.2 实验二

    1.实验准备

    第一个 scanf 语句去掉,并去掉与 int_input 变量相关的所有语句,修改后的 vul_prog.c 程序如下图所示
    图片描述
    使用如下命令设置关闭地址随机化选项,关闭地址随机化后,这样每次运行程序得到的 secret 地址就都一样了。

    sudo sysctl -w kernel.randomize_va_space=0
    

    图片描述

    2. 编写程序修改secret[0]的值

    新建一个程序 write_string.c,将一个格式化字符串写入了一个叫 mystring 的文件,
    前 4 个字节由任意想放入格式化字符串的数字构成,接下来的字节由键盘输入。
    图片描述
    图片描述
    编译 vul_prog.c 与 write_string.c
    图片描述
    然后通过 write_string 程序将内容输入进 mystring 文件中,文件内容包括代码中加入的头四个字节和之后输入的内容。先运行 vul_prog 程序,输入 4 个 %016llx 。再运行 write_string 程序,输入 8 个 %016llx 和 1 个 %n ,此操作会生成一个 mystring 文件。然后输入如下命令,重定向

    ./vul_prog < mystring
    

    图片描述
    修改成功(0x8c = 140 = 8*16+8 个逗号+开头 4 个字节)。

    3.总结

    通过本周的实验,我初步了解了格式化字符串存在的漏洞,在接下来的学习中,将继续对linux系统中的内容进行深入理解。

  • 相关阅读:
    java子类父类的this相同吗
    activity与fragment交叉生命周期
    关于java内部类的一些疑问?
    java的super()
    android构建build之后的class文件分析。
    odoo 防暴力破解masterpassword,使用odoo 服务管控
    odoo 防暴力破解_on_login_cooldown
    odoo 结合fail2ban
    PowerShell Loops
    PowerShell Error Handling
  • 原文地址:https://www.cnblogs.com/w-a-n-s-d-j/p/11981922.html
Copyright © 2020-2023  润新知