Posts 栈与堆基础
Post
Cancel

栈与堆基础


栈溢出引发漏洞

栈溢出分析文章详见: 栈基础 & 栈溢出 & 栈溢出进阶 https://www.52pojie.cn/forum.php?mod=viewthread&tid=974510&extra=page%3D1%26filter%3Dtypeid%26typeid%3D156%26digest%3D1

函数参数传递: x86

1
2
3
4
5
6
7
    通过栈传参
    先压入最后一个参数

x64

    rdi rsi rdx rcx r8 r9 接收后六个参数
    之后的参数通过栈传参

栈溢出目的

1
2
3
破坏程序内存结构
执行system(/bin/sh)
执行shellcode

栈溢出思路:判断溢出点

1
2
3
4
5
6
常见的危险函数:

输入:gets scanf vscanf

输出:sprintf
字符串:strcpy strcat bcopy

内存分配引发漏洞

c/c++内存分配函数:malloc,calloc,realloc,_alloca ExAllocatePoolWithTag函数:根据指定存储区类型参数分配一段空间,并把该空间的首地址作为返回值发送给调用者

栈:编译器自动分配释放,存放函数参数值局部变量。 堆区(heap) — 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS(操作系统)回收。频繁的 new/delete 势必会造成内存空间的不连续

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int  a=0;   全局初始化区    
char *p1;   全局未初始化区    
int  main()    
{    
  int  b; //栈    
  char  s[]="abc"; //栈    
  char  *p2; //栈    
  char  *p3="123456"; //123456/0在常量区,p3在栈上。    

  static int c =0//全局(静态)初始化区    
  p1 =  (char  *)malloc(10);  //分配得来得10和20字节的区域就在堆区
  p2  = (char  *)malloc(20);      
  strcpy(p3,"123456"); //123456/0放在常量区,编译器可能会将它与p3所指向的"123456"  优化成一个地方。    
}

生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。

内存分配漏洞原因:由于内存申请函数(函数ExAllocatePoolWithTag)申请的内存大小发生了整数溢出,导致这块内存的大小远小于我们的预期,之后进行大量写入操作的时候,将会造成OOB覆盖其他内容,从而导致系统BSOD的触发。 详见:https://www.52pojie.cn/forum.php?mod=viewthread&tid=1256851&extra=page%3D1%26filter%3Dtypeid%26typeid%3D156%26digest%3D1

字符串溢出

目录 1. CVE-2020-1301 SMBLost漏洞分析吾爱破解 2. CVE-2017-13089_wget栈溢出

一、详见:CVE-2020-1301 SMBLost漏洞分析吾爱破解为了将文件名创建到前面说到的内核内存池中,首先将共享路径复制到内存池中,然后检查共享文件名是否以’'结尾。如果是,进一步检查SourceFileName或DestinationFileName的第一个字符是否位’'或’\0’;如果是,就使用memcpy函数从SourceFileName或DestinationFileName的第二个字符开始复制,复制的长度为SourceFileNameLength - 2或DestinationFileNameLength - 2。漏洞触发主要由于没有检查这两个长度是否大于1。如果SourceFileNameLength或者DestinationFileNameLength的值为1,那么就会发生整数溢出,复制长度变为了0xffffffff,从而会溢出前面分配的内存池缓冲区。

详见:漏洞分析(一)CVE-2017-13089_wget栈溢出http://www.gandalf.site/2019/01/cve-2017-13089wget.html 检索skip_short_body,定位到./src/http.c中,skip_short_body代码如下。这段代码逻辑大致为,wget 在检测 short_body 的时候先要检测出传输的块的大小,假若传入的块的大小的值不大于 4096 则进入进入这个漏洞的受害逻辑内;而在contlen = MIN (remaining_chunk_size, SKIP_SIZE)里,只需remaining_chunk_size小于SKIP_SIZE=512,contlen即可控;而之后fd_read()使用了该受控向量,从 fd 读取 bufsize= contlen= remaining_chunk_size个字节到 dlbuf 中,当remaining_chunk_size为负数时,则会引发缓冲区溢出漏洞。

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
79
80
81
82
83
84
85
86
87
88
/* Read the body of the request, but don't store it anywhere and don't
   display a progress gauge.  This is useful for reading the bodies of
   administrative responses to which we will soon issue another
   request.  The response is not useful to the user, but reading it
   allows us to continue using the same connection to the server.

   If reading fails, false is returned, true otherwise.  In debug
   mode, the body is displayed for debugging purposes.  */

