Linux系统无线网络抓包程序(分析手机WIFI MAC地址)

前面讲述了使用tcpdump和wireshark抓WIFI包,但这只是使用工具的层面,再深一层则是自己写代码实现这个功能。本文在前面文章《Linux系统有线网络抓包程序》的基础上添加实现无线网络的抓包功能。

首先要介绍ieee802.11的帧格式,只有知道帧格式才能正确解析对应字段,拿到我们感兴趣的信息。其次介绍Linux raw socket编程抓包。最后解析ieee802.11数据包,从而获取到MAC地址。实际上,从数据包中可以得到很多信息,这些信息就是后续需要继续进行的事了。

一、ieee802.11帧格式

ieee802.11帧格式如下图所示:

上图来自ieee802.11标准文档《802.11-2012.pdf》的8.2.3小节。它比802.3以太帧不同。帧类型有很三大类:数据帧、管理帧、控制帧。每种类型帧又分很多种“子帧”。在手机WIFI开启扫描热点、连接热点、过程主要涉及管理帧。手机或PC在开启WIFI时,会向周边发出probe request帧(子帧类型为4),热点会回应probe response帧(子帧类型为5),其中probe request帧头部包含了手机MAC号信息。抓到此包,就能解析出手机MAC号了。不同的帧的Frame Body不同,但这不是本文关注的重点。关于帧类型,具体参考ieee802.11标准文档8.2.4.1.3 (Type and Subtype fields)小节。

二、C实现socket抓包

Linux系统抓包使用SOCK_RAW方式,类型为ETH_P_ALL(表示抓取所有类型的帧,不管是IP帧还是ARP帧)。下面给出代码重要函数代码片段。为减小文章篇幅,保留主要代码函数,至于完整代码,请参阅文章后面的附录。

1、设置混杂模式

抓包工具都会开启混杂模式(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包括创建RAW socket,绑定指定网卡。

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;

}

// 注意与下面绑定时协议一致

fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

// 绑定网卡

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、获取网卡信息

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;

}

注意,函数返回的sa_family十分重要,它是判断抓取的帧类型的依据。跟踪SIOCGIFHWADDR调用过程,发现内核驱动将dev->dev_addr赋值给ifr->ifr_hwaddr.sa_data,而dev->type赋值给ifr->ifr_hwaddr.sa_family。

4、接收数据

一般网络接收数据都会使用select模型,使用recv即可收到内核传递的数据。

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;

}

函数最后根据arphrd类型调用不同的解析函数。这样就能在同一个程序中把有线网络、无线网络抓包合二为一了。但本文只针对无线网络包解析,即函数parse_packet_wlan。

三、解析

下图是笔者手机发的probe request帧截图(使用tcpdump抓包,再用wireshark查看)。

其中第一部分是radiotap头部,第二部分是probe request头部,第三部分是probe request的frame body。本文只关心第二部分的MAC地址。其它跳过忽略。

1、radiotap头部

radiotap包含大量有用信息,比如SSI信号强度。但我们暂时不需要,直接跳过。它的结构体定义如下:

// radiotap头部

// radiotap官网: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成员表示整个radiotap头部的大小。因此在代码中直接使用it_len作偏移量计算出ieee802.11头部地址。

2、ieee802.11头部

ieee802.11头部结构体如下:

// 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为发送者(Transmitter)MAC地址,addr3为BSSID。不同类型的帧,Receiver和Transmitter不同,对于probe request类型帧来说,Transmitter地址即为所需要的MAC号——因为probe都是广播,目标地址都是ff。

下面是解析ieee802.11的函数代码:

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表示整个radiotap信息,包括头部,因此直接跳过到ieee80211头部

int radiotap_len = radiotap_header->it_len;

wh = (struct wlan_frame*)(buffer+radiotap_len);

fc = le16toh(wh->fc); // 传输格式为little endian,要转换成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);

// 数据帧

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;

}

看上去十分简单,因为我们只需要其中一种帧的MAC地址信息,其它一概忽视。

下图是扫描probe得到的MAC地址,并且根据OUI查出MAC所属组织名称(代码需修改):

修改后的版本演示结果如下(代码需修改):

PS:本文所述代码工程将会不断完善,并择机上传至github。

附代码:

李迟 2016.11.01 夜

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容

  • make menuconfig过程解析作者 codercjg 在 28 九月 2015, 5:27 下午 make...
    codercjg阅读 921评论 0 1
  • 系统与网络编程 小作业 公交车停发车程序 线程 并发执行:看起来像同时运行,实际上在单核cpu里只有一个。将其排成...
    I踏雪寻梅阅读 446评论 0 3
  • 大纲 一.Socket简介 二.BSD Socket编程准备 1.地址 2.端口 3.网络字节序 4.半相关与全相...
    y角阅读 2,356评论 2 11
  • 串口操作 串口操作需要的头文件 #include /*标准输入输出定义*/ #include /*标准函数库定...
    旅行家John阅读 1,264评论 0 3
  • 系统与网络编程 select函数 select和pselect多用于I/O操作,他们见识多个文件描述符的集合,判断...
    I踏雪寻梅阅读 606评论 0 1