这个 C 代码中的缓冲区溢出漏洞在哪里?

Posted

技术标签:

【中文标题】这个 C 代码中的缓冲区溢出漏洞在哪里?【英文标题】:Where is the Buffer Overflow vulnerability in this C code? 【发布时间】:2022-01-22 16:57:33 【问题描述】:

所以我正在学习 C 中的缓冲区溢出攻击。我了解它们是什么,并且我可以在简单的 C 代码中找到缓冲区溢出漏洞。简单就好:)。

但这段代码似乎超出了我对“简单”的定义。

到目前为止,我了解到在这段 C 代码中,缓冲区溢出漏洞可能主要发生在以下行中: strcpy(retstr, "Process Error.");,但我认为该行上方有一条 if 语句可以防止该行出现缓冲区溢出。

如果能在此代码中找到缓冲区溢出漏洞,我将不胜感激。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <time.h>


#define CANBUFSIZE 106
#define MSGBUFSIZE 256
#define TIMEBUFSIZE 128

char msgbuf[MSGBUFSIZE];
char canarybuf[CANBUFSIZE];

void get_time(char* format, char* retstr, unsigned received)

  // memory for our local copy of the timestring
  char timebuf[TIMEBUFSIZE];
  time_t curtime;

  // if the format string esceeds our local buffer ...
  if(strlen(format) > TIMEBUFSIZE)
  
    strcpy(retstr,"Process Error.");
    return;
  

  // otherwise create a local working copy
  memcpy(timebuf,format,received);

  // Get the current time.
  curtime = time (NULL);

  // Convert it to local time representation.
  // and convert the format string to the real timestring
  struct tm *loctime = localtime (&curtime);
  strftime(retstr,TIMEBUFSIZE,timebuf,loctime);

  return;



int main(int argc, char** argv)

  int port;                     // the portnumber of our service
  struct in_addr bind_addr;     // bind address of the server
  int sd;                       // the socketdescriptor
  struct sockaddr_in addr;      // address of our service
  struct sockaddr_in addr_from; //address of the client
  int addrlen = sizeof(addr_from);
  int pid;                      // our process id
  int sid;                      // our session id
  unsigned received;            // number of bytes received from network


  // resolve command line arguments
  if(argc != 3)
  
    printf("Usage: timeservice <bind address> <portnum>\n");
    return 1;
  
  
  if (inet_aton(argv[1], &bind_addr) == 0)
  
       fprintf(stderr, "Invalid bind address\n");
       exit(EXIT_FAILURE);
  
  
  port = atoi(argv[2]); 
  if ((port < 1024) || (port > 65535))
  
    printf("Portrange has to be between 1024 and 65535.\n");
    exit(EXIT_FAILURE);
  


  // forking to background
  pid = fork();
  if(pid < 0)
  
    printf("fork() failed\n");
    exit(EXIT_FAILURE);
  
  // we are parent
  else if(pid > 0)
  
    return 0;
  

  /*
   * we are the child process
   * because of the termination of our parent, we need a new session id,
   * else we are zombie
   */
  sid = setsid();
  if (sid < 0) 
    return 1;
  

  /*
   * since we are a system service we have to close all standard file 
   * descriptors
   */
  close(STDIN_FILENO);
  close(STDOUT_FILENO);
  close(STDERR_FILENO);

  // create an udp socket
  if((sd = socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP)) < 0)
  
    return 1;
  

  // clear the memory of our addr struct
  memset(&addr,0,sizeof(addr));

  // Protocol Family = IPv4
  addr.sin_family = PF_INET; 
  
  // Listen on bindAddr and bindPort only
  addr.sin_addr.s_addr = bind_addr.s_addr;
  addr.sin_port = htons(port);

  // bind to the udp socket
  if(bind(sd,(struct sockaddr*)&addr,sizeof(addr)) != 0)
  
    return 1;
  

  for(;;)
  
    // prepare memory
    memset(&msgbuf, 0, sizeof(msgbuf));

    received = recvfrom(sd,msgbuf,MSGBUFSIZE,MSG_WAITALL,
      (struct sockaddr*)&addr_from,(socklen_t*) &addrlen);

    // fork a new child
    pid = fork();

    // we are parent
    if (pid > 0)
    
      // wait for the child to finish
      waitpid(pid,NULL,0);
    
    else
    
      /*
       * we are inside the child process
       */

      // reserve some memory for our response
      char * returnstr = (char*) malloc(TIMEBUFSIZE);

      // analyse the client request and format the time string
      get_time(msgbuf, returnstr, received);

      // send our response to the client
      sendto(sd,returnstr,strlen(returnstr)+1,MSG_DONTWAIT,
        (struct sockaddr *) &addr_from, addrlen);

      free(returnstr);
      return EXIT_SUCCESS;
    
  

  close(sd);

  return 0;

