计算机网络安全之网络嗅探器

作者: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

程序实现说明及源码分析

程序框架

计算机网络安全之网络嗅探器_第1张图片

如框图所示,程序可以获取到MAC帧、IP包以及传输层数据包。传输层的数据包中,只实现了对TCP, UPD, ICMP, IGMP报文的解析。

首先读取本主机和网卡的信息,在数据链路层接收经过本网卡的所有类型的数据包。在解析完MAC帧之后,将头部拆掉,并将payload继续解析,读出IP头部后,记下protocol类型,将头部拆掉,依据protocol 的类型进行分类解析。待三层都解析完毕后,重头解析下一个数据包,重复以上过程。

global.h

引用了程序可能用到的头文件,定义了数据链路层帧长度变量、必要的结构体、本机主机、网卡信息等内容。

#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帧格式。帧格式如下:
计算机网络安全之网络嗅探器_第2张图片
接收到的数据帧前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首部格式如下:
计算机网络安全之网络嗅探器_第3张图片
将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部分显示了

每个报文解析结束会给出总计数据包数。

计算机网络安全之网络嗅探器_第4张图片
计算机网络安全之网络嗅探器_第5张图片
计算机网络安全之网络嗅探器_第6张图片
计算机网络安全之网络嗅探器_第7张图片

参考资料

Linux网络编程:原始套接字的魔力:http://blog.chinaunix.net/uid-23069658-id-3283534.html

你可能感兴趣的:(计算机网络安全之网络嗅探器)