libpcap抓包并分析

基于libpcap的数据包抓取

1.libpcap安装

前提安装gcc

然后安装输入如下命令:

yum -y install flex
yum -y install bison

在一个文件夹下下载libpcap源码并解压,在安装如下:

wget -c http://www.tcpdump.org/release/libpcap-1.7.4.tar.gz
进入libpcap-1.7.4,输入:
./configure
make
make install

这里写图片描述

2.库函数及相应功能了解

1)pcap_lookupdev();

函数用来查找网络设备,返回可被pcap_open_live()函数调用的网络设备名指针。

2)pcap_open_live(char *device,int catch_byte,int promisc,int time,char *errbuf);

函数pcap_open_live是用于获取网络接口的函数,函数返回一个指针用于后续对该接口的操作,device是设备名,第二个是每次抓包抓取多少个字节,最多65535个字节,第三个参数是将设备设置成混杂模式,1为混杂模式,其他非混杂,第四个参数是抓取时间,指定需要等地的毫秒数,超过这个时间后,获得数据包的函数会立即返回,0表示一直等待直到有数据包到来,errbuf保存错误信息。

函数返回数据类型为pcap_t指针。

3)pcap_lookupnet(char *device,bpf_u_int32 *ipaddress,bpf_u_int32 *ipmask,char *errbuf);

函数pcap_lookupnet用于查看设备ip和掩码

4)pcap_compile(pcap_t *p,struct bpf_program *fp,char *filterstr,int opt,bpf_u_int32 netmask);
5)pcap_setfilter(pcap_t *p,struct bpf_program *fp);

函数pcap_compile是用来设置过滤条件的,即设置抓取的包的条件,条件为filterstr,配合pcap_setfilter函数完成设置。

6)pcap_loop();

与pcap_next()和pcap_next_ex()两个函数一样用来捕获数据包

3.代码+实验

参考 官方文档

1.确定嗅探设备,比如ens33

可以自己传入一个string作为嗅探设备,也可以使用pcap_lookupdev()函数来让程序给我们提供嗅探设备。

代码分别如下:

#include <stdio.h>
#include <pcap.h>

int main(int argc, char *argv[])
{
    char *dev = argv[1];//传入参数做嗅探设备

    printf("Device: %s\n", dev);
    return(0);
}

这里写图片描述

注意编译方式,见上图。
#include <stdio.h>
#include <pcap.h>

int main(int argc, char *argv[])
{
    char *dev,errbuf[1024];

    dev=pcap_lookupdev(errbuf);

    if(dev==NULL){
        printf("%s\n",errbuf);
        return 0;
    }

    printf("Device: %s\n", dev);
    return 0;
}

这里写图片描述

2.打开嗅探设备,准备嗅探

函数讲解见部分2.库函数及相应功能了解

    pcap_t *pcap_handle=pcap_open_live(dev,65535,1,0,errbuf);

    if(pcap_handle==NULL){
        printf("%s\n",errbuf);
        return 0;
    }

pcap_t*这个指针很重要,后面基本所有操作都会用到。

获取网络号(ip)和掩码:int pcap_lookupnet(char *device,bpf_u_int32 *ipaddress,bpf_u_int32 *ipmask,char *errbuf);

device为设备,ipaddress记录设备ip,ipmask记录设备掩码,errbuf存储错误信息,返回-1出错,0成功

#include <stdio.h>
#include <pcap.h>
#include<time.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<errno.h>
#include<string.h>

int main(int argc, char *argv[])
{
    char *dev,errbuf[1024];

    //确定网络接口
    dev=argv[1];
    if(dev==NULL){
        printf("device is null\n");
        return 0;
    }

    //打开网络接口
    pcap_t *pcap_handle=pcap_open_live(dev,65535,1,0,errbuf);

    if(pcap_handle==NULL){
        printf("%s\n",errbuf);
        return 0;
    }

    //获取网络号ip和掩码
    struct in_addr addr;
    bpf_u_int32 ipaddress, ipmask;
    char *dev_ip,*dev_mask;

    if(pcap_lookupnet(dev,&ipaddress,&ipmask,errbuf)==-1){
        printf("%s\n",errbuf);
        return 0;
    }
    //输出ip
    addr.s_addr=ipaddress;
    dev_ip=inet_ntoa(addr);
    printf("ip address : %s\n",dev_ip);
    //输出掩码
    addr.s_addr=ipmask;
    dev_mask=inet_ntoa(addr);
    printf("netmask : %s\n",dev_mask);

    return 0;
}

这里写图片描述

