————————————————
版权声明:本文为CSDN博主「Ezioooooo」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u012877472/article/details/49817875
一、配置(VS2015)
项目右键属性,在C/C++目录预处理器添加WPCAP和HAVE_REMOTE,预编译头改为不使用预编译头,链接器输入wpcap.lib和ws2_32.lib,VC++目录包含目录添加下载的include文件,库目录添加下载的lib文件。
二、获取设备列表
(编写winpcap程序的第一件事)
pcap_findalldevs_ex()函数来实现这个功能:创建一个可以被函数pcap_open()打开的网络设备链表
pcap_findalldevs_ex(
char * source, //字符指针,保存来源的位置,设为PCAP_SRC_IF_STRING;
struct pcap_rmthauth* auth, //指向pcap_rmtauth结构体的指针,保存需要远程设备捕获协议认证的信息。捕获本地设备设为NULL;
pcap_if_t** alldevs, //一个pcap_if_t结构体指针,函数返回时用来保存找到的适配器的信息;
char* errbuf //用来保存错误信息;
); //返回值:如果返回0,表示函数运行成功,说明找到合适的适配器,通过alldevs参数返回;如果返回-1,发生错误,或者没有在本地找到合适的适配器;
pcap_if_t结构体定义如下:
pcap_if* next; //指向链表的下一个节点,如果不为空指向一个pcap_if元素;
char* name; //字符串指针,存储传向pcap_open_live函数的设备名称;
char* description; //设备的描述;
pcap_addr* addresses;//这项这个设备接口地址链表的第一个元素;
u_int flags; //当前唯一的值是PCAP_IF_LOOPBACK,当当前接口是回路接口的时候设置;
当我们完成设备列表的使用后,应当调用pcap_freealldevs()函数释放内存资源。
三、获取已安装设备的详细信息
函数pcap_findalldevs_ex()返回的pcap_if结构体中,都有一个pcap_addr的结构体,这个结构体用来保存地址信息。
pcap_addr* next;//指向下一个节点
sockaddr* addr;//一个地址列表
sockaddr* netmask;//一个掩码列表
sockaddr* broadaddr;//一个广播地址列表
sockaddr* dstaddr;//一个目的地址列表
sockaddr结构用来存储与Windows套接字通讯的计算机上的一个IP地址。
四、打开适配器并捕获数据包
①打开适配器:函数pcap_open():
pcap_t* pcap_open (
const char * source, //设备名称
int snaplen, //制定要捕获数据包中的哪些部分,将值定为65535确信总能收到完整数据包(比最大MTU大
int flags, //*最重要,用来指示适配器是否要被设置成混杂模式(不管这个数据包是不是发给我的,我都会去捕获。WinPcap能捕获其他主机的所有的数据包)
int read_timeout, //读取超时时间
struct pcap_rmtauth * auth, //远程机器验证
char * errbuf //错误缓冲池,存储错误信息
);
函数返回:一个可以作为调用参数的指向pcap_t结构的指针,并且指定一个WinPcap会话。如果出现错误,返回NULL,errbuf中存储错误信息。
typedef struct pcap pcap_t //一个已打开的捕捉实例的描述符
②捕获工作:
pcap_dispatch() 或 pcap_loop()函数:
int pcap_loop (
pcap_t * p, //pcap的句柄
int cnt, //捕获包的数量,如果是负数表示永不停止直到出现错误
pcap_handler callback, //回调函数,当捕获到数据包时调用此函数
u_char * user //留给用户使用的
);
其中回调函数:
void(*) pcap_handler(
u_char *user, //函数pcap_loop()传递过来的,就是pcap_loop()函数中的user参数
const struct pcap_pkthdr *pkt_header, //表示捕获到的数据包的基本信息
const u_char *pkt_data //表示捕获到的数据包的内容
);
结构体:
struct pcap_pkthdr {
struct timeval ts; /* 时间戳 */
bpf_u_int32 caplen; /* 已捕获部分的长度 */
bpf_u_int32 len; /* 该包的脱机长度 */
}; //保存捕获到的包的基本信息,由pcap_loop函数自动填充
③不用回调方法捕获:
pcap_next_ex()函数代替pcap_loop()函数来实现捕获数据包,当只有显示调用时才能捕获数据包。
int pcap_next_ex (
pcap_t * p, //句柄
struct pcap_pkthdr ** pkt_header, //指向一个pcap_pkthdr结构的指针,用于保存捕获数据包的基本信息
const u_char ** pkt_data //保存捕获的数据
);
//捕获一个数据包,然后用捕获的数据包填充pkt_header和pkt_data参数,根据结果不同,返回不同的数字(1成功)。
五、过滤数据包
pcap_compile() 和 pcap_setfilter() 函数:
int pcap_compile(
pcap_t *p, //返回数据包捕获的指针
struct bpf_program *fp, //一个bpf_program结构的指针,用于过滤,在pcap_compile()函数中被赋值。
char *str, //规定过滤规则
int optimize, //控制结果代码的优化。规定了在结果代码上的选择是否被执行
bpf_u_int32 netmask); //指定本地网络的网络掩码(该网卡的子网掩码),通过pcap_lookupnet()获取
将str指定的规则整合到分fp过滤程序中,并生成过滤程序入口地址,用于过滤选择期望的数据包。成功返回0,否则返回-1。
过滤规则str由一个或多个原语组成,如果为""表示不进行过滤。原语通常由一个标识id(名字或序列)和限定词(输入(type,指明哪些东西是id所代表的,可能的输入是host,net和port,默认host),方向(dir,id指明了一个特定的传输方向,可能的方向是src,dst,src or dst,默认src/dst),协议(proto,所匹配的协议ether,fddi,tr,ip,ip6,arp,rarp,decnet,tcp和udp,默认所有都可))组成。很复杂,记得查询。
六、分析数据包
此时已经可以可以捕获并过滤网络流量,以TCP/UDP为例分析。
网络中的数据包每经过一个层次都会加上那个层的报头来标注一些重要的信息。
捕获到的数据包首先有个mac报头,14字节,包含6字节目的mac地址、6字节源mac地址,和2字节上一层协议(不关注mac),接下来是IP数据包有20字节的报头。
IP报头定义:
/* IPv4 首部 */
typedef struct ip_header {
u_char ver_ihl; // 版本 (4 bits) + 首部长度 (4 bits)
u_char tos; // 服务类型(Type of service)
u_short tlen; // 总长(Total length)
u_short identification; // 标识(Identification)
u_short flags_fo; // 标志位(Flags) (3 bits) + 段偏移量(Fragment offset) (13 bits)
u_char ttl; // 存活时间(Time to live)
u_char proto; // 协议(Protocol)
u_short crc; // 首部校验和(Header checksum)
ip_address saddr; // 源地址(Source address)
ip_address daddr; // 目的地址(Destination address)
u_int op_pad; // 选项与填充(Option + Padding)
}ip_header;
通过首部长度(ip_len = (ih->ver_ihl & 0xf) * 4;)得到UDP数据包的位置。
UDP报头定义:
/* UDP 首部*/
typedef struct udp_header {
u_short sport; // 源端口(Source port)
u_short dport; // 目的端口(Destination port)
u_short len; // UDP数据包长度(Datagram length)
u_short crc; // 校验和(Checksum)
TCP报头定义:
/* tcp 首部 */
typedef struct tcp_header {
u_short sport; //源端口
u_short dport; //目的端口
u_int th_seq; //序列号
u_int th_ack; //确认号
u_short doff : 4, hlen : 4, fin : 1, syn : 1, rst : 1, psh : 1, ack : 1, urg : 1, ece : 1, cwr : 1; //4 bits 首部长度,6 bits 保留位,6 bits 标志位
u_short th_window; //窗口大小
u_short th_sum; //校验和
u_short th_urp; //紧急指针
}tcp_header;
}udp_header;
UDP报头:
TCP报头:
七、保存数据包到堆文件
①pcap_dump_open()函数打开一个可以保存数据的文件。
pcap_dumper_t* pcap_dump_open (
pcap_t * p, //一个libpcap存储文件的描述符,对用户不可见
const char * fname //要写入的文件名
);//只有当接口打开时,调用 pcap_dump_open() 才是有效的。 这个调用将打开一个堆文件,并将它关联到特定的接口上。
②pcap_dump()函数将数据包写入用户指定的文件中。
void pcap_dump (
u_char * user, //文件描述符dumpfp
const struct pcap_pkthdr * h, //pkt_header
const u_char * sp //要用pkt_data
);
八、从堆文件读取数据包
pcap_open_offline()函数将堆文件打开。
pcap_t* pcap_open_offline (
const char * fname, //要打开的文件名
char * errbuf //保存错误信息
);
通过pcap_createsrcstr()函数根据新WinPcap语法创建一个源字符串。
int pcap_createsrcstr (
char * source, //要存入的源字符串
int type, //要创建的源字符串类型
const char * host, //远程主机
const char * port, //远程端口
const char * name, //我们要打开的文件名
char * errbuf //存储错误信息
); //返回值:0:没有错误;-1:创建出错,错误写入errbuf中