【python--教程】使用Raw_socket构建一个数据包捕捉器

前言

实际上网络并没有我们想想的那么神秘,本篇教程希望能够帮助各位学习socket编程的同学明白数据包的构成。
博主先讲以下数据包的结构
|--------ip头---------|----------tcp头---------|-----------data---------|
|-------20字节------|---------20字节--------|------剩余的字节----|
比如说我收到一个50字节的数据包,那么这个数据包实际上的分布是
20字节的IP头 + 20字节的TCP头 + 10字节的数据内容

IP头和TCP头是使用struct压缩过的,需要先使用struct.unpack按对应格式解压缩。
IP头的压缩格式是 !BBHHHBBH4s4s,tcp的压缩格式是:!HHLLBBHHH
解压缩的代码是:struct.unpack(’!BBHHHBBH4s4s’,ip_header)
解压缩后会以元组的形式弹出IP头,如:(69, 0, 797, 64692, 0, 55, 6, 28236, b'\xdd\xb57\x10', b'\xc0\xa8\x00h')
各部分对应是:

源数据如下:
(69,           0,          797,       64692,   0,           55,         6,             28236,    b'\xdd\xb57\x10',  b'\xc0\xa8\x00h')
内容如下:
(IP版本和头长度,区分服务标记,数据包总长度,数据包id,数据包的标志位,数据包的TTL值,上层协议的编号,IP头校验和,源IP,                目的IP)

各部分的组成如下:

IP版本和头长度 : 4位的版本号加上4位的IP头长度的二进制拼接在一起的十进制数字,例如:ipv4 头长度的二进制是 0100,20字节的头长度的二进制是 0101 ,拼接在一起就是 01000101 ,转换位十进制就是69
区分服务标记:6字节的区分服务代码点,默认为0,也就是 000000。2字节的显式拥塞通知,一般是0,也就是00。拼接在一起就是 00000000,转换为十进制值就是0。
数据包总长度:就是字面上的意思。IP头的长度加上TCP的头长度再加上数据的长度。
数据包id:应该是这个十进制数据的十六进制表现形式才是。例如现在的64692,实际上应该是0xfcb4
数据包的标志为:一字节的保留位,默认为0。一字节的是否分段标记,通常为0。一字节的更多片段标记,通常为0。13字节的片段偏移标记,通常为0。拼接在一起就是 0000 0000 0000 0000 。转换成十进制就是0。
数据包TTL值:就是字面上的十进制值。
上层协议的编号:6代表TCP。
IP头校验和:这个实际上是十六进制的。当前的28236代表0x6e4c
源IP:使用socket.inet_aton转换过的IP地址,使用socket.inet_ntoa可以逆向转换回去,当前的b'\xdd\xb57\x10'转换回去是'221.181.55.16'
目的IP:使用socket.inet_aton转换过的IP地址,使用socket.inet_ntoa可以逆向转换回去,当前的b'\xc0\xa8\x00h'转换回去是'192.168.0.104'

至于数据,就是二进制的使用.decode()函数进行转换就行
本次教程仅讲解解析IP头和数据部分。
使用raw_socket需要管理员权限,请各位读者练习时使用管理员权限启动编辑器

代码教程

from socket import * #导入socket模块
from struct import *  #导入解包加密包的模块

#创建一个IPv4的使用IP的原始套接字
#通过修改IPPROTO_IP可以使用不同的协议
#例如使用ICMP的是:socket = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP)
s = socket(AF_INET,SOCK_RAW,IPPROTO_IP)

#绑定使用的网卡
s.bind(('192.168.0.104',0))

#设置套接字选项是IP的,使用套接字选项IP_HDRINC,1
s.setsockopt(IPPROTO_IP,IP_HDRINCL,1)

#设置混杂类型,必须是单张网卡
s.ioctl(SIO_RCVALL, RCVALL_ON)

#捕捉四个包
for a in range(1,5):
	#捕捉数据包,去除socket模块添加的源IP 地址
    data =  s.recvfrom(65535)[0]
    print ('数据总长度:'+str(len(data))+'字节')


    #提取去除IP头20字节和TCP头20字节剩余的数据内容    
    msg = data[40:]
    
    #使用!BBHHHBBH4s4s解析IP头
    ip_header = unpack('!BBHHHBBH4s4s',data[0:20])
    
    #将十六进制的源ip地址转换成十进制的源IP    
    s_addr = inet_ntoa(ip_header[-2])

    #将十六进制的目的IP转换为十进制的目的IP
    d_addr = inet_ntoa(ip_header[-1])

    #提取版本及头长度
    v_h = '{:0>8}'.format(str(bin(ip_header[0]))[2:])
    version = int('0b'+v_h[0:4],2)


    #提取数据包的区分服务位
    deff = hex(ip_header[1])

    #获取数据包总长度
    total_len = ip_header[2]

    #获取数据包的标识
    data_id = ip_header[3]

    #获取标志位
    flag = ip_header[4]

    #获取数据包的TTL值
    ttl = ip_header[5]

    #获取上层协议
    proto = ip_header[6]

    #获取头校验和
    head_checksum = hex(ip_header[7])
    
    print ('源IP是:'+ str(s_addr))
    
    print ('目的IP是:'+str(d_addr))

    print ('IP包的版本是:'+str(version))

    print ('ip包的区分服务标记是:'+str(deff))
    
    print ("数据包的长度是:"+str(total_len))

    print ('数据包的id是:'+str(data_id))

    print ('数据包的标志位是:'+str(flag))

    print ('数据包的TTL值是:'+str(ttl))

    print ('上层协议的编号是:'+str(proto))

    print ('IP头的校验和是:'+str(head_checksum))

    try:
        print ('数据内容是:'+msg.decode())
    except:
        print ('数据包的内容是:',end='')
        print (msg)
    print ("\n\n\n")

运行的效果是:

数据总长度:60字节
源IP是:114.114.114.114
目的IP是:192.168.0.104
IP包的版本是:4
ip包的区分服务标记是:0x4
数据包的长度是:60
数据包的id是:45829
数据包的标志位是:0
数据包的TTL值是:72
上层协议的编号是:1
IP头的校验和是:0x19c3
数据内容是:mnopqrstuvwabcdefghi




数据总长度:52字节
源IP是:192.168.0.104
目的IP是:60.205.236.78
IP包的版本是:4
ip包的区分服务标记是:0x0
数据包的长度是:52
数据包的id是:26998
数据包的标志位是:16384
数据包的TTL值是:64
上层协议的编号是:6
IP头的校验和是:0xe721
数据包的内容是:b'\x02\x04\x05\xb4\x01\x03\x03\x08\x01\x01\x04\x02'




数据总长度:41字节
源IP是:192.168.0.104
目的IP是:211.142.196.174
IP包的版本是:4
ip包的区分服务标记是:0x0
数据包的长度是:41
数据包的id是:5330
数据包的标志位是:16384
数据包的TTL值是:64
上层协议的编号是:6
IP头的校验和是:0xccaf
数据内容是:
发布了83 篇原创文章 · 获赞 276 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/qq_43017750/article/details/100857055