软件漏洞篇(上)


漏洞

漏洞也称为脆弱性(Vulnerability),是计算机系统的硬件、软件、协议在系统设计、具体实现、系统配置或安全策略上存在的缺陷

缺陷一旦被发现并被恶意利用,就会使攻击者在未授权的情况下访问或破坏系统,从而影响计算机系统的正常运行甚至造成安全损害。

对于漏洞有多种称呼,包括Hole, Error, Fault, Weakness, Failure等,这些称呼都不能涵盖漏洞的含义(脆弱性)。

软件漏洞专指计算机系统中的软件系统漏洞

缓冲区溢出漏洞

基本概念

缓冲区

缓冲区是一块连续的内存区域,用于存放程序运行时加载到内存的运行代码和数据。

缓冲区溢出

缓冲区溢出是指程序运行时,向固定大小的缓冲区写入超过其容量的数据,多余的数据会越过缓冲区的边界覆盖相邻内存空间,从而造成溢出。

缓冲区的大小是由用户输入的数据决定的,如果程序不对用户输入的超长数据作长度检查,同时用户又对程序进行了非法操作或者错误输入,就会造成缓冲区溢出。

缓冲区溢出攻击

缓冲区溢出攻击是指发生缓冲区溢出时,溢出的数据会覆盖相邻内存空间的返回地址、函数指针、堆管理结构等合法数据,从而使程序运行失败、或者发生转向去执行其它程序代码、或者执行预先注入到内存缓冲区中的代码

缓冲区溢出后执行的代码,会以原有程序的身份权限运行。

造成缓冲区溢出的根本原因

缺乏类型安全功能的程序设计语言(C、C++等)出于效率的考虑,部分函数不对数组边界条件和函数指针引用等进行边界检查。例如,C 标准库中和字符串操作有关的函数,像strcpy,strcat,sprintf,gets等函数中,数组和指针都没有自动边界检查

程序员开发时必须自己进行边界检查,防范数据溢出,否则所开发的程序就存在缓冲区溢出的安全隐患,而实际上这一行为往往被程序员忽略或者检查不充分。

缓冲区溢出通常包括栈溢出堆溢出异常处理SEH结构溢出单字节溢出等。

栈溢出漏洞

基本概念

栈溢出漏洞,即发生在栈区的溢出漏洞。被调用的子函数中写入数据的长度,大于栈帧的基址到esp之间预留的保存局部变量的空间时,就会发生栈的溢出。要写入数据的填充方向是从低地址向高地址增长,多余的数据就会越过栈帧的基址,覆盖基址以上的地址空间。

栈溢出漏洞示例

下面的程序演示了一个溢出漏洞,代码如下 :

void why_here(void)
{    printf("why u r here?!\n"); 
      exit(0); 
}
void f()
{     int buff[1];
       buff[2] = (int)why_here;
} 
int main(int argc, char * argv[])
{      f();
        return 0;
}

如程序所示,主函数将调用函数f,并没有调用why_here函数,但是运行结果如下:在这里插入图片描述

在函数f中,所声明的数组buff长度为1,但是由于没有对访问下标的值进行校验,程序中对数组外的内存进行了读写,这是一个典型的溢出漏洞。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RTMsaz5O-1655273214921)(31f362c510b71e12cd2eaec670fc9f1c.png)]

void why_here(void)
{    printf("why u r here?!\n"); 
      exit(0); 
}
void f()
{     int buff; int * p = &buff; 
       ________= (int)why_here;
} 
int main(int argc, char * argv[])
{      f();
        return 0;
}
//答案:*(p+2)或者p[2]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m3OBxqRh-1655273214921)(c14d0d5f8964a6b0d4d23eafbab7bb10.png)]

溢出漏洞利用示例

修改返回地址

栈的存取采用先进后出的策略,程序用它来保存函数调用时的有关信息,如函数参数、返回地址,函数中的非静态局部变量存放在栈中。如果返回地址被覆盖,当覆盖后的地址是一个无效地址,则程序运行失败。如果覆盖返回地址的是恶意程序的入口地址,则源程序将转向去执行恶意程序

