初探數據包分析程序設計 zz

初探數據包分析程序設計
Author :maigan
From : 第八軍團-信息安全小組(www.cnhacking.com www.juntuan.org)
Mail : [email protected]
Warning: 轉載本文請註明作者及出處


整天在網上轉,也看到許多不錯的文章,但我發現大多文章要麼只停留在理論上,要麼就是太高深。對問題詳細分析介紹的很少。今天,我就想以數據包分析程序爲主題和大家討論一下網絡編程的的相關問題,我也是新手,有不到之處,還望大家不吝指正。
通過對數據包的分析,我們可以判斷通信雙方的操作系統、網絡信息流量、經過的路由、數據包的大小,以及數據包的內容等等。對於喜歡網絡安全的人來說,掌握這方面的知識是相當重要的。現在的網絡通信中,大部分數據都沒有加密,我們可以輕易地從數據包中提取賬號、密碼之類我們關心的數據.大家在看本文時如有困難,可先讀一讀計算機網絡及C程序設計還有協議分析方面的書。下面我將分TCP/IP族協議結構、程序部分函數及數據結構說明、案例程序剖析三個部分與大家共同學習數據包分析程序的設計方法。

一、TCP/IP族協議結構
在說TCP/IP之前,先讓我們來認識一下以太網,因爲我們現在接觸最多的就是以太網,並且研究數據包又是離不開以太網的幀的。在以太網中,數據是以被稱爲幀的數據結構本爲單位進行交換的。以太網中常用的協議是CSMA/CD(carrier sense multiple access with collision detection)即載波監聽多點接入/碰撞檢測,在這裏,我們關注的是幀的格式。常用的以太網幀的格式有兩種標準,一種是DIX Ethernet V2標準,另一種是IEEE的802.3標準。現在最常用的MAC幀是V2格式,這也是我們所要研究的格式,至於802.3幀我們不再討論。以太網V2幀的格式如下:
(插入8字節)目的地址(6字節)->源地址(6字節)->類型(2字節)->數據(46-1500)->FCS(4字節)
以太網的地址由48位的二進制來表示,也就是我們常說的MAC地址及硬件地址。在MAC幀前還有8字節的前同步碼和幀的開始定界符,之後纔是地址等報頭信息。接收端和發送端的地址之後是2字節的類型字段,存放幀中傳送數據的上層協議類型,RFC1700號文檔規定了這些,如下:
ETHER TYPES(十六進制) PROTOCOlS
800 IP
806 ARP
8035 Revese ARP
809B Apple Talk
8137/8138 Novel
814c SNMP
幀的數據部分長度爲46-1500字節,當小於46時,會在後面加入一個整數字節的填充字段。FCS(Frame Check Sequence)在以太網常用循環冗佘校檢(CRC:cyclic redandancy check)。
IP協議爲網絡層協議,網絡層的數據結構體被稱爲IP數據報。IP地址及域名這兩個概念我們就不說了,下面我們來看一看IP數據報的結構:
成員名 字節數 說明
version 1/2 IP的版本,現在爲IPV4
IHL(報送長度) 1/2 最常用爲20,取5-15之前的值,最大60字節
Type Of Service 1 優先和可靠性服務要求的數值
Total Lenth 2 IP數據報的全長
Identification 2 識別IP數據報的編號
Flags 3/8 1位爲0表示有碎塊,2位爲0表示是最後的碎塊,爲1表示接收中。
Fragment Offset 13/8 分片在原分組中的位置
TTL 1 數據報壽命,建議值爲32秒
Protocol 1 上層協議
Headerchecksum 2 報頭檢驗碼
Source Address 4 發送端IP地址
Destination Address 4 接收端IP地址
Options And Padding 4 選項及填充位
其中協議字段的值對我們分析數據包是很重要的,下面列出來給大家看看:
值 協議 意義
1 ICMP Internet Control Message Protocol
6 TCP              Tranfer Control Protocol
8 EGP Exterior Gateway Protocol
     9 IGP Interior Gateway Protocol
