初始化

/* Send (mutated) messages in order to the server under test */
int send_over_network()
{

首先定义一些变量,likely_buggy 标志服务器是否可能出现 crash。

  int n;
  u8 likely_buggy = 0;
  struct sockaddr_in serv_addr;
  struct sockaddr_in local_serv_addr;

如果存在清除脚本,则执行,一般用来清除执行服务端所产生的痕迹。

  //Clean up the server if needed
  if (cleanup_script) system(cleanup_script);

等待服务器初始化:

  //Wait a bit for the server initialization
  usleep(server_wait_usecs);

清除缓冲区并充值缓冲区大小:

  //Clear the response buffer and reset the response buffer size
  if (response_buf) {
    ck_free(response_buf);
    response_buf = NULL;
    response_buf_size = 0;
  }
 
  if (response_bytes) {
    ck_free(response_bytes);
    response_bytes = NULL;
  }

创建 TCP/UDP 套接字:

  //Create a TCP/UDP socket
  int sockfd = -1;
  if (net_protocol == PRO_TCP)
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
  else if (net_protocol == PRO_UDP)
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
 
  if (sockfd < 0) {
    PFATAL("Cannot create a socket");
  }

设置超时时间,这里的 socket_timeout_usecs 默认为 1000 也就是 1000us=1ms,按照作者注释,不设置的话,如果服务器在处理完所有请求后仍然处于活动状态则会导致巨大延迟。

  //Set timeout for socket data sending/receiving -- otherwise it causes a big delay
  //if the server is still alive after processing all the requests
  struct timeval timeout;
  timeout.tv_sec = 0;
  timeout.tv_usec = socket_timeout_usecs;
  setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout));

设置目标服务器的端口:

  memset(&serv_addr, '0', sizeof(serv_addr));
 
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons(net_port);
  serv_addr.sin_addr.s_addr = inet_addr(net_ip);

设置本地测试端的端口。有些服务(例如 Kamailio SIP)只会发送给特定的端口,也正因如此,AFLNet 提供了 -l 参数指定端口。

  //This piece of code is only used for targets that send responses to a specific port number
  //The Kamailio SIP server is an example. After running this code, the intialized sockfd 
  //will be bound to the given local port
  if(local_port > 0) {
    local_serv_addr.sin_family = AF_INET;
    local_serv_addr.sin_addr.s_addr = INADDR_ANY;
    local_serv_addr.sin_port = htons(local_port);
 
    local_serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    if (bind(sockfd, (struct sockaddr*) &local_serv_addr, sizeof(struct sockaddr_in)))  {
      FATAL("Unable to bind socket on local source port");
    }
  }

连接服务端,如果没有连接上则等待一段事件后多次尝试:

  if(connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
    //If it cannot connect to the server under test
    //try it again as the server initial startup time is varied
    for (n=0; n < 1000; n++) {
      if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == 0) break;
      usleep(1000);
    }
    if (n== 1000) {
      close(sockfd);
      return 1;
    }
  }

发送数据

首先接受一次数据,这是为了处理刚连上服务端的时候服务端发来的一些消息,如果出现问题则会跳转到 HANDLE_RESPONSES

  //retrieve early server response if needed
  if (net_recv(sockfd, timeout, poll_wait_msecs, &response_buf, &response_buf_size)) goto HANDLE_RESPONSES;

循环发送数据

接下来循环发送保存在 kl_messages 中的数据。一些变量的含义:

  • messages_sent:已发送的消息数量
  • kl_messages:保持待发送消息的链表
  //write the request messages
  kliter_t(lms) *it;
  messages_sent = 0;
 
  for (it = kl_begin(kl_messages); it != kl_end(kl_messages); it = kl_next(it)) {
    n = net_send(sockfd, timeout, kl_val(it)->mdata, kl_val(it)->msize);
    messages_sent++;

在接收前重置 [[00-notes/00-fuzz/AFLNet/source-code/afl-fuzz.c/aflnet-specific-vars#response_bytes|response_bytes]]。

    //Allocate memory to store new accumulated response buffer size
    response_bytes = (u32 *) ck_realloc(response_bytes, messages_sent * sizeof(u32));

如果没发完报文还是跳到 HANDLE_RESPONSES

    //Jump out if something wrong leading to incomplete message sent
    if (n != kl_val(it)->msize) {
      goto HANDLE_RESPONSES;
    }

一切正常后,从服务器接收数据并跳到 HANDLE_RESPONSES,这里还保存了上一个返回的 buf_size,用于后续的判断:

    //retrieve server response
    u32 prev_buf_size = response_buf_size;
    if (net_recv(sockfd, timeout, poll_wait_msecs, &response_buf, &response_buf_size)) {
      goto HANDLE_RESPONSES;
    }

接下来将 [[00-notes/00-fuzz/AFLNet/source-code/afl-fuzz.c/aflnet-specific-vars#response_bytes|response_bytes]] 的最后一个元素更新为返回的 buf 的大小。

    //Update accumulated response buffer size
    response_bytes[messages_sent - 1] = response_buf_size;

这里用到了上文保存的 prev_buf_size,如果执行前后 buf_size 没有变化,则可能是出现了 crash,将 likely_buggy 设置为 1(但实际上如果进入了 403 等情况也会相同吧,所以这里是 likely_buggy)。

    //set likely_buggy flag if AFLNet does not receive any feedback from the server
    //it could be a signal of a potentiall server crash, like the case of CVE-2019-7314
    if (prev_buf_size == response_buf_size) likely_buggy = 1;
    else likely_buggy = 0;
  }

HANDLE_RESPONSES

进行一次接收,可能是为了避免服务端反应慢导致一些数据没收到的情况,然后像上文那样更新 response_bytes

HANDLE_RESPONSES:
 
  net_recv(sockfd, timeout, poll_wait_msecs, &response_buf, &response_buf_size);
 
  if (messages_sent > 0 && response_bytes != NULL) {
    response_bytes[messages_sent - 1] = response_buf_size;
  }

接下来等待服务端执行完毕,首先将 session_virgin_bits 置为 1,然后不断执行 [[00-notes/00-fuzz/afl/source-code/afl-fuzz.c/has_new_bits|has_new_bits]] 函数。因为上一步全

  //wait a bit letting the server to complete its remaining task(s)
  memset(session_virgin_bits, 255, MAP_SIZE);
  while(1) {
    if (has_new_bits(session_virgin_bits) != 2) break;
  }

结束阶段

  close(sockfd);

如果看上去 crash 的话就直接返回 0,否则才去 kill 它(我觉得这里可以判断一下这个 pid 是否存在,存在的话再 kill 更好吧)

  if (likely_buggy && false_negative_reduction) return 0;
 
  if (terminate_child && (child_pid > 0)) kill(child_pid, SIGTERM);
 
  //give the server a bit more time to gracefully terminate
  while(1) {
    int status = kill(child_pid, 0);
    if ((status != 0) && (errno == ESRCH)) break;
  }
 
  return 0;
}
/* End of AFLNet-specific variables & functions */

参考资料