不太清楚最后的一个字节为什么为0…可以看看源码研究一下。

3.获取网络数据包

方法1:const u_char *pcap_next(pcap_t *p,struct pcap_pkthdr *h);

捕获一个网络数据包,收到一个数据包立即返回,p:pcap_open_live()返回的pcap_t类型的指针h:数据包头,返回的是接收到的数据的起始地址,即实际数据内容

pcap_pkthdr类型的定义如下:

struct pcap_pkthdr  
{  
    struct timeval ts; // 抓到包的时间  
    bpf_u_int32 caplen; // 表示抓到的数据长度,抓取时长度
    bpf_u_int32 len; // 表示数据包的实际长度,本来应有长度
}  

使用:

    const char *p_packet_content;//实际接收数据起始地址
    struct pcap_pkthdr protocol_header;//数据包头
    p_packet_content=pcap_next(pcap_handle,&protocol_header);
    printf("capture time : %s",ctime((const time_t*)&protocol_header.ts.tv_sec));
    printf("packet length : %d\n",protocol_header.len);

这里写图片描述

方法2:int pcap_loop(pcap_t *p,int cnt,pcap_handler callback,u_char *user);

循环捕获网络数据包,直到遇到错误或者满足退出条件,每次捕获一个数据包就会调用callback指定的回调函数,所以,可以在回调函数中进行数据包的处理操作,返回值:成功返回0,失败返回负数

·p:pcap_open_live()返回的pcap_t类型的指针

·cnt:指定捕获数据包的个数,一旦抓到cnt个数据包,pcap_loop立即返回,如果是-1,就会一直捕获直到出错

·callback:回调函数,名字任意,可在其中处理数据包

·user:向回调函数中传递的参数

回调函数callback意义:void callback(u_char *userarg,const struct pcap_pkthdr *pkthdr,const u_char *packet);

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

·pkthdr:是收到数据包的pcap_pkthdr类型的指针,和pcap_next()第二个参数是一样的

·packet:收到的数据包数据首地址

代码:

#include <stdio.h>
#include <pcap.h>
#include<time.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<errno.h>
#include<string.h>
//回调函数
void pcap_callback(unsigned char * arg,const struct pcap_pkthdr *packet_header,const unsigned char *packet_content){
    int *id=(int *)arg;//记录包ID
    printf("id=%d\n",++(*id));

    printf("Packet length : %d\n",packet_header->len);
    printf("Number of bytes : %d\n",packet_header->caplen);
    printf("Received time : %s\n",ctime((const time_t*)&packet_header->ts.tv_sec));
    int i;
    for(i=0;i<packet_header->caplen;i++){
        printf(" %02x",packet_content[i]);
        if((i+1)%16==0){
            printf("\n");
        }
    }
    printf("\n\n");
}

int main(int argc, char *argv[])
{
    char *dev,errbuf[1024];

    dev=argv[1];
    if(dev==NULL){
        printf("device is null\n");
        return 0;
    }

    pcap_t *pcap_handle=pcap_open_live(dev,65535,1,0,errbuf);

    if(pcap_handle==NULL){
        printf("%s\n",errbuf);
        return 0;
    }

    struct in_addr addr;
    bpf_u_int32 ipaddress, ipmask;
    char *dev_ip,*dev_mask;

    if(pcap_lookupnet(dev,&ipaddress,&ipmask,errbuf)==-1){
        printf("%s\n",errbuf);
        return 0;
    }

    addr.s_addr=ipaddress;
    dev_ip=inet_ntoa(addr);
    printf("ip address : %s\n",dev_ip);

    addr.s_addr=ipmask;
    dev_mask=inet_ntoa(addr);
    printf("netmask : %s\n",dev_mask);

    printf("---------packet--------\n");
    int id=0;//传入回调函数记录ID
    if(pcap_loop(pcap_handle,10,pcap_callback,(unsigned char *)&id)<0){//接收十个数据包
        printf("error\n");
        return 0;
    }

    pcap_close(pcap_handle);

    return 0;
}

这里写图片描述

4.过滤数据包

设置过滤条件:

举一些例子:

  • src host 192.168.1.177:只接收源ip地址是192.168.1.177的数据包

  • dst port 80:只接收tcp、udp的目的端口是80的数据包

  • not tcp:只接收不使用tcp协议的数据包

  • tcp[13] == 0x02 and (dst port 22 or dst port 23) :只接收 SYN
    标志位置位且目标端口是 22 或 23 的数据包( tcp 首部开始的第 13 个字节)

  • icmp[icmptype] == icmp-echoreply or icmp[icmptype] == icmp-echo:只接收 icmp 的 ping 请求和 ping 响应的数据包

  • ehter dst 00:e0:09:c1:0e:82:只接收以太网 mac 地址是 00:e0:09:c1:0e:82 的数据包

  • ip[8] == 5:只接收 ip 的 ttl=5 的数据包(ip首部开始的第8个字节)