17 UDP User Datagram Protocol
下面這些協議的值在後面的程序中我們可以見到,請大家留心記一下。接着我們介紹地址解析協議(ARP/RARP):
成員名 字節數 說明
Hardware address 2 硬件類型,以太網爲1
Protocol address 2 上層協議類型,IP爲800
Byte length of each hardware 1     查詢物理地址的字節長度,以太網爲6
Byte length of each protocol address 1 查詢上層協議的字節長度,IPv4時爲4
Opcode 2 1爲ARP請求,2爲響應;3爲RARP請求,4爲響應
Hardware address of sender of this packet 6 發送端硬件地址
protocol address of sender of this packet 4 發送端IP地址
Hardware address of target of this packet 6 查詢對象硬件地址
Protocol address of target of this packet 4 查詢對象IP地址
ARP/RARP協議用來查詢IP對應的硬件地址或反過來查詢IP地址,這在我們分析數據包時也會見到。下面介紹ICMP協議。我們常用的PING命令就是用的這個協議,這個協議比較簡單,由類型(1字節)、代碼(1字節)、檢驗和(2字節)、還有四個字節的與類型相關的可變部分及數據構成。
數據包在運輸層還有兩個重要的協議,即TCP/UDP,TCP/UDP中使用端口的概念,以區別計算機上不同的程序。下面我們先來看看TCP數據報的首部構成:
成員名 字節數 說明
Source Port 2 發送端端口號
Destination Port 2 接收端端口號
Sequence NO 4 本報文段所發送的第一個字節的序號
ACk Number 4 期望收到的下一個報文段的序號
DAta Offset 1/2 首部的長度
Reserved 3/4 保留今後用
Contol Bits 3/4 控制位
Window 2 滑動窗口的大小
Checksum 2 檢驗和
Urgent Pointer 2 緊急指針
Options And Padding 4 可選,真充項
Tcp被使用在跨越路由器進行網絡服務的網絡應用程序中,如WWW、電子郵件、新聞、FTP等。UDP則是在IP的基礎上加入了端口的概念,其結構很簡單,只有八個字節首部如下:
源端口(2字節)->目的端口(2字節)->長度(2字節)->檢驗和(2字節)

二、程序部分函數及數據結構說明
在此部分我們將介紹後面程序中用到的部分函數及數據結構。在程序中我們使用了PCAP程序庫,大家可以從
ftp://ftp.ee.lbl.gov/libpcap.tar.z下載。我們主要在Redhat Linux下測試程序,這裏簡單介紹一下程序庫的安裝方法,其它環境請大家自行解決。我的目的是給大家編寫數據包分析程序提供思路,至於實用程序的實現這裏不做介紹,第三部分給出的程序也不具實用性,爲了演示,程序中實現的功能較多而有些地方又不夠詳細,編寫實用程序時請適當取捨並加入你所需要的功能實現部分。PCAP程序庫的安裝方法如下:
1、解壓文件
2、進入文件目錄執行./configure 及make
3、使用Make命令,設定手冊和Include文件(要有Root權限),執行以下命令:
make install -man
make install -incl
4、如出現不存在Include及Include/net目錄,則建立此目錄並重新執行 make install -incl
5、檢查/usr/include/netinet/目錄是否存在Protocols.h文件,不存在則拷貝過去。至此程序庫安裝完畢。
下面介紹程序中出現的部分函數及數據結構:
1、PCAP_t *pd;
此型數據結構稱爲數據包捕捉描述符。
2、Pcap_Open_Live(argv[1],DEFAUT_SNALEN,1,1000,ebuf)
此函數對Pcap程序庫進行初始化並返回指向Pcap_t型數據的指針,其參數列表如下:
        char * 指定網絡接口
int 取得數據的最大字節數
int 指定網絡接口卡,一般用1
int 讀出暫停時間
char * 錯誤消息用緩衝區
3、Pcap_loop(pd,-1,packet_proce,NUll)
     此函數程序的核心,反覆執行,利用Pcap取得數據包,返回的是讀入數據包的個數,錯誤時返回-1,其參數列表如下:
Pcap_t * 指定取得數據包的數據包捕捉描述符
int 取得數據包的個數,-1爲無限
返回指向函數的指針 指定數據包處理的函數
U_char * 指向賦給數據包處理函數字符串的指針
4、struct ether_header * eth
此結構體存儲以太網報頭信息,其成員如下:
ether_dhost[6] 接收端的MAC地址
ether_shost[6] 發送端的MAC地址
ether_type 上層協議的種類
5、fflush(stdout)
此函數完成的是強制輸出,參數Stdout,強制進行標準輸出。
6、noths(((struct ether_header *P)->ether_type))
此函數將短整型網絡字節順序轉換成主機字節順序。此類函數還有:
ntohl 長整型 功能同上
htons 短整型 將主機字節順序轉換成網絡字節順序
htons 長整型 同上
7、struct IP *iph
ip型結構體在IPh文件中定義,其成員和第一部分講到的IP數據報結構對應,如下:
成員名 類型 說明
ip_hl 4位無符號整數 報頭長度
ip_v 同上 版本,現爲4
ip_tos 8位無符號整數 Type of service
ip_len 16位無符號整數 數據報長度
ip_id 同上 標識
ip_off 同上 數據塊偏移和標誌
ip_ttl 8位無符號整數 TTL值
ip_p 同上 上層協議
ip_sum 16位無符號整數 檢驗和
ip_src in_addr結構體 發送端IP
ip_dst 同上 接收端IP
8、struct ether_arp *arph
ether_arp型結構體成員如下:
成員名 類型 說明
ea_hdr arphdr型結構體 報頭中地址以外的部分
arp_sha 8位無符號整數數組 發送端MAC地址
arp_spa 同上 發送端IP地址
arp_tha 同上 目標MAC地址
arp_tpa 同上 目標IP地址
9、struct icmphdr * icmp
icmphdr型結構體中包含共用體根據數據報類型的不同而表現不同性質,這裏不再列出,只列能通用的三個成員
成員名 說明
type 類型字段
code 代碼
checksum 檢驗和

