­

数据包发送与嗅探

  • 2019 年 10 月 6 日
  • 筆記

数据包发送与嗅探

0.概述

这几日数据包发送与嗅探方法与实现总结。

发送:libnetRaw Socket

嗅探:libpcapRaw Socket

实验过程中采用过libnetlibpcap,最后全部转为Raw Socket发送与嗅探。

1.发送与嗅探初识

1.1 什么是libnet、libpcap?

目前众多的网络安全程序、工具和软件都是基于socket设计和开发的。由于在安全程序中通常需要对网络通讯的细节(如连接双方地址/端口、服务类型、传输控制等)进行检查、处理或控制,象数据包截获、数据包头分析、数据包重写、甚至截断连接等,都几乎在每个网络安全程序中必须实现。为了简化网络安全程序的编写过程,提高网络安全程序的性能和健壮性,同时使代码更易重用与移植,最好的方法就是将最常用和最繁复的过程函数,如监听套接口的打开/关闭、数据包截获、数据包构造/发送/接收等,封装起来,以API library的方式提供给开发人员使用。

在众多的API library中,对于类Unix系统平台上的网络安全工具开发而言,目前最为流行的C API library有libnet、libpcap、libnids和libicmp等。它们分别从不同层次和角度提供了不同的功能函数。使网络开发人员能够忽略网络底层细节的实现,从而专注于程序本身具体功能的设计与开发。其中,

libnet提供的接口函数主要实现和封装了数据包的构造和发送过程

libpcap提供的接口函数主要实现和封装了与数据包截获有关的过程

利用这些C函数库的接口,网络安全工具开发人员可以很方便地编写出具有结构化强、健壮性好、可移植性高等特点的程序。因此,这些函数库在网络安全工具的开发中具有很大的价值,在scanner、sniffer、firewall、IDS等领域都获得了极其广泛的应用,著名的tcpdump软件、ethereal软件等就是在libpcap的基础上开发的。

libnet是一个小型的接口函数库,主要用C语言写成,提供了低层网络数据报的构造、处理和发送功能。libnet的开发目的是:建立一个简单统一的网络编程接口以屏蔽不同操作系统低层网络编程的差别,使得程序员将精力集中在解决关键问题上。他的主要特点是:

  • 高层接口 libnet主要用C语言写成
  • 可移植性 libnet目前可以在Linux、FreeBSD、Solaris、WindowsNT等操作系统上运行,并且提供了统一的接口
  • 数据报构造 libnet提供了一系列的TCP/IP数据报文的构造函数以方便用户使用
  • 数据报的处理 libnet提供了一系列的辅助函数,利用这些辅助函数,帮助用户简化那些烦琐的事务性的编程工作
  • 数据报发送: libnet允许用户在两种不同的数据报发送方法中选择。

说起libpcap就得了解一下嗅探器,那什么是网络嗅探器?

网络嗅探也叫网络侦听,指的是使用特定的网络协议来分解捕获到的数据包,并根据对应的网络协议识别对应数据片断。

作用:

  • 管理员可以用来监听网络的流量情况
  • 开发网络应用的程序员可以监视程序的网络情况
  • 黑客可以用来刺探网络情报

嗅探器有四中工作方式也就是网卡的四种接受模式:

  • 广播模式;
  • 组播模式;
  • 直接模式;
  • 混杂模式;

通常,网卡的缺省配置是支持前三种模式。为了监听网络上的流量,必须设置为混杂模式

libpcap的英文意思是 Packet Capture library,即数据包捕获函数库。该库提供的C函数接口可用于需要捕获经过网络接口(通过将网卡设置为混杂模式,可以捕获所有经过该接口的数据报,目标地址不一定为本机)数据包的系统开发上。

著名的TCPDUMP就是在libpcap的基础上开发而成的。libpcap提供的接口函数主要实现和封装了与数据包截获有关的过程。这个库为不同的平台提供了一致的编程接口,在安装了libpcap的平台上,以libpcap为接口写的程序,能够自由的跨平台使用。在Linux系统下,libpcap可以使用BPF(Berkeley Packet Filter)分组捕获机制来获得很高的性能。

1.2 什么是Raw Socket?

原始套接字(raw socket)是一种网络套接字,允许直接发送/接收IP协议数据包而不需要任何传输层协议格式。

SOCK_RAW 原始套接字编程可以接收到本机网卡上的数据帧或者数据包,对与监听网络的流量和分析是很有作用的。

一共可以有 3 种方式创建这种socket:

socket(AF_INET, SOCK_RAW, IPPROTO_XXX)           // 发送、接收网络层IP数据包  socket(PF_PACKET, SOCK_RAW, htons(ETH_P_XXX))    // 发送、接收数据链路层数据帧(目前只有Linux支持)  socket(AF_INET, SOCK_PACKET, htons(ETH_P_XXX))   // 过时了  