编译BPF过滤规则:int pcap_compile(pcap_t *p,struct bpf_program *fp,char *buf,int optimize,bpf_u_int32 mask);

参数:

  • p:pcap_open_live()返回的pcap_t类型的指针

  • fp:存放编译后的bpf,应用过来规则时需要使用这个指针

  • buf:过滤规则

  • optimize:是否需要优化过滤表达式

  • mask:指定本地网络的网络掩码,不需要时可写0

  • 返回值:成功返回0,失败返回-1

应用BPF过滤规则:int pcap_setfilter(pcap_t *p,struct bpf_program *fp);

功能:应用BPF过滤规则

参数:

  • p:pcap_open_live()返回的pcap_t类型的指针

  • fp:pcap_compile()的第二个参数

  • 返回值:成功返回0,失败返回-1

使用:

    struct bpf_program filter;
    pcap_compile(pcap_handle,&filter,"dst port 80",1,0);
    pcap_setfilter(pcap_handle,&filter);

以上代码运行,过滤条件是dst port 80,打开浏览器,数据包就接收到了。

这里写图片描述

附:保存抓取到的数据,并保存至aaa.pcap文件里,可用wireshark打开

函数:

pcap_dumper_t* dumpfp=pcap_dump_open(pcap_t *p,char[] path);

pcap_dump_close(pcap_dumper_t* dumpfp);

还需使用pcap_dump(arg,packet_header,packet_content);放在回调函数里,从而保存数据

以上两个函数是一对

具体代码:

这里写图片描述

这里写图片描述

以上基本函数使用讲解完毕。

4.具体数据包

以下只提供代码,来解析数据包。不同协议分析可仿照如下:(需要学习网络协议)

#include <stdio.h>
#include <pcap.h>
#include<time.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<errno.h>
#include<string.h>

typedef struct eth_hdr
{
    u_char dst_mac[6];
    u_char src_mac[6];
    u_short eth_type;
}eth_hdr;
eth_hdr *ethernet;

typedef struct ip_hdr
{
    int version:4;
    int header_len:4;
    u_char tos:8;
    int total_len:16;
    int ident:16;
    int flags:16;
    u_char ttl:8;
    u_char protocol:8;
    int checksum:16;
    u_char sourceIP[4];
    u_char destIP[4];
}ip_hdr;
ip_hdr *ip;

typedef struct tcp_hdr
{
    u_short sport;
    u_short dport;
    u_int seq;
    u_int ack;
    u_char head_len;
    u_char flags;
    u_short wind_size;
    u_short check_sum;
    u_short urg_ptr;
}tcp_hdr;
tcp_hdr *tcp;

typedef struct udp_hdr
{
    u_short sport;
    u_short dport;
    u_short tot_len;
    u_short check_sum;
}udp_hdr;
udp_hdr *udp;

