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

2021-06-03 23:58:41 字數 4151 閱讀 3806

1.概述

通常在同乙個網段的所有網路介面都有訪問在物理**上傳輸的所有資料的能力,而每個網路介面都還應該有乙個硬體位址,該硬體位址不同於網路中存在的其他網路介面的硬體位址,同時,每個網路至少還要乙個廣播位址。(代表所有的介面位址),在正常情況下,乙個合法的網路介面應該只響應這樣的兩種資料幀: 

1、幀的目標區域具有和本地網路介面相匹配的硬體位址。 

2、幀的目標區域具有"廣播位址"。 

在接受到上面兩種情況的資料報時,nc通過cpu產生乙個硬體中斷,該中斷能引起作業系統注意,然後將幀中所包含的資料傳送給系統進一步處理。 

而sniffer就是一種能將本地nc狀態設成(promiscuous)狀態的軟體,當nc處於這種"混雜"方式時,該nc具備"廣播位址",它對所有遭遇到的每乙個幀都產生乙個硬體中斷以便提醒作業系統處理流經該物理**上的每乙個報文包。(絕大多數的nc具備置成 promiscuous方式的能力) 

可見,sniffer工作在網路環境中的底層,它會攔截所有的正在網路上傳送的資料,並且通過相應的軟體處理,可以實時分析這些資料的內容,進而分析所處的網路狀態和整體布局。值得注意的是:sniffer是極其安靜的,它是一種消極的安全攻擊。

2. sniffer的簡單實現

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include /* 需要裡面的 glibc 版本號 */

#if __glibc__ >= 2 && __glibc_minor >= 1

#include

#include /* 鏈路層(l2)協議 */

#else

#include

#include

#include /* 鏈路層協議 */

#endif

#include

/**接收發往本地的arp,ip,rarp所有資料幀,並將網絡卡設定為混雜模式,用於接收非發往本地的mac幀

乙太網的頭部結構:

struct ether_header

__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)

ifr.ifr_flags|=iff_promisc;

//在標誌位加入混雜模式

ret=ioctl(fd,siocsifflags,&ifr);

if(ret<0)

}//解析tcp包

static void parsetcp(struct ip* iphdr)

//    printf("\n");

//   }

}//解析udp包

static void parseudp(struct ip*iphdr)

//    printf("\n");

}//解析icmp包

static void parseicmp(struct ip*iphdr)else if(icmph->icmp_type==icmp_echoreply)

}//解析ip包

static void parseip(struct ether_header*ethhead)

printf("\n");

for(i=0;iether_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))else if(!strncmp(pro->p_name,"udp",3))else if(!strncmp(pro->p_name,"icmp",4))

}//解析arp包

static void parsearp(struct ether_header *ethhead)

printf("\n");

for(i=0;iether_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)else if(ntohs(ea->arp_op)==2)

printf("\n");}}

int main(int agrc,char*argv)

//繫結位址結構,用於接收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)

//將網絡卡設定為"混雜模式,用於偵聽資料"

set_promisc(recvfd);

//偵聽資料幀

for(;;)

//如果是arp包

if(ntohs(ethhead->ether_type)==0x0806)

}   }}

執行結果:

總結:本文通過獲得mac幀,然後得到ip,tcp(udp)報文資訊,對於應用層的解析還沒有實現.

Linux 原始套接字

原始套接字可以用來自行組裝ip資料報,然後將資料報傳送到其他終端。必須在管理員許可權下才能使用原始套接字。總結自 unix網路程式設計 卷1 套接字聯網api 1 原始套接字的建立 int sockfd socket af inet,sock raw,ipproto 後面的 可以是icmp,udp,...

linux原始套接字

通常情況下程式設計師接所接觸到的套接字 socket 為兩類 1 流式套接字 sock stream 一種面向連線的 socket,針對於面向連線的tcp 服務應用 2 資料報式套接字 sock dgram 一種無連線的 socket,對應於無連線的 udp 服務應用。從使用者的角度來看,sock ...

原始套接字

資料出處 實際上,我們常用的網路程式設計都是在應用層的報文的收發操作,也就是大多數程式設計師接觸到的流式套接字 sock stream 和資料報式套接字 sock dgram 而這些資料報都是由系統提供的協議棧實現,使用者只需要填充應用層報文即可,由系統完成底層報文頭的填充並傳送。然而在某些情況下需...