封包解析要從最底層解析上去,先從乙太網路的Ethernet frame開始。
- Destination MAC Address:6 bytes。
- Source MAC Address:6 bytes。
- Type:2 bytes。
- 長度:14 bytes。
一開始一樣要include一些ethernet相關的header。
#ifndef __linux #include <net/if.h> #include <netinet/in.h> #include <net/if_dl.h> #include <net/ethernet.h> #else /* if BSD */ #define __FAVOR_BSD #include <linux/if_ether.h> #include <netpacket/packet.h> #include <linux/if_link.h> #include <netinet/ether.h> #endif /* if linux */
在核心為linux時候,define了一個marcos:
__FAVOR_BSD
,在BSD核心系統中定義的封包header比較常用,但在linux預設並不是這些表頭欄位,但是只要define這個marcos就可以與BSD相容了。一樣從預設的device抓封包,抓三個就好。
char errbuf[PCAP_ERRBUF_SIZE]; char *device = NULL; //get default interface name device = pcap_lookupdev(errbuf); if(!device) { fprintf(stderr, "pcap_lookupdev(): %s\n", errbuf); exit(1); }//end if printf("Sniffing: %s\n", device); pcap_t *handle = pcap_open_live(device, 65535, 1, 1, errbuf); if(!handle) { fprintf(stderr, "pcap_open_live(): %s\n", errbuf); exit(1); }//end if //start capture pcap_loop(handle, 3, pcap_callback, NULL);
當抓到封包時候一樣呼叫callback函數。
static int d = 0; struct tm *ltime; char timestr[16]; time_t local_tv_sec; local_tv_sec = header->ts.tv_sec; ltime = localtime(&local_tv_sec); strftime(timestr, sizeof timestr, "%H:%M:%S", ltime); printf("No. %d\n", ++d); //print header printf("\tTime: %s.%.6d\n", timestr, (int)header->ts.tv_usec); printf("\tLength: %d bytes\n", header->len); printf("\tCapture length: %d bytes\n", header->caplen); //dump ethernet dump_ethernet(header->caplen, content); printf("\n");
一樣都是解析時戳、封包長度,重點是接下來的解析函數
dump_ethernet()
。函數
dump_ethernet()
是我們拿來解析ethernet frame表頭的函數;先宣告出表頭的欄位,目的mac和來源mac都是字串格式,長度是macros的,type是無號的16 bit整數。char dst_mac[MAC_ADDRSTRLEN] = {0}; char src_mac[MAC_ADDRSTRLEN] = {0}; u_int16_t type;
巨集
MAC_ADDRSTRLEN
宣告:#define MAC_ADDRSTRLEN 2*6+5+1
接著宣告結構指標
struct ether_header *
來指向封包本體content。struct ether_header *ethernet = (struct ether_header *)content;
結構
struct ether_header
宣告:/* * The number of bytes in an ethernet (MAC) address. */ #define ETHER_ADDR_LEN 6 /* * Structure of a 10Mb/s Ethernet header. */ struct ether_header { u_char ether_dhost[ETHER_ADDR_LEN]; u_char ether_shost[ETHER_ADDR_LEN]; u_short ether_type; };
這邊直接放上我之前在別的地方寫的教學。
指向封包本體後,就可以直接使用變數ethernet直接來取得各個欄位。
接著就把指向的data儲存到剛剛宣告的幾個變數內。
//copy header snprintf(dst_mac, sizeof(dst_mac), "%s", mac_ntoa(ethernet->ether_dhost)); snprintf(src_mac, sizeof(src_mac), "%s", mac_ntoa(ethernet->ether_shost));
來源和目的mac本身是十六進位資料,需要轉成字串後再用函數
snprintf()
儲存進變數。函數
mac_ntoa()
是將十六進位轉成字串格式,在之前有用過函數ether_ntoa()
;雖然功能一樣,但是ether_ntoa()
會有回傳值被修改的問題,所以我們重新改寫這個函數。要傳回一個字串最常用的方法就是傳回一個由函數
malloc()
相關的函數,所分配的合法空間;但是缺點就是需要呼叫對應的函數free()
來回收。函數
ether_ntoa()
的作法就傳回一個static
變數,一個程式有五個區塊Text、Data、BSS、Heap以及Stack。- Text:程式碼(編譯後的機器碼)放置區。
- Data:全域變數,以及區塊(Block)內的static變數。
- BSS:未初始化的全域變數。
- Heap:malloc()、calloc()和realloc()配置的記憶體。
- Stack:區域變數、函數返回點、參數...等。
static
變數是被放置在Data區域,所以它並不會因為函數返回而被銷毀(Stack區域會銷毀);但函數ether_ntoa()
只有一個buffer,也就是說每次傳回的位置都相同,所以傳回後沒有馬上使用的話,只要再呼叫一次就會把之前的資訊給覆蓋掉。所以我們根據傳回
static
變數的方法,增加buffer的功能。static const char *mac_ntoa(u_int8_t *d) { #define STR_BUF 16 static char mac[STR_BUF][MAC_ADDRSTRLEN]; static int which = -1; which = (which + 1 == STR_BUF ? 0 : which + 1); memset(mac[which], 0, MAC_ADDRSTRLEN); snprintf(mac[which], sizeof(mac[which]), "%02x:%02x:%02x:%02x:%02x:%02x", d[0], d[1], d[2], d[3], d[4], d[5]); return mac[which]; }//end mac_ntoa
Line: 64
宣告一個傳回用的變數,第一維有16個buffer空間,第二維是mac地址長度;Line: 65
是目前使用到第幾個buffer。Line: 67
負責循環整個buffer,所以變數which的值會一直在0-15循環。接著
Line: 69-72
就將對應的空間給清空再格式化字串,最後再傳回。這個做法增加了buffer機制,所以不用在傳回後馬上複製,但是在呼叫了16次後,還是會回到第一個buffer空間。
type = ntohs(ethernet->ether_type);
函數
ntohs
是將網路順序轉成主機順序的函數。相關函數原型:
uint32_t htonl(uint32_t hostlong); uint32_t ntohl(uint32_t netlong); uint16_t htons(uint16_t hostshort); uint16_t ntohs(uint16_t netshort);
有兩種byte order:Network byte order和Host byte order,一般來講Network byte order是
Big‐Endian
,而Host byte order則通常是Little‐Endian
,這是電腦在儲存資料byte放的順序方式。剛學程式的時候,一定會有一種題目這像這樣:
int x = 0x12345678; char c = x; c = ?
答案c=0x78,因為電腦是Host byte order,是從後面取的資料。
但是在網路上可能會跟常理有點顛倒。同樣一個資料0x12ac,在封包內為:0x12 0xac;但是我們直接用指標指向這段資料後,列印出來的資料卻是0xac12。
幸運的是我們不必了解我們系統到底是屬於哪種Byte order,我們只要利用
htonl()
、ntohl()
、htons()
以及ntohs()
即可完成,這四個函數的記法很簡單,h表示host,n表示network,l表示四個byte的資料(unsigned long),s表示兩個byte的資料(unsigned short)。所以今天如果要從封包內讀取一個兩個byte的資料,就要使用函數
ntohs()
(network to host short)來轉換。最後就將封包的內容給畫出來:
//print if(type <= 1500) printf("IEEE 802.3 Ethernet Frame:\n"); else printf("Ethernet Frame:\n"); printf("+-------------------------+-------------------------+-------------------------+\n"); printf("| Destination MAC Address: %17s|\n", dst_mac); printf("+-------------------------+-------------------------+-------------------------+\n"); printf("| Source MAC Address: %17s|\n", src_mac); printf("+-------------------------+-------------------------+-------------------------+\n"); if (type < 1500) printf("| Length: %5u|\n", type); else printf("| Ethernet Type: 0x%04x|\n", type); printf("+-------------------------+\n"); printf("Next protocol is "); switch (type) { case ETHERTYPE_ARP: printf("ARP\n"); break; case ETHERTYPE_IP: printf("IP\n"); break; case ETHERTYPE_REVARP: printf("RARP\n"); break; case ETHERTYPE_IPV6: printf("IPv6\n"); break; default: printf("%#06x\n", type); break; }//end switch
編譯:
libpcap % gcc -I/usr/local/opt/libpcap/include -Wall -std=gnu99 -L/usr/local/opt/libpcap/lib -lpcap dump-ethernet.c -o dump-ethernet
執行結果(Mac OS X):
libpcap % ./dump-ethernet Sniffing: en0 No. 1 Time: 14:22:29.295935 Length: 60 bytes Capture length: 60 bytes Ethernet Frame: +-------------------------+-------------------------+-------------------------+ | Destination MAC Address: ff:ff:ff:ff:ff:ff| +-------------------------+-------------------------+-------------------------+ | Source MAC Address: 28:28:5d:9b:3d:9e| +-------------------------+-------------------------+-------------------------+ | Ethernet Type: 0x8899| +-------------------------+ Next protocol is 0x8899 No. 2 Time: 14:22:31.165444 Length: 145 bytes Capture length: 145 bytes Ethernet Frame: +-------------------------+-------------------------+-------------------------+ | Destination MAC Address: d8:fe:e3:a4:d3:78| +-------------------------+-------------------------+-------------------------+ | Source MAC Address: 6c:40:08:bc:ae:98| +-------------------------+-------------------------+-------------------------+ | Ethernet Type: 0x0800| +-------------------------+ Next protocol is IP No. 3 Time: 14:22:31.166128 Length: 112 bytes Capture length: 112 bytes Ethernet Frame: +-------------------------+-------------------------+-------------------------+ | Destination MAC Address: d8:fe:e3:a4:d3:78| +-------------------------+-------------------------+-------------------------+ | Source MAC Address: 6c:40:08:bc:ae:98| +-------------------------+-------------------------+-------------------------+ | Ethernet Type: 0x0800| +-------------------------+ Next protocol is IP
執行結果(CentOS):
[root@tutu libpcap]# ./dump-ethernet Sniffing: eth0 No. 1 Time: 14:23:35.698669 Length: 54 bytes Capture length: 54 bytes Ethernet Frame: +-------------------------+-------------------------+-------------------------+ | Destination MAC Address: 1:0:5e:0:0:16| +-------------------------+-------------------------+-------------------------+ | Source MAC Address: 0:c:29:b0:81:f7| +-------------------------+-------------------------+-------------------------+ | Ethernet Type: 0x0800| +-------------------------+ Next is IP No. 2 Time: 14:23:36.098591 Length: 42 bytes Capture length: 42 bytes Ethernet Frame: +-------------------------+-------------------------+-------------------------+ | Destination MAC Address: ff:ff:ff:ff:ff:ff| +-------------------------+-------------------------+-------------------------+ | Source MAC Address: 0:c:29:b0:81:f7| +-------------------------+-------------------------+-------------------------+ | Ethernet Type: 0x0806| +-------------------------+ Next is ARP No. 3 Time: 14:23:36.104002 Length: 60 bytes Capture length: 60 bytes Ethernet Frame: +-------------------------+-------------------------+-------------------------+ | Destination MAC Address: 0:c:29:b0:81:f7| +-------------------------+-------------------------+-------------------------+ | Source MAC Address: d8:fe:e3:a4:d3:78| +-------------------------+-------------------------+-------------------------+ | Ethernet Type: 0x0806| +-------------------------+ Next is ARP
Source code on Github
沒有留言:
張貼留言