void pcap_callback(unsigned char * arg,const struct pcap_pkthdr *packet_header,const unsigned char *packet_content){
    static int id=1;
    printf("id=%d\n",id++);

    pcap_dump(arg,packet_header,packet_content);

    printf("Packet length : %d\n",packet_header->len);
    printf("Number of bytes : %d\n",packet_header->caplen);
    printf("Received time : %s\n",ctime((const time_t*)&packet_header->ts.tv_sec));
    int i;
    for(i=0;i<packet_header->caplen;i++){
        printf(" %02x",packet_content[i]);
        if((i+1)%16==0){
            printf("\n");
        }
    }
    printf("\n\n");

    u_int eth_len=sizeof(struct eth_hdr);
    u_int ip_len=sizeof(struct ip_hdr);
    u_int tcp_len=sizeof(struct tcp_hdr);
    u_int udp_len=sizeof(struct udp_hdr);

    printf("analyse information:\n\n");

    printf("ethernet header information:\n");
    ethernet=(eth_hdr *)packet_content;
    printf("src_mac : %02x-%02x-%02x-%02x-%02x-%02x\n",ethernet->src_mac[0],ethernet->src_mac[1],ethernet->src_mac[2],ethernet->src_mac[3],ethernet->src_mac[4],ethernet->src_mac[5]);
    printf("dst_mac : %02x-%02x-%02x-%02x-%02x-%02x\n",ethernet->dst_mac[0],ethernet->dst_mac[1],ethernet->dst_mac[2],ethernet->dst_mac[3],ethernet->dst_mac[4],ethernet->dst_mac[5]);
    printf("ethernet type : %u\n",ethernet->eth_type);

    if(ntohs(ethernet->eth_type)==0x0800){
        printf("IPV4 is used\n");
        printf("IPV4 header information:\n");
        ip=(ip_hdr*)(packet_content+eth_len);
        printf("source ip : %d.%d.%d.%d\n",ip->sourceIP[0],ip->sourceIP[1],ip->sourceIP[2],ip->sourceIP[3]);
        printf("dest ip : %d.%d.%d.%d\n",ip->destIP[0],ip->destIP[1],ip->destIP[2],ip->destIP[3]);
        if(ip->protocol==6){
            printf("tcp is used:\n");
            tcp=(tcp_hdr*)(packet_content+eth_len+ip_len);
            printf("tcp source port : %u\n",tcp->sport);
            printf("tcp dest port : %u\n",tcp->dport);
        }
        else if(ip->protocol==17){
            printf("udp is used:\n");
            udp=(udp_hdr*)(packet_content+eth_len+ip_len);
            printf("udp source port : %u\n",udp->sport);
            printf("udp dest port : %u\n",udp->dport);
        }
        else {
            printf("other transport protocol is used\n");
        }
    }
    else {
        printf("ipv6 is used\n");
    }

    printf("------------------done-------------------\n");
    printf("\n\n");
}

int main(int argc, char *argv[])
{
    char *dev,errbuf[1024];


    dev=argv[1];
    if(dev==NULL){
        printf("device is null\n");
        return 0;
    }


    pcap_t *pcap_handle=pcap_open_live(dev,65535,1,0,errbuf);

    if(pcap_handle==NULL){
        printf("%s\n",errbuf);
        return 0;
    }


    struct in_addr addr;
    bpf_u_int32 ipaddress, ipmask;
    char *dev_ip,*dev_mask;

    if(pcap_lookupnet(dev,&ipaddress,&ipmask,errbuf)==-1){
        printf("%s\n",errbuf);
        return 0;
    }

    addr.s_addr=ipaddress;
    dev_ip=inet_ntoa(addr);
    printf("ip address : %s\n",dev_ip);

    addr.s_addr=ipmask;
    dev_mask=inet_ntoa(addr);
    printf("netmask : %s\n",dev_mask);

    struct bpf_program filter;
    if(pcap_compile(pcap_handle,&filter,"dst port 80",1,0)<0){
        printf("error\n");
        return 0;
    }
    if(pcap_setfilter(pcap_handle,&filter)<0){
        printf("error\n");
        return 0;
    }


    printf("---------packet--------\n");

    int id=0;


    pcap_dumper_t* dumpfp=pcap_dump_open(pcap_handle,"./save1.pcap");

    if(pcap_loop(pcap_handle,20,pcap_callback,(unsigned char *)dumpfp)<0){
        printf("error\n");
        return 0;
    }

    pcap_dump_close(dumpfp);

    pcap_close(pcap_handle);

    return 0;
}

结果:

这里写图片描述

参考:

https://www.cnblogs.com/danielStudy/p/7007689.html

http://www.tcpdump.org/pcap.html

另:我的Tenkey同学用python抓包,代码如下,可以学习一波,代码链接

#!/usr/bin/env python3
"""
Use DPKT to read in a pcap file and print out the contents of the packets
This example is focused on the fields in the Ethernet Frame and IP packet
Using Pypcap module to capture the packet and Using the Dpkt to read the packet
and save it to a .pcap file by timestamp or you can name it by yourself
by Tenkey 
"""
import pcap
import dpkt
import datetime
import socket
import os
from dpkt.compat import compat_ord

file_name_time = None


def mac_addr(address):
    """Convert a MAC address to a readable/printable string
       Args:
           address (str): a MAC address in hex form (e.g. '\x01\x02\x03\x04\x05\x06')
       Returns:
           str: Printable/readable MAC address
    """
    return ':'.join('%02x' % compat_ord(b) for b in address)


def inet_to_str(inet):
    """Convert inet object to a string
        Args:
            inet (inet struct): inet network address
        Returns:
            str: Printable/readable IP address
    """
    # First try ipv4 and then ipv6
    try:
        return socket.inet_ntop(socket.AF_INET, inet)
    except ValueError:
        return socket.inet_ntop(socket.AF_INET6, inet)


