封包解析要從最底層解析上去,先從乙太網路的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



沒有留言:
張貼留言