网络安全编程——Python编写基于UDP的主机发现工具(解码IP header)

网络嗅探是网络安全分析的基础手段。本文基于Python原始socket实现网络嗅探器,完成IP报文头的结构解析,详细说明代码逻辑与实现原理,并通过实际抓包效果验证功能。

  • 文章涵盖原理讲解、代码实现、效果演示等内容,帮助理解网络数据包的底层结构与嗅探技术的实现方式,适用于网络安全学习与实践参考。

文章目录


前情提要

从本篇开始,我们要着手开始编写一个流量嗅探器:

嗅探工具的主要目标:是发现目标网络里存活的主机。攻击者希望能找出网络里的所有潜在目标,以便有针对性地开展侦察和渗透;

原理讲解

UDP请求的过程:

  • 主机存活:当我们向主机发送一个UDP数据包时,如果主机上的UDP端口没有开启,一般会返回一个ICMP包来提示目标端口不可访问。

  • 主机不存在:如果主机根本不存在的话,我们应该不会收到任何信息

    • 前提是:选中的UDP端口没有被使用,所以未来避免这种情况,我们应该尽可能多的探测多个端口;
  • UDP的优势:在整个子网里滥发UDP数据包并等待对方回复ICMP消息的开销很小;

    • 主要的工作就是解码并分析各种网络协议的数据头

原始socket嗅探器

这里我们先编写一个最简单的socket嗅探器:只能读取一个数据包

python 复制代码
import os
import socket

# 定义了需要监听的主机 IP 地址
host = '192.168.1.10'

def main():
    # 定义所需的参数socket_protocol
    if os.name == 'nt':
        socket_protocol = socket.IPPROTO_IP
    else:
        socket_protocol = socket.IPPROTO_ICMP

    # 创建socket对象,并绑定端口
    sniffer = socket.socket(socket.AF_INET,socket.SOCK_RAW,socket_protocol)
    sniffer.bind((host,0))

    # 抓包时,包含IP头;
    # socket.IP_HDRINCL=1 时,代表包含IP头
    sniffer.setsockopt(socket.IPPROTO_IP,socket.IP_HDRINCL,1)

    # 打开混杂模式,监听所有数据包(但是该程序只能读取一个)
    if os.name == 'nt':
        sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_ON)

    # 读取收到的数据包
    print(f"Receive data: {sniffer.recvfrom(65535)}")

    # 如果是Windows,记得关闭混杂模式
    if os.name == 'nt':
        sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_OFF)

if __name__ == '__main__':
    main()

代码解释

  • HOST = '192.168.44.142': 定义了需要监听的主机 IP 地址。
  • 判断操作系统 : 脚本首先检查了 os.name 是否为 'nt'(代表 Windows 系统)。
    • 如果是 Windows,则协议设置为 socket.IPPROTO_IP,这意味着它可以嗅探所有的 IP 数据包。
    • 如果是 Linux/Unix,则设置为 socket.IPPROTO_ICMP。因为在 Linux 上捕获所有 IP 数据包通常需要更复杂的设置,这里作为基础脚本,后退了一步只去捕获 ICMP(例如 Ping 请求)数据包。

(1)创建并绑定原始套接字 (Raw Socket)

  • sniffer = socket.socket(...):这里创建了一个原始套接字SOCK_RAW
    • 普通的套接字(如 TCP/UDP)会由操作系统自动处理网络头信息,而原始套接字允许程序直接访问底层的网络数据包。
    • sniffer.bind((HOST, 0)):将该套接字绑定到之前设定的公开网卡 IP 上。
    • 端口指定为 0,因为原始套接字关注的是网络层协议,而不是应用层端口。

(2) 配置捕获 IP 头部信息

  • sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
    • IP_HDRINCL (IP Header Include) 标志位设为 1,是在告诉操作系统:"在返回给我的数据中,请包含完整的 IP 数据包头部信息(源IP、目标IP、协议类型等)",而不仅仅是数据内容本身。

(3)开启网卡的混杂模式 (Promiscuous Mode) - 仅限 Windows

  • sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON):默认情况下,网卡只会接收发往自己 MAC 地址的数据包。
  • 开启"混杂模式"后,网卡会接收局域网内流经它的所有数据包,不管是不是发给它的。这一步是通过底层的 I/O 控制 (IOCTL) 命令实现的。

(4)读取数据包
recvfrom(65565):调用 recvfrom 方法接收数据。65565 是缓冲区大小,足够容纳目前最大可能的 IP 数据包。

注意:运行此类涉及原始套接字和混杂模式的代码,通常需要管理员权限 (Win)Root 权限 (Linux)

效果演示

这里我用Windows进行测试:

未开启"管理员权限"执行:

用管理员执行:

这里我们打开另一个cmd窗口,访问baidu.com,成功抓到第一关握手包:

bash 复制代码
作用:正在向一个 HTTPS 网站发起连接请求(TCP SYN)
↓
访问某个加密网站
↓
发起 TCP三次握手 的第一个包

这里我们成功编写了最简单的一个socket抓包嗅探器;

但是不觉得少了很多东西吗:

  • 解码IP头,得到 源 / 目的 地址端口
  • 解码ICMP包,判断目标主机是否存活

所以接下来我们需要做的就是学会如何解码 IP头 以及 ICMP包,并将它们打印出来;


解码IP头结构(Python实现)

虽然当前我们的嗅探器可以捕获到TCP、UDP、ICMP等任何高层协议的IP头,但里面的信息是以二进制形式封装的:

(是不是很难读懂?)

所以我们需要讲这些数据转化为人类看得懂的参数,这里需要使用到struct库 来帮助我们解析:

典型IPv4头结构

但在此之前,我们需要了解IP头的基本构成:

这里我们的目的:提取协议类型源IP地址目的IP地址等信息;

