看了太多的“自己动手”,这次咱也“自己动手”一下,写个简单的网络抓包工具吧。要写出像tcpdump和wireshark(ethereal)这样的大牛程序来,咱也没那能耐,呵呵。所以这个工具只能抓取本地IP数据报,同时它还使用了BPF,目的是了解如何进行简单有效的网络抓包。

当打开一个标准SOCKET套接口时,我们比较熟悉的协议往往是用AF_INET来建立基于TCP(SOCK_STREAM)或UDP(SOCK_DGRAM)的链接。但是这些只用于IP层以上,要想从更底层抓包,我们需要使用AF_PACKET来建立套接字,它支持SOCK_RAW和SOCK_DGRAM,它们都能从底层抓包,不同的是后者得到的数据不包括以太网帧头(最开始的14个字节)。好了,现在我们就知道该怎样建立SOCKET套接口了:

  1. sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP));

最后一个参数 ETH_P_IP 指出,我们只对IP包感兴趣,而不是ARP,RARP等。之后就可以用recvfrom从套接口读取数据了。

现在我们可以抓到发往本地的所有IP数据报了,那么有没有办法抓到那些“流经”本地的数据呢?呵呵,当然可以了,这种技术叫网络嗅探(sniff),它很能威胁网络安全,也非常有用,尤其是当你对网内其他用户的隐私感兴趣时:(  由于以太网数据包是对整个网段广播的,所以网内所有用户都能收到其他用户发出的数据,只是默认的,网卡只接收目的地址是自己或广播地址的数据,而把不是发往自己的数据包丢弃。但是多数网卡驱动会提供一种混杂模式(promiscous mode),工作在这种模式下的网卡会接收网络内的所有数据,不管它是发给谁的。下面的方法可以把网卡设成混杂模式:

  1. // set NIC to promiscous mode, so we can recieve all packets of the network
  2. strncpy(ethreq.ifr_name, "eth0", IFNAMSIZ);
  3. ioctl(sock, SIOCGIFFLAGS, &ethreq);
  4. ethreq.ifr_flags |= IFF_PROMISC;
  5. ioctl(sock, SIOCSIFFLAGS, &ethreq);

通过ifconfig可以很容易的查看当前网卡是否工作在混杂模式(PROMISC)。但是请注意,程序退出后,网卡的工作模式不会改变,所以别忘了关闭网卡的混杂模式:

  1. // turn off promiscous mode
  2. ethreq.ifr_flags &= ~IFF_PROMISC;
  3. ioctl(sock, SIOCSIFFLAGS, &ethreq);

现在我们可以抓到本网段的所有IP数据包了,但是问题也来了:那么多的数据,怎么处理?CPU可能会被严重占用,而且绝大多数的数据我们可能根本就不敢兴趣!那怎么办呢?用if语句?可能要n多个,而且丝毫不会降低内核的繁忙程度。最好的办法就是告诉内核,把不感兴趣的数据过滤掉,不要往应用层送。BPF就为此而生。

BPF(Berkeley Packet Filter)是一种类是汇编的伪代码语言,它也有命令代码和操作数。例如,如果我们只对用户192.168.1.4的数据感兴趣,可以用tcpdump的-d选项生成BPF代码如下:

  1. $tcpdump -d host 192.168.1.4
  2. (000) ldh      [12]
  3. (001) jeq      #0x800           jt 2 jf 6
  4. (002) ld       [26]
  5. (003) jeq      #0xc0a80104      jt 12 jf 4
  6. (004) ld       [30]
  7. (005) jeq      #0xc0a80104      jt 12 jf 13
  8. (006) jeq      #0x806           jt 8 jf 7
  9. (007) jeq      #0x8035          jt 8 jf 13
  10. (008) ld       [28]
  11. (009) jeq      #0xc0a80104      jt 12 jf 10
  12. (010) ld       [38]
  13. (011) jeq      #0xc0a80104      jt 12 jf 13
  14. (012) ret      #96
  15. (013) ret      #0

其中第一列代表行号,第二列是命令代码,后面是操作数。下面我们采用汇编注释的方式简单的解释一下:

(000) ldh      [12] ;load h?? (2 bytes) from ABS offset 12 (the TYPE of ethernet header)
      (001) jeq      #0x800           jt 2 jf 6 ;compare and jump, jump to line 2 if true; else jump to line 6
      (002) ld       [26] ;load word (4 bytes) from ABS offset 26 (src IP address of IP header)
      (003) jeq      #0xc0a80104      jt 12 jf 4 ;compare and jump, jump to line 12 if true, else jump to line 4
      (004) ld       [30] ; load word (4 bytes) from ABS offset 30 (dst IP address of IP header)
      (005) jeq      #0xc0a80104      jt 12 jf 13 ;see line 3
      (006) jeq      #0x806           jt 8 jf 7 ;compare with ARP, see line 1
      (007) jeq      #0x8035          jt 8 jf 13 ;compare with RARP, see line 1
      (008) ld       [28] ;src IP address for other protocols
      (009) jeq      #0xc0a80104      jt 12 jf 10
      (010) ld       [38] ;dst IP address for other protocols
      (011) jeq      #0xc0a80104      jt 12 jf 13
      (012) ret      #96 ;return 96 bytes to user application
      (013) ret      #0 ;drop the packet

但是这样的伪代码我们是无法在应用程序里使用的,所以tcpdum提供了一个-dd选项来输出一段等效的C代码:

  1. $tcpdump -dd host 192.168.1.4
  2. { 0x28, 0, 0, 0x0000000c },
  3. { 0x15, 0, 4, 0x00000800 },
  4. { 0x20, 0, 0, 0x0000001a },
  5. { 0x15, 8, 0, 0xc0a80104 },
  6. { 0x20, 0, 0, 0x0000001e },
  7. { 0x15, 6, 7, 0xc0a80104 },
  8. { 0x15, 1, 0, 0x00000806 },
  9. { 0x15, 0, 5, 0x00008035 },
  10. { 0x20, 0, 0, 0x0000001c },
  11. { 0x15, 2, 0, 0xc0a80104 },
  12. { 0x20, 0, 0, 0x00000026 },
  13. { 0x15, 0, 1, 0xc0a80104 },
  14. { 0x6, 0, 0, 0x00000060 },
  15. { 0x6, 0, 0, 0x00000000 },

该代码对应的数据结构是struct sock_filter,该结构在linux/filter.h中定义如下:

  1. struct sock_filter  // Filter block
  2. {
  3. __u16 code; // Actual filter code
  4. __u8 jt;    // Jump true
  5. __u8 jf;    // Jump false
  6. __u32 k;   // Generic multiuse field
  7. };

code对应命令代码;jt是jump if true后面的操作数,注意这里用的是相对行偏移,如2就表示向前跳转2行,而不像伪代码中使用绝对行号;jf为jump if false后面的操作数;k对应伪代码中第3列的操作数。

了解了BPF伪代码和结构,我们就可以自己定制更加简单有效的BPF filter了,如上例中的6-11行不是针对IP协议的,而我们的套接字已经指定只读取IP数据了,所以就可以把他们删除,不过要注意,行偏移也要做相应的修改。

另外,tcpdump默认只返回96字节的数据,但对大部分应用来说,96字节是远远不够的,所以tcpdump提供了-s选项用于指定返回的数据长度。

OK,下面我们就来看看怎样把过滤器安装到套接口上吧:

  1. $tcpdump ip -d -s 2048 host 192.168.1.2
  2. (000) ldh      [12]
  3. (001) jeq      #0x800           jt 2 jf 7
  4. (002) ld       [26]
  5. (003) jeq      #0xc0a80102      jt 6 jf 4
  6. (004) ld       [30]
  7. (005) jeq      #0xc0a80102      jt 6 jf 7
  8. (006) ret      #2048
  9. (007) ret      #0
  10. struct sock_filter bpf_code[] = {
  11. { 0x28, 0, 0, 0x0000000c },
  12. { 0x15, 0, 5, 0x00000800 },
  13. { 0x20, 0, 0, 0x0000001a },
  14. { 0x15, 2, 0, 0xc0a80102 },
  15. { 0x20, 0, 0, 0x0000001e },
  16. { 0x15, 0, 1, 0xc0a80102 },
  17. { 0x6, 0, 0, 0x00000800 },
  18. { 0x6, 0, 0, 0x00000000 }
  19. };
  20. struct sock_fprog filter;
  21. filter.len = sizeof(bpf_code)/sizeof(bpf_code[0]);
  22. filter.filter = bpf_code;
  23. setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter));

