这个程序来源于我一个专业选修课程的实验之一,用来检测一个内网网段中,网卡开了混合模式的主机。要做到这个功能,有以下几种方法:
(一)识别恶意主机的原理
使用“广播地址”进行判断
原理是:正常情况下,网卡检测接收到的数据包是不是广播数据包,要看收到的数据帧的目的MAC地址是否等于ff.ff.ff.ff.ff.ff,如果是则认为是广播地址,但当网卡的工作模式为“混杂模式”时,网卡检测是不是广播包只看收到的数据帧的目的MAC地址的第一个八位组值,是0xf则认为是广播地址。利用这点细微差别就可以检测出Sniffer。
测试时,测试主机首先向被测试的局域网内的所有设备发送伪造的ARP请求包,伪造目标主机MAC地址,比如:ff.00.00.00.00.00或ff.ff.00.00.00.00等。如果接收到包的目标主机没有处于混杂模式,它将不会回复,但如果处于混杂模式,它将会回应测试主机的ARP请求,通过监视向测试主机发送的回应信息就可以知道哪个目标主机处于混杂模式。
由于某些网卡在正常工作时,识别广播包时仅仅识别数据包中的目的MAC地址的的第一个字段是否是“0xf”,所以这类网卡不适合用此方法检测,对于这种情况,可以通过修改伪造的目标主机的MAC地址进行检测。
2.使用ping的响应时间进行判断
测试时,测试主机首先利用ICMP请求及响应计算出目标机器的平均响应时间。在得到这个数据后,测试主机再次向本地网络发送大量的伪造数据包,与此同时再次发送测试数据包以确定目标主机的平均响应时间的变化值。
非混杂模式的机器的响应时间变化量会很小,而混杂模式的机器的响应时间变化量则通常会有1—4个数量级。这种测试已被证明是最有效的,它能够发现网络中处于混杂模式的机器,而不管其操作系统是什么,但缺点是这个测试会在很短的时间内产生巨大的通讯流量。
最终我选择了第一种方法,因为理解起来也比较简单。
(二)ARP协议基础
编程识别的前提是要能够发出自己构造的ARP报文,下面是ARP报文的格式,编程之前,首先要熟悉这个结构:
一个ARP报文是由ARP首部和以太网首部共同组成的。 以太帧首部中2字节的帧类型字段指定了其上层所承载的具体协议,常见的有0x0800表示是IP报文、0x0806表示RARP协议、0x0806即为我们将要讨论的ARP协议。
硬件类型: 1表示以太网。
协议类型: 0x0800表示IP地址(ARP在TCP/IP中位于网络层)。和以太头部中帧类型字段相同。
硬件地址长度和协议地址长度:对于以太网中的ARP协议而言,分别为6和4;
操作码:1表示ARP请求;2表示ARP应答;3表示RARP请求;4表示RARP应答。
我们这里只讨论硬件地址为以太网地址、协议地址为IP地址的情形,所以剩下四个字段就分别表示发送方的MAC和IP地址、接收方的MAC和IP地址了。
那么有了协议的基础,我们该如何发出链路层的包呢?我们知道,系统自带的socket系列API是位于传输层的,并且报文字段的填充是由网络驱动程序自动完成,那么如何自行发出位于传输层之下的包呢?这就要用到原始套接字。
(三)编程基础
为了实现直接从链路层收发数据帧,我们要用到原始套接字的如下形式:
socket(PF_PACKET, type, protocol)
1、其中type字段可取SOCK_RAW或SOCK_DGRAM。它们两个都使用一种与设备无关的标准物理层地址结构struct sockaddr_ll{},但具体操作的报文格式不同:
SOCK_RAW:直接向网络硬件驱动程序发送(或从网络硬件驱动程序接收)没有任何处理的完整数据报文(包括物理帧的帧头),这就要求我们必须了解对应设备的物理帧帧头结构,才能正确地装载和分析报文。也就是说我们用这种套接字从网卡驱动上收上来的报文包含了MAC头部,如果我们要用这种形式的套接字直接向网卡发送数据帧,那么我们必须自己组装我们MAC头部。这正符合我们的需求。
SOCK_DGRAM:这种类型的套接字对于收到的数据报文的物理帧帧头会被系统自动去掉,然后再将其往协议栈上层传递;同样地,在发送时数据时,系统将会根据sockaddr_ll结构中的目的地址信息为数据报文添加一个合适的MAC帧头。
2、protocol字段,常见的,一般情况下该字段取ETH_P_IP,ETH_P_ARP,ETH_P_RARP或ETH_P_ALL,当然链路层协议很多,肯定不止我们说的这几个,但我们一般只关心这几个就够我们用了。
另外,由于要自己构造链路层的报文,那么在传输层我们使用的struct sockaddr_in这种地址显然不能再用了。而是使用sockaddr_in{}:
struct sockaddr_ll{
unsigned short sll_family; /* 总是 AF_PACKET */
unsigned short sll_protocol; /* 物理层的协议 */
int sll_ifindex; /* 接口号 */
unsigned short sll_hatype; /* 报头类型 */
unsigned char sll_pkttype; /* 分组类型 */
unsigned char sll_halen; /* 地址长度 */
unsigned char sll_addr[8]; /* 物理层地址 */
};
sll_ifindex需要我们去指定一个可用的网卡设备。
然后我们就可以按照我们自己的需要去填充ARP报文了,比如,将以太网首部中的目的MAC填成一个“伪广播地址”:0xff:0xff:0x00:0x00:0x00:0x00,然后使用socket把这个包发出去就行了。验证的时候,只需要打开wireshark或者tcpdump或者其他的抓包工具即可,如果发现目标IP确实发来一个回应报文,那么就能确定这个设备的网卡可能开了混合模式,为什么说是可能呢?因为某些特殊网卡在不开混合模式时,也是这么判断广播地址的。
(四)利用Libpcap抓包自动识别
本来可以不做到这一步的,但是我想了想,觉得手动+脑力判断似乎太挫了,所以我觉得自己写个抓包的代码段实现自动识别。
在Linux上进行抓包通常使用Libpcap,与Windows平台上的Winpcap一样。都是用来抓取网络数据包的。Linux平台上在gcc编译时,要加上相应的编译项,否则会报错。
由于抓包为永真循环,如果把这段程序设置在主程序中,会产生阻塞而无法进行下一步工作,所以我就另外开启一个子线程专门用来抓包,主线程中设置这个子线程的超时时间即可。
用到的一些重要函数:
descr =pcap_open_live(message->interface,MAXBYTES2CAPTURE,1,512,errbuf) ;
descr是一个pcap_t类型的指针,用来描述网络设备,我这里把它理解为一种文件描述符。
上面的pcap_open_live就是用来打开一个可用的网卡设备,返回一个描述符。
由于我们使用libpcap进行抓包时,会把所以类型的包全抓上来,但是我们只需要捕获ARP,这时候就要使用libpcap中的BPF程序设置过滤语法了。
//获取网卡的IP和掩码
pcap_lookupnet(message->interface,&netaddr,&mask,errbuf);
//BPF过滤程序编译
pcap_compile(descr,&filter,"arp",1,mask);
pcap_setfilter(descr,&filter) ;
抓包时就使用packet = pcap_next(descr,&pkthdr)即可,packet是一个指向具体信息的指针,用packet指针做偏移就能拿到我们想要的部分。Pcap_next一次只能抓一个包,只需要做个永真循环就能持续抓包。在这个永真循环中我们就可以控制,一旦发现目标就exit出去,当然如果长时间没收到目标来信,那么就默认目标主机是“清白”的。实现这个,只需要在主线程中去设置子线程的超时时间就行了:
//等待子线程
struct timespec joinDelay ;
clock_gettime(CLOCK_REALTIME,&joinDelay) ;
joinDelay.tv_sec += 5 ;
pthread_timedjoin_np(subthread, NULL,&joinDelay);
(五)运行测试
测试开了混杂模式的主机:
测试正常主机:
(六)代码分享
anti_arp_test.c代码如下,gcc编译命令:
gcc anti_arp_test.c -lpthread -lpcap -lrt-o anti_arp_test
/*
* Author:Chongrui
* Compile:gcc anti_arp_test.c -lpthread -lpcap -lrt -o anti_arp_test
* Description:You can use this program to detect if a host in LAN opening sniffer
* */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ARP_REQUEST 1 //arp请求
#define ARP_REPLY 2 //arp回应
#define MAXBYTES2CAPTURE 2048
//线程的回调函数
void* thread_function(void*args);
//以太网头部
typedef struct ehhdr
{
unsigned char eh_dst[6]; /* destination ethernet addrress */
unsigned char eh_src[6]; /* source ethernet addresss */
unsigned short eh_type; /* ethernet pachet type */
}EHHDR, *PEHHDR;
//ARP报文格式(14+28=42)
struct frame_arp{
struct ehhdr fh; //以太帧首部
struct arphdr ah; //arp首部
unsigned char src_mac[6]; //源mac
unsigned char src_ip[4]; //源IP
unsigned char dst_mac[6]; //目的MAC
unsigned char dst_ip[4]; //目的IP
};
//arp报文
typedef struct arp_hdr{
u_int16_t htype; //hardware type
u_int16_t ptype; //protocol type
u_char hlen; //hardware address length
u_char plen; //protocol address length
u_int16_t oper; //operation code
u_char sha[6]; //sender hardware address
u_char spa[4]; //sender ip address
u_char tha[6]; //target hardware address
u_char tpa[4]; //target ip address
}arphdr_t;
//主线程与子线程传递参数
struct infos{
char *ip_str ;
char *interface ;
} ;
//显示出错信息
void show_error(const char* func_name){
printf("%s has an error:%s\n",func_name,strerror(errno)) ;
}
//输出欢迎信息
void welcome(char*ip){
printf("Anti Arp Test!\nPowered by Chongrui\nE-mail:[email protected]") ;
printf("Target:%s\n",ip) ;
printf("Net Device:eth2\n") ;
}
int main(int args,char* argv[]){
if(args < 3){
printf("Usage:anti_arp_test devName targetIP\n") ;
exit(0) ;
}
//开启子线程
struct infos infos ;
infos.interface = argv[1] ;
infos.ip_str = argv[2] ;
welcome(infos.ip_str) ;
pthread_t subthread ;
pthread_create(&subthread,NULL,thread_function,(void*)&infos) ;
sleep(2) ;
int i = 0;
//网络接口的IP和掩码
bpf_u_int32 netaddr = 0,mask = 0;
//BPF过滤程序
struct bpf_program filter ;
//错误信息保存
char errbuf[PCAP_ERRBUF_SIZE] ;
//设备指针
pcap_t *descr = NULL ;
//抓取的包信息
struct pcap_pkthdr pkthdr ;
//报信息的指针
const unsigned char *packet = NULL ;
//ARP首部
arphdr_t *arpheader = NULL ;
memset(errbuf,0,PCAP_ERRBUF_SIZE) ;
struct in_addr targetIP ;
int sockfd ;
int ret = 0;
struct sockaddr_ll toaddr ; //物理层地址格式 不能再使用sockaddr_in
struct ifreq req ;
struct frame_arp arp; //arp包
//设置packet类型的sockfd,实现在链路层发包
sockfd = socket(AF_PACKET,SOCK_RAW,ETH_P_ARP) ;
if(sockfd == -1){
show_error("socket") ;
}
//初始化sockaddr_ll 物理层数据包寻址地址结构
memset(&toaddr,0,sizeof(toaddr)) ;
memset(&req,0,sizeof(req)) ;
toaddr.sll_family = AF_PACKET ;
//找到eth2网卡
strcpy(req.ifr_name,argv[1]) ;
ret = ioctl(sockfd,SIOCGIFINDEX,&req) ;
if(ret == -1){
show_error("ioctl") ;
}
toaddr.sll_ifindex = req.ifr_ifindex ; //填网卡
toaddr.sll_protocol = htons(ETH_P_ARP) ; //填协议
//填充arp报文
memset(&arp,0,sizeof(arp)) ;
//假的广播地址 前1个字节为ff
unsigned char fake_mac[6] = {0xff,0xff,0x00,0x00,0x00,0x00} ;
//源MAC地址 随便填
unsigned char src_mac[6] = {0x00,0x0c,0x29,0x92,0x64,0x10} ;
//源IP 随便填
unsigned char src_ip[4] = {10,10,10,132} ;
//目的IP
//unsigned char dst_ip[4] = {10,10,10,128} ;
//unsigned char dst_ip[4] = {p[0],p[1],p[2],p[3]} ;
//目的MAC 随便填
unsigned char dst_mac[6] = {0x00,0x00,0x00,0x00} ;
//填充以太网帧
arp.fh.eh_type = htons(0x0806) ; //arp协议
memcpy(arp.fh.eh_dst,fake_mac,6) ;
memcpy(arp.fh.eh_src,src_mac,6) ;
//填充arp头部
arp.ah.ar_hln = sizeof(src_mac) ; //mac长度
arp.ah.ar_hrd = htons(ARPHRD_ETHER) ; //硬件类型:以太网
arp.ah.ar_op = htons(ARPOP_REQUEST) ; //arp类型 请求报文
arp.ah.ar_pln = sizeof(src_ip) ; //ip长度
arp.ah.ar_pro = htons(ETH_P_IP) ; //协议:IP协议
//填充剩余数据
inet_pton(AF_INET,argv[2],&targetIP); //填充目标IP地址
memcpy(arp.dst_ip,&targetIP,4) ; //设置用户输入的targetIP
memcpy(arp.dst_mac,dst_mac,6) ;
memcpy(arp.src_ip,src_ip,4) ;
memcpy(arp.src_mac,src_mac,6) ;
//发送
ret = sendto(sockfd,&arp,sizeof(arp),0,(struct sockaddr*)&toaddr,sizeof(toaddr)) ;
if(ret == -1){
show_error("sendto") ;
}
close(sockfd) ;
printf("Send Data End!\n") ;
//等待子线程
struct timespec joinDelay ;
clock_gettime(CLOCK_REALTIME, &joinDelay) ;
joinDelay.tv_sec += 5 ;
pthread_timedjoin_np(subthread, NULL, &joinDelay);
printf("Timeout...\nTarget host:%s may be a good guy....\n",argv[2]) ;
return 0 ;
}
/**
* 线程的回调函数
*/
void* thread_function(void*args){
printf("Sub thread working...\n") ;
unsigned char ip_dec[4] = {0,0,0,0} ;
struct sockaddr_in tmpip ;
char capture_ip[256] = {0} ;
int i = 0;
struct infos *message = (struct infos*)args ;
//网络接口的IP和掩码
bpf_u_int32 netaddr = 0,mask = 0;
//BPF过滤程序
struct bpf_program filter ;
//错误信息保存
char errbuf[PCAP_ERRBUF_SIZE] ;
//设备指针
pcap_t *descr = NULL ;
//抓取的包信息
struct pcap_pkthdr pkthdr ;
//报信息的指针
const unsigned char *packet = NULL ;
//ARP首部
arphdr_t *arpheader = NULL ;
memset(errbuf,0,PCAP_ERRBUF_SIZE) ;
//获取可用网卡
descr = pcap_open_live(message->interface,MAXBYTES2CAPTURE,1,512,errbuf) ;
//获取网卡的IP和掩码
pcap_lookupnet(message->interface,&netaddr,&mask,errbuf) ;
//BPF过滤程序编译
pcap_compile(descr,&filter,"arp",1,mask) ;
pcap_setfilter(descr,&filter) ;
while(1){
packet = pcap_next(descr,&pkthdr) ;
if(packet == NULL){
continue;
}
//以太网帧为14bytes
arpheader = (struct arp_hdr *)(packet + 14) ;
if(ntohs(arpheader->htype)==1 && ntohs(arpheader->ptype)==0x0800){
printf("Captrue One Packet...Analysing...") ;
for(i=0;i<4;i++){
ip_dec[i] = arpheader->spa[i] ;
}
//判断源IP是否是检测IP
memcpy(&tmpip,ip_dec,4) ;
inet_ntop(AF_INET,&tmpip,capture_ip,sizeof(capture_ip)) ;
if(!strcmp(capture_ip,message->ip_str)){
printf("Target host:%s==========>A very bad guy!!!!!!!\n",message->ip_str) ;
exit(1) ;
}
printf("\n") ;
}
}
}