首页 > 技术文章 > libpcap安装与使用

shandianchengzi 2021-08-13 19:53 原文

Ubuntu 20.04。

起步

一、下载libpcap 库

前往http://www.tcpdump.org/release/ ,下载格式为xxxx.tar.gz的文件。

二、安装

  1. 解压文件到你的当前目录
tar zxvf xxxx.tar.gz 
  1. 进入刚才解开的libpcap目录,生成Makefile文件
sudo ./configure 

配置过程如果出现错误,
请查看你是否安装了所有的依赖包bison, m4, flex以及libpcap-dev(安装方法 sudo apt install ****)
尝试运行 sudo apt install bison m4 flex libpcap-dev -y

  1. 将生成的库安装到系统默认目录中。此目录为 /usr/lib ,如果需要修改,可以修改文件Makefile 的 prefix。
sudo make install

三、使用

  1. 编译选项:
gcc 源文件.c -lpcap -o 输出文件名
  1. Warning不用管:
    在这里插入图片描述

  2. 测试程序:

#include <pcap.h>  
#include <stdio.h>  
  
int main()  
{  
  char errBuf[PCAP_ERRBUF_SIZE], * device;  
  //look for the net device
  device = pcap_lookupdev(errBuf);  
    
  if(device)  
  {  
    printf("success: device: %s\n", device);  
  }  
  else  
  {  
    printf("error: %s\n", errBuf);  
  }  
    
  return 0;  
} 
  1. 测试程序运行结果:
    在这里插入图片描述

完整程序 (们)

注意,
解析报文的时候,
结构体很可能因为字节对齐出现问题!
如下:

struct CAN_layer{
  u_int16_t pkg_type;
  u_int16_t layer_addr_type;
  u_int16_t layer_addr_len;
  u_int64_t unused;  //。。。字节对齐
  u_int16_t protocol;
};

应改写成:

struct CAN_layer{
  u_int16_t pkg_type;
  u_int16_t layer_addr_type;
  u_int16_t layer_addr_len;
  u_char unused[8];  //。。。字节对齐
  u_int16_t protocol;
};

1. 头文件集锦

myheader.h

/* ethernet headers are always exactly 14 bytes [1] */
#define SIZE_ETHERNET 14

/* Ethernet addresses are 6 bytes */
#define ETHER_ADDR_LEN  6

#define PACKET_LEN   1500


/* Ethernet header */
struct ethheader {
    u_char  ether_dhost[ETHER_ADDR_LEN];    /* destination host address */
    u_char  ether_shost[ETHER_ADDR_LEN];    /* source host address */
    u_short ether_type;                     /* IP? ARP? RARP? etc */
};


/* IP Header */
struct ipheader {
	unsigned char      iph_ihl:4, iph_ver:4; //IP Header length & Version.
	unsigned char      iph_tos; //Type of service
	unsigned short int iph_len; //IP Packet length (Both data and header)
	unsigned short int iph_ident; //Identification
	unsigned short int iph_flag:3, iph_offset:13; //Flags and Fragmentation offset
	unsigned char      iph_ttl; //Time to Live
	unsigned char      iph_protocol; //Type of the upper-level protocol
	unsigned short int iph_chksum; //IP datagram checksum
	struct  in_addr    iph_sourceip; //IP Source address (In network byte order)
	struct  in_addr    iph_destip;//IP Destination address (In network byte order)
};

/* ICMP Header */
struct icmpheader {
	unsigned char icmp_type; //ICMP message type
	unsigned char icmp_code; //Error code
	unsigned short int icmp_chksum; //Checksum for ICMP Header and data
	unsigned short int icmp_id; //Used in echo request/reply to identify request
 	unsigned short int icmp_seq;//Identifies the sequence of echo messages, 
				    //if more than one is sent.
};