def print_packets(pcap):
    """Print out information about each packet in a pcap
       Args:
           pcap: an pcap.pcap object (a network packet) 
    """

    # For each packet in the pcap process the contents
    global file_name_time

    with open('unnamed.pcap', 'wb') as file:
        writer = dpkt.pcap.Writer(file)

        for timestamp, buf in pcap:

            if file_name_time == None:
                file_name_time = str(
                    datetime.datetime.utcfromtimestamp(timestamp))

            writer.writepkt(buf, timestamp)

            # Print out the timestamp in UTC
            print('Timestamp: ', str(datetime.datetime.utcfromtimestamp(timestamp)))

            # Unpack the Ethernet frame (mac src/dst, ethertype)
            eth = dpkt.ethernet.Ethernet(buf)
            print('Ethernet Frame: ', mac_addr(
                eth.src), mac_addr(eth.dst), eth.type)

            # Make sure the Ethernet data contains an IP packet
            if not isinstance(eth.data, dpkt.ip.IP):
                print('Non IP Packet type not supported %s\n' %
                      eth.data.__class__.__name__)
                continue

            # Now unpack the data within the Ethernet frame (the IP packet)
            # Pulling out src, dst, length, fragment info, TTL, and Protocol
            ip = eth.data

            # Pull out fragment information (flags and offset all packed into off field, so use bitmasks)
            do_not_fragment = bool(ip.off & dpkt.ip.IP_DF)
            more_fragments = bool(ip.off & dpkt.ip.IP_MF)
            fragment_offset = ip.off & dpkt.ip.IP_OFFMASK

            # Print out the info
            print('IP: %s -> %s   (len=%d ttl=%d DF=%d MF=%d offset=%d)' %
                  (inet_to_str(ip.src), inet_to_str(ip.dst), ip.len, ip.ttl, do_not_fragment, more_fragments, fragment_offset))

            # Print out the detials in packet
            if isinstance(ip.data, dpkt.tcp.TCP):
                tcp = ip.data
                print('TCP: SrcPort[%d] -> DstPort[%d] Seq=%d Ack=%d Win=%d\n' %
                      (tcp.sport, tcp.dport, tcp.seq, tcp.ack, tcp.win))
            elif isinstance(ip.data, dpkt.udp.UDP):
                udp = ip.data
                print('UDP: SrcPort[%d] -> DstPort[%d] Len=%d Check=%d\n' %
                      (udp.sport, udp.dport, udp.ulen, udp.sum))
            elif isinstance(ip.data, dpkt.icmp.ICMP):
                print("ICMP: This is ICMP packet for checking error on route\n")
            else:
                print("Other Protocol: there may be other Protocols\n")


def getPcap():
    nic = "ens33"
    fil = None

    ans_nc = input("do you want to set filter for network card ? [y/n]\n")
    if ans_nc == "y":
        nic = input("typing in the network card name:\n")
        print("......setting filter for network card sucessfully......\n")

    ans_fil = input(
        "do you want to set filter in th BPF(Berkeley Packet Filter) syntax ? [y/n]\n")
    if ans_fil == "y":
        fil = input("typing the BPF syntax Filter:\n")
        print("......setting filter in BPF syntax sucessfully......\n")

    sniffer = pcap.pcap(nic)

    if fil != None:
        sniffer.setfilter(fil)

    return sniffer


def main():
    """Using Pypcap(pcap) and DPKT(dpkt) modules to capture the network packet
       and unpack the packet to show the detials in every layer(this demo just show parts of them)
       and then save it into a .pcap file which can be opened by various open-source network capturing tools such as wireshark 
    """
    sniffer = getPcap()

    print("-------------Start to Unpack-------------\n")

    try:
        print_packets(sniffer)
    except KeyboardInterrupt:
        print("-------------Unpack Ended-------------\n")
        nf = input(
            "Do you want to name your capture file ? (or it will automatically named by time) [y/n]\n")
        if nf == "y":
            file_name_user = input("Just type the name: \n")
            os.rename('unnamed.pcap', file_name_user+'.pcap')
        else:
            os.rename('unnamed.pcap', file_name_time+'.pcap')
            print("File will automatically be named after time , Bye~\n")
        print("-------------Saving sucessfully-------------\n")


if __name__ == '__main__':
    main()

注:以上所有操作均在作者在网上搜集资料后,在个人电脑上实验成功,若读者实验时失败,可能由一些未知因素导致,可与作者联系。编写的教程可能由于疏忽出错,请与作者联系。

  • 15
    点赞
  • 96
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值