您的位置:首页 > 理论基础 > 计算机网络

底层网络开发库之libnids

2006-12-08 10:56 513 查看
 一、简介

    libnids的英文意思是 Network Intrusion Detect System library,即网络入侵监测系统函数库。它是在前面介绍的两种C函数接口库libnet和libpcap的基础上开发的,封装了开发NIDS所需的许多通用型函数。linids提供的接口函数监视流经本地的所有网络通信,检查数据包等。除此之外,还具有重组TCP数据段、处理IP分片包和监测TCP端口扫描的功能。利用libnids接口函数库,NIDS开发者不需要再编写底层的网络处理代码,只需专注于NIDS本身功能的实现即可。

    libnids支持Linux、Solaris和*BSD系统平台,目前最新版本为1.13。

二、IP分片数据包

    为了使libnids能接收所有的IP数据包(包括分片包、畸形包等),程序员需要定义如下的回调函数:

        void ip_frag_func(struct ip * a_packet)

在调用nids_init()函数初始化后,使用nids的函数进行注册:

        nids_register_ip_frag(ip_frag_func);

这样回调函数ip_frag_func会在适当的时候由libnids调用,参数a_packet指针将指向接收到的数据报。

    类似地,如果仅接收目标主机会接受的数据包(如非碎片包、重组包或头部校验正确的数据包等),需要定义如下回调函数:

        void ip_func(struct ip * a_packet)

然后注册:

        nids_register_ip(ip_func);

三、TCP数据流重组

    要接收TCP流在交换的数据,必须定义如下回调函数:

        void tcp_callback(struct tcp_stream * ns, void ** param)

tcp_stream结构提供了一个TCP连接的所有信息。例如,它包含了客户端与服务器端的half_stream结构。下文会对该结构的字段进行解释。

    tcp_stream结构有一个名为nids_state的字段。此字段的数值将决定tcp_callback的操作。

    

    (a) ns->nids_state==NIDS_JUST_EST时,ns表示一个刚刚建立的连接。

        tcp_callback可以据此决定是否对该连接的后续数据进行检查。如需要检查,tcp_callback回调函数将通知libnids它希望接收哪些数据(如到客户端的数据、到服务器端的数据、到客户端的紧急数据或到服务器端的紧急数据等),然后返回。

    (b) ns->nids_state==NIDS_DATA时,表示ns连接接收到新的数据。

        half_stream结构中的缓冲区用于存放这些数据。

    (c) nids_state字段为其它数值(NIDS_CLOSE、NIDS_RESET、NIDS_TIMEOUT)时,表示该连接已经关闭了。tcp_callback函数应释放相关资源。

四、一个简单的实例

    下面的源代码是一个非常简单的程序,它将libnids捕获的所有TCP连接交换的数据输出显示到标准输出设备上。

-----------------------BEGINING OF CODE--------------------------------

#include "nids.h"

#include <string.h>

#include <stdio.h>

extern char * inet_ntoa(unsigned long);

// tuple4结构包含了TCP连接两端的IP地址和端口,以下函数将它们转换为字符串

// 格式,如10.0.0.1,1024, 10.0.0.2,23

char *

adres (struct tuple4 addr)

{

  static char buf[256];

  strcpy (buf, inet_ntoa (addr.saddr));

  sprintf (buf + strlen (buf), ",%i,", addr.source);

  strcat (buf, inet_ntoa (addr.daddr));

  sprintf (buf + strlen (buf), ",%i", addr.dest);

  return buf;

}

void

tcp_callback (struct tcp_stream *a_tcp, void ** this_time_not_needed)