/* TCP Header */
struct tcpheader {
    u_short tcp_sport;               /* source port */
    u_short tcp_dport;               /* destination port */
    u_int   tcp_seq;                 /* sequence number */
    u_int   tcp_ack;                 /* acknowledgement number */
    u_char  tcp_offx2;               /* data offset, rsvd */
#define TH_OFF(th)      (((th)->tcp_offx2 & 0xf0) >> 4)
    u_char  tcp_flags;
#define TH_FIN  0x01
#define TH_SYN  0x02
#define TH_RST  0x04
#define TH_PUSH 0x08
#define TH_ACK  0x10
#define TH_URG  0x20
#define TH_ECE  0x40
#define TH_CWR  0x80
#define TH_FLAGS        (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)
    u_short tcp_win;                 /* window */
    u_short tcp_sum;                 /* checksum */
    u_short tcp_urp;                 /* urgent pointer */
};


/* UDP Header */
struct udpheader
{
  u_int16_t udp_sport;           /* source port */
  u_int16_t udp_dport;           /* destination port */
  u_int16_t udp_ulen;            /* udp length */
  u_int16_t udp_sum;             /* udp checksum */
};

struct pseudo_tcp
{
        unsigned saddr, daddr;
        unsigned char mbz;
        unsigned char ptcl;
        unsigned short tcpl;
        struct tcpheader tcp;
        char payload[PACKET_LEN];
};

// DNS layer header's structure
struct dnsheader {
	unsigned short int query_id;
	unsigned short int flags;
	unsigned short int QDCOUNT;
	unsigned short int ANCOUNT;
	unsigned short int NSCOUNT;
	unsigned short int ARCOUNT;
};

2. 打印报文内容

sniff.c

#include <pcap.h> 
#include <stdio.h>
#include <arpa/inet.h>
#include "myheader.h"
#include <ctype.h>

/* This function will be invoked by pcap for each captured packet. 
We can process each packet inside the function. */

void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet) 
{ 
	int i=0;
	int size_data=0;
	printf("\nGot a packet\n"); 
	struct ethheader *eth=(struct ethheader *)packet;
	
	if(ntohs(eth->ether_type) == 0x800)
	{
		struct ipheader *ip = (struct ipheader *)(packet + sizeof(struct ethheader));
		printf("	From: %s\n",inet_ntoa(ip->iph_sourceip));
		printf("	To: %s\n",inet_ntoa(ip->iph_destip));

		struct tcpheader *tcp = (struct tcpheader *)(packet + sizeof(struct ethheader) + sizeof(struct ipheader));

		printf("	Source Port: %d\n",ntohs(tcp->tcp_sport));
		printf("	Destination Port: %d\n",ntohs(tcp->tcp_dport));
	

		switch(ip->iph_protocol) {
			case IPPROTO_TCP:
				printf("	Protocol: TCP\n");
				break;
			case IPPROTO_UDP:
				printf("	Protocol: UDP\n");
				break;
			case IPPROTO_ICMP:
				printf("	Protocol: ICMP\n");
				break;
			default:
				printf("	Protocol: Others\n");
				break;
			}


		char *data = (u_char *)packet + sizeof(struct ethheader) + sizeof(struct ipheader) + sizeof(struct tcpheader);
		size_data = ntohs(ip->iph_len) - (sizeof(struct ipheader) + sizeof(struct tcpheader));
		if (size_data > 0) {
  			printf("   Payload (%d bytes):\n", size_data);
  			for(i = 0; i < size_data; i++) {
  			if (isprint(*data))
   				printf("%c", *data);
 			 else
   				printf(".");
  			data++;
			}
		}
 	}
	
	
return;

}