最后加上信号处理器,以便能在程序退出前恢复网卡的工作模式。到现在我们已经可以看到一个小聚规模抓包小工具了,呵呵,麻雀虽小,但也五脏俱全啊!下面给出完整的代码。

  1. #include <sys/types.h>
  2. #include <sys/time.h>
  3. #include <sys/ioctl.h>
  4. #include <sys/socket.h>
  5. #include <linux/types.h>
  6. #include <netinet/in.h>
  7. #include <netinet/udp.h>
  8. #include <netinet/ip.h>
  9. #include <netpacket/packet.h>
  10. #include <net/ethernet.h>
  11. #include <arpa/inet.h>
  12. #include <string.h>
  13. #include <signal.h>
  14. #include <net/if.h>
  15. #include <stdio.h>
  16. #include <sys/uio.h>
  17. #include <fcntl.h>
  18. #include <unistd.h>
  19. #include <linux/filter.h>
  20. #include <stdlib.h>
  21. #define ETH_HDR_LEN 14
  22. #define IP_HDR_LEN 20
  23. #define UDP_HDR_LEN 8
  24. #define TCP_HDR_LEN 20
  25. static int sock;
  26. void sig_handler(int sig)
  27. {
  28. struct ifreq ethreq;
  29. if(sig == SIGTERM)
  30. printf("SIGTERM recieved, exiting.../n");
  31. else if(sig == SIGINT)
  32. printf("SIGINT recieved, exiting.../n");
  33. else if(sig == SIGQUIT)
  34. printf("SIGQUIT recieved, exiting.../n");
  35. // turn off the PROMISCOUS mode
  36. strncpy(ethreq.ifr_name, "eth0", IFNAMSIZ);
  37. if(ioctl(sock, SIOCGIFFLAGS, &ethreq) != -1) {
  38. ethreq.ifr_flags &= ~IFF_PROMISC;
  39. ioctl(sock, SIOCSIFFLAGS, &ethreq);
  40. }
  41. close(sock);
  42. exit(0);
  43. }
  44. int main(int argc, char ** argv) {
  45. int n;
  46. char buf[2048];
  47. unsigned char *ethhead;
  48. unsigned char *iphead;
  49. struct ifreq ethreq;
  50. struct sigaction sighandle;
  51. #if 0
  52. $tcpdump ip -s 2048 -d host 192.168.1.2
  53. (000) ldh      [12]
  54. (001) jeq      #0x800           jt 2 jf 7
  55. (002) ld       [26]
  56. (003) jeq      #0xc0a80102      jt 6 jf 4
  57. (004) ld       [30]
  58. (005) jeq      #0xc0a80102      jt 6 jf 7
  59. (006) ret      #2048
  60. (007) ret      #0
  61. #endif
  62. struct sock_filter bpf_code[] = {
  63. { 0x28, 0, 0, 0x0000000c },
  64. { 0x15, 0, 5, 0x00000800 },
  65. { 0x20, 0, 0, 0x0000001a },
  66. { 0x15, 2, 0, 0xc0a80102 },
  67. { 0x20, 0, 0, 0x0000001e },
  68. { 0x15, 0, 1, 0xc0a80102 },
  69. { 0x6, 0, 0, 0x00000800 },
  70. { 0x6, 0, 0, 0x00000000 }
  71. };
  72. struct sock_fprog filter;
  73. filter.len = sizeof(bpf_code)/sizeof(bpf_code[0]);
  74. filter.filter = bpf_code;
  75. sighandle.sa_flags = 0;
  76. sighandle.sa_handler = sig_handler;
  77. sigemptyset(&sighandle.sa_mask);
  78. //sigaddset(&sighandle.sa_mask, SIGTERM);
  79. //sigaddset(&sighandle.sa_mask, SIGINT);
  80. //sigaddset(&sighandle.sa_mask, SIGQUIT);
  81. sigaction(SIGTERM, &sighandle, NULL);
  82. sigaction(SIGINT, &sighandle, NULL);
  83. sigaction(SIGQUIT, &sighandle, NULL);
  84. // AF_PACKET allows application to read pecket from and write packet to network device
  85. // SOCK_DGRAM the packet exclude ethernet header
  86. // SOCK_RAW raw data from the device including ethernet header
  87. // ETH_P_IP all IP packets
  88. if((sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP))) == -1) {
  89. perror("socket");
  90. exit(1);
  91. }
  92. // set NIC to promiscous mode, so we can recieve all packets of the network
  93. strncpy(ethreq.ifr_name, "eth0", IFNAMSIZ);
  94. if(ioctl(sock, SIOCGIFFLAGS, &ethreq) == -1) {
  95. perror("ioctl");
  96. close(sock);
  97. exit(1);
  98. }
  99. ethreq.ifr_flags |= IFF_PROMISC;
  100. if(ioctl(sock, SIOCSIFFLAGS, &ethreq) == -1) {
  101. perror("ioctl");
  102. close(sock);
  103. exit(1);
  104. }
  105. // attach the bpf filter
  106. if(setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) == -1) {
  107. perror("setsockopt");
  108. close(sock);
  109. exit(1);
  110. }
  111. while(1) {
  112. n = recvfrom(sock, buf, sizeof(buf), 0, NULL, NULL);
  113. if(n < (ETH_HDR_LEN+IP_HDR_LEN+UDP_HDR_LEN)) {
  114. printf("invalid packet/n");
  115. continue;
  116. }
  117. printf("%d bytes recieved/n", n);
  118. ethhead = buf;
  119. printf("Ethernet: MAC[%02X:%02X:%02X:%02X:%02X:%02X]", ethhead[0], ethhead[1], ethhead[2],
  120. ethhead[3], ethhead[4], ethhead[5]);
  121. printf("->[%02X:%02X:%02X:%02X:%02X:%02X]", ethhead[6], ethhead[7], ethhead[8],
  122. ethhead[9], ethhead[10], ethhead[11]);
  123. printf(" type[%04x]/n", (ntohs(ethhead[12]|ethhead[13]<<8)));
  124. iphead = ethhead + ETH_HDR_LEN;
  125. // header length as 32-bit
  126. printf("IP: Version: %d HeaderLen: %d[%d]", (*iphead>>4), (*iphead & 0x0f), (*iphead & 0x0f)*4);
  127. printf(" TotalLen %d", (iphead[2]<<8|iphead[3]));
  128. printf(" IP [%d.%d.%d.%d]", iphead[12], iphead[13], iphead[14], iphead[15]);
  129. printf("->[%d.%d.%d.%d]", iphead[16], iphead[17], iphead[18], iphead[19]);
  130. printf(" %d", iphead[9]);
  131. if(iphead[9] == IPPROTO_TCP)
  132. printf("[TCP]");
  133. else if(iphead[9] == IPPROTO_UDP)
  134. printf("[UDP]");
  135. else if(iphead[9] == IPPROTO_ICMP)
  136. printf("[ICMP]");
  137. else if(iphead[9] == IPPROTO_IGMP)
  138. printf("[IGMP]");
  139. else if(iphead[9] == IPPROTO_IGMP)
  140. printf("[IGMP]");
  141. else
  142. printf("[OTHERS]");
  143. printf(" PORT [%d]->[%d]/n", (iphead[20]<<8|iphead[21]), (iphead[22]<<8|iphead[23]));
  144. }
  145. close(sock);
  146. exit(0);
  147. }

