当前位置:   article > 正文

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

linux wifi radiotap parser

前面讲述了使用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),下面是代码:

  1. // 混杂模式
  2. bool set_promisc_mode(const char* eth, bool promisc)
  3. {
  4. int org_errno = 0;
  5. int fd;
  6. struct ifreq ifreq;
  7. if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
  8. return false;
  9. memset(&ifreq, 0, sizeof(ifreq));
  10. strncpy(ifreq.ifr_name, eth, IF_NAMESIZE - 1);
  11. ioctl(fd, SIOCGIFFLAGS, &ifreq);
  12. // check if eth is up
  13. if (!(ifreq.ifr_flags & IFF_UP))
  14. {
  15. printf("%s is not up yet.\n", eth);
  16. return false;
  17. }
  18. if (promisc)
  19. ifreq.ifr_flags |= IFF_PROMISC;
  20. else
  21. ifreq.ifr_flags &= ~IFF_PROMISC;
  22. ioctl(fd, SIOCSIFFLAGS, &ifreq);
  23. if (close(fd))
  24. return false;
  25. return true;
  26. }

2、初始化socket

初始化socket包括创建RAW socket,绑定指定网卡。

  1. int init_socket(const char* eth)
  2. {
  3. int ret = 0;
  4. int fd = -1;
  5. // 混杂模式
  6. if (!set_promisc_mode(eth, true))
  7. {
  8. //printf("set %s to promisc mode failed.\n", eth);
  9. return -1;
  10. }
  11. // 注意与下面绑定时协议一致
  12. fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
  13. // 绑定网卡
  14. struct ifreq req;
  15. strcpy(req.ifr_name, eth);
  16. ioctl(fd, SIOCGIFINDEX, &req);
  17. struct sockaddr_ll addr;
  18. addr.sll_family = PF_PACKET;
  19. addr.sll_ifindex = req.ifr_ifindex;
  20. addr.sll_protocol = htons(ETH_P_ALL);
  21. ret = bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_ll));
  22. return fd;
  23. }

3、获取网卡信息

  1. int get_hwinfo(int fd, char* eth, unsigned char* mac)
  2. {
  3. struct ifreq ifr;
  4. memset(&ifr, 0, sizeof(ifr));
  5. strncpy(ifr.ifr_name, eth, IFNAMSIZ - 1);
  6. ifr.ifr_name[IFNAMSIZ - 1] = '\0';
  7. if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0)
  8. {
  9. printf("Could not get arptype\n");
  10. return -1;
  11. }
  12. printf("ARPTYPE %d\n", ifr.ifr_hwaddr.sa_family);
  13. memcpy(mac, ifr.ifr_hwaddr.sa_data, 6);
  14. return ifr.ifr_hwaddr.sa_family;
  15. }

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

4、接收数据

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

  1. int receive_packet(int socket)
  2. {
  3. int ret = 0;
  4. struct timeval tv;
  5. static fd_set read_fds;
  6. tv.tv_sec = 0;
  7. tv.tv_usec = 100;
  8. FD_ZERO(&read_fds);
  9. FD_SET(socket, &read_fds);
  10. ret = select(socket+1, &read_fds, NULL, NULL, &tv);
  11. if (ret == -1 && errno == EINTR) /* interrupted */
  12. return -1;
  13. if (ret == 0)
  14. return -1;
  15. else if (ret < 0)
  16. return -1;
  17. if (FD_ISSET(socket, &read_fds))
  18. {
  19. memset(buffer, '\0', BUFFER_SIZE);
  20. ret = recv(socket, buffer, BUFFER_SIZE, MSG_DONTWAIT);
  21. if (ret <= 0)
  22. return -1;
  23. //printf("--recv len: %d\n", ret);
  24. if (debug_level)
  25. {
  26. dump(buffer, ret);
  27. printf("====================\n");
  28. }
  29. if (arphrd == 1)
  30. parse_packet(buffer, ret); // ieee802.3包
  31. else if (arphrd == 802 || arphrd == 803) // ieee802.11包
  32. parse_packet_wlan(buffer, ret);
  33. }
  34. return 0;
  35. }