python 复制代码
import ipaddress
import struct

class IP:
    def __init__(self,buff=None):
        # 使用 struct 模块解析前 20 个字节的 IP 头
        # '<' 代表小端序,'B'代表1字节无符号整数,'H'代表2字节,'4s'代表4字节字符串
        header = struct.unpack('<BBHHHBBH4s4s',buff[0:20])

        # 通过位运算提取版本号 (Version) 和头部长度 (IHL)
        self.ver = header[0] >> 4
        self.h_len = header[0] & 0xF

        self.server_type = header[1]
        self.len = header[2]
        self.id = header[3]
        self.offset = header[4]
        self.ttl = header[5]
        self.protocol_num = header[6]
        self.sum = header[7]
        self.src = header[8]
        self.dst = header[9]

        # 将二进制的源/目的 IP 转换为人类可读格式 (如 192.168.10.1)
        self.src_address = ipaddress.ip_address(self.src)
        self.dst_address = ipaddress.ip_address(self.dst)

        # # 协议号映射表
        self.protocol_map = {1:"ICMP",6:"TCP",17:"UDP"}

        try:
            self.protocol = self.protocol_map(self.protocol_num)
        except KeyError:
            self.protocol = str(self.protocol_num)

# 接下来的代码,就是之前的原始socket嗅探器的功能了
def sniff(host):
    if os.name == 'nt':
        socket_protocol = socket.IPPROTO_IP
    else:
        socket_protocol = socket.IPPROTO_ICMP

    # 创建socket对象,并绑定端口
    sniffer = socket.socket(socket.AF_INET,socket.SOCK_RAW,socket_protocol)
    sniffer.bind((host,0))

    # 抓包时,包含IP头;
    # socket.IP_HDRINCL=1 时,代表包含IP头
    sniffer.setsockopt(socket.IPPROTO_IP,socket.IP_HDRINCL,1)

    # 打开混杂模式,监听所有数据包(但是该程序只能读取一个)
    if os.name == 'nt':
        sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_ON)

    # 读取收到的数据包
    print(f"[*] 正在 {host} 上嗅探流量.... ")

    try:
        while True:
            # 读取一个数据包
            raw_buffer = sniffer.recvfrom(65535)[0]

            # 解析IP头
            ip_header = IP(raw_buffer[0:20])

            print(f"Protocol:{ip_header.protocol} | {ip_header.src_address} --> {ip_header.dst_address}")

    except KeyboardInterrupt as e:
        print(f"[*] 用户中止嗅探....")

        # 关闭嗅探模式,如果Windows
        if os.name == 'nt':
            sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_OFF)

        sys.exit()

if __name__ == "__main__":
    # 这里必须填你运行代码的本机的真实局域网 IP
    host_ip = '192.168.1.10'
    sniff(host_ip)

代码解释:

  • SOCK_RAW(原始套接字)
    • 作用: 原始套接字允许我们直接读取和伪造 IP 报文头(包含源 IP、目的 IP、TTL 存活时间等)
  • struct.unpack(解码器的核心):
    • 作用:根据上图,知道 IP 报文头固定是 20 个字节,且规定了第几个字节代表什么(比如前 4 个 bit 代表 IPv4 还是 IPv6)
  • socket.IP_HDRINCL:一般情况下,操作系统会自动把 IP 报文头部剥离掉;
    • 标志位设为 1,是在告诉操作系统:"在返回给我的数据中,请包含完整的 IP 数据包头部信息;

1byte(字节) = 8bit

代码变量 对应 IP 头字段 字节位置
self.ver / self.h_len 版本 / IHL 0
self.server_type 服务类型 1
self.len 总长度 2-3
self.id 标识 4-5
self.offset 标志 + 段偏移 6-7
self.ttl TTL 8
self.protocol_num 协议 9
self.sum 头校验和 10-11
self.src 源 IP 12-15
self.dst 目的 IP 16-19

效果演示

这里我们执行代码:

  • TCP 流量:你电脑正在和公网服务器建立 / 维持加密网页(HTTPS)连接
  • UDP 流量:你电脑正在进行 DNS 查询、音视频 / 游戏等无连接通信
  • 双向通信:每一组请求都对应了服务器的响应,符合网络通信的基本原理

当然,如果想看ICMP包的话,直接ping jd.com 即可:

总结

因为我们没有深入地解码数据包的内容,所以这里只能猜测整个数据流的含义。

但整体来说,还是实现了基本功能;下一篇我们就要实现ICMP包解析等功能了;

期待下次再见;

相关推荐
北冥有羽Victoria4 小时前
OpenCLI 操作网页 从0到1完整实操指南
vscode·爬虫·python·github·api·ai编程·opencli
handsomestWei4 小时前
scikit-learn数据预处理模块
python·机器学习·scikit-learn
北京耐用通信4 小时前
不换设备、不重写程序:耐达讯自动化网关如何实现CC-Link IE转Modbus TCP的高效互通?
人工智能·科技·物联网·网络协议·自动化·信息与通信
w_t_y_y4 小时前
机器学习常用的python包(二)工具箱scikit-learn
python·机器学习·scikit-learn
上海云盾商务经理杨杨4 小时前
WAF绕过技巧与防护加固:攻防实战,彻底封堵Web安全漏洞
安全·web安全
用户8356290780514 小时前
Python 自动拆分 Word 文档教程:按分节符与分页符处理
后端·python
陈天伟教授4 小时前
心电心音同步分析-案例:原型设计一
开发语言·人工智能·python·语言模型·架构
我的xiaodoujiao4 小时前
API 接口自动化测试详细图文教程学习系列9--Requests模块
python·学习·测试工具·pytest
liweiweili1264 小时前
http数据传输过程数据编码解码问答
网络协议·http·状态模式