2.发送

2.1 使用libnet发送

源码编译与安装

tar  -zxvf  libnetxxxx.tar.gz  cd   libnet  ./configure  make  make install  gcc  -o xxx  arp.c -lnet  

程序核心函数简介

(1)初始化

libnet_init()  

(2)构造TCP协议块

Libnet_build_tcp_options()  

(3)构造TCP头

Libnet_build_tcp()  

(4)构造IP头

Libnet_build_ipv4()  

(5)构造以太网头

libnet_build_ethernet()  

(6)发送数据包

libnet_write()  

(7)销毁

libnet_destroy()  

2.2 使用Raw Socket发送

特殊的数据结构scokaddr_ll

源代码在:<netpacket/packet.h>

struct sockaddr_ll  {      unsigned short int sll_family; /* 一般为AF_PACKET */      unsigned short int sll_protocol; /* 上层协议 */      int sll_ifindex; /* 接口类型 */      unsigned short int sll_hatype; /* 报头类型 */      unsigned char sll_pkttype; /* 包类型 */      unsigned char sll_halen; /* 地址长度 */      unsigned char sll_addr[8]; /* MAC地址 */  };  

ifreq结构定义在/usr/include/net/if.h,用来配置ip地址,激活接口,配置MTU等接口信息的。

其中包含了一个接口的名字和具体内容——(是个共用体,有可能是IP地址,广播地址,子网掩码,MAC号,MTU或其他内容)。