参考资料:
[1] Linux下Sniffer程序的实现
[2] 使用socket BPF

自己动手写网络抓包工具相关推荐

  1. Microsoft Message Analyzer (微软消息分析器,“网络抓包工具 - Network Monitor”的替代品)官方正式版现已发布...

    来自官方日志的喜悦 被誉为全新开始的消息分析器时代,由MMA为您开启,博客原文写的很激动,大家可以点击这里浏览:http://blogs.technet.com/b/messageanalyzer/a ...

  2. 黑客必用神器,网络抓包工具

    点击上方" 程序IT圈 ",选择"置顶公众号" 每天早晨8点50分,准点开车打卡 来源:blog.csdn.net/xjpdf10/article/detail ...

  3. 网络抓包工具 Wireshark 和 tcpdump(三)

    今天我们分享网络抓包工具 Wireshark 和 tcpdump 一.WireShark工具  1.为什么要抓包 1).定位网络问题: 2).分析接口数据: 3).学习网络协议,使用抓包工具分析网络数 ...

  4. 跨平台网络抓包工具-Microsoft Message Analyzer

    Microsoft Message Analyzer (MMA 2013)是微软最受欢迎的Netmon的最新版本. 在Netmon网络跟踪和排除故障功能的基础上提供了更强大的跨平台网络分析追踪能力.园 ...

  5. 1.2.2 网络抓包工具之:Fiddler

    1.2.2 网络抓包工具之:Fiddler 标签: StudyNote 本文声明: 本文由Coder-pig编写,想了解其他内容,可见CoderPig's Android Study Note--目录 ...

  6. 网络抓包工具Charles的介绍与使用

    在复杂的App开发过程中,我们会涉及各种复杂的网络操作,各种API的调用和数据接收.如果我们只是通过控制台来查看网络的输入输出,就会非常麻烦.在Mac上有一款非常优秀的网络抓包工具--Charles, ...

  7. 02Tcpdump命令详解-网络抓包工具

    1.概述 今天我们要介绍的是一款网络抓包工具tcpdump,重点讨论并介绍一些有用的命令及最佳实践. tcpdump是一个功能最强大,应用最广泛的命令行数据包嗅探器或包分析工具,用于抓取或过滤制定接口 ...

  8. Wireshark网络抓包工具的使用

    实验目的 1.熟悉网络监听的原理与技术,实现捕捉HTTP等协议的数据包,以理解TCP/IP协议中协议的数据结构,通过实验了解HTTP等协议明文传输的特性. 2.熟悉Wireshark软件的使用,加深对 ...

  9. 基于原始套接字(raw socket)的网络抓包工具

    基于raw socket的网络抓包工具 1. 原始套接字(raw socket)简介 原始套接字可以接收本机网卡上的数据帧或者数据包,利用raw socket可以编写基于IP协议的程序.一般的TCP/ ...

