前面講述了使用tcpdump和wireshark抓WIFI包左腔,但這只是使用工具的層面擒贸,再深一層則是自己寫代碼實(shí)現(xiàn)這個(gè)功能。本文在前面文章《Linux系統(tǒng)有線網(wǎng)絡(luò)抓包程序》的基礎(chǔ)上添加實(shí)現(xiàn)無線網(wǎng)絡(luò)的抓包功能紊遵。
首先要介紹ieee802.11的幀格式沐悦,只有知道幀格式才能正確解析對(duì)應(yīng)字段,拿到我們感興趣的信息崔兴。其次介紹Linux raw socket編程抓包彰导。最后解析ieee802.11數(shù)據(jù)包,從而獲取到MAC地址敲茄。實(shí)際上位谋,從數(shù)據(jù)包中可以得到很多信息,這些信息就是后續(xù)需要繼續(xù)進(jìn)行的事了堰燎。
一掏父、ieee802.11幀格式
ieee802.11幀格式如下圖所示:
上圖來自ieee802.11標(biāo)準(zhǔn)文檔《802.11-2012.pdf》的8.2.3小節(jié)。它比802.3以太幀不同秆剪。幀類型有很三大類:數(shù)據(jù)幀赊淑、管理幀、控制幀仅讽。每種類型幀又分很多種“子幀”陶缺。在手機(jī)WIFI開啟掃描熱點(diǎn)、連接熱點(diǎn)何什、過程主要涉及管理幀组哩。手機(jī)或PC在開啟WIFI時(shí),會(huì)向周邊發(fā)出probe request幀(子幀類型為4)处渣,熱點(diǎn)會(huì)回應(yīng)probe response幀(子幀類型為5),其中probe request幀頭部包含了手機(jī)MAC號(hào)信息蛛砰。抓到此包罐栈,就能解析出手機(jī)MAC號(hào)了。不同的幀的Frame Body不同泥畅,但這不是本文關(guān)注的重點(diǎn)荠诬。關(guān)于幀類型,具體參考ieee802.11標(biāo)準(zhǔn)文檔8.2.4.1.3 (Type and Subtype fields)小節(jié)位仁。
二柑贞、C實(shí)現(xiàn)socket抓包
Linux系統(tǒng)抓包使用SOCK_RAW方式,類型為ETH_P_ALL(表示抓取所有類型的幀聂抢,不管是IP幀還是ARP幀)钧嘶。下面給出代碼重要函數(shù)代碼片段。為減小文章篇幅琳疏,保留主要代碼函數(shù)有决,至于完整代碼闸拿,請(qǐng)參閱文章后面的附錄。
1书幕、設(shè)置混雜模式
抓包工具都會(huì)開啟混雜模式(promisc)新荤,下面是代碼:
// 混雜模式
bool set_promisc_mode(const char* eth, bool promisc)
{
int org_errno = 0;
int fd;
struct ifreq ifreq;
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
return false;
memset(&ifreq, 0, sizeof(ifreq));
strncpy(ifreq.ifr_name, eth, IF_NAMESIZE - 1);
ioctl(fd, SIOCGIFFLAGS, &ifreq);
// check if eth is up
if (!(ifreq.ifr_flags & IFF_UP))
{
printf("%s is not up yet.\n", eth);
return false;
}
if (promisc)
ifreq.ifr_flags |= IFF_PROMISC;
else
ifreq.ifr_flags &= ~IFF_PROMISC;
ioctl(fd, SIOCSIFFLAGS, &ifreq);
if (close(fd))
return false;
return true;
}
2、初始化socket
初始化socket包括創(chuàng)建RAW socket台汇,綁定指定網(wǎng)卡苛骨。
int init_socket(const char* eth)
{
int ret = 0;
int fd = -1;
// 混雜模式
if (!set_promisc_mode(eth, true))
{
//printf("set %s to promisc mode failed.\n", eth);
return -1;
}
// 注意與下面綁定時(shí)協(xié)議一致
fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
// 綁定網(wǎng)卡
struct ifreq req;
strcpy(req.ifr_name, eth);
ioctl(fd, SIOCGIFINDEX, &req);
struct sockaddr_ll addr;
addr.sll_family = PF_PACKET;
addr.sll_ifindex = req.ifr_ifindex;
addr.sll_protocol = htons(ETH_P_ALL);
ret = bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_ll));
return fd;
}
3、獲取網(wǎng)卡信息
int get_hwinfo(int fd, char* eth, unsigned char* mac)
{
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, eth, IFNAMSIZ - 1);
ifr.ifr_name[IFNAMSIZ - 1] = '\0';
if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0)
{
printf("Could not get arptype\n");
return -1;
}
printf("ARPTYPE %d\n", ifr.ifr_hwaddr.sa_family);
memcpy(mac, ifr.ifr_hwaddr.sa_data, 6);
return ifr.ifr_hwaddr.sa_family;
}
注意苟呐,函數(shù)返回的sa_family十分重要痒芝,它是判斷抓取的幀類型的依據(jù)。跟蹤SIOCGIFHWADDR調(diào)用過程掠抬,發(fā)現(xiàn)內(nèi)核驅(qū)動(dòng)將dev->dev_addr賦值給ifr->ifr_hwaddr.sa_data吼野,而dev->type賦值給ifr->ifr_hwaddr.sa_family。
4两波、接收數(shù)據(jù)
一般網(wǎng)絡(luò)接收數(shù)據(jù)都會(huì)使用select模型瞳步,使用recv即可收到內(nèi)核傳遞的數(shù)據(jù)。
int receive_packet(int socket)
{
int ret = 0;
struct timeval tv;
static fd_set read_fds;
tv.tv_sec = 0;
tv.tv_usec = 100;
FD_ZERO(&read_fds);
FD_SET(socket, &read_fds);
ret = select(socket+1, &read_fds, NULL, NULL, &tv);
if (ret == -1 && errno == EINTR) /* interrupted */
return -1;
if (ret == 0)
return -1;
else if (ret < 0)
return -1;
if (FD_ISSET(socket, &read_fds))
{
memset(buffer, '\0', BUFFER_SIZE);
ret = recv(socket, buffer, BUFFER_SIZE, MSG_DONTWAIT);
if (ret <= 0)
return -1;
//printf("--recv len: %d\n", ret);
if (debug_level)
{
dump(buffer, ret);
printf("====================\n");
}
if (arphrd == 1)
parse_packet(buffer, ret); // ieee802.3包
else if (arphrd == 802 || arphrd == 803) // ieee802.11包
parse_packet_wlan(buffer, ret);
}
return 0;
}
函數(shù)最后根據(jù)arphrd類型調(diào)用不同的解析函數(shù)腰奋。這樣就能在同一個(gè)程序中把有線網(wǎng)絡(luò)单起、無線網(wǎng)絡(luò)抓包合二為一了。但本文只針對(duì)無線網(wǎng)絡(luò)包解析劣坊,即函數(shù)parse_packet_wlan嘀倒。
三、解析
下圖是筆者手機(jī)發(fā)的probe request幀截圖(使用tcpdump抓包局冰,再用wireshark查看)测蘑。
其中第一部分是radiotap頭部,第二部分是probe request頭部康二,第三部分是probe request的frame body碳胳。本文只關(guān)心第二部分的MAC地址。其它跳過忽略沫勿。
1挨约、radiotap頭部
radiotap包含大量有用信息,比如SSI信號(hào)強(qiáng)度产雹。但我們暫時(shí)不需要诫惭,直接跳過。它的結(jié)構(gòu)體定義如下:
// radiotap頭部
// radiotap官網(wǎng):http://www.radiotap.org/
struct ieee80211_radiotap_header {
uint8_t? ? ? ? it_version;? ? /* set to 0 */
uint8_t? ? ? ? it_pad;
uint16_t? ? ? it_len;? ? ? ? /* entire length */
uint32_t? ? ? it_present;? ? /* fields present */
} __attribute__((__packed__));
其中的it_len成員表示整個(gè)radiotap頭部的大小蔓挖。因此在代碼中直接使用it_len作偏移量計(jì)算出ieee802.11頭部地址夕土。
2、ieee802.11頭部
ieee802.11頭部結(jié)構(gòu)體如下:
// 802.11幀頭
struct wlan_frame {
uint16_t fc;
uint16_t duration;
uint8_t addr1[6];
uint8_t addr2[6];
uint8_t addr3[6];
uint16_t seq;
union {
uint16_t qos;
uint8_t addr4[6];
struct {
uint16_t qos;
uint32_t ht;
} __attribute__ ((packed)) ht;
struct {
uint8_t addr4[6];
uint16_t qos;
uint32_t ht;
} __attribute__ ((packed)) addr4_qos_ht;
} u;
} __attribute__ ((packed));
從前面的圖示知道时甚,addr1為接收方(Receiver)MAC地址隘弊,addr2為發(fā)送者(Transmitter)MAC地址哈踱,addr3為BSSID。不同類型的幀梨熙,Receiver和Transmitter不同开镣,對(duì)于probe request類型幀來說,Transmitter地址即為所需要的MAC號(hào)——因?yàn)閜robe都是廣播咽扇,目標(biāo)地址都是ff邪财。
下面是解析ieee802.11的函數(shù)代碼:
int parse_packet_wlan(const char* buffer, int len)
{
int hdrlen = 0;
uint16_t fc = 0;
uint8_t* ra = NULL;
uint8_t* ta = NULL;
uint8_t* bssid = NULL;
struct ieee80211_radiotap_header* radiotap_header = NULL;
struct wlan_frame* wh = NULL;
if (buffer == NULL)
{
return -1;
}
radiotap_header = (struct ieee80211_radiotap_header*)buffer;
// it_len表示整個(gè)radiotap信息,包括頭部质欲,因此直接跳過到ieee80211頭部
int radiotap_len = radiotap_header->it_len;
wh = (struct wlan_frame*)(buffer+radiotap_len);
fc = le16toh(wh->fc); // 傳輸格式為little endian树埠,要轉(zhuǎn)換成host格式
int wlan_type = (fc & 0xfc);
int type = (fc & 0xc)>>2;
int stype = (fc & 0xf0)>>4;
//printf("fc:0x%x wlan_type 0x%x - type 0x%x - stype 0x%x \n", fc, wlan_type, type, stype);
// 數(shù)據(jù)幀
if (type == 0x02)
{
}
// 控制幀
else if (type == 0x01)
{
}
// 管理幀
else if (type == 0x0)
{
if (stype == 0x04) // probe幀
{
ra = wh->addr1;
ta = wh->addr2;
bssid = wh->addr3;
if (ta)
printf("SRC MAC: [" MACFMT "] --> ", MAC2ADDR(ta));
if (ra)
printf("DST MAC: [" MACFMT "]", MAC2ADDR(ra));
if (bssid)
printf(" BSSID MAC: [" MACFMT "]", MAC2ADDR(bssid));
printf("\n");
}
}
else
{
printf("unknown frame.\n");
return -1;
}
return 0;
}
看上去十分簡(jiǎn)單,因?yàn)槲覀冎恍枰渲幸环N幀的MAC地址信息嘶伟,其它一概忽視怎憋。
下圖是掃描probe得到的MAC地址,并且根據(jù)OUI查出MAC所屬組織名稱(代碼需修改):
修改后的版本演示結(jié)果如下(代碼需修改):
PS:本文所述代碼工程將會(huì)不斷完善九昧,并擇機(jī)上傳至github绊袋。
附代碼:
李遲 2016.11.01 夜