struct ifreq {  #define IFHWADDRLEN    6      union      {          char    ifrn_name[IFNAMSIZ];        /* if name, e.g. "en0" */      } ifr_ifrn;        union {          struct  sockaddr ifru_addr;          struct  sockaddr ifru_dstaddr;          struct  sockaddr ifru_broadaddr;          struct  sockaddr ifru_netmask;          struct  sockaddr ifru_hwaddr;          short   ifru_flags;          int ifru_ivalue;          int ifru_mtu;          struct  ifmap ifru_map;          char    ifru_slave[IFNAMSIZ];   /* Just fits the size */          char    ifru_newname[IFNAMSIZ];          void *  ifru_data;          struct  if_settings ifru_settings;      } ifr_ifru;  };  

大端与小端

不同机器内部对变量的字节存储顺序不同,有的采用大端模式(big-endian),有的采用小端模式(little-endian)。

大端模式是指高字节数据存放在低地址处,低字节数据放在高地址处。

小端模式是指低字节数据存放在低地址处,高字节数据放在高地址处。

在网络上传输数据时,由于数据传输的两端可能对应不同的硬件平台,采用的存储字节顺序也可能不一致,因此 TCP/IP协议规定了在网络上必须采用网络字节顺序(也就是大端模式) 。

通过对大小端的存储原理分析可发现,对于 char 型数据,由于其只占一个字节,所以不存在这个问题,这也是一般情况下把数据缓冲区定义成 char 类型 的原因之一。对于 IP 地址、端口号等非 char 型数据,必须在数据发送到网络上之前将其转换成大端模式,在接收到数据之后再将其转换成符合接收端主机的存储模式。

在C/C++写网络程序的时候,往往会遇到字节的网络顺序和主机顺序的问题。这是就可能用到htons(), ntohl(), ntohs()htons()这4个函数。

网络字节顺序与本地字节顺序之间的转换函数:

htonl()--"Host to Network Long"  ntohl()--"Network to Host Long"  htons()--"Host to Network Short"  ntohs()--"Network to Host Short"  

之所以需要这些函数是因为计算机数据表示存在两种字节顺序:NBO与HBO。

大端->网络字节顺序NBO(Network Byte Order):

按从高到低的顺序存储,在网络上使用统一的网络字节顺序,可以避免兼容性问题。

小端->主机字节顺序(HBO,Host Byte Order):

不同的机器HBO不相同,与CPU设计有关,数据的顺序是由cpu决定的,而与操作系统无关。

由于不同体系结构的机器之间无法通信,所以要转换成一种约定的数序,也就是网络字节顺序 。在PC开发中有ntohl和htonl函数可以用来进行网络字节和主机字节的转换。

本次使用的实例:

void netsend(char *rev_buf,int size,char *device,char A,char B){      int sockfd;      struct sockaddr_ll sll;      struct ifreq ifstruct;      if((sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0)      {          perror("create socket error:");          // return -1;          exit(-1);      }      memset(&sll, 0, sizeof(sll));      sll.sll_family = PF_PACKET;      sll.sll_protocol = htons(ETH_P_ALL);      /*      协议类型一共有四个      ETH_P_IP   0x800     只接收发往本机mac的IP类型的数据帧      ETH_P_ARP  0x806     只接收发往本机mac的ARP类型的数据帧      ETH_P_ARP  0x8035    只接收发往本机mac的RARP类型的数据帧      ETH_P_ALL  0x3       接收发往本机mac的所有类型(IP、ARP、RARP)的数据帧,并接收从本机发出的所有类型的数据帧                          (混杂模式打开的情况下,会接收到非发往本地mac的数据帧)      */      //get netcard interface index ethx->ifindex      strcpy(ifstruct.ifr_name, device);      ioctl(sockfd, SIOCGIFINDEX, &ifstruct);      sll.sll_ifindex = ifstruct.ifr_ifindex;        //get the local netcard mac      strcpy(ifstruct.ifr_name, device);      ioctl(sockfd, SIOCGIFHWADDR, &ifstruct);      memcpy(sll.sll_addr, ifstruct.ifr_ifru.ifru_hwaddr.sa_data, ETH_ALEN);      sll.sll_halen = ETH_ALEN;        //bind the netcard      if(bind(sockfd, (struct sockaddr *)&sll, sizeof(sll)) == -1)      {          perror("bind error:");          exit(-1);      }        //get the netcard work mode      memset(&ifstruct, 0, sizeof(ifstruct));      strcpy(ifstruct.ifr_name, device);      if(ioctl(sockfd, SIOCGIFFLAGS, &ifstruct) == -1)      {          perror("iotcl error:");          exit(-1);      }        //设置混杂模式      ifstruct.ifr_flags |= IFF_PROMISC;        if(ioctl(sockfd, SIOCSIFFLAGS, &ifstruct) == -1)      {          perror("iotcl()n");          printf("Fun:%s Line:%dn", __func__, __LINE__);          // return -1;          exit(-1);      }      int n_res = 0;      //rawsocket发送      n_res = sendto(sockfd, rev_buf, size, 0, (struct sockaddr *)&sll, sizeof(sll));      if(n_res < 0)      {          perror("sendto error:");          exit(-1);      }      netmesg(rev_buf,size,A,B);  }  

3.接收

3.1 使用libpcap接收

libpcap编译与安装、使用

1. 判断系统是否有安装 Libpcap 库  rpm –qa|grep pcap  2. 到 www.tcpdump.org 下载 Libpcap 安装包  3. 将源码包 libpcap.tar.gz 拷贝到 /usr 目录下  4. 释放源码包的内容  tar xvzf libpcap.tar.gz  5. 检查系统配置生成 Libpcap 的文件 make  ./configure  6. 编译 Libpcap 库: make  7. 安装库文件: make install  8. 使用:gcc –o 目标文件 源文件 – l pcap  

3.2 libpcap使用

(1)获取网络接口

首先需要获取监听的网络接口:可以手动指定或让libpcap自动选择。

自动选择如下:

char * pcap_lookupdev(char * errbuf)    

上面这个函数返回第一个合适的网络接口的字符串指针,如果出错,则errbuf存放出错信息字符串,errbuf至少应该是PCAP_ERRBUF_SIZE个字节长度的。

手动选择:

则是直接通过ifconfig查看网卡名字,直接设为char *类型即可。

(2)释放网络接口

在操作为网络接口后,我们应该要释放它:

void pcap_close(pcap_t * p)    

该函数用于关闭pcap_open_live()获取的pcap_t的网络接口对象并释放相关资源。

(3)打开网络接口

获取网络接口后,我们需要打开它:

pcap_t * pcap_open_live(const char * device, int snaplen, int promisc, int to_ms, char * errbuf)  

上面这个函数会返回指定接口的pcap_t类型指针,后面的所有操作都要使用这个指针。

第一个参数是第一步获取的网络接口字符串,可以直接使用硬编码。

第二个参数是对于每个数据包,从开头要抓多少个字节,我们可以设置这个值来只抓每个数据包的头部,而不关心具体的内容。典型的以太网帧长度是1518字节,但其他的某些协议的数据包会更长一点,但任何一个协议的一个数据包长度都必然小于65535个字节。

第三个参数指定是否打开混杂模式(Promiscuous Mode),0表示非混杂模式,任何其他值表示混合模式。如果要打开混杂模式,那么网卡必须也要打开混杂模式,可以使用如下的命令打开eth0混杂模式: ifconfig eth0 promisc

第四个参数指定需要等待的毫秒数,超过这个数值后,第3步获取数据包的这几个函数就会立即返回。0表示一直等待直到有数据包到来。

第五个参数是存放出错信息的数组。

(4)获取数据包 a) 单次获取,立即返回退出

u_char * pcap_next(pcap_t * p, struct pcap_pkthdr * h)    

如果返回值为NULL,表示没有抓到包。

第一个参数是第2步返回的pcap_t类型的指针

第二个参数是保存收到的第一个数据包的pcap_pkthdr类型的指针

pcap_pkthdr类型的定义如下:

struct pcap_pkthdr  {    struct timeval ts;    /* time stamp */    bpf_u_int32 caplen;   /* length of portion present */    bpf_u_int32 len;      /* length this packet (off wire) */  };  

注意这个函数只要收到一个数据包后就会立即返回。

b) 循环获取

int pcap_loop(pcap_t * p, int cnt, pcap_handler callback, u_char * user)    

第一个参数是第2步返回的pcap_t类型的指针

第二个参数是需要抓的数据包的个数,一旦抓到了cnt个数据包,pcap_loop立即返回。负数的cnt表示pcap_loop永远循环抓包,直到出现错误。

第三个参数是一个回调函数指针,它必须是如下的形式:

void callback(u_char * userarg, const struct pcap_pkthdr * pkthdr, const u_char * packet)    

第一个参数是pcap_loop的最后一个参数,当收到足够数量的包后pcap_loop会调用callback回调函数,同时将pcap_loop()的user参数传递给它

第二个参数是收到的数据包的pcap_pkthdr类型的指针

第三个参数是收到的数据包数据

c)定时获取

int pcap_dispatch(pcap_t * p, int cnt, pcap_handler callback, u_char * user)    

这个函数和pcap_loop()非常类似,只是在超过to_ms毫秒后就会返回(to_ms是pcap_open_live()的第4个参数)

本次使用案例:

//回调函数  void pcap_handle(u_char* user,const struct pcap_pkthdr* header,const u_char* pkt_data)  {      netmesg((char *)pkt_data,header->len);  }    int devicefunc(char * device){      char errbuf[1024];      pcap_t *phandle;        bpf_u_int32 ipaddress,ipmask;      int datalink;          printf("device: %sn",device);        phandle=pcap_open_live(device,200,0,500,errbuf);      if(phandle==NULL){          perror(errbuf);          return 1;      }        if(pcap_lookupnet(device,&ipaddress,&ipmask,errbuf)==-1){          perror(errbuf);          return 1;      }      else{          char ip[INET_ADDRSTRLEN],mask[INET_ADDRSTRLEN];          if(inet_ntop(AF_INET,&ipaddress,ip,sizeof(ip))==NULL)              perror("inet_ntop error");          else if(inet_ntop(AF_INET,&ipmask,mask,sizeof(mask))==NULL)              perror("inet_ntop error");          printf("IP address: %s, Network Mask: %sn",ip,mask);      }        int flag=1;        if((datalink=pcap_datalink(phandle))==-1){          fprintf(stderr,"pcap_datalink: %sn",pcap_geterr(phandle));          return 1;      }        printf("datalink= %dn",datalink);      lib_func();      pcap_loop(phandle,-1,pcap_handle,NULL);  }      int main(int argc, char **argv)  {        char *device="eno1";      int flag = devicefunc(device);        return 0;  }  

3.3 使用Raw Socket接收

具体的结构体在上述接收处说明了一下,此处重点学习recvfrom

本次使用案例:

struct netData netrecv(char A,char B){      int sock, n_read;      char buffer[RECV_BUFF_LEN];        struct sockaddr_ll sll;      struct ifreq ifstruct;        if((sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0)      {          perror( "create socket error");          exit(-1);      }        memset(&sll, 0, sizeof(sll));      sll.sll_family = PF_PACKET;      sll.sll_protocol = htons(ETH_P_ALL);        //get net card index ethx->index      strcpy(ifstruct.ifr_name, "eno1");      ioctl(sock, SIOCGIFINDEX, &ifstruct);      sll.sll_ifindex = ifstruct.ifr_ifindex;        //bind net card      if (bind(sock, (struct sockaddr *)&sll, sizeof(sll)) == -1)      {          perror("bind error:n");          exit(-1);      }          n_read = recvfrom(sock, buffer, 2048, 0, NULL, NULL);      if(n_read <= 0)      {          perror("recvfromn");          exit(-1);      }      //process packet      netmesg(buffer,n_read,A,B); //输出返回包信息      //发送到usb      // bulk_send(buffer,n_read);      struct netData recvdata;      recvdata.buffer = buffer;      recvdata.n_read = n_read;      return recvdata;  }  

4.Makefile管理

使用libusb_client.h文件封装函数名及相关常量、结构体的定义。

使用libusb_client1.c封装函数实现

使用libusb_client.c实现main函数调用

最后,使用Makefile管理。

lib_client:libusb_client1.o      gcc libusb_client.c -o lib_client libusb_client1.o -lusb-1.0 -lpthread -lm -lnet  libusb_client1.o:libusb_client1.c      gcc -c libusb_client1.c  clean:      rm -rf libusb_client1.o lib_client  

5.学习文章

大端与小端

Linux中ifreq 结构体分析和使用

使用libnet与libpcap构造TCP/IP协议软件

libpcap使用

Raw Socket 接收和发送数据包