static bool
skip_short_body (int fd, wgint contlen, bool chunked)
{
  enum {
    SKIP_SIZE = 512,                /* size of the download buffer */
    SKIP_THRESHOLD = 4096        /* the largest size we read */
  };
  wgint remaining_chunk_size = 0;
  char dlbuf[SKIP_SIZE + 1];
  dlbuf[SKIP_SIZE] = '\0';        /* so DEBUGP can safely print it */

  /* If the body is too large, it makes more sense to simply close the
     connection than to try to read the body.  */
  if (contlen > SKIP_THRESHOLD)    //contlen > 4096退出
    return false;

  while (contlen > 0 || chunked)
    {
      int ret;
      if (chunked)
        {
          if (remaining_chunk_size == 0)
            {
              char *line = fd_read_line (fd);
              char *endl;
              if (line == NULL)
                break;

              remaining_chunk_size = strtol (line, &endl, 16);
              xfree (line);

              if (remaining_chunk_size == 0)
                {
                  line = fd_read_line (fd);
                  xfree (line);
                  break;
                }
            }

          contlen = MIN (remaining_chunk_size, SKIP_SIZE);
          //MIN为取小者,remaining_chunk_size小于SKIP_SIZE=512,contlen可控,remaining_chunk_size可小于0
        }

      DEBUGP (("Skipping %s bytes of body: [", number_to_static_string (contlen)));

      ret = fd_read (fd, dlbuf, MIN (contlen, SKIP_SIZE), -1);  int仅接受一个int bufsize参数,int类型数据在32/64位系统中都只有4个字节,当试图放入8字节的负参数,块长度高4字节被丢弃
      //fd_read() 使用了受控向量contlen,从 fd 读取 contlen 个字节到 dlbuf 中
     //dlbuf=SKIP_SIZE+1,但contlen可为负数,所以会引发缓冲区溢出漏洞
      if (ret <= 0)
        {
          /* Don't normally report the error since this is an
             optimization that should be invisible to the user.  */
          DEBUGP (("] aborting (%s).\n",
                   ret < 0 ? fd_errstr (fd) : "EOF received"));
          return false;
        }
      contlen -= ret;

      if (chunked)
        {
          remaining_chunk_size -= ret;
          if (remaining_chunk_size == 0)
            {
              char *line = fd_read_line (fd);
              if (line == NULL)
                return false;
              else
                xfree (line);
            }
        }

      /* Safe even if %.*s bogusly expects terminating \0 because
         we've zero-terminated dlbuf above.  */
      DEBUGP (("%.*s", ret, dlbuf));
    }

  DEBUGP (("] done.\n"));
  return true;
}

字符串未检查引发溢出

此漏洞是由于FTPShell中的sub42D23C函数,在调用strcat时会进行一次字符串拼接组成一个完整路径,在进行字符串拼接前后,没有对文件名进行合法性检查,导致拼接完成后,在后续的sub464A20函数调用后,会在函数内部进行一次字符串赋值,因为没有检查长度,因此在赋值后会导致缓冲区被覆盖,返回地址被覆盖从而使返回地址可控,可接管程序流程,下面对此漏洞进行详细分析。 首先触发漏洞,附加windbg,到达崩溃现场。

详见:FTPSHELL CLIENT 5.24本地文件创建功能缓冲区溢出漏洞http://www.mottoin.com/detail/1215.html

This post is licensed under CC BY 4.0 by the author.