作者:shmily
实验题目:网络嗅探器实现
实验目的:熟悉并实现网络监听的基本原理
实验环境:linux/windows
实验内容:用C/C++语言(必须用socket函数)编写一个监听网络流量的程序,并对截取的报文进行解析。
Linux kali 4.19.0-kali1-686-pae #1 SMP Debian 4.19.13-1kali1 (2019-01-03) i686 GNU/Linux
make
如框图所示,程序可以获取到MAC帧、IP包以及传输层数据包。传输层的数据包中,只实现了对TCP, UPD, ICMP, IGMP报文的解析。
首先读取本主机和网卡的信息,在数据链路层接收经过本网卡的所有类型的数据包。在解析完MAC帧之后,将头部拆掉,并将payload继续解析,读出IP头部后,记下protocol类型,将头部拆掉,依据protocol 的类型进行分类解析。待三层都解析完毕后,重头解析下一个数据包,重复以上过程。
引用了程序可能用到的头文件,定义了数据链路层帧长度变量、必要的结构体、本机主机、网卡信息等内容。
#ifndef _NET_GLOBAL_H_
#define _NET_GLOBAL_H_
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
#include
#include
#else
#include
#include
#include
#endif
#include
#include
/* 以太网帧首部长度 */
#define ETHER_HEADER_LEN sizeof(struct ether_header)
/* MAC地址长度 */
#define MAC_ADDR_LEN 6
/* IP地址长度 */
#define IP_ADDR_LEN 4
#define IP_CHAR_MAX_LEN 18
/* 广播地址 */
#define BROADCAST_ADDR \
{ \
0xff, 0xff, 0xff, 0xff, 0xff, 0xff\
} \
#define err_exit(err_msg) \
{ \
perror(err_msg); \
exit(EXIT_FAILURE); \
} \
// 数据链路层
struct sockaddr_ll saddr_ll;
// 本地IP、子网掩码和本地MAC
struct in_addr local_ip, subnet_mask;
unsigned char local_mac_addr[MAC_ADDR_LEN];
// 网卡名称
char if_name[0x20];
#endif
init_net.c
文件与Scanner文件中的完全一样,主要通过socket函数和ioctl()获取相关信息。
为了实现直接从链路层收发数据帧,需要用到socket,为了手动实现解析,需要把type设置成SOCK_RAW
,直接从网络硬件驱动程序接收没有任何处理的完整数据报文,包括物理帧的帧头。并且将protocol字段设置成ETH_P_ALL
,接收发往目的MAC是本机的所有类型的数据帧,同时还可以接收从本机发出去的所有数据帧。
由于可能收到UDP报文,所以这次使用recvfrom(),收到UDP这种无连接的报文时,可以很方便地进行回复。
为了防止收到过多数据包,设置一个较大缓冲区buffer,每次收到一个MAC帧就放进去,直到缓冲区满,每次从缓冲区中读数据进行解析。
void Getflow()
{
int saddr_size, data_size;
struct sockaddr saddr;
struct in_addr in;
/* 缓冲区 */
unsigned char *buffer = (unsigned char *) malloc(65536);
/* 创建socket,接收所有类型数据包 */
sock_raw = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sock_raw < 0) {
printf("Socket Error: %s\n", strerror(errno));
exit(0);
}
while (1) {//不停收数据直到缓冲区满
saddr_size = sizeof saddr;
/* 接收数据 */
data_size = recvfrom(sock_raw, buffer, 2048, 0, &saddr, (socklen_t *) &saddr_size);
if (data_size < 0) {
printf("Recvfrom error , failed to get packets\n");
exit(0);
}
/* 处理数据包 */
ProcessPacket(buffer, data_size);
}
close(sock_raw);
}
对数据包的处理包括读头部信息、去头部(除传输层)、将传输层报文分类统计。
从缓冲区取出一个数据包后,它是MAC帧格式。帧格式如下:
接收到的数据帧前6字节是目的MAC地址,紧接着6字节是源MAC地址,2字节帧类型用来指示该数据帧所承载的上层网络层协议是IP、ARP或其他。将头部的三个部分依次取出即可。将buffer强制类型转化为 struct ether_header
,对应位的数据可存到相应成员中。MAC Header结构如下:
struct ether_header
{
u_int8_t ether_dhost[ETH_ALEN]; // destination eth addr
u_int8_t ether_shost[ETH_ALEN]; // source ether addr
u_int16_t ether_type; // packet type ID field
} __attribute__ ((__packed__));
解析完MAC header后,将首部剥去,也就是size = size - sizeof(struct ether_header)
,将IP首部暴露在前。IP首部格式如下:
将buffer强制类型转化为struct iphdr
后,对应位的数据可存到相应成员中。IP Header结构如下:
struct iphdr
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int ihl:4;
unsigned int version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN
unsigned int version:4;
unsigned int ihl:4;
#else
# error "Please fix "
#endif
u_int8_t tos;
u_int16_t tot_len;
u_int16_t id;
u_int16_t frag_off;
u_int8_t ttl;
u_int8_t protocol;
u_int16_t check;
u_int32_t saddr;
u_int32_t daddr;
};
要注意取出的IP地址是网络字节序,需要用inet_ntoa()函数转化成点分十进制。这些成员中需要特别注意iph->protocol
,它指示IP报文携带的数据使用的是哪种协议,TCP的协议号为6,UDP的协议号为17。ICMP的协议号为1,IGMP的协议号为2。和前面的思路一样,将buffer强制类型转化,对应位的数据可存到相应成员中,输出即可。
void ProcessPacket(unsigned char *buffer, int size) {
/* 打印MAC HEADER信息 */
print_ether_header(buffer, size);
buffer = buffer + sizeof(struct ether_header);
size = size - sizeof(struct ether_header);
/* 打印IP HEADER信息 */
print_ip_header(buffer, size);
struct iphdr *iph = (struct iphdr *) buffer;
++total;
/* 从IP头部信息得到报文类型 */
switch (iph->protocol)
{
/* ICMP */
case 1:
++icmp;
print_icmp_packet(buffer, size);
break;
/* IGMP */
case 2:
++igmp;
break;
/* TCP */
case 6:
++tcp;
print_tcp_packet(buffer, size);
break;
/* UDP */
case 17:
++udp;
print_udp_packet(buffer, size);
break;
default:
++others;
break;
}
printf("\n\33[;33mTCP : %d UDP : %d ICMP : %d IGMP : %d Others : %d Total : %d\r\n\n\33[0m", tcp, udp, icmp, igmp, others,
total);
}
void print_ether_header(unsigned char *buffer, int size) {
struct ether_header *ethhdr = (struct ether_header *) buffer;
printf("\n");
printf("\33[;34mMAC Header\n\33[0m");
/* 接收到的数据帧ether_dhost前6字节是目的MAC地址*/
printf(" > Destination Mac : %02x:%02x:%02x:%02x:%02x:%02x\n",
ethhdr->ether_dhost[0], ethhdr->ether_dhost[1], ethhdr->ether_dhost[2], ethhdr->ether_dhost[3],
ethhdr->ether_dhost[4], ethhdr->ether_dhost[5]);
/* 紧接着6字节是源MAC地址 */
printf(" > Source Mac : %02x:%02x:%02x:%02x:%02x:%02x\n",
ethhdr->ether_shost[0], ethhdr->ether_shost[1], ethhdr->ether_shost[2], ethhdr->ether_shost[3],
/* 最后两字节是type */
ethhdr->ether_shost[4], ethhdr->ether_shost[5]);
printf(" > Protocol : %d\n", ethhdr->ether_type);
}
void print_ip_header(unsigned char *Buffer, int Size) {
unsigned short iphdrlen;
struct iphdr *iph = (struct iphdr *) Buffer;
iphdrlen = iph->ihl * 4;
memset(&source, 0, sizeof(source));
source.sin_addr.s_addr = iph->saddr;
memset(&dest, 0, sizeof(dest));
dest.sin_addr.s_addr = iph->daddr;
printf("\n");
printf("\33[;34mIP Header\n\33[0m");
printf(" > IP Version : %d\n", (unsigned int) iph->version);
printf(" > IP Header Length : %d DWORDS or %d Bytes\n", (unsigned int) iph->ihl,
((unsigned int) (iph->ihl)) * 4);
printf(" > Type Of Service : %d\n", (unsigned int) iph->tos);
printf(" > IP Total Length : %d Bytes(Size of Packet)\n", ntohs(iph->tot_len));
printf(" > Identification : %d\n", ntohs(iph->id));
printf(" > TTL : %d\n", (unsigned int) iph->ttl);
printf(" > Protocol : %d\n", (unsigned int) iph->protocol);
printf(" > Checksum : %d\n", ntohs(iph->check));
printf(" > Source IP : %s\n", inet_ntoa(source.sin_addr));
printf(" > Destination IP : %s\n", inet_ntoa(dest.sin_addr));
}
void print_tcp_packet(unsigned char *Buffer, int Size) {
unsigned short iphdrlen;
struct iphdr *iph = (struct iphdr *) Buffer;
iphdrlen = iph->ihl * 4;
struct tcphdr *tcph = (struct tcphdr *) (Buffer + iphdrlen);
printf("\n");
printf( "\n\33[;34mTCP Header\n\33[0m");
printf( " > Source Port : %u\n", ntohs(tcph->source));
printf( " > Destination Port : %u\n", ntohs(tcph->dest));
printf( " > Sequence Number : %u\n", ntohl(tcph->seq));
printf( " > Acknowledge Number : %u\n", ntohl(tcph->ack_seq));
printf( " > Header Length : %d DWORDS or %d BYTES\n", (unsigned int) tcph->doff,
(unsigned int) tcph->doff * 4);
printf( " > Urgent Flag : %d\n", (unsigned int) tcph->urg);
printf( " > Acknowledgement Flag : %d\n", (unsigned int) tcph->ack);
printf( " > Push Flag : %d\n", (unsigned int) tcph->psh);
printf( " > Reset Flag : %d\n", (unsigned int) tcph->rst);
printf( " > Synchronise Flag : %d\n", (unsigned int) tcph->syn);
printf( " > Finish Flag : %d\n", (unsigned int) tcph->fin);
printf( " > Window : %d\n", ntohs(tcph->window));
printf( " > hecksum : %d\n", ntohs(tcph->check));
printf( " > Urgent Pointer : %d\n", tcph->urg_ptr);
printf( "\n");
printf( "\33[;31m DATA Dump \33[0m");
printf( "\n\n");
PrintData(Buffer + iphdrlen + tcph->doff * 4, (Size - tcph->doff * 4 - iph->ihl * 4));
}
void print_udp_packet(unsigned char *Buffer, int Size) {
unsigned short iphdrlen;
struct iphdr *iph = (struct iphdr *) Buffer;
iphdrlen = iph->ihl * 4;
struct udphdr *udph = (struct udphdr *) (Buffer + iphdrlen);
printf( "\n\33[;34mUDP Header\n\33[0m");
printf( " > Source Port : %d\n", ntohs(udph->source));
printf( " > Destination Port : %d\n", ntohs(udph->dest));
printf( " > UDP Length : %d\n", ntohs(udph->len));
printf( " > UDP Checksum : %d\n", ntohs(udph->check));
printf( "\n");
printf( "\33[;31m DATA Dump \33[0m");
printf( "\n\n");
PrintData(Buffer + iphdrlen + sizeof udph, (Size - sizeof udph - iph->ihl * 4));
}
void print_icmp_packet(unsigned char *Buffer, int Size) {
unsigned short iphdrlen;
struct iphdr *iph = (struct iphdr *) Buffer;
iphdrlen = iph->ihl * 4;
struct icmphdr *icmph = (struct icmphdr *) (Buffer + iphdrlen);
printf( "\n\33[;34mICMP Header\n\33[0m");
printf( " > Type : %d", (unsigned int) (icmph->type));
if ((unsigned int) (icmph->type) == 11)
printf( " > (TTL Expired)\n");
else if ((unsigned int) (icmph->type) == ICMP_ECHOREPLY)
printf( " > (ICMP Echo Reply)\n");
printf( " > Code : %d\n", (unsigned int) (icmph->code));
printf( " > Checksum : %d\n", ntohs(icmph->checksum));
printf( "\n");
printf( "\33[;31m DATA Dump \33[0m");
printf( "\n\n");
PrintData(Buffer + iphdrlen + sizeof icmph, (Size - sizeof icmph - iph->ihl * 4));
}
如果上层协议为TCP或UDP协议,将数据以16进制与ASCII的两种方式同时打印出来,不可打印字符以 .
代替,每行打印16个字节,以16进制表示,
void PrintData(unsigned char *data, int Size) {
for (i = 0; i < Size; i++) {
if (i != 0 && i % 16 == 0)
{
printf( " ");
for (j = i - 16; j < i; j++) {
if (data[j] >= 32 && data[j] <= 128)
printf( "%c", (unsigned char) data[j]);
//不可打印字符
else printf( ".");
}
printf("\n");
}
if (i % 16 == 0) printf( " ");//字节格式与字符格式以空格分隔
printf( " %02X", (unsigned int) data[i]);
if (i == Size - 1)
{
for (j = 0; j < 15 - i % 16; j++) printf( " ");
printf( " ");
for (j = i - i % 16; j <= i; j++) {
if (data[j] > 31 && data[j] < 128) printf( "%c", (unsigned char) data[j]);
else printf( ".");
}
printf( "\n");
}
}
}
部分结果如图:
在启动程序的同时开启Firefox,就可以接收到大量TCP、UDP等传输层报文,data dump部分显示了
每个报文解析结束会给出总计数据包数。
Linux网络编程:原始套接字的魔力:http://blog.chinaunix.net/uid-23069658-id-3283534.html