python 利用Raw Socket进行以太网帧嗅探

1. Raw Socket基础

  • 提供了一种方法来绕过整个网络堆栈遍历和直接将以太网帧输送到一个应用程序。

  • 有很多种方法来创建raw sockets,例如AF_PACKET,PF_PACKET。这里使用PF_PACKET,它是linux系统上才有的选项,如果是windows或者是mac的系统的话,可以使用AF_PACKET。

技术分享

1.1 PF_SOCKET

  • 在链路层接收和发送包得应用接口。
  • 所有接收到的包都包含完整的头部和数据部分。
  • 所有发送的包都会由内核无更改的传递到媒介中。
  • 支持使用Berkley Packet 过滤。(更详细信息可参考:http://howtounix.info/man/FreeBSD/man4/bpf.4

2. 创建Raw Socket

2.1 理解Packet Header

  下图为以太网帧头部和IP头部格式示意图:

技术分享

  以太网帧的头部有14字节,前6字节为目的mac地址,后6字节为源mac地址,之后2字节为内部协议的类型,比如IP协议的类型为0x0800。关于各种协议的类型可在/usr/include/linux/if_ether.h文件中查看(linux系统)。往里一层是IP等网络层协议,IP层内部包含TCP、UDP等传输层协议,再往里就是应用层协议,如HTTP、ssh等。
  图中最下侧是IP的头部格式,最重要的部分是前20字节。

2.2 提取二进制数据到变量中

技术分享

  • 使用struct模块中的unpack()函数。
  • 返回的时tuple格式。(tuple的更详细内容可参考:廖雪峰讲的python中tuple部分)
  • 第一个字符标明字节序。
    • 网络字节序通常是大端。

举个例子, 终端下输入python,然后进行一下操作:

>>> import struct
>>>
>>>
>>> struct.pack(“B”,1)
‘\x01’
>>> struct.pack(“H”,1)
‘\x01\x00’
>>> struct.pack(“>H”,1)
‘\x00\x01’
>>> struct.pack(“!H”,1)
‘\x00\x01’
>>> struct.pack(“!L”,1)
‘\x00\x00\x00\x01’
>>> exit()

2.3 raw Socket嗅探

主要思路:
  

  • 使用Socket模块。

  • 读取接收到的包。

  • 解释和分析包。

  • 也可以发送应答

linux下打开终端,进入python环境(raw_socket需要root权限,输入命令是要以sudo python命令进入。),依次按一下方式操作:

jeanphorn@ubuntu:~/workspace/python$ sudo python
[sudo] password for jeanphorn:
Python 2.7.3 (default, Apr 20 2012, 22:44:07)
[GCC 4.6.3] on linux2
Type “help”, “copyright”, “credits” or “license” for more information.
>>>
>>>
>>>
>>> import socket
>>> import struct
>>> import binascii
>>>
>>>
>>> rawSocket = socket.socket(socket.PF_PACKET,socket.SOCK_RAW,socket.htons(0x0800))
>>>
>>> pkt = rawSocket.recvfrom(2048)
>>> pkt
(“\x08\x00’5BY@l\x8f.\xd5\xc4\x08\x00E\x10\x004W\x06@\x00@\x06_\xf0\xc0\xa8\x01_\xc0\xa8\x01\x0e\xf0u\x00\x16\xa9^i\x03\xe3\xa0V\xb4\x80\x10\x0f\xfeGr\x00\x00\x01\x01\x08\n\x1e\xd3\x0f\xc7\x00\x00/\xb2”, (‘eth0’, 2048, 0, 1, ‘@l\x8f.\xd5\xc4’))
>>> ethernetHeader = pkt[0][0:14]
>>>ethernetHeader
“\x08\x00’5BY@l\x8f.\xd5\xc4\x08\x00”
>>> eth_hdr = struct.unpack(“!6s6s2s”,ethernetHeader)
>>> eth_hdr = struct.unpack(“!6s6s2s”,ethernetHeader)
>>> eth_hdr
(“\x08\x00’5BY”, ‘@l\x8f.\xd5\xc4’, ‘\x08\x00’)
>>> binascii.hexlify(eth_hdr[0])
‘080027354259’
>>> binascii.hexlify(eth_hdr[1])
‘406c8f2ed5c4’
>>> binascii.hexlify(eth_hdr[2])
‘0800’
>>> ipHeader = pkt[0][14:34]
>>> pkt
(“\x08\x00’5BY@l\x8f.\xd5\xc4\x08\x00E\x10\x004W\x06@\x00@\x06_\xf0\xc0\xa8\x01_\xc0\xa8\x01\x0e\xf0u\x00\x16\xa9^i\x03\xe3\xa0V\xb4\x80\x10\x0f\xfeGr\x00\x00\x01\x01\x08\n\x1e\xd3\x0f\xc7\x00\x00/\xb2”, (‘eth0’, 2048, 0, 1, ‘@l\x8f.\xd5\xc4’))
>>> ip_hdr = struct.unpack(“!12s4s4s”,ipHeader)
>>> ip_hdr
(‘E\x10\x004W\x06@\x00@\x06_\xf0’, ‘\xc0\xa8\x01_’, ‘\xc0\xa8\x01\x0e’)
>>> socket.inet_ntoa(ip_hdr[1])
‘192.168.1.95’
>>> socket.inet_ntoa(ip_hdr[2])
‘192.168.1.14’
>>> tcpHeader = pkt[0][34:54]
>>> tcp_hdr = struct.unpack(“!HH16s”,tcpHeader)
>>> tcp_hdr
(61557, 22, ‘\xa9^i\x03\xe3\xa0V\xb4\x80\x10\x0f\xfeGr\x00\x00’)


3. 完整的python代码

  将以上代码写在一个packetSniffer.py的文件中,运行时要加上sudo ./packetSniffer.py

#!/usr/bin/env python
# _*_ coding=utf-8 _*_

import socket
import struct
import binascii

rawSocket = socket.socket(socket.PF_PACKET,socket.SOCK_RAW,socket.htons(0x0800))

pkt = rawSocket.recvfrom(2048)

ethernetHeader = pkt[0][0:14]   #提取以太网帧头
eth_hdr = struct.unpack("!6s6s2s",ethernetHeader) #6字节目的mac地址,6字节源mac地址,2字节协议类型

binascii.hexlify(eth_hdr[0])
binascii.hexlify(eth_hdr[1])
binascii.hexlify(eth_hdr[2])

ipHeader = pkt[0][14:34]        #提取IP协议头,不包含option和padding字段。
ip_hdr = struct.unpack("!12s4s4s",ipHeader)         # !标示转换网络字节序,前12字节为版本、头部长度、服务类型、总长度、标志等其他选项,后面的两个四字节依次为源IP地址和目的IP地址。

print "source IP address: " + soket.inet_ntoa(ip_hdr[1])

print "destination IP address: " + soket.inet_ntoa(ip_hdr[2])

tcpHeader = pkt[0][34:54]
tcp_hdr = struct.unpack("!HH16s",tcpHeader)

print tcp_hdr

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。