int main() 
{ 
	pcap_t *handle; 
	char errbuf[PCAP_ERRBUF_SIZE]; 
	struct bpf_program fp; 
	char filter_exp[] = "proto TCP and dst portrange 10-100"; 
	bpf_u_int32 net;

	// Step 1: Open live pcap session on NIC with interface name
	handle = pcap_open_live("enp0s3", BUFSIZ, 1, 1000, errbuf);

	// Step 2: Compile filter_exp into BPF psuedo-code 
	pcap_compile(handle, &fp, filter_exp, 0, net); 
	pcap_setfilter(handle, &fp);

	// Step 3: Capture packets 
	pcap_loop(handle, -1, got_packet, NULL);

	pcap_close(handle); //Close the handle 
	return 0;
}

3. 嗅探与伪造

icmpspoof.c

#include <pcap.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#include "myheader.h"

char DEST_IP[]="172.17.0.2";        //被欺骗机docker的IP地址

unsigned short in_cksum(unsigned short *buf,int length);
void send_raw_ip_packet(struct ipheader* ip);
void send_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);

int main(){
    pcap_t *handle;
    char errbuf[PCAP_ERRBUF_SIZE];
    struct bpf_program fp;
    //设置报文过滤条件:来源于docker的icmp请求报文
    char* filter_exp = "(icmp[0] == 8) && (src 172.17.0.2)";  
    printf("filter: %s\n",filter_exp);

    bpf_u_int32 net;
    send_packet();
    // Step 1: 用网卡名字docker0打开pcap会话
    handle = pcap_open_live("docker0", BUFSIZ, 1, 1000, errbuf);

    // Step 2: 为侦听进程(bpf_program)fp添加过滤条件
    pcap_compile(handle, &fp, filter_exp, 0, net);
    pcap_setfilter(handle, &fp);

    // Step 3: 抓取报文,回调函数send_packet,功能是抓取到请求报文时发欺骗答复包
    pcap_loop(handle, -1, send_packet, NULL);

    pcap_close(handle); //关闭句柄

    return 0;
}

/******************************************************************
  抓取到请求报文时,伪造并发送欺骗答复包
*******************************************************************/
void send_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet){
   //获得真实请求报文的IP头和ICMP头
   struct ipheader *ip_true=(struct ipheader *)(packet+sizeof(struct ethheader));
   struct icmpheader *icmp_true=(struct icmpheader *)(packet+sizeof(struct ethheader)+sizeof(struct ipheader));
   printf("Get a icmp request packet from %s,to %s\n",DEST_IP,inet_ntoa(ip_true->iph_destip));
   printf("Send a cheat reply.\n");
   
   char buffer[PACKET_LEN];
   memset(buffer, 0, PACKET_LEN);

   // 构造ICMP头
   struct icmpheader *icmp;
   icmp = (struct icmpheader *)(buffer + sizeof(struct ipheader));

   icmp->icmp_type = 0; //设置类型为答复

   // 计算icmp的数据校验位(调用in_cksum函数)
   icmp->icmp_chksum = 0; 
   icmp->icmp_chksum = in_cksum((unsigned short *)icmp, 
                                sizeof(struct icmpheader));
                               
   // 将真实的ICMP头的id和seq填入伪造的ICMP头中,显得更真实一些
   icmp->icmp_id = icmp_true->icmp_id;
   icmp->icmp_seq = icmp_true->icmp_seq;

   //  构造IP头,填入常规参数
   struct ipheader *ip = (struct ipheader *) buffer;
   ip->iph_ver = 4;
   ip->iph_ihl = 5;
   ip->iph_tos = 16;
   ip->iph_ident = htons(54321);
   ip->iph_ttl = 128; 
   // 设置源地址为真实地址的目的地址,目的地址为被欺骗主机的IP地址
   ip->iph_sourceip.s_addr = inet_addr(inet_ntoa(ip_true->iph_destip));
   ip->iph_destip.s_addr = inet_addr(DEST_IP);
   ip->iph_protocol = IPPROTO_ICMP; // The value is 1, representing ICMP.
   ip->iph_len = htons(sizeof(struct ipheader) + sizeof(struct icmpheader));
	
   // ip->iph_chksum 可由系统补全,不需要手动设置

   //  最后,结合IP头和ICMP头发送报文
   send_raw_ip_packet (ip);

}