下面以一段程序为例说明栈溢出的原理。


void stack_overflow(char* argument)
{
    char local[4];
        for(int i = 0; argument[i];i++)
             local[i] = argument[i];
}

函数stack_overflow被调用时堆栈布局如下图所示。图中local是栈中保存局部变量的缓冲区,根据char local[4]预先分配的大小为4个字节,当向local中写入超过4个字节的字符时,就会发生溢出。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MIaEdUNS-1655273214922)(1bc74d210d06304d4d84b985572dbd6b.png)]

覆盖临接变量

在第三章,我们通过修改机器码实现了软件破解,接下来我们通过在输入上做文章(也是漏洞利用方式),试着覆盖临近变量的值,以便更改程序执行流程

函数的局部变量在栈中一个挨着一个排列。如果这些局部变量中有数组之类的缓冲区,并且程序中存在数组越界的缺陷,那么越界的数组元素就有可能破坏栈中相邻变量的值,甚至破坏栈帧中所保存的EBP值、返回地址等重要数据。
用一个简单例子来说明破坏栈内局部变量对程序的安全性有什么影响(VC6)。


#include <stdio.h>
#include <iostream>
#define PASSWORD "1234567"
int verify_password(char * password)
{
    int authenticated;
    char buffer[8];  //add local buff to be overflowed
    authenticated = strcmp(password, PASSWORD);
    strcpy(buffer, password);
    return authenticated;
}

void main()
{
    int valid_flag = 0;
    char password[1024];
    while(1)
   {
        printf("please input password:    ");
        scanf("%s", password);
        valid_flag = verify_password(password);
        if(valid_flag)
        {           printf ("incorrect password!\n\n");
         }
        else
         {           printf("Congratulation! You have passed the verification!\n");
                     break;
         }
   }
}

观察一下源代码不难发现,authenticated变量的值来源于strcmp函数的返回值,之后会返回给main函数作为密码验证成功与否的标志变量:
当authenticated为0时,表示验证成功;反之,验证不成功。

如果我们输入的密码超过了7个字符(注意:字符串截断符NULL将占用一个字节),则越界字符的ASCII码会修改掉authenticated的值。如果这段溢出数据恰好把authenticated改为0,则程序流程将被改变。 要**成功覆盖临近变量并使其为0**,有两个条件:

  • 输入一个8位的字符串的时候,比如“22334455”,此时,字符串的结束符恰恰是0,则覆盖变量authenticated的高字节并使其为0;
  • 输入的字符串应该大于“12345678”,因为执行strcmp之后要确保变量authenticated的值为1,也就是只有高字节是1,其它字节为0。
实验细节

打开OllyDBG,装载程序后,会停在程序入口点,单步执行可以定位到主函数:第一,主函数通过OllyDBG的信息提示区域,会显示main函数信息;第二,Windows控制台程序的主函数参数包含三个,即_argc、_argv和_environ,在函数调用前面的参数入栈环节具有鲜明的特征,截图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VenkQlUT-1655273214923)(1fc8a63dbdb8c6cf9150dc515da760df.png)]

此时,选择步入执行即可转到主函数。之后继续一步步执行程序,会遇到Scanf函数,弹出对话框,接受用户输入,我们输入“22334455”,然后会回到原来程序,继续单步运行,直到调用verify_password函数后,进入该函数代码区域。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lhmDC3TK-1655273214923)(1fbff66e84966cedf1c72d238f7590c2.png)]

在执行完口令比较后,运行完mov dword part [ebp-4], eax语句,该语句含义为将EAX寄存器的值(刚执行的strcmp函数的返回值)复制给地址ebp-4的局部变量。也就是,将口令比较的结果复制给我们定义的局部变量authenticated。

