缓冲区溢出攻击技术文伟平博士副教授weipingwen@北京大学软件与微电子学院信息安全系北京大学信科学院软件所信息安全实验室课程内容缓冲区溢出相关背景概念 缓冲区溢出原理溢出保护技术安全编程技术引子988o s蠕虫事件 1988MorrisCERT统计数据缓冲区¾从程序的角度,缓冲区就是应用程序用来保存用户输入数据程序临时数据的内存间输入数据、程序临时数据的内存空间¾缓冲区的本质z数组¾存储位置z Stackz Heapz数据段缓冲区溢出¾如果用户输入的数据长度超出了程序为其分配的内存空间,这些数据就会覆盖存间,这些数据就会覆程序为其它数据分配的内存空间,形成所谓的缓冲区溢出缓冲区溢出攻击的发展历史 1980s¾Morris蠕虫-fingerd缓冲区溢出攻击1996¾Aleph One,Smashing the Stack for Funand Profit,Phrack491998¾Dildog:提出利用栈指针的方法完成跳转¾The Tao of Windows Buffer Overflows1999¾Dark Spyrit:提出使用系统核心DLL中的JmpESP指令完成跳转,Phrack55¾M.Conover:基于堆的缓冲区溢出教程缓冲区溢出攻击背景知识与技巧 编译器调试器的使用编译器、调试器的使用¾Linux:gcc+gdb¾Win32:VC6.0+OllyDbgVC60+OllyDbg进程内存空间结构汇编语言基本知识栈的基本结构函数调用过程GCC编译器基础著名的的/编译器著名的GNU的Ansi c/c++编译器¾gcc[options][filenames]¾编译:gcc c test.c生成test.o-c test c test o¾连接:gcc-o test test.o¾同时搞定:gcc test.c o testtest c-omake:用于控制编译过程¾Makefile How ToGDB调试器的使用 断点相关指令/,//¾break/clear,disable/enable/delete¾watch–表达式值改变时,程序中断执行相关指令¾run/continue/next/step¾attach–调试已运行的进程¾finish/return信息查看相关指令reg/break/files/args/frame/functions/¾info reg/break/files/args/frame/functions/…¾backtrace–函数调用栈¾print/f exp–显示表达式的值/f dd¾x/nfu addr–显示指定内存地址的内容¾list–列出源码¾disass func–反汇编指定函数VC6.0命令行环境变量我的电脑属性高级环境变量¾我的电脑-属性-高级-环境变量¾PATH:z C:\Program Files\Microsoft Visual Studio\VC98\Bin;z C:\Program Files\Microsoft VisualStudio\Common\MSDev98\Bin;¾INCLUDE:z C:\Program Files\Microsoft VisualStudio\VC98\Include¾LIB:z C:\Program Files\Microsoft Visual Studio\VC98\Lib 命令行指令¾cl sourcefilename–编译并链接Win32平台调试器OllyDbg¾32-bit assembler level analysing debugger by Oleh Yuschuk¾Free¾支持插件机制z OllyUni:查找跳转指令功能z Uiltra String Referennce:查找字符串SofticeSyser DebuggerWinDbgP W32DASMIDA Pro,W32DASM简单溢出实例#include<stdio.h>int main(){char name[8]={0};printf(Your name:);printf(“Your name:”);gets(name);printf(Hello,%s!,name);printf(“Hello%s!”name);return0;}test0.c缓冲区溢出的危害应用程序异常系统不稳定甚至崩溃统稳定甚崩溃程序跳转到恶意代码,控制权被窃缓冲区溢出原理 预备知识¾理解程序内存空间¾理解堆栈¾理解函数调用过程¾理解缓冲区溢出的原理Windows环境下的堆栈程序空间由何构成程序空间由何构成?堆栈是什么?堆栈里面放的都是什么信息?堆栈面放的都是什么信息程序使用超过了堆栈默认的大小怎么办? 在一次函数调用中,堆栈是如何工作的?Win32进程内存空间系统核心内存区间¾0xFFFFFFFF~0x80000000(4G~2G)¾为Win32操作系统保留用户内存区间¾0x00000000~0x80000000(2G~0G)¾堆:动态分配变量(Malloc),向高地址增长¾静态内存区间:全局变量、静态变量¾代码区间:从0x00400000开始¾栈:向低地址增长z单线程进程:(栈底地址:0x0012FFXXXX)¾多线程进程拥有多个堆/栈程序在内存中的映像堆(Heap)……内存低地址(p)堆的增长方向堆栈段内存栈(stack)栈的增长方向数据段递增方初始化数据段非初始化数据段(BSS)文本(代码)段向系统DLL 代码段内存高地址0x800000000x 7FFFFFFF PEB&TEB 内核数据代码080000000栈栈是块连续的内存间栈是一块连续的内存空间¾先入后出¾生长方向与内存的生长方向正好相反,从高地址向低地址生长每一个线程有自己的栈¾提供一个暂时存放数据的区域使用POP/PUSH指令来对栈进行操作 使用ESP寄存器指向栈顶,EBP指向栈帧底栈内容函数的参数函数返回地址的值EBP的值些通用寄存器(EDI,ESI)的值一些通用寄存器(EDI,ESI…)的值 当前正在执行的函数的局部变量三个重要的寄存器()SP(ESP)¾即栈顶指针,随着数据入栈出栈而发生变化BP(EBP)¾即基地址指针,用于标识栈中一个相对稳定的位置。
通过BP,可以方便地引用函数参数以及局部变量IP(EIP)¾即指令寄存器,在将某个函数的栈帧压入栈中时,其中就包含当前的IP值,即函数调用返回后下一个执行语句的地址函数调用过程把参数压入栈保存指令寄存器中的内容,作为返回地址放入堆栈当前的基址寄存器放堆栈当前的基址寄存把当前的栈指针(ESP)拷贝到基址寄存器,作为新的基地址为本地变量留出一定空间,把ESP减去适为本地变量留出定空间把ESP减去适当的数值函数调用中栈的工作过程 调用函数前¾压入栈级函数传给函数的参数z上级函数传给A函数的参数z返回地址(EIP)z当前的EBPz函数的局部变量调用函数后¾恢复EBP¾恢复EIP¾局部变量不作处理例子-(逆向工程演示)#include <stdio.h> #define PASSWORD "1234567"main(){ int valid_flag=0; char password[1024];1234567int verify_password (char *password)char password[1024];while(1){printf("please input password:");{int authenticated; char buffer[8]; printf(please input password: ); scanf("%s",password);valid_flag =verify_password(password); if(lid fl)// add local buff to be overflowed if(valid_flag){ printf("incorrect password!\n\n"); } elseauthenticated=strcmp(passwo rd,PASSWORD);strcpy(buffer,password);{printf("Congratulation! You have passed the verification!\n");strcpy(buffer,password); //over flowed here!return authenticated;break; } }}} test1.c例子二int main()int main(){AFunc(5,6);return 0;}int AFunc(int i,int j) {int BFunc(int i,int j) {int m = 3; int n = 4; m=i;int m=1; int n=2;m = i;n = j; BFunc(m,n);m=i; n=j;(,); return 8;}return m; }test2.cpp函数调用中栈的工作过程EDI AFunc(5,6);push 6push 5ESI EBXcall _AFunc add esp+8当前EBP AFunc48h3(m=3)4(n=4)当前ESP5EIP(Next)_AFunc push ebp mov ebp,esp EBP 6语句执行前的ESPsub esp,48h //压入环境变量语句执行前的EBP//为局部变量分配空间栈中数据分配EDI ESI S EBX48h4(n=4)EIP(Next)EBP 3(m=3)65函数调用中栈的工作过程当前ESP AFunc(56)EDI前AFunc(5,6);……call AFunc ESI EBXcall _AFunc add esp+8AFunc当前EBP_AFunc {……return 0;}pop edi 48h3(m=3)4(n=4)5EIP pop edi pop esi pop ebx EBP 6语句执行前的ESPp padd esp,48h //栈校验b 语句执行前的EBPpop ebp ret当缓冲区溢出发生时……int AFunc(int i,int j){int m = 3;int n=4;int n 4;char szBuf[8] = {0};strcpy(szBuf, This is a overflow buffer!);strcpy(szBuf,“This is a overflow buffer!”);m = i;n = j;j;BFunc(m,n);return 8;}Linux系统下的栈溢出攻击 栈溢出攻击¾NSR模式¾NRS模式¾RS模式ShellcodeNSR溢出模式…RETRET …NOP shellcodeNOPNOP…RETLow Address HighAddress vulnerable1.c:stackexploit1.c:RNS溢出模式RET shellcode RET …RET …NOP NOP …NOP Low Address High AddressRS 溢出模式-利用环境变量shellcode Low AddressFILE NULL …High Address vulnerable2c:stackexploit3c:0xc0000000RET RET …RET vulnerable2.c:stackexploit3.c:栈溢出模式分析挑战¾溢出点(在哪改写返回地址?)¾Shellcode地址(将返回地址改写成什么?)NSR模式¾最经典的方法–Alpha One¾需要漏洞程序有足够大的缓冲区RNS模式¾能够适合小缓冲区情况,更容易计算返回地址RS模式¾最新的方法:(filename[]execve(filename,argv[],envp[]);¾Ret=0xc0000000–4–strlen(FILENAME)–strlen(shellcode),不需要任何NOP¾但对远程缓冲区溢出攻击不适用Shellcode C版本Shellcode 汇编版本shellcode_asm.c shellcode_asm_fix.c 去除’\0’Shellcode Opcode版本 31d2xor%edx,%edx52push%edx686e2f7368push$0x68732f6e682f2f6269push$0x69622f2f89e3mov%esp,%ebx52push%edx53push%ebxp,89e1mov%esp,%ecx8d420b lea0xb(%edx),%eaxcd80int$0x80远程登录的Shellcode缓冲区溢出攻击发生的直接原因没有内嵌支持的界保护没有内嵌支持的边界保护¾User funcs¾Ansi C/C++:strcat(),strcpy(),sprintf(),vsprintf(),bcopy(),gets(),scanf()…程序员安全编程技巧和意识可执行的栈(堆)¾给出Shell或执行任意的代码缓冲区溢出原理及其利用 缓冲区溢出种类¾栈溢出¾堆溢出¾整型溢出¾格式化字符串溢出¾其他溢出栈溢出特点¾缓冲区在栈中分配¾拷贝的数据过长¾覆盖了函数的返回地址或其它一些重要数据结构、函数指针栈溢出实例int AFunc(int i,int j)用BFunc的地址替换正常的AFunc返回地址,使程{int m = 3;int n =4;的返回地,使程序运行至BFunc int n 4;char szBuf[8] = {0}; *(int *)((int)szBuf+20)=BFunc;(int )((int)szBuf 20) BFunc;m = i;n = j;j;BFunc(m,n);return 8;}堆溢出(Heap Overflow)内存中的一些数据区¾.text包含进程的代码¾.data包含已经初始化的数据(全局的,或者static的、并且已经初始化的数据)¾.bss包含未经初始化的数据(全局的,或者static的、并且未经初始化的数据)¾heap运行时刻动态分配的数据区还有一些其他的数据区¾还有些其他的数据区在.data、.bss和heap中溢出的情形,都称为heap overflow:p,这些数据区的特点是数据的增长由低地址向高地址堆溢出堆和栈有何区别#define BUFLEN32¾内存的动态分配与静态分配int main(int argc,char*argv[]) {堆溢出特点¾char*buf1;buf1=(char*)malloc(BUFLEN); strcpy(buf1,argv[1]);缓冲区在堆中分配¾拷贝的数据过长¾覆盖了堆管理结构printf("%s\n",buf1); free(buf1);return0;}test3.c整型溢出宽度溢出()宽度溢出(Widthness Overflow)¾尝试存储一个超过变量表示范围的大数到变量中 运算溢出(Arithmetic Overflow)¾如果存储值是一个运算操作,稍后使用这个结果的程序的任何一部分都将错误的运行,因为这个计算程序的任何部分都将错误的运行因为这个计算结果是不正确的。