1. 程式人生 > >Linux原始套接字之sniffer部分實現

Linux原始套接字之sniffer部分實現

1.概述

 通常在同一個網段的所有網路介面都有訪問在物理媒體上傳輸的所有資料的能力,而每個網路介面都還應該有一個硬體地址,該硬體地址不同於網路中存在的其他網路介面的硬體地址,同時,每個網路至少還要一個廣播地址。(代表所有的介面地址),在正常情況下,一個合法的網路介面應該只響應這樣的兩種資料幀: 
  1、幀的目標區域具有和本地網路介面相匹配的硬體地址。 
  2、幀的目標區域具有"廣播地址"。 
  在接受到上面兩種情況的資料包時,nc通過cpu產生一個硬體中斷,該中斷能引起作業系統注意,然後將幀中所包含的資料傳送給系統進一步處理。 
  而sniffer就是一種能將本地nc狀態設成(promiscuous)狀態的軟體,當nc處於這種"混雜"方式時,該nc具備"廣播地址",它對所有遭遇到的每一個幀都產生一個硬體中斷以便提醒作業系統處理流經該物理媒體上的每一個報文包。(絕大多數的nc具備置成 promiscuous方式的能力) 
  可見,sniffer工作在網路環境中的底層,它會攔截所有的正在網路上傳送的資料,並且通過相應的軟體處理,可以實時分析這些資料的內容,進而分析所處的網路狀態和整體佈局。值得注意的是:sniffer是極其安靜的,它是一種消極的安全攻擊。

2. sniffer的簡單實現

#include <stdio.h>
#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)報文資訊,對於應用層的解析還沒有實現.