漏洞类型及攻略方法

全写出来不太可能,图示大概这样:

Stack

以x86环境为例

每个进程在内存区域底部都有一个stack

  • 上半部分是在32位机器中运行32位程序
  • 下半部分是在64位机器中运行32位程序

它与C语言函数密切相关

1
2
3
4
5
6
7
8
9
10
int main(void){ 
int a, b;
..
func(a, b); ..
return 0;
}
void func(int a, int b){
int x, y;
..
}

例如,调用这个函数时stack状态如下:

ESP/EBP不容易理解,这里解释一下

  • ESP寄存器始终指向栈顶
  • EBP寄存器始终指向栈底
  • EBP-0x8能偶访问本地变量2
  • EBP-0x4能够访问本地变量1
  • EBP+0x8能够访问参数1
  • EBP+0xC能够访问参数2

调用原函数也有相同的帧并保存EBP寄存器,当函数被调用时,EBP会以新的帧为基础更新。因此,调用原函数的帧暂时保存到stack中

栈溢出

对策及解决方法

首先来看经典问题

  • 曾经,stack和heap都是有X权限的

  • BOF:buffer overflow

    • 写入数据超出区域

    • 覆盖写入后续区域

    • 例如这段代码:

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      int main(void){ 
      int a, b;
      ..
      func(a, b);
      ..
      return 0;
      }
      void func(int a, int b){
      char buf1[4];
      char buf2[4];
      strcpy(buf2,"AAAABBBB");
      }

  • 如果用户可以输入任意值,会发生什么?

    • 如果没有size限制,就可以任意进行覆盖

    • 甚至可以指定返回地址

    • 例如这段代码:

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      int main(void){ 
      int a;
      char s[256];
      ..
      read(stdin, s, 256);
      func(a, s);
      ..
      return 0;
      }
      void func(int a, char* s){
      char buf1[4];
      char buf2[4];
      strcpy(buf2, s);
      }

  • 添加shellcode

    • 如果Evil Return addr指向shellcode

      • 那么返回前会执行shellcode并执行shell(/bin/sh)
      • 可以执行任意代码

1-猜测地址

  • stack地址是动态的,取决于环境

    • 如何让它到达shellcode

  • 可以略微转移

    • nop-sled
      • nop = 0x90(空指令)
      • 尽可能多的拼接
      • 如果到达nop-sled的某个地方,shellcode将自动执行
  • 多次尝试

    • brute force
      • 如果不知道地址,那么可以尝试所有的可能
      • 结合使用nop-sled可以减少尝试次数

2-溢出量的问题

  • 考虑不能过多溢出的情况

    • nop-sled无法准备好(或者太短了)
      • 不能猜测地址
  • brute force需要太长时间

    • 如果运气好的话,能够执行shellcode
    • 实践中很少会成功

  • 使用保存栈地址的ESP比较好

  • ret2esp

    • 但是,如果代码中没有jmp esp或者call esp就很糟糕
    • 实际上很可能找不到

3-溢出量的问题

  • 只能够少量溢出的情况

    • nop-sled无法构造
    • 甚至连填充shellcode都不够

  • 可以使用较短的代码来读取后面更多内容

    • Stager

      • 通过溢出写入较短的汇编代码
      • 先运行这部分代码,读取其他shellcode
      • 通过读取的内容来控制程序运行流程
    • 具体大概像这样( 只有17个byte):

      • 1
        2
        3
        4
        5
        6
        7
        push byte 0x3 ; eax = NR_read pop eax
        push byte 0x4 ; ebx = fd
        pop ebx
        push byte 0xff; edx = size pop edx
        call next
        next:
        pop ecx ; ecx = buf(=ret_addr) int 0x80 ; read(fd, buf, size)

    • stager的优点

      • 使用Shellcode时,目的是首先执行Stager
      • 如果Stager移动,Shellcode也会移动
      • 因为它很短,很容易绕过限制
      • 可以轻松更改后续shellcode

对策 - Stack不可执行(NX)

如果stack和heap不能执行就太好了

  • 这就是NX(No eXecute / Never eXecute)

  • 有各种实现,例如NX比特位,PaX,Exec Shield(Redhat)等

    • 硬件实现或软件实现
  • 结果都是让stack之类不可执行

考虑绕过NX

  • 该怎么做
  • (例)创建可写的固定地址,编写shellcode并执行
    • 通过mmap或者mprotect获取RWX权限的内存区域
    • 在那里写入shellcode并返回

问题点

  1. 如何调用到mmap/mprotect?
  2. 通过溢出如何写入到那里
    • 溢出是在栈向下写入的攻击
    • 不能够反向写入

ret2plt

  • 让栈的状态,在函数调用的瞬间是相同的(非常重要的概念)