漏洞类型及攻略方法

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

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

    • 写入数据超出区域

    • 覆盖写入后续区域

    • 例如这段代码:

    • 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");
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12

      ![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019040108.jpg)

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

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

      - 甚至可以指定返回地址

      - 例如这段代码:

      -
      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); }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78

      ![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019040109.jpg)

      - 添加shellcode

      - 如果Evil Return addr指向shellcode

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

      ![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019040110.jpg)

      ## 1-猜测地址

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

      - 如何让它到达shellcode

      ![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019040111.jpg)

      - 可以略微转移

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

      - 多次尝试

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

      ![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019040112.jpg)

      ## 2-溢出量的问题

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

      - nop-sled无法准备好(或者太短了)
      - 不能猜测地址

      - brute force需要太长时间

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

      ![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019040201.jpg)

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

      - ret2esp

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

      ![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019040202.jpg)

      ## 3-溢出量的问题

      - 只能够少量溢出的情况

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

      ![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019040203.jpg)

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

      - Stager

      - 通过溢出写入较短的汇编代码
      - 先运行这部分代码,读取其他shellcode
      - 通过读取的内容来控制程序运行流程

      - 具体大概像这样( 只有17个byte):

      -
      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

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