{

  char buf[1024];

  strcpy (buf, adres (a_tcp->addr)); // we put conn params into buf

  if (a_tcp->nids_state == NIDS_JUST_EST)

    {

    // a_tcp所定义的连接已经建立。此处可视程序需要添加额外

    // 的判断处理。如if (a_tcp->addr.dest != 23) return;表

    // 示不处理目标端口为23的数据包。

    // 本例需要处理(显示)所有数据包,故:

      a_tcp->client.collect++; // 需要处理客户端接收的数据

      a_tcp->server.collect++; // 和服务器端接收的数据

      a_tcp->server.collect_urg++; // 需要处理服务器端接收的紧急数据

#ifdef WE_WANT_URGENT_DATA_RECEIVED_BY_A_CLIENT

      a_tcp->client.collect_urg++; // 需要处理客户端接收的紧急数据

                                   // (打开编译选项才有效)

#endif

      fprintf (stderr, "%s established/n", buf);

      return;

    }

  if (a_tcp->nids_state == NIDS_CLOSE)

    {

      // TCP连接正常关闭

      fprintf (stderr, "%s closing/n", buf);

      return;

    }

  if (a_tcp->nids_state == NIDS_RESET)

    {

      // TCP连接因RST数据包而关闭

      fprintf (stderr, "%s reset/n", buf);

      return;

    }

  if (a_tcp->nids_state == NIDS_DATA)

    {

      // 接收到新数据,下面判断决定是否显示

      struct half_stream *hlf;

      if (a_tcp->server.count_new_urg)

      {

        // 服务器端接收的紧急数据

        strcat(buf,"(urgent->)");

        buf[strlen(buf)+1]=0;

        buf[strlen(buf)]=a_tcp->server.urgdata;

        write(1,buf,strlen(buf));

        return;

      }

#ifdef WE_WANT_URGENT_DATA_RECEIVED_BY_A_CLIENT

      if (a_tcp->client.count_new_urg)

      {

        // 客户端接收的紧急数据

        strcat(buf,"(urgent->)");

        buf[strlen(buf)+1]=0;

        buf[strlen(buf)]=a_tcp->server.urgdata;

        write(1,buf,strlen(buf));

        return;

      }

#endif

      if (a_tcp->client.count_new)

        {

          // 客户端接收的数据

          hlf = &a_tcp->client; // 准备显示客户端接收的数据

          strcat (buf, "(<-)"); // 指示数据流方向

        }

      else

        {

          hlf = &a_tcp->server; // 准备显示服务器端接收的数据

          strcat (buf, "(->)"); // 指示数据流方向

        }

    fprintf(stderr,"%s",buf); // 首先输出显示连接双方的IP地址、端口

                              // 和数据流方向

   write(2,hlf->data,hlf->count_new); // 输出显示接收到的新数据

      

    }

  return ;

}

int

main ()

{

  // 此处可自定义libnids的全局变量,如:

  // nids_params.n_hosts=256;

  if (!nids_init () )

  {

        fprintf(stderr,"%s/n",nids_errbuf);

        exit(1);

  }

  nids_register_tcp (tcp_callback);

  nids_run ();

  // NOT REACHED

  return 0;

}

---------------------------END OF CODE------------------------------------  

五、libnids的数据结构及接口函数

    libnids库的所有数据结构及接口函数都在"nids.h"头文件中声明。

   struct tuple4 // TCP连接参数

   {

   unsigned short source,dest; // 客户端和服务器端的端口号

   unsigned long saddr,daddr;  // 客户端和服务器端的IP地址

   };

   struct half_stream // TCP连接一端的数据结构

   {

   char state;            // 套接字状态(如TCP_ESTABLISHED)

   char collect;          // 如果大于0,则保存其数据到缓冲区中,否则忽略

   char collect_urg;      // 如果大于0,则保存紧急数据,否则忽略

   char * data;           // 正常数据的缓冲区

   unsigned char urgdata; // 紧急数据缓冲区

   int count;             // 自从连接建立以来保存到"data"缓冲区的数据字节

                          // 数总和

   int offset;            // 保存到"data"缓冲区的首字节数据偏移量

   int count_new;         // 最近一次接收到的数据字节数;如果为0,则无数

                          // 到达

   char count_new_urg;    // 如果非0,表示有新的紧急数据到达

   ... // libnids库使用的辅助字段

   };

   struct tcp_stream

   {

   struct tuple4 addr;   // TCP连接参数(saddr, daddr, sport, dport)

   char nids_state;                  // TCP连接的逻辑状态

   struct half_stream client,server; // 描述客户端与服务器端的数据结构

   ...                               // libnids库使用的辅助字段

   };

     在上面的实例程序中,回调函数tcp_callback输出显示hlf->data缓冲区中的数据到标准输出设备上。这些数据在tcp_callback函数返回后,由libnids自动释放这些数据所占用的内存空间。同时,hlf->offset字段将增加被丢弃数据的字节数,而新接收到的数据则存放到"data"缓冲区的起始处。

    如果在其它应用中不进行如上例的操作(例如,数据处理过程至少需要N个字节的输入数据,而libnids只接收到的数据字节数count_new<N),则需要在tcp_callback函数返回前调用如下函数:

        void nids_discard(struct tcp_stream * a_tcp, int num_bytes)