【问题讨论】:

but there is an if statement above the line that I think protects against buffer overflow at this line - 不,它与retstr 无关,它检查format 的大小。 memset(&amp;msgbuf, ...) 没有&amp; 会更好,就像这样:memset(msgbuf, ...)。如果您的数组被指针和动态内存分配替换,此更改可以防止出现严重错误。 int addrlen ... (socklen_t*) &amp;addrlen 是一个候选问题。最好使用socklen_t addrlen ... &amp;addrlen 【参考方案1】:

get_time 存在差异:strlen 用于检查传入缓冲区的“大小”,但memcpy 与用户提供的received 参数一起使用。在第一个 TIMEBUFSIZE 字节内传递一个带有 NUL 字节的缓冲区就足够了。

如果这样做,您可以直接在代码中触发崩溃:

received = 256;
memset(msgbuf, 'A', MSGBUFSIZE);
msgbuf[0] = 0;

这将用 256 个字节“填充”msgbuf,然后继续写入 128 个字节,将堆栈上的返回地址覆盖到您选择的地址。因为第一个字节是 NUL,所以strlen 检查通过。

如果你想在实际的二进制文件上触发它,你可能需要类似的东西:(假设它在 localhost:1234 上运行)

perl -MIO::Socket::IP -E '
  $buf = "\0" . ("A"x255);
  my $s = IO::Socket::IP->new(PeerHost => "127.0.0.1", PeerPort => 1234, Type => SOCK_DGRAM);
  $s->autoflush(1);
  print $s $buf;
'

当然你需要修改缓冲区来执行实际的代码流

【讨论】:

该缓冲区来自套接字输入recvfrom。漏洞在于该函数的使用,而不是 strlen 调用。一般而言,漏洞总是与缺乏输入卫生有关,而与使用众所周知的标准 C 函数无关。 是的,我相信这就是练习的重点? recvfrom 最多可以接收 MSGBUFSIZE=256 字节。这意味着您可以使用received=256 输入get_time。如果您设法绕过strlen 检查(如我的回答所示),则memcpy 调用会溢出timebuf,大小为TIMEBUFSIZE=128(如我的回答所示)一个不易受攻击的程序将检查received &lt; TIMEBUFSIZE 正是@Botje,感谢您的评论。有效:) .... 但现在的问题是绕过strlen 检查。如何绕过strlen 检查 这是我回答的一部分? 谢谢@Botje。我已尝试实施您的答案,但经过几次尝试后还没有运气。该程序似乎还没有“崩溃”。由于您建议在第一个 TIMEBUFSIZE 字节内传入一个带有 NULL 字节的缓冲区,所以使用这样的缓冲区是否足够:\0_and_then_the_extra_bits_up_to_128_bytes。问题是程序将\0 读取为单独的字符。期待您的回复

以上是关于这个 C 代码中的缓冲区溢出漏洞在哪里?的主要内容,如果未能解决你的问题,请参考以下文章

什么是C语言缓冲区溢出漏洞?怎么利用?谁可以提供详细的资料

溢出漏洞,缓冲区溢出漏洞

20165333 缓冲区溢出漏洞实验

20199319 缓冲区溢出漏洞试验

缓冲区溢出漏洞实验 20199321

2018-2019-1 20165334《信息安全系统设计基础》第三周学习总结及缓冲区溢出漏洞实验