1.概述
通常在同一个网段的所有网络接口都有访问在物理媒体上传输的所有数据的能力,而每个网络接口都还应该有一个硬件地址,该硬件地址不同于网络中存在的其他网络接口的硬件地址,同时,每个网络至少还要一个广播地址。(代表所有的接口地址),在正常情况下,一个合法的网络接口应该只响应这样的两种数据帧:
1、帧的目标区域具有和本地网络接口相匹配的硬件地址。
2、帧的目标区域具有"广播地址"。
在接受到上面两种情况的数据包时,nc通过cpu产生一个硬件中断,该中断能引起操作系统注意,然后将帧中所包含的数据传送给系统进一步处理。
而sniffer就是一种能将本地nc状态设成(promiscuous)状态的软件,当nc处于这种"混杂"方式时,该nc具备"广播地址",它对所有遭遇到的每一个帧都产生一个硬件中断以便提醒操作系统处理流经该物理媒体上的每一个报文包。(绝大多数的nc具备置成 promiscuous方式的能力)
可见,sniffer工作在网络环境中的底层,它会拦截所有的正在网络上传送的数据,并且通过相应的软件处理,可以实时分析这些数据的内容,进而分析所处的网络状态和整体布局。值得注意的是:sniffer是极其安静的,它是一种消极的安全攻击。
2. sniffer的简单实现
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>
#include <sys/types.h>
#include <asm/types.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>
#include <features.h> /* 需要里面的 glibc 版本号 */
#if __GLIBC__ >= 2 && __GLIBC_MINOR >= 1
#include <netpacket/packet.h>
#include <net/ethernet.h> /* 链路层(L2)协议 */
#else
#include <asm/types.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h> /* 链路层协议 */
#endif
#include <netinet/if_ether.h>
/**
接收发往本地的arp,ip,rarp所有数据帧,并将网卡设置为混杂模式,用于接收非发往本地的MAC帧
以太网的头部结构:
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__));
整个以太网的头部包括: 目的地址(6字节),源地址(6字节),类型(2字节),帧内数据(46-1500个字节),CRC校验和(4字节)
#define ETH_ALEN 6 //以太网地址的长度
#define ETH_HALEN 14 //以太网头部的总长度 (6+6+2)
#define ETH_ZLEN 60 //不含CRC校验数据的数据最小长度(46+14)
#define ETH_DATA_LEN 1500 //帧内数据的最大长度
#define ETH_FRAME_LEN 1514//不含CRC最大以太网长度(1500+14)
**/
static char buffer[ETH_FRAME_LEN];
static void set_promisc(int fd){
char*ethname="eth0";
struct ifreq ifr;//网络接口定义
int ret;
int i=0;
strcpy(ifr.ifr_name,ethname);
//获得标志值
ret=ioctl(fd,SIOCGIFFLAGS,&ifr);
if(ret<0){
perror("ioctl get");
return;
}
ifr.ifr_flags|=IFF_PROMISC;
//在标志位加入混杂模式
ret=ioctl(fd,SIOCSIFFLAGS,&ifr);
if(ret<0){
perror("ioctl set");
return;
}
}
//解析TCP包
static void parseTcp(struct ip* iphdr){//解析TCP包
struct tcphdr *tcphead;//tcp包头
tcphead=(struct tcphdr*)(buffer+ETH_HLEN+iphdr->ip_hl*4);
//打印TCP的源端口号与目的端口号
printf("source port:%d\n",ntohs(tcphead->source));
printf("dst port:%d\n",ntohs(tcphead->dest));
//应用层长度
int i=0;
int applen=ntohs(iphdr->ip_len)-iphdr->ip_hl*4-20;//IP报文的长度减去IP首部长度再减去tcp首部长度
// if(ntohs(tcphead->dest)==80||ntohs(tcphead->source)==80){//如果是http服务
// printf("-----------HTTP MESSAGE-----------\n");
// for(i=0;i<applen;i++){
// printf("%0x",buffer[ETH_HLEN+iphdr->ip_hl*4+20+i]);
// }
// printf("\n");
// }
}
//解析UDP包
static void parseUdp(struct ip*iphdr){//解析UDP包
struct udphdr*udph;
int datalen;
int i=0;
int databuffer[1024];
bzero(databuffer,1024);
udph=(struct udphdr*)(buffer+ETH_HLEN+iphdr->ip_hl*4);//buffer是MAC头部地址,buffer+14是IP头部地址,buffer+14+ip_hl就是UDP首部地址
//UDP首部包含源端口,目的端口,数据长度和校验和
//打印出UDP的源端口
printf("source port:%d\n",ntohs(udph->source));
printf("dst port:%d\n",ntohs(udph->dest));
printf("udp data len:%d 字节\n",ntohs(udph->len));
//打印出数据
datalen=ntohs(udph->len)-8;//UDP包头8个字节
//解析http报文,得到内容
// printf("-----------\n");
// for(i=0;i<datalen+10;i++){
// printf("%c ",buffer[ETH_HLEN+iphdr->ip_hl*4+8+i]);
//
// }
// printf("\n");
}
//解析ICMP包
static void parseIcmp(struct ip*iphdr){//解析ICMP包
struct icmp *icmph;
icmph=(struct icmp*)(buffer+ETH_HLEN+iphdr->ip_hl*4);
//打印ICMP报文的类型
if(icmph->icmp_type==ICMP_ECHO){
printf("ICMP request\n");
}else if(icmph->icmp_type==ICMP_ECHOREPLY){
printf("ICMP response\n");
}
}
//解析IP包
static void parseIP(struct ether_header*ethhead){//如果是IP包
//从以太网的帧得到源MAC与目的MAC
printf("\n");
printf("-------IP--------\n");
struct ip *p_iphdr;
struct protoent *pro;//协议信息指针
int i=0;
printf("目的MAC地址:\n");
for(i=0;i<ETH_ALEN;i++){
printf("%02x-",ethhead->ether_dhost[i]);
}
printf("\n");
printf("源MAC地址:\n");
for(i=0;i<ETH_ALEN;i++){
printf("%02x-",ethhead->ether_shost[i]);
}
printf("\n");
//得到相应的IP包的头部
p_iphdr=(struct ip*)(buffer+ETH_HLEN);//加14个字节
//计算IP包头的长度
printf("IP header len:%d 字节\n",p_iphdr->ip_hl*4);
//IP包的总长度
printf("IP total len:%d 字节\n",ntohs(p_iphdr->ip_len));
//打印出IP版本号
printf("IP version:%0x\n",p_iphdr->ip_v);
//打印出协议类型
printf("protocol type:%d\n",p_iphdr->ip_p);//协议号为10进制
pro=getprotobynumber(p_iphdr->ip_p);
printf("protocol name:%s\n",pro->p_name);
//打印源IP与目的IP
char ip[16];
bzero(&ip,0);
struct in_addr ad;
ad=p_iphdr->ip_src;
inet_ntop(AF_INET,&ad,ip,16);
printf("src ip:%s\n",ip);
printf("dst ip:%s\n",inet_ntoa(p_iphdr->ip_dst));
//根据协议所属于的类型,分别对TCP与UDP协议进行处理
if(!strncmp(pro->p_name,"tcp",3)){
parseTcp(p_iphdr);//处理TCP包
}else if(!strncmp(pro->p_name,"udp",3)){
parseUdp(p_iphdr);//处理UDP包
}else if(!strncmp(pro->p_name,"icmp",4)){
parseIcmp(p_iphdr);//处理ICMP包
}
}
//解析ARP包
static void parseARP(struct ether_header *ethhead){//解析ARP
//以太网信息
printf("\n");
printf("-------ARP---------\n");
int i=0;
printf("目的MAC地址:\n");
for(i=0;i<ETH_ALEN;i++){
printf("%02x-",ethhead->ether_dhost[i]);
}
printf("\n");
printf("源MAC地址:\n");
for(i=0;i<ETH_ALEN;i++){
printf("%02x-",ethhead->ether_shost[i]);
}
printf("\n");
//ARP包结构
struct ether_arp *ea;
ea=(struct ether_arp*)(buffer+ETH_HLEN);//ARP
//判断是ARP请求包还是ARP响应包
if(ntohs(ea->arp_op)==1){//ARP请求包
printf("ARP REQUEST\n");
//打印出源IP地址与目的IP地址
printf("发送方的IP:");
char ip[16];
inet_ntop(AF_INET,ea->arp_spa,ip,16);//arp_spa是一个unsigned char数组
printf("%s\n",ip);
printf("接收方的IP地址为:");
inet_ntop(AF_INET,ea->arp_tpa,ip,16);
printf("%s\n",ip);
}else if(ntohs(ea->arp_op)==2){
printf("ARP RESPONSE\n");
//打印出源IP地址与目的IP地址
printf("发送方的IP:");
char ip[16];
inet_ntop(AF_INET,ea->arp_spa,ip,16);//arp_spa是一个unsigned char数组
printf("%s\n",ip);
printf("接收方的IP地址为:");
inet_ntop(AF_INET,ea->arp_tpa,ip,16);
printf("%s\n",ip);
printf("发送方的MAC:");
for(i=0;i<ETH_ALEN;i++){
printf("%02x-",ea->arp_sha[i]);
}
printf("\n");
}
}
int main(int agrc,char*argv[]){
int recvfd;
struct ifreq ifrq;
int ret;
struct ether_header *ethhead;//以太网包头
struct sockaddr_ll local;//与设备无关的物理地址
struct sockaddr_ll from;
int size;//接收到帧的大小
int len=sizeof(from);
//建立原始套接字
recvfd=socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL));//第一个参数表示接收的是MAC帧,第二个参数采用原始套接字,第三个参数表示接收发往本地或非发往本地的数据帧
if(recvfd<0){
perror("ETH_P_ALL:");
return -1;
}
//绑定地址结构,用于接收MAC帧
memset(&local,0,sizeof(local));
local.sll_family=PF_PACKET;//帧类型
strcpy(ifrq.ifr_name,"eth0");
ioctl(recvfd,SIOCGIFINDEX,&ifrq);//得到接口eth0的编号
local.sll_ifindex=ifrq.ifr_ifindex;
local.sll_protocol=htons(ETH_P_ALL);//协议号
//将物理地址绑定到原始套接字上
ret=bind(recvfd,(struct sockaddr*)&local,sizeof(local));
if(ret<0){
perror("bind error");
return -1;
}
//将网卡设置为"混杂模式,用于侦听数据"
set_promisc(recvfd);
//侦听数据帧
for(;;){
memset(&from,0,sizeof(from));
bzero(buffer,sizeof(buffer));//将接收数据的缓冲区清0
size=recvfrom(recvfd,buffer,ETH_FRAME_LEN,0,(struct sockaddr*)&from,&len);
//printf("size=%d\n",size);
if(size>0){
printf("\n");
printf("-----------------------\n");
printf("以太网报文总长度:%d\n",size);
printf("------------------------\n");
//打印出包的类型,是IP包还是ICMP,ARP,RARP
ethhead=(struct ether_header*)buffer;
// printf("%0x\n",ntohs(ethhead->ether_type));
//判断包的类型,获取包的信息 0x0800为IP协议,0x0806为ARP协议,0x8035为RARP协议
if(ntohs(ethhead->ether_type)==0x0800){//IP包
parseIP(ethhead);
}
//如果是ARP包
if(ntohs(ethhead->ether_type)==0x0806){//解析ARP包
parseARP(ethhead);
}
}
}
}
运行结果:
总结:本文通过获得MAC帧,然后得到IP,TCP(UDP)报文信息,对于应用层的解析还没有实现.