此时,当回调函数tcp_callback返回后linids将"data"缓冲区的前num_bytes字节数据,同时计算调整offset字段的数值,并将剩余数据移动到缓冲区的起始处。

     如果始终不调用nids_discard()函数(如上面实例),hlf->data缓冲区中将包含hlf-> count_new字节数据。通常情况下,在hlf->data缓冲区中的数据字节数等于hlf->count - hlf->offset。

    有了nids_discard()函数,程序员就不必拷贝接收到的数据到另外的缓冲区中, hlf->data缓冲区将总是尽可能保存足够的数据。然后,有时会有保留数据包特定数据的需要。例如,我们希望能监测到针对wu-ftpd服务器"CWD"溢出攻击,就需要跟踪检查 ftp客户端发送的"CWD"命令。此时就需要tcp_callback回调函数具有第二个参数了。此参
数是某TCP连接私有数据的指针。处理过程如下:

    

   void

   tcp_callback_2 (struct tcp_stream * a_tcp, struct conn_param **ptr)

   {

   if (a_tcp->nids_state==NIDS_JUST_EST)

   {

        struct conn_param * a_conn;

        if the connection is uninteresting, return;

        a_conn=malloc of some data structure

        init of a_conn

        *ptr=a_conn // this value will be passed to tcp_callback_2 in future

                    // calls

        increase some of "collect" fields

        return;

   }

   if (a_tcp->nids_state==NIDS_DATA)

   {

        struct conn_param *current_conn_param=*ptr;

        using current_conn_param and the newly received data from the net

        we search for attack signatures, possibly modyfying

        current_conn_param  

        return ;

   }

   ...

   }

    nids_register_tcp和nids_register_ip*函数可被任意次调用。在同一个TCP连接中使用两种不同的回调函数是允许的。

    libnids库定义了一个全局变量结构nids_params,其声明如下:

    

   struct nids_prm

   {

   int n_tcp_streams; // 存放tcp_stream结构的hash表大小。

                      // 缺省值:1024

   int n_hosts;       // 存放IP分片信息的hash表大小

                      // 缺省值:256

   char * device;     // libnids监听的接口设备名

                      // 缺省值 == NULL,即由pcap_lookupdev函数确定

   int sk_buff_size;  // (Linux内核)sk_buff结构大小

                      // 缺省值:168

   int dev_addon;     // sk_buff为网络接口保留的字节数

                      // 如果dev_addon==-1,则由nids_init函数确定

                      // 缺省值:-1

   void (*syslog)();  // 日志函数指针

   int syslog_level;  // 如果nids_params.syslog==nids_syslog,则此字段值

                      // 将确定日志等级loglevel

                      // 缺省值:LOG_ALERT

   int scan_num_hosts;// 存放端口扫描信息的hash表大小。

                      // 如果为0,则关闭端口扫描监测功能。

                      // 缺省值:256

   int scan_num_ports;// 来自同一IP地址所扫描的TCP端口数上限

                      // 缺省值:10

   int scan_delay;    // 在两次端口扫描中的间隔时间上限(毫秒)

                      // 缺省值:3000

   void (*no_mem)();  // 内存不足时被调用,此时应终止当前进程

   int (*ip_filter)(struct ip*);  // 当接收到一个IP数据包时调用。如返回值

                                  // 非零,则处理该数据包,否则忽略。

                                  // 缺省为(nids_ip_filter)且总返回1

   char *pcap_filter; // 传递给pcap过滤器的字符串。

                      // 缺省值:NULL

   } nids_params;

    nids_params的syslog字段缺省时指向nids_syslog函数,声明如下:

        void nids_syslog (int type, int errnum, struct ip *iph, void *data);

    nids_params.syslog函数用于记录异常情况,如端口扫描企图,无效TCP头标志 等。该字段应指向自定义的日志处理函数。nids_syslog()仅作为一个例子。nids_syslog()函数向系统守护服务syslogd发送日 志消息。

    使用nids_run有一个缺陷:应用程序将完全由数据包驱动(运行)。有时需要在没有数据包到达时也能处理一些任务,则作为nids_run()函数的替代,程序员可使用如下函数:

        int nids_next()

