内容纲要

ShellCode --- 修改函数返回地址 , 让其指向溢出数据中的一段指令

该类攻击方法的主要任务是 : 在溢出数据内包含一段攻击指令 , 用攻击指令的起始地址覆盖函数的返回地址

攻击指令一般是用来打开Shell的 , 从而可以获得当前进程的控制权 , 所以这类指令片段也被称为 : ShellCode

ShellCode可以用汇编语言写后转换成对应的机器码 , 也可以从网上直接获得 . 但学习必须要知其所以然 , 因此下面分析溢出数据的组成 , 再确定对应的填充信息

  • 溢出数据组成分析

    在函数调用发生后 , 栈中的结构是这样的

    png

    这里最主要的任务是覆盖 callee 的返回地址 , 因此填充信息的组成应该如下图所示
    栈中的结构是这样的

    png

     padding1 : 可以用任意数据填充( 但如果利用字符串程序输入溢出数据时不要包含 " \x00 " , 因为 " \x00 " 会截断后面的数据) , 数据的长度应该刚好覆盖 callee 的基地址
    
     address of shellcode : 是shellcode的起始地址 , 用它来覆盖返回地址.
    
     padding2 : 可以用任意数据填充 , 长度由 shellcode 的位置决定
    
     shellcode : 应该为十六进制的机器码
    

    根据上述内容 , 完整的 Payload 如下

    Payload : padding1 + address of shellcode + padding2 + shellcode

  • 解决构造 Payload 的两个问题

    • 在返回地址前的填充数据( padding1 )应该多长?
      可以用调试工具( gdb , objdump , ... )查看汇编代码来确定这个距离 , 也可以在运行程序时不断修改输入长度来试探( 如果返回地址被无效地址( 比如 "AAAA" )覆盖 , 则程序会报错并终止 )

    • shellcode 的起始地址应该是多少?
      可以在调试工具里查看返回地址的位置( 查看ebp的内容然后加4 , 这个值是不固定的 ) . 并且调试工具里的这个地址和正常运行时是不一致的 , 由运行时环境变量等因素有所不同造成 . , 这种情况下我们只能拿到大致但是不确定的shellcode起始地址.

      比较好的解决方法是在 padding2 中填充若干长度的 " \x90 " , 该机器码对应的指令为 NOP ( No Operation , 告诉CPU什么都不做 , 直接跳转到下一条指令 ) , 有了这一段 NOP 的填充 , 只需要返回地址能够命中这一段指令中的任意位置 , 都可以无副作用的跳转到shellcode的起始地址

      这种方法被称为 NOP Sled( 滑雪撬 ) , 通过增加 NOP 填充来配合试验shellcode的起始地址

    操作系统可以将函数调用栈的起始地址设置为随机化( 这种技术被称为 " 内存空间布局随机化 , Address Space Layout Randomization( ASLR )" ) , 这样每次程序运行时函数的返回地址会随机变化

    反之如果操作系统关闭了 ASLR 技术 , 那么程序每次运行时函数的返回地址都是相同的 , 这样才可以使用填充无效的溢出数据来生成 core 文件 , 再通过调试工具在 core 文件中找到返回地址的位置 , 从而确定shellcode的起始地址

    因此 , 操作系统没有开启 ASLR 是填充无效溢出数据找出shellcode初始地址的第一个前提

  • shellcode溢出数据的最终构造

    通过上面的内容可以更进一步的构造 Shellcode 的溢出数据 , 如下图所示

    png

    该攻击方法生效需要两个前提

    • 第一个前提是上文提到的操作系统关闭 ASLR( 地址空间布局随机化 )

    • 第二个前提是在函数调用栈上的数据( 也就是攻击者构造的shellcode )有可执行权限

      很多时候操作系统会关闭函数调用栈的可执行权限 , 这样shellcode的方法就失效了

      不过可以尝试使用内存中已有的指令或者函数 , 毕竟这部分内容是本来就存在的 , 不会受到函数执行权限的限制


后续

上文提到在调用函数栈上数据没有执行权限时 , 可以使用内存中已有的数据 , 那么如何使用这些已有的指令或者函数呢 ? 这就涉及到 Return2libcROP 两种攻击技术了

  • Return2libc : 修改返回地址 , 让其指向内存中已有的某个函数

  • ROP : 修改返回地址 , 让其指向内存中已有的某段指令

最后修改日期:2019年9月17日

作者

留言

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。