通过寄存器窗口,可知当前EBP寄存器值为0x0012FB24,观察此时栈区变化,观察此时ebp-4地址处的变量值,同时,我们将数据窗口定位到地址0x0012FB20处(数据窗口区域,点右键,选择“转到->表达式”,出现表达式后,输入0x0012FB20或EBP-4,然后选择“跟随表达式”),来观察后续的变化,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-doI6uuZt-1655273214924)(ffaa975b4396692e09039b8471dd5fed.png)]

当程序执行到strcpy溢出覆盖后的“mov eax, dword ptr [ebp-4]”之前,我们可以观察到,溢出成功的覆盖了变量authenticated的值为0x00000000。

堆溢出漏洞

堆溢出漏洞示例

堆溢出是指在堆中发生的缓冲区溢出。堆溢出后,数据可以覆盖堆区的不同堆块的数据,带来安全威胁。

我们将通过下面一个简单例子,来演示一个简单的堆溢出漏洞:该漏洞在产生溢出的时候,将覆盖一个目标堆块的块身数据。

示例:从堆区申请两个堆块,处于低地址的buf1和处于高地址的buf2。buf2存储了一个名为myoutfile 的字符串,用来存储文件名。buf1用来接收输入,同时将这些输入字符在程序执行过程中写入到buf2 存储的文件名myoutfile 所指向的文件中。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#define FILENAME "myoutfile"
int main(int argc, char *argv[])
{
    FILE *fd;
    long diff;
    char bufchar[100];
    char *buf1 = (char *)malloc(20);
    char *buf2 = (char *)malloc(20);
    diff = (long long)buf2 - (long long)buf1;
    strcpy(buf2, FILENAME);
    printf("----信息显示----\n");
    printf("buf1 存储地址:%p\n", buf1);
    printf("buf2 存储地址:%p,存储内容为文件名:%s\n", buf2, buf2);
    printf("两个地址之间的距离:%d 个字节 \n", diff);
    printf("----信息显示----\n\n");
    if (argc < 2)
    {
        printf("请输入要写入文件%s 的字符串:\n", buf2);
        gets(bufchar);
        strcpy(buf1, bufchar);//很明显,往buf1复制,没有边界检查
    }
    else
    {
        strcpy(buf1, argv[1]);//很明显,往buf1复制,没有边界检查
    }
    //溢出后,导致buf2可能变成设计的目标文件,而非原始文件
    printf("----信息显示----\n");
    printf("buf1 存储内容:%s \n", buf1);
    printf("buf2 存储内容:%s \n", buf2);
    printf("----信息显示----\n");
    printf("将%s\n 写入文件 %s 中\n\n", buf1, buf2);
    fd = fopen(buf2, "a");
    if (fd == NULL)
    {
        fprintf(stderr, "%s 打开错误\n", buf2);
        if (diff <= strlen(bufchar))
        {
            printf("提示:buf1 内存溢出!\n");
        }
        getchar();
        exit(1);
    }
    fprintf(fd, "%s\n\n", buf1);
    fclose(fd);
    if (diff <= strlen(bufchar))
    {
        printf("提示:buf1 已溢出,溢出部分覆盖 buf2 中的 myoutfile\n");
    }
    getchar();
    return 0;
}

通过malloc命令,申请了两个堆的存储空间。接着定义了diff变量,它记录了buf1和buf2之间的地址距离,也就是说buf1和buf2之间还有多少存储空间。(具体空间大小看编译器应该)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ddM1kBYC-1655273214924)(3e4e99794a43aab6e66997bac952b5be.png)]

输入字符串的长度为大于72个字节,而且刻意构造一个自定义的字符串“hostility”,是输入为“72字节填充数据”+“hostility”。可见buf1的内容长度是超过了72个字节的,而buf2的内容就变成了hostility。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2DX2CatJ-1655273214925)(73e3229bfec0038e1b1d0b8c6a1257a7.png)]