此函数将调用pcap_next()函数(而不是pcap_loop()函数)。(详细资料请参阅《网络安全工具开发函数库介绍之二 ——libpcap》。) nids_next()函数成功时返回1,出错时返回0,且nids_errbuf缓冲区存放相应错误消息。

    典型地,当使用nids_next()函数时,应用程序调用I/O复用函数select()阻塞,监听套接字fd在“读”描述字集合fd_set中设置。该套接字可通过如下函数获得:

        int nids_getfd()

成功时返回一个文件描述字,出错时返回-1,且nids_errbuf缓冲区存放相应错误消息。

---[[ libnids应用实例 ]]----------------------------------

1、nids_next()函数的应用

============================ cut here ============================

/*

This is an example how one can use nids_getfd() and nids_next() functions.

You can replace printall.c's function main with this file.

*/

#include <sys/time.h>

#include <sys/types.h>

#include <unistd.h>

int

main ()

{

  // here we can alter libnids params, for instance:

  // nids_params.n_hosts=256;

  int fd;

  int time = 0;

  fd_set rset;

  struct timeval tv;

  if (!nids_init ())

  {

        fprintf(stderr,"%s/n",nids_errbuf);

        exit(1);

  }

  nids_register_tcp (tcp_callback);

  fd = nids_getfd ();

  for (;;)

    {

      tv.tv_sec = 1;

      tv.tv_usec = 0;

      FD_ZERO (&rset);

      FD_SET (fd, &rset);

      // add any other fd we need to take care of

      if (select (fd + 1, &rset, 0, 0, &tv))

        {

                if (FD_ISSET(fd,&rset)  // need to test it if there are other

                                        // fd in rset

                        if (!nids_next ()) break;

        }

      else

        fprintf (stderr, "%i ", time++);

    }

  return 0;

}

============================ cut here ============================

2、Simple sniffer

============================ cut here ============================

/*

   Copyright (c) 1999 Rafal Wojtczuk <nergal@avet.com.pl>. All rights 
reserved.

   See the file COPYING for license details.

*/

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <netinet/in_systm.h>

#include <arpa/inet.h>

#include <stdio.h>

#include <fcntl.h>

#include "nids.h"

#define LOG_MAX 100

#define SZLACZEK "/n--------------------------------------------------/n"

#define int_ntoa(x)     inet_ntoa(*((struct in_addr *)&x))

char *

adres (struct tuple4 addr)

{

  static char buf[256];

  strcpy (buf, int_ntoa (addr.saddr));

  sprintf (buf + strlen (buf), ",%i,", addr.source);

  strcat (buf, int_ntoa (addr.daddr));

  sprintf (buf + strlen (buf), ",%i : ", addr.dest);

  return buf;

}

int logfd;

void

do_log (char *adres_txt, char *data, int ile)

{

  write (logfd, adres_txt, strlen (adres_txt));

  write (logfd, data, ile);

  write (logfd, SZLACZEK, strlen (SZLACZEK));

}

void

sniff_callback (struct tcp_stream *a_tcp, void **this_time_not_needed)

{

  int dest;

  if (a_tcp->nids_state == NIDS_JUST_EST)

    {

      dest = a_tcp->addr.dest;

      if (dest == 21 || dest == 23 || dest == 110 || dest == 143 || dest == 
513)

        a_tcp->server.collect++;

      return;

    }

  if (a_tcp->nids_state != NIDS_DATA)

    {

      // seems the stream is closing, log as much as possible

      do_log (adres (a_tcp->addr), a_tcp->server.data,

              a_tcp->server.count - a_tcp->server.offset);

      return;

    }

  if (a_tcp->server.count - a_tcp->server.offset < LOG_MAX)

    {

      // we haven't got enough data yet; keep all of it

      nids_discard (a_tcp, 0);

      return;

    }

    

  // enough data  

  do_log (adres (a_tcp->addr), a_tcp->server.data, LOG_MAX);

  // Now procedure sniff_callback doesn't want to see this stream anymore.

  // So, we decrease all the "collect" fields we have previously increased.

  // If there were other callbacks following a_tcp stream, they would still

  // receive data

  a_tcp->server.collect--;

}

int

main ()

{

  logfd = open ("./logfile", O_WRONLY | O_CREAT | O_TRUNC, 0600);

  if (logfd < 0)

    {

      perror ("opening ./logfile:");

      exit (1);

    }

  if (!nids_init ())

    {

      fprintf (stderr, "%s/n", nids_errbuf);

      exit (1);

    }

  nids_register_tcp (sniff_callback);

  nids_run ();

  return 0;

}

============================ cut here ============================

3、Wu-FTPd overflow attack detector

============================ cut here ============================

/*

Copyright (c) 1999 Rafal Wojtczuk <nergal@avet.com.pl>. All rights reserved.

See the file COPYING for license details.

*/

/*

This code attempts to detect attack against imapd (AUTHENTICATE hole) and

wuftpd (creation of deep directory). This code is to ilustrate use of libnids;

in order to improve readability, some simplifications were made, which enables

an attacker to bypass this code (note, the below routines should be improved,

not libnids)

*/  

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <netinet/in_systm.h>

#include <arpa/inet.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <syslog.h>

#include "nids.h"

#define int_ntoa(x)     inet_ntoa(*((struct in_addr *)&x))

char *

adres (struct tuple4 addr)

{

  static char buf[256];

  strcpy (buf, int_ntoa (addr.saddr));

  sprintf (buf + strlen (buf), ",%i,", addr.source);

  strcat (buf, int_ntoa (addr.daddr));

  sprintf (buf + strlen (buf), ",%i", addr.dest);

  return buf;

}

/*

if we find a pattern AUTHENTICATE {an_int} in data stream sent to an imap

server, where an_int >1024, it means an buffer overflow attempt. We kill the

connection.

*/

#define PATTERN "AUTHENTICATE {"

#define PATLEN strlen(PATTERN)

void

detect_imap (struct tcp_stream *a_tcp)

{

  char numbuf[30];

  int i, j, datalen, numberlen;

  struct half_stream *hlf;

  if (a_tcp->nids_state == NIDS_JUST_EST)

    {

      if (a_tcp->addr.dest == 143)

        {

          a_tcp->server.collect++;

          return;

        }

      else

        return;

    }

  if (a_tcp->nids_state != NIDS_DATA)

    return;

  hlf = &a_tcp->server;

  datalen = hlf->count - hlf->offset;

  if (datalen < PATLEN)

    {

      // we have too small amount of data to work on. Keep all data in buffer.

      nids_discard (a_tcp, 0);

      return;

    }

  for (i = 0; i <= datalen - PATLEN; i++)

    if (!memcmp (PATTERN, hlf->data + i, PATLEN)) //searching for a pattern

      break;

  if (i > datalen - PATLEN)

    {

      // retain PATLEN bytes in buffer

      nids_discard (a_tcp, datalen - PATLEN);

      return;

    }

  for (j = i + PATLEN; j < datalen; j++) // searching for a closing '}'

    if (*(hlf->data + j) == '}')

      break;

  if (j > datalen)

    {

      if (datalen > 20)

        {

          //number too long, perhaps we should log it, too

        }

      return;

    }

  numberlen = j - i - PATLEN;

  memcpy (numbuf, hlf->data + i + PATLEN, numberlen); //numbuf contains

                                                      // AUTH argument

  numbuf[numberlen] = 0;

  if (atoi (numbuf) > 1024)

    {

      // notify admin

      syslog(nids_params.syslog_level,

      "Imapd exploit attempt, connection %s/n",adres(a_tcp->addr));

      // kill the connection

      nids_killtcp (a_tcp);

    }

  nids_discard (a_tcp, datalen - PATLEN);

  return;

}

// auxiliary structure, needed to keep current dir of ftpd daemon

struct supp

{

  char *currdir;

  int last_newline;

};

// the below function adds "elem" string to "path" string, taking care of

// ".." and multiple '/'. If the resulting path is longer than 768,

// return value is 1, otherwise 0

int

add_to_path (char *path, char *elem, int len)

{

int plen;

char * ptr;

  if (len > 768)

    return 1;

  if (len == 2 && elem[0] == '.' && elem[1] == '.')

    {

      ptr = rindex (path, '/');

      if (ptr != path)

        *ptr = 0;

    }

  else if (len > 0)

    {

      plen = strlen (path);

      if (plen + len + 1 > 768)

        return 1;

        if (plen==1)

        {

        strncpy(path+1,elem,len);

        path[1+len]=0;

        }

        else

        {

      path[plen] = '/';

      strncpy (path + plen + 1, elem, len);

      path[plen + 1 + len] = 0;

        }

    }

return 0;

}

void

do_detect_ftp (struct tcp_stream *a_tcp, struct supp **param_ptr)

{

  struct supp *p = *param_ptr;

  int index = p->last_newline + 1;

  char *buf = a_tcp->server.data;

  int offset = a_tcp->server.offset;

  int n_bytes = a_tcp->server.count - offset;

  int path_index, pi2, index2, remcaret;

  for (;;)

    {

      index2 = index;

      while (index2 - offset < n_bytes && buf[index2 - offset] != '/n')

        index2++;

      if (index2 - offset >= n_bytes)

        break;

      if (!strncasecmp (buf + index - offset, "cwd ", 4))

        {

          path_index = index + 4;

          if (buf[path_index - offset] == '/')

            {

              strcpy (p->currdir, "/");

              path_index++;

            }

          for (;;)

            {

              pi2 = path_index;

              while (buf[pi2 - offset] != '/n' && buf[pi2 - offset] != '/')

                pi2++;

                if (buf[pi2-offset]=='/n' && buf[pi2-offset-1]=='/r')

                remcaret=1;

                else remcaret=0;

              if (add_to_path (p->currdir, buf + path_index-offset, pi2 - 
path_index-remcaret))

                {

                  // notify admin

                  syslog(nids_params.syslog_level,

                  "Ftpd exploit attempt, connection %s/n",adres(a_tcp->addr));

                  nids_killtcp (a_tcp);

                  return;

                }

              if (buf[pi2 - offset] == '/n')

                break;

              path_index = pi2 + 1;

            }

        }

      index = index2 + 1;

    }

  p->last_newline = index - 1;

  nids_discard (a_tcp, index - offset);

}

void

detect_ftpd (struct tcp_stream *a_tcp, struct supp **param)

{

  if (a_tcp->nids_state == NIDS_JUST_EST)

    {

      if (a_tcp->addr.dest == 21)

        {

          struct supp *one_for_conn;

          a_tcp->server.collect++;

          one_for_conn = (struct supp *) malloc (sizeof (struct supp));

          one_for_conn->currdir = malloc (1024);

          strcpy (one_for_conn->currdir, "/");

          one_for_conn->last_newline = 0;

          *param=one_for_conn;

        }

      return;

    }

  if (a_tcp->nids_state != NIDS_DATA)

    {

      free ((*param)->currdir);

      free (*param);

      return;

    }

  do_detect_ftp (a_tcp, param);

}

int

main ()

{

  if (!nids_init ())

  {

        fprintf(stderr,"%s/n",nids_errbuf);

        exit(1);

  }

  nids_register_tcp (detect_imap);

  nids_register_tcp (detect_ftpd);

  nids_run ();

  return 0;

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息