函数最后根据arphrd类型调用不同的解析函数。这样就能在同一个程序中把有线网络、无线网络抓包合二为一了。但本文只针对无线网络包解析,即函数parse_packet_wlan。

三、解析

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


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

1、radiotap头部

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

  1. // radiotap头部
  2. // radiotap官网:http://www.radiotap.org/
  3. struct ieee80211_radiotap_header {
  4. uint8_t it_version; /* set to 0 */
  5. uint8_t it_pad;
  6. uint16_t it_len; /* entire length */
  7. uint32_t it_present; /* fields present */
  8. } __attribute__((__packed__));
其中的it_len成员表示整个radiotap头部的大小。因此在代码中直接使用it_len作偏移量计算出ieee802.11头部地址。

2、ieee802.11头部

ieee802.11头部结构体如下:

  1. // 802.11帧头
  2. struct wlan_frame {
  3. uint16_t fc;
  4. uint16_t duration;
  5. uint8_t addr1[6];
  6. uint8_t addr2[6];
  7. uint8_t addr3[6];
  8. uint16_t seq;
  9. union {
  10. uint16_t qos;
  11. uint8_t addr4[6];
  12. struct {
  13. uint16_t qos;
  14. uint32_t ht;
  15. } __attribute__ ((packed)) ht;
  16. struct {
  17. uint8_t addr4[6];
  18. uint16_t qos;
  19. uint32_t ht;
  20. } __attribute__ ((packed)) addr4_qos_ht;
  21. } u;
  22. } __attribute__ ((packed));
从前面的图示知道,addr1为接收方(Receiver)MAC地址,addr2为发送者(Transmitter)MAC地址,addr3为BSSID。不同类型的帧,Receiver和Transmitter不同,对于probe request类型帧来说,Transmitter地址即为所需要的MAC号——因为probe都是广播,目标地址都是ff。
下面是解析ieee802.11的函数代码:

  1. int parse_packet_wlan(const char* buffer, int len)
  2. {
  3. int hdrlen = 0;
  4. uint16_t fc = 0;
  5. uint8_t* ra = NULL;
  6. uint8_t* ta = NULL;
  7. uint8_t* bssid = NULL;
  8. struct ieee80211_radiotap_header* radiotap_header = NULL;
  9. struct wlan_frame* wh = NULL;
  10. if (buffer == NULL)
  11. {
  12. return -1;
  13. }
  14. radiotap_header = (struct ieee80211_radiotap_header*)buffer;
  15. // it_len表示整个radiotap信息,包括头部,因此直接跳过到ieee80211头部
  16. int radiotap_len = radiotap_header->it_len;
  17. wh = (struct wlan_frame*)(buffer+radiotap_len);
  18. fc = le16toh(wh->fc); // 传输格式为little endian,要转换成host格式
  19. int wlan_type = (fc & 0xfc);
  20. int type = (fc & 0xc)>>2;
  21. int stype = (fc & 0xf0)>>4;
  22. //printf("fc:0x%x wlan_type 0x%x - type 0x%x - stype 0x%x \n", fc, wlan_type, type, stype);
  23. // 数据帧
  24. if (type == 0x02)
  25. {
  26. }
  27. // 控制帧
  28. else if (type == 0x01)
  29. {
  30. }
  31. // 管理帧
  32. else if (type == 0x0)
  33. {
  34. if (stype == 0x04) // probe帧
  35. {
  36. ra = wh->addr1;
  37. ta = wh->addr2;
  38. bssid = wh->addr3;
  39. if (ta)
  40. printf("SRC MAC: [" MACFMT "] --> ", MAC2ADDR(ta));
  41. if (ra)
  42. printf("DST MAC: [" MACFMT "]", MAC2ADDR(ra));
  43. if (bssid)
  44. printf(" BSSID MAC: [" MACFMT "]", MAC2ADDR(bssid));
  45. printf("\n");
  46. }
  47. }
  48. else
  49. {
  50. printf("unknown frame.\n");
  51. return -1;
  52. }
  53. return 0;
  54. }
看上去十分简单,因为我们只需要其中一种帧的MAC地址信息,其它一概忽视。 

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

 

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



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

 

附代码:


李迟 2016.11.01 夜
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/518656
推荐阅读
相关标签
  

闽ICP备14008679号