fopen语句将buf2指向的文件打开,打开的形式是追加行,用了关键字“a”。即打开这个文件后,如果这个文件是以前存在的,那么写入的文件就添加到已有的内容之后;如果是以前不存在的一个文件,就创建这个文件并写入相应的内容。
用fprintf 语句将buf1中已经获得的语句写入到这个文件里。然后关闭文件。

因为buf1后面没有字符串结束符,所以读取不会停止!

堆溢出漏洞利用

相比于栈溢出,堆溢出的实现难度更大,而且往往要求进程在内存中具备特定的组织结构。然而,堆溢出攻击也已经成为缓冲区溢出攻击的主要方式之一。堆溢出带来的威胁远远不止上面示例演示的那样,结合堆管理结构,堆溢出漏洞可以在任意位置写入任意数据!

堆管理结构

在Windows系统中,占有态的堆块被使用它的程序索引,而堆表只索引所有空闲态的堆块。其中,最重要的堆表有两种:空闲双向链表freelist(简称空表)和快速单向链表lookaside(简称快表)。

堆块三类操作:堆块分配、堆块释放和堆块合并,归根结底是对空表链的修改。这些修改无外乎要向链表里链入和卸下堆块。根据对链表操作的常识,我们可以知道,从链表上卸载(unlink)一个节点的时候会发生如下操作:

node—>blink—>flink = node—>flink ;
node—>flink—>blink = node—>blink ;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-82YytH3x-1655273214926)(9bf243ed5dead30b5acb2d195150a416.png)]

具体的,在Windows堆内存分配时会调用函数RtlAllocHeap,该函数从空闲堆链上摘下一空闲堆块,完成双向链表里相关节点的前后指针的变更操作,它会执行如下操作:


//空闲堆块的前向指针(数值)写入到空闲堆块的后向指针(地址)里去

mov dword ptr [edi], ecx ;
mov dword ptr [ecx+4], edi ;

//ecx相当于node->flink,edx相当于node->blink

//[edi]相当于node—>blink—>flink,[ecx+4]相当于node—>flink—>blink

//[ecx]相当于node—>flink—>flink,前向指针的地址在后向指针前面!!!

其中ecx为空闲可分配的堆区块的前向指针,edi为该堆区块的后向指针。这两条汇编语句恰好对应了上述两个链表卸载节点对应的前后向指针变化的操作。

Dword Shoot攻击

如果我们通过堆溢出覆写了一个空闲堆块的块首的前向指针flink和后向指针blink,我们可以精心构造一个地址和一个数据当这个空闲堆块从链表里卸下的时候,就获得一次向内存构造的任意地址写入一个任意数据的机会。这种能够向内存任意位置写入任意数据的机会称为“Arbitrary Dword Reset”(又称Dword Shoot)。具体如下图所示。

why?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gvBIsGHS-1655273214926)(14bcf26258852f34bd8da072a45d669c.png)]

基于Dword Shoot攻击,攻击者甚至可以劫持进程,运行植入的恶意代码。比如,当构造的地址为重要函数调用地址、栈帧中函数返回地址、栈帧中SEH的句柄等时,写入的任意数据可能就是恶意代码的入口地址。

Dword Shoot攻击示例

堆溢出漏洞示例:以下列程序为例,演示堆块分配过程中潜在的Dword Shoot攻击。
实验环境:VC6.0、Windows XP SP3、Debug模式

在讲这个实验之前,先介绍一下Windows的堆使用。
在Windows里,可以使用Windows缺省堆,也可以用户自己创建新堆:

  • 获取缺省堆可以通过GetProcessHeap函数(无参数)得到句柄;
  • 创建新堆可以用HeapCreat函数。
  • 除了malloc、new等函数外,C/C++也提供了HeapAlloc、HeapFree等函数用于堆的分配和释放。
#include <windows.h>
main()
{
    HLOCAL h1, h2,h3,h4,h5,h6;
    HANDLE hp;
    hp = HeapCreate(0,0x1000,0x10000); //创建自主管理的堆
    h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);//从堆里申请空间
    h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
    h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
    h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
    h5 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
    h6 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);