三、案例程序剖析
//example.c
//使用方法:example〈網絡接口名〉 > 〈輸出文件名〉
//例如:example etho > temp.txe
//結束方法:ctrl+c
//程序開始,讀入頭文件
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/in_systm.h>
#include<netinet/ip.h>
#include<netinet/if_ether.h>
#include<pcap.h> //pcap程序庫
#include<netdb.h> //DNS檢索使用
#define MAXSTRINGSIZE 256 //字符串長度
#define MAXSIZE 1024 //主機高速緩存中的最大記錄條數
#fefine DEFAULT_SNAPLEN 68 /數據包數據的長度
typedef struct
{
    unsigned long int ipaddr; //IP地址
    char hostname[MAXSTRINGSIZE]; //主機名
}dnstable; //高速緩存數據結構
typedef struct
{
    dnstable table[MAXSIZE];
    int front;
    int rear;
}sequeue;
sequeue *sq; //定義緩存隊列
sq->rear=sq->front=0; //初始化隊列
//輸出MAC地址函數
void print_hwadd(u_char * hwadd)
{
    for(int i=0,i<5;++i)
        printf("%2x:",hwadd[i]);
    printf("%2x",hwadd[i]);
}
//輸出IP地址的函數
void print_ipadd(u_char *ipadd)
{
    for(int i=0;i<3;++i)
        printf("%d.",ipadd[i]);
    printf("%d",ipadd[i]);
}
//查詢端口函數
void getportname(int portno,char portna[],char* proto)
{
    if(getservbyport(htons(portno),proto)!=NULL)
    {
        strcpy(portna,getservbyport(htons(portno),proto)->s_name);
    }
    else
        sprintf(portna,"%d",portno);
}
//將IP轉化爲DNS名
void iptohost(unsigned long int ipad,char* hostn)
{
    struct hostent * shostname;
    int m,n,i;
    m=sq->rear;
    n=sq->front;
    for(i=n%MAXSIZE;i=m%MAXSIZE;i=(++n)%MAXSIZE)
    {
        //檢查IP是否第一次出現
        if(sq->table[i].ipaddr==ipad)
        {
            strcpy(hostn,sq->table[i].hostname);
            break;
        }
    }
    if(i=m%MAXSIZE)
    {//不存在則從域名服務器查詢並把結果放入高速緩存
        if((sq->rear+1)%MAXSIZE=sq->front) //判隊滿
            sq->front=(sq->front+1)%MAXSIZE; //出隊列
        sq->table[i].ipaddr=ipad;
        shostname=gethostbyaddr((char*)&ipad,sizeof(ipad),AF_INET);
        if(shostname!=NULL)
            strcpy(sq->table[i].hostname,shostname->h_name);
        else
            strcpy(sq->table[i].hostname,"");
        sq->rear=(sq->rear+1)%MAXSIZE;
    }
}
void print_hostname(u_char* ipadd)
{
    unsigned long int ipad;
    char hostn[MAXSTRINTSIZE];
    ipad=*((unsigned long int *)ipadd);
    iptohost(ipad,hostn)
        if(strlen(hostn)>0)
            printf("%s",hostn);
        else
            print_ipadd(ipadd);
}
//處理數據包的函數
void packet_proce(u_char* packets,const struct pcap_pkthdr * header,const u_char *pp)
{
    struct ether_header * eth; //以太網幀報頭指針
    struct ether_arp * arth; //ARP報頭
    struct ip * iph; //IP報頭
    struct tcphdr * tcph;
    struct udphdr * udph;
    u_short srcport,dstport; //端口號
    char protocol[MAXSTRINGSIZE]; //協議類型名
    char srcp[MAXSTRINGSIZE],dstp[MAXSTRINGSIZE]; //端口名
    unsigned int ptype; //協議類型變量
    u_char * data; //數據包數據指針
    u_char tcpudpdata[MAXSTRINGSIZE]; //數據包數據
    int i;
    eth=(struct ether_header *)pp;
    ptype=ntohs(((struct ether_header *)pp)->ether_type);
    if((ptype==ETHERTYPE_ARP)||(ptype==ETHERTYPE_RARP))
    {
        arph=(struct ether_arp *)(pp+sizeof(struct ether_header));
        if(ptype==ETHERTYPE_ARP)
            printf("arp ");
        else
            printf("rarp "); //輸出協議類型
        print_hwadd((u_char *)&(arph->arp_sha));
        printf("(");
        print_hostname((u_char *)&(arph->arp_spa));
        printf(")->");
        print_hwadd((u_char *)&(arph->arp_tha));
        printf("(");
        print_hostname((u_char *)&(arph->arp_tpa));
        printf(")/tpacketlen:%d",header->len);
    }
    else if(ptype==ETHERTYPE_IP) //IP數據報
    {
        iph=(struct ip *)(pp+sizeof(struct ether_header));
        if(iph->ip_p==1) //ICMP報文
        {
            strcpy(protocol,"icmp");
            srcport=dstport=0;
        }
        else if(iph->ip_p==6) //TCP報文
        {
            strcpy(protocol,"tcp");
            tcph=(struct tcphdr *)(pp+sizeof(struct ether_header)+4*iph->ip_hl);
            srcport=ntohs(tcph->source);
            dstport=ntohs(tcph->dest);
            data=(u_char *)(pp+sizeof(struct ether_header)+4*iph->ip_hl+4*tcph->doff);
            for(i=0;i<MAXSTRINGSIZE-1;++i)
            {
                if(i>=header->len-sizeof(struct ether_header)-4*iph->ip_hl-4*tcph->doff);
                break;
                else
                    tcpudpdata[i]=data[i];
            }
        } //TCP數據處理完畢
        else if(iph->ip_p=17) //UDP報文
        {
            strcpy(protocol,"udp");
            udph=(struct udphdr *)(pp+sizeof(struct ether_header)+4*iph->ip_hl);
            srcport=ntohs(udph->source);
            dstport=ntohs(udph->dest);
            data=(u_char *)(pp+sizeof(struct ether_header)+4*iph->ip_hl+8);
            for(i=0;i<MAXSTRINGSIZE-1;++i)
            {
                if(i>=header->len-sizeof(struct ether_header)-4*iph->ip_hl-8);
                break;
                else
                    tcpudpdata[i]=data[i];
            }
        }
        tcpudpdata[i]='/0';
        getportname(srcport,srcp,protocol);
        getportname(dstport,dstp,protocol);
        printf("ip ");
        print_hwadd(eth->ether_shost);
        printf("(");
        print_hostname((u_char *)&(iph->ip_src));
        printf(")[%s:%s]->",protocol,srcp);
        print_hwadd(eth->ether_dhost);
        printf("(");
        print_hostname((u_char *)&(iph->ip_dst));
        printf(")[%s:%s]",protocol,dstp);
        printf("/tttl:%d packetlen:%d,iph->ttl,header->len);
     printf("/n");
        printf("%s",tcpudpdata);
        printf("==endpacket==");
    }
    printf("/n");
}
//Main函數取數據包並初始化程序環境
int main(int argc,char ** argv)
{
    char ebuf[pcap_ERRBUF_SIZE];
    pcap * pd;
    if(argc<=1) //參數檢查
    {
        printf("usage:%s<network interface>/n",argv[0]);
        exit(0);
    }
    //設置PCAP程序庫
    if((pd=pcap_open_live(argv[1],DEFAULT_SNAPLEN,1,1000,ebuf))=NULL)
    {
        (void)fprintf(stderr,"%s",ebuf);
        exit(1);
    }
    //循環取數據包
    //改變參數-1爲其它值,可確定取數據包的個數,這裏爲無限個
    if(pcap_loop(pd,-1,packet_proce,NULL)<0)
    {
        (void)fprintf(stderr,"pcap_loop:%s/n",pcap_geterr(pd));
        exit(1);
    }
    pcap_colse(pd);
    exit(0);
}
//程序結束
到此爲止,我的這篇文章就寫完了,希望能給大家以啓發和幫助。平時和網友交流中發現大多數朋友在網上見到長的文章往往都沒有信心看下去,其實以前我也是這樣,但在這裏我想告訴大家,不踏踏實實的深入進去學習,是得不到收穫的。一分耕耘,一分收穫。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章