/******************************************************************************* 
  用 raw socket 发 IP报文
*******************************************************************************/
void send_raw_ip_packet(struct ipheader* ip)
{
    struct sockaddr_in dest_info;
    int enable = 1;

    // 创建一个原始网络套接字sock, and 设置相关参数
    int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &enable, sizeof(enable));

    // 提供必要的信息和地址
    dest_info.sin_family = AF_INET;
    dest_info.sin_addr = ip->iph_destip;

    // 把数据包发出去
    printf("Sending spoofed IP packet...\n");
    if(sendto(sock,ip,ntohs(ip->iph_len),0,(struct sockaddr *)&dest_info,sizeof(dest_info)) < 0)
    {//要是发送失败
        perror("PACKET NOT SENT\n");
        return;
    }
    else {//发送成功时,打印发送的报文的源IP和目标IP
        printf("\n---------------------------------------------------\n");
        printf("   From: %s\n",inet_ntoa(ip->iph_sourceip));
        printf("   To: %s\n",inet_ntoa(ip->iph_destip));
        printf("\n---------------------------------------------------\n");
    }
    
    close(sock);//关闭sock
}

/******************************************************************************* 
  生成ICMP头的数据校验码,其内在逻辑是既定的,无需多加注释
*******************************************************************************/
unsigned short in_cksum(unsigned short *buf,int length)
{
        unsigned short *w = buf;
        int nleft = length;
        int sum = 0;
        unsigned short temp=0;

        /*
        * The algorithm uses a 32 bit accumulator (sum), adds
        * sequential 16 bit words to it, and at the end, folds back all the
        * carry bits from the top 16 bits into the lower 16 bits.
        */
        while (nleft > 1)  {
                sum += *w++;
                nleft -= 2;
        }

        /* treat the odd byte at the end, if any */
        if (nleft == 1) {
                *(u_char *)(&temp) = *(u_char *)w ;
                sum += temp;
        }

        /* add back carry outs from top 16 bits to low 16 bits */
        sum = (sum >> 16) + (sum & 0xffff);     // add hi 16 to low 16 
        sum += (sum >> 16);                     // add carry 
        return (unsigned short)(~sum);
}

附加内容

unmask(0)

原文链接
linux中的 umask 函数主要用于:在创建新文件或目录时 屏蔽掉新文件或目录不应有的访问允许权限。文件的访问允许权限共有9种,分别是:r w x r w x r w x(它们分别代表:用户读 用户写 用户执行 组读 组写 组执行 其它读 其它写 其它执行)。
其实这个函数的作用,就是设置允许当前进程创建文件或者目录最大可操作的权限,比如这里设置为0,它的意思就是0取反再创建文件时权限相与,也就是:(~0) & mode 等于八进制的值0777 & mode了,这样就是给后面的代码调用函数mkdir给出最大的权限,避免了创建目录或文件的权限不确定性。

共享内存

参考文章:
Linux 进程间通信(IPC)—大总结
共享内存无锁队列的实现
写得实在太好了。

报文时间戳处理

时间戳相减函数

报文的时间戳存储在回调函数的const struct 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) */ 
};

其中struct timeval结构如下:

struct timeval
{
__time_t tv_sec;        /* Seconds. */
__suseconds_t tv_usec;  /* Microseconds. */
};

根据两个结构的特点,报文相对时间戳的计算函数如下:

void diffstamp(struct timeval* stamp1, struct timeval stamp2)
{
  stamp1->tv_sec = stamp1->tv_sec - stamp2.tv_sec;
  stamp1->tv_usec = stamp1->tv_usec - stamp2.tv_usec;
  if(stamp1->tv_usec<0)
  {
    stamp1->tv_sec -=1;
    stamp1->tv_usec += 1000000;
  }
}

时间戳处理结果:
在这里插入图片描述

推荐阅读