_asm int 3  //手动增加int3中断指令,会让调试器在此处中断
//依次释放奇数堆块,避免堆块合并
HeapFree(hp,0,h1); //释放堆块
HeapFree(hp,0,h3); 
HeapFree(hp,0,h5); //现在freelist[2]有3个元素

h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8); 

return 0;

}
整个流程解析
  1. 程序首先创建了一个大小为 0x1000 的堆区,并从其中连续申请了6个块身大小为 8 字节的堆块加上块首实际上是6个16字节的堆块
  2. 释放奇数次申请的堆块是为了防止堆块合并的发生
  3. 三次释放结束后,会形成三个16个字节的空闲堆块放入空表。因为是16个字节,所以会被依次放入freelist[2]所标识的空表,它们依次是h1、h3、h5。
  4. 再次申请8字节的堆区内存,加上块首是16个字节,因此会从freelist[2]所标识的空表中摘取第一个空闲堆块出来,即h1
  5. 如果我们手动修改h1块首中的前后向指针,能够观察到 DWORD SHOOT 的发生。
实验过程
  • 执行 HeapFree(hp,0,h1)语句时

hp为0x003a0000, h1为0x003a0688,根据堆块结构,我们知道 h1 堆块的块身起始位置为0x003a0688,块首起始位置为0x003a0680。观察该语句执行后,对应的内存变化,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NMJ47qvs-1655273214927)(a1d7bc333512ca33e9c11db9997e67b7.png)]

可见,除了块首状态变化外,0x003a0688开始的**块身位置的前8个字节(Flink 和 Blink)**发生了变化,由0x000000变为具体的有效地址。

注意到,这个是第一个16字节的堆块释放,将
被链入到freelist[2]空表中,而此时Flink和Blink的值都是0x003a0198,也是freelist[2]的地址。我们转到0x003a0198处,观察内存为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zNad6rx3-1655273214927)(11f8dc7b87b0515b5bb1bcb38fa62830.png)]

可见,freelist[2]的Flink和Blink都是0x003a0688。意味着,当前 freelist[2]唯一后继节点就是刚刚空闲的h1块(地址为0x003a0688),而h1块是唯一前继节点是freelist[2]。其它地址(freelist[3]、freelist[4]、freelist[5])的Flink和Blink均指向自身,说明都是空表。

  • 执行HeapFree(hp,0,h3)和HeapFree(hp,0,h5)后

可知,此时 freelist[2]链表状态为:freelist[2]<=>h1<=>h3<=>h5。

  • 执行HeapAlloc(hp,HEAP_ZERO_MEMORY,8)语句时

此时,当再次分配空间的时候,从freelist[2]的双向链表里摘下一块大小为16字节的堆块,首先摘得h1(地址为0x003a0688)。

观察此时:

  1. freelist[2](地址为0x003a0198)所存储的信息为:Flink(前4个字节)为0x003a0688,Blink(后 4 个字节)为0x003a0708。
  2. h1(地址为0x003a0688)所存储的信息为:Flink 为 0x003a06c8,Blink 为 0x003a0198。
  3. h3(地址为0x003a06c8)所存储的信息为:Flink 为0x003a0708,Blink为0x003a0688。摘走h1之后,内存变为:
  4. freelist[2](地址为0x003a0198)的前 4 个字节变为0x003a06c8,实际发生了将h1 后向指针(值为0x003a0198)地址处的值写为h1前向指针的值。
  5. h3(地址为0x003a06c8)的Blink 变为h1->Blink,即0x003a0198,实际发生了将 h1 前向指针(值为0x003a06c8)地址处的值写为 h1后向指针的值。
  • Dword Shoot 攻击

假设在执行该语句之前,h1的Flink和Blink被改写为特定地址和特定数值,那么就完成一
次Dword Shoot攻击。

注意:在Windows XP以后的操作系统中,因为引入地址随机化等防护措施,使得此类的堆溢出Dowrd Shoot攻击变的越来越难。

其它溢出漏洞