最新文章

  1. Android:面试官死亡问答,如何优化一个网络请求?大牛多个网络优化方案帮你解决!
  2. BDC创建物料主数据各个视图
  3. 北斗导航 | GPS原理与接收机设计——琉璃剑(GPS概述)
  4. linux 定时器中断 imx,NXP iMX8 存储性能测试
  5. python 使用标准库连接linux实现scp和执行命令
  6. day36 Pyhton 网络编程03
  7. Java CharArrayWriter size()方法与示例
  8. Node.js 使用 JWT 进行用户认证
  9. 土拍熔断意味着什么_火爆!楼面价14615元/㎡,土拍过后房价涨,常州买房正当时!...
  10. JDBC衔接DB2、Oracle、MySQL、PostgreSQL
  11. HashMap与LinkedHashMap的结构对比
  12. 手机b站封面提取网站_手机b站封面自定义图片大全及获取bilibili视频封面提取网站网址...
  13. 小程序AppID当前开发者未绑定此AppId,请到小程序管理后台操作后重试
  14. 刘文智《产品经理深入浅出》培训课程笔记
  15. Centos Piranha安装过程
  16. 将活跃天数转化为等级,输入等级查询活跃天数
  17. n918st能刷Android5吗?,中兴 N918st(V5S 双4G版)获取Root权限服务含精简系统方案
  18. python字符串常见方法
  19. NMOS和PMOS管 电流方向和应用电路
  20. 正数的平均值(调用函数)

热门文章

  1. php怎么做一键收获,↗ 点这里,一键收获你的研会手写跨年寄语
  2. 加载3dtile研究
  3. android与h5交互设计,H5与native交互之JSBridge
  4. XML无法解析大字段数据、富文本数据
  5. 电脑象棋开发-网上资料
  6. 【BUG】解决80端口被占用
  7. 冰桶挑战,逻辑思维玩出了互联网的味道
  8. Netscaler SSL 证书更换记录
  9. SaaS 网站设计获客之道-框架篇
  10. 幼儿园计算机教学计划,幼儿园大班计算机教学计划