SEH结构溢出

为了保证系统在遇到错误时不至于崩溃,仍能够健壮稳定地继续运行下去,Windows会对运行在其中的程序提供一次补救的机会来处理错误,这种机制就是异常处理机制
异常处理结构体SEH是Windows异常处理机制所采用的重要数据结构:

  • SHE结构体==存放在栈中==,栈中的多个SEH通过链表指针在栈内由栈顶向栈底串成单向链表;
  • 位于链表最顶端的SEH通过线程环境块(TEB,Thread Environment Block)0字节偏移处的指针标识;
  • 每个SEH包含两个DWORD指针:SEH链表指针和异常处理函数句柄,共8个字节。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KfH1DGWZ-1655273214928)(4fe8d0e44169e83eed814f14c701a7af.png)]

SEH结构用作异常处理,主要包括如下三个方面:

  • **当线程初始化时,会自动向栈中安装一个SEH,作为线程默认的异常处理。**如果程序源代码中使用了_try{}_except{}或者Assert宏等异常处理机制,编译器将最终通过向当前函数栈帧中安装一个SEH来实现异常处理。
  • 当异常发生时,操作系统会中断程序,并首先从TEB的0字节偏移处取出距离栈顶最近的SEH,使用异常处理函数句柄所指向的代码来处理异常当最近的异常处理函数运行失败时,将顺着SEH链表依次尝试其他的异常处理函数。
  • 如果程序安装的所有异常处理函数都不能处理这个异常,系统会调用默认的系统处理程序,通常显示一个对话框,你可以选择关闭或者最后将其附加到调试器上的调试按钮。如果没有调试器能被附加于其上或者调试器也处理不了,系统就调用ExitProcess终结程序。

SEH攻击

SEH攻击是指通过栈溢出或者其他漏洞,使用精心构造的数据覆盖SEH链表的入口地址、异常处理函数句柄或链表指针等,实现程序执行流程的控制。
因为发生异常的时候,程序会基于SEH链表转去执行一个预先设定的回调函数,攻击者可以利用这个结构进行漏洞利用攻击。

  • 由于SEH存放在栈中,利用缓冲区溢出可以覆盖SHE。
  • 如果精心设计溢出数据,则有可能把SEH中异常处理函数的入口地址更改为恶意程序的入口地址,实现进程的控制。

SEH链表在栈中的实际分布

char shellcode[] = "";
void HackExceptionHandler()
{
    printf("got an exception, press Enter to kill processn");
    getchar();
    ExitProcess(1);
}
void test(char* input){
    char buf[200];
    int zero = 0;
 
    __try    {
        strcpy(buf, input);
        zero = 4 / zero;
    }__except(HackExceptionHandler())
    {    }
} 
int main(){
    test(shellcode);    
    return 0;
}

拖入OllyDBG动态调试,选择View下的SEH chain选项,就能看到当前栈中的SEH的情况。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KIZ3io6L-1655273214928)(bac98b5900116c2c3eed7dc206112731.png)]

从图中能看出,0012FF18是离栈顶最近的SHE(此时栈顶为0x0012FFC4)。接着我们在调试的栈窗口去验证存在的SEH链,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D6cCvD9J-1655273214929)(856a46f639c316d5b9523bfba49dc50f.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O4qpnM16-1655273214930)(9cc8723beb1d5cd1a31bf041725d8e1e.png)]

单字节溢出

单字节溢出是指程序中的缓冲区仅能溢出一个字节。


void single_func(char *src){
    char buf[256];
    int i;
    for(i = 0;i <= 256;i++)
         buf[i] = src[i];            
}

//感觉是水平不高的程序员写出的程序缺陷...

缓冲区溢出一般是通过覆盖堆栈中的返回地址,使程序跳转到shellcode或指定程序处执行。然而在一定条件下,单字节溢出也是可以利用的,它溢出的一个字节必须与栈帧指针紧挨,就是要求必须是函数中首个变量,一般这种情况很难出现。

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值