Scapy 解析 pcap 文件从HTTP流量中提取图片

news2024/11/19 7:34:09

Scapy 解析 pcap 文件从HTTP流量中提取图片

      • 前言
      • 一、网络环境示例
      • 二、嗅探流量示例
      • 三、pcap 文件处理
      • 最后
      • 参考

​ 作者:高玉涵

​ 时间:2023.9.17 10:25

​ 环境:Linux kali 5.15.0-kali3-amd64,Python 3.11.4,scapy 2.5.0

只有在拿到一些数据之后事情才会变得有趣起来。

前言

​ 通常我在网络嗅探与数据包分析中,使用 Wireshark 就可以很方便地浏览 pcap 文件的内容。但当捕获得流量很大或数据包特征不太明显,再或者数据包特征已确定,要从中进一步分析(提取)流量。以往采用人工方式可以说是种恶梦。幸运的是 Philippe Biondi 为 Python 开发的数据包处理库 Scapy 以精巧和令人惊叹,一两行代码就能解决上述问题(功能远远不止如此)。这里我会演示如何借助 Scapy 的 pcap 数据处理能力,从嗅探到的 HTTP 流量中提取图片。

​ 建议你在 Linux 上使用 Scapy,因为它最初就是按照兼容 Linux 来设计的。最新版本的 Scapy 虽然支持 Windows,但是我假设你使用的和我一样的环境。

一、网络环境示例

在这里插入图片描述

图 1-1 网络架构

二、嗅探流量示例

​ 为了感受一下 Scapy 的用法,我先给出用 Wireshark 嗅探的流量,图 2-1 是原始流量样式,图 2-2 为指定一个数据包过滤器(这里是 HTTP)表示要进一步分析的流量。

在这里插入图片描述

图 2-1 原始流量样式

在这里插入图片描述

图 2-2 HTTP 流量样式

三、pcap 文件处理

​ 我会试着从 HTTP 流量中提取出图片文件,为此编写分析 pcap 文件的 recapper.py 程序,定位数据流中出现的所有图片,并把它们保存到磁盘上。在接下来的代码中,我将用到 namedtuple(命名元组),它是 Python 的一种数据结构,可以通过名称来访问某个字段。标准的元组(tuple)是用来存储一串不可变的值的,跟列表(list)差不多,只是没法修改里面的数据。使用标准元组时,要用数字索引来访问其内部的字段:

point = (1.1, 2.5)
print(point[0], point[1])

​ 要命名元组跟标准元组基本相同,唯一的区别是可以通过属性名来访问它的字段。它能让你写出可读性更高的代码,却又比字典(dictionary)

消耗的内存更少。创建命名元组需要两个参数:元组本身的名字,以及由逗号分隔的若干字段名。举个例子,假设你想定义一个名为 Point 的数据结构,它有两个属性:x 和 y。你可以这样定义:

Point = namedtuple('Point', ['x', 'y'])

​ 接着你可以创建一个,比方说叫 p 的 Point 命名元组,p = Point(35, 65),然后使用 p.x 和 p.y 来访问它的 x 和 y 属性,就像是在访问一个类的属性一样。这比使用一堆数字索引来访问标准元组要好懂得多。在接下来的示例代码中,我们将创建一个名为 Response 的命名元组:

Response = namedtuple('Response', ['header', 'payload'])

​ 现在,无须用数字索引,直接用 Response.header 和 Response.payload 就能访问 Response 的成员数据,大大改善了代码的可读性。

​ 接下来是 recapper.py 文件,代码如下:

from scapy.all import TCP, rdpcap
import collections
import os
import re
import sys
import zlib

OUTDIR = '/home/kali/dev/python/scapy/pic'
PCAPS = '/home/kali/dev/python/scapy'

Response = collections.namedtuple('Response', ['header', 'payload'])

def get_header(payload):
    pass

def extract_content(Response, content_name='image'):
    pass


class Recapper:
    def __init__(self, fname):
        pass

    def get_responses(self):
        pass

    def write(self, content_name):
        pass


if __name__ == '__main__':
    pfile = os.path.join(PCAPS, 'face.pcap')
    recapper = Recapper(pfile)
    recapper.get_responses()
    recapper.write('image')

​ 这是整个脚本的主要框架,之后我会往里面填充辅助函数。首先,导入所需的库,然后指定保存图片的目录和 pcap 文件的路径。接着,定义一个名为 Response 的命名元组,它有两个属性:数据包的头(header)和载荷(payload)。我编写两个辅助函数,分别负责获取数据包头和提取数据包内容。这两个函数将用在 Recapper 类里,而这个类负责重构在数据包流中出现过的图片。除了 __init__ 函数外,Recapper 类还有两个函数:get_responses,负责从 pcap 文件中读取响应数据;write,负责把在响应数据中找到的图片写到输出目录里。

​ 现在我开始写 get_header函数:

def get_header(payload):
    try:
        header_raw = payload[:payload.index(b'\r\n\r\n')+2]# 切片前包后不包,+2 是为了将分隔数据行的'\r\n'也含进去
    except ValueError:
        sys.stdout.write('-')
        sys.stdout.flush()
        return None
    
    header = dict(re.findall(r'(?P<name>.*?):(?P<value>.*?)\r\n', header_raw.decode()))
    if 'Content-Type' not in header:
        return None
    return header

get_header函数会读取原始 HTTP 流量,并把 HTTP 头数所单独切出来。我提取 HTTP 头的方式是,从数据包开头一路往下找两个连续的 “\r\n”,把这整段数据切出来。图 3-1 HTTP 报文结构。图 3-2 真实示例。
在这里插入图片描述

图 3-1 HTTP 报文结构

在这里插入图片描述

图 3-2 真实示例

​ 如果拿到的数据里不存在两个连续的 ”\r\n“,就会产生一个 ValueError 异常,这时我只会在屏幕上输出一个横杠(-),然后返回。如果没有发生异常,我就创建一个名为 header 的字典,把 HTTP 头里的每一行以冒号分割,冒号左边的是字段名,右边的是字段值,按这样的方式存进 header 字典里。如果 HTTP 头里面没有名为 Content-Type 的字段(图 3-3 Content-Type 字段),就返回 None,表示数据包里没有我感兴趣的内容。现在写一个函数,从响应数据里提取内容:

def extract_content(Response, content_name='image'):
    content, content_type = None, None
    if content_name in Response.header['Content-Type']:
        content_type = Response.header['Content-Type'].split('/')[1]
        content = Response.payload[Response.payload.index(b'\r\n\r\n')+4:]

        if 'Content-Encoding' in Response.header:
            if Response.header['Content-Encoding'] == 'gzip':
                content = zlib.decompress(Response.payload, zlib.MAX_WBITS | 32)
            elif Response.header['Content-Encoding'] == 'deflate':
                content = zlib.decompress(Response.payload)

    return content, content_type

extract_content函数会接受一段 HTTP 响应数据(Response),以及我想提取的数据类型的名字作为参数。这段响应数据是一个命名元组,里面有两部分:header 和 payload。

​ 如果检测到响应数据被 gzip 或 deflate 之类的工具压缩过,就调用 zlib 库来解压这段数据。任何一个含有图片的响应包,其数据头的 Content-Type 属性里都会有 image 字样(图 3-3)。遇到这种数据头,我就创建一个 content_type 变量,将数据头里指定的实际数据类型保存下来。我还创建另一个变量 content 来保存数据内容,也就是 payload 中 HTTP 头之后的全部数据。最后,将 content 和 content_type 打包成一个元组返回。

在这里插入图片描述

图 3-3 Content-Type 字段

​ 写完这两个辅助函数后,就可以开始编写 Recapper 类了:

class Recapper:
    def __init__(self, fname):
        pcap = rdpcap(fname)
        self.sessions = pcap.sessions()
        self.responses = list()

​ 首先,初始化这个对象,把要读取的 pcap 文件路径传给它。接着我用到 Scapy 的一个美妙功能,自动切分每个 TCP 会话,并保存到一个字典里,字典里面的每个会话都是一段完整的 TCP 数据流。最后,创建一个名为 responses 的空列表,之后我会把在 pcap 文件中读到的响应填进去。

get_responses函数会遍历整个 pcap 文件,将找到的每个单独的 Response 都填入刚才的 responses 列表:

def get_responses(self):
        for session in self.sessions:
            payload = b''
            for packet in self.sessions[session]:
                try:
                    if packet[TCP].dport == 80 or packet[TCP].sport == 80:
                        payload += bytes(packet[TCP].payload)
                except IndexError:
                    sys.stdout.write('x')
                    sys.stdout.flush()

            if payload:
                header = get_header(payload)
                if header is None:
                    continue
                self.responses.append(Response(header=header, payload=payload))

get_responses函数先遍历整个 sessions 字典中的每个会话,以及每个会话中的每个数据包。然后过滤这些数据,只处理发往 80 端口或者从 80 端口接收的数据。接着,把从所有流量中读取到的数据载荷,拼接成一个单独的名为 payload 的缓冲区。这个操作相当于在 Wireshark 中右键单击一个数据包,选择”Follow TCP Stream“选项(图 3-4)。如果没能成功地拼接 payload 缓冲区(最有可能的情况是,数据包里没有出现 TCP 数据),就在屏幕上打印一个 ”x" 然后继续。

在这里插入图片描述

图 3-4 Follow TCP Steram

​ 重组 HTTP 数据后,如果 payload 缓冲区里有数据,就把它交给解析 HTTP 头的函数 get_header,它能帮我逐个检查 HTTP 头的内容。然后,把构造出的 Response 对象附加到 responses 列表里。

​ 最后,遍历整个 responses 列表,如果发现任何含有图片的响应,就用 write 函数将这些图片写到磁盘上:

def write(self, content_name):
        for i, response in enumerate(self.responses):
            content, content_type = extract_content(response, content_name)
            if content and content_type:
                fname = os.path.join(OUTDIR, f'ex_{i}.{content_type}')
                print(f'Writeing {fname}')
                with open(fname, 'wb') as f:
                    f.write(content)

​ 由于我已经提取完所有响应,所以 write 函数只需要遍历这些响应,提取其中的内容,并将内容写到一个文件里就可以了。这个文件会被创建到指定的输出目录里,文件名由 enumerate 函数提供的计数和 content_type 两个值拼接而成,例如 ex_2.jpg 就是一个可能出现的图片文件名。当我运行这个程序时,会创建一个 Recapper 对象,调用它的 get_responses 函数来搜索 pcap 文件中的所有响应,然后将提取出的图片写入磁盘。图 3-5。图 3-6.

在这里插入图片描述

图 3-5 程序执行样式

在这里插入图片描述

图 3-5 提取的图片

最后

​ 当然,你也可以进一步拓展这个程序,让它不仅能从 pcap 文件中提取图片。

参考

  • Scapy
  • HTTP 标头

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1017937.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决Springboot使用Junit测试时对数据库的修改无效

现象 在使用Junit做单元测试的过程中&#xff0c;比如对mybatis的dao进行单元测试&#xff0c;发现对数据库的select操作正常&#xff0c;可以获取数据&#xff0c;但insert、update、delete操作即使运行不报错&#xff0c;仍然不能不能对数据产生修改和插入。 原因和解决 原…

d3dx9_42.dll丢失修复指南,如何修复丢失的d3dx9_42.dll文件

d3dx9_42.dll是DirectX 9的一个动态链接库文件&#xff0c;它是许多游戏和软件的必需组件。如果缺少这个文件&#xff0c;可能会导致程序无法正常运行。本文将详细讲解d3dx9_42.dll的作用以及丢失的原因&#xff0c;并提供5种修复方法。 一、d3dx9_42.dll的作用 1. d3dx9_42.d…

IDEA创建完Maven工程后,右下角一直显示正在下载Maven插件

原因&#xff1a; 这是由于新建的Maven工程&#xff0c;IDEA会用它内置的默认的Maven版本&#xff0c;使用国外的网站下载Maven所需的插件&#xff0c;速度很慢 。 解决方式&#xff1a; 每次创建 Project 后都需要设置 Maven 家目录位置&#xff08;就是我们自己下载的Mav…

操作系统学习笔记-精简复习版

文章目录 操作系统概述1、操作系统2、主要功能3、用户态和内核态4、系统调用 进程管理1、进程和线程2、引入线程的好处3、线程间同步4、进程控制块 PCB5、进程的状态6、进程的通信方式7、进程的调度算法8、僵尸进程&孤儿进程9、死锁 内存管理1、内存碎片2、内存管理3、虚拟…

2023面试知识点一

1、新生代和老年代的比例 默认的&#xff0c;新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 )&#xff0c;即&#xff1a;新生代 ( Young ) 1/3 的堆空间大小。老年代 ( Old ) 2/3 的堆空间大小。其中&#xff0c;新生代 ( …

WebGoat搭建和Yakit学习

环境搭建 jdk版本&#xff1a;openjdk version "17.0.5“ WebGoat版本&#xff1a;webgoat-server-8.1.0.jar 环境不同有很大可能不能搭建成功 运行命令&#xff1a;java -jar webgoat-server-8.1.0.jar --server.port8888 --server.address192.168.142.131 搭建完成后…

SOLIDWORKS Composer位置关键帧的使用

SOLIDWORKS Composer是专业的SOLIDWORKS及3D文件处理的动画制作软件&#xff0c;作为SOLIDWORKS 产品线下的一个明星存在。 SOLIDWORKS Composer几乎可以处理任何SOLIDWORKS的模型文件并将之转化成可以动作的机械动画&#xff0c;可以引用在企业的网站、产品说明书以及工作指导…

卡尔曼滤波(Kalman Filter)原理浅析-数学理论推导-1

目录 前言数学理论推导1. 递归算法2. 数学基础结语参考 前言 最近项目需求涉及到目标跟踪部分&#xff0c;准备从 DeepSORT 多目标跟踪算法入手。DeepSORT 中涉及的内容有点多&#xff0c;以前也就对其进行了简单的了解&#xff0c;但是真正去做发现总是存在这样或者那样的困惑…

springboot和vue:一、cs/bs区别+maven介绍与其仓库配置

cs/bs的区别&#xff1a; ​​ C/S&#xff1a; 1.交互性强&#xff0c;具有安全访问模式&#xff0c;网络流量低&#xff0c;响应速度快&#xff0c; 2.因为客户端负责大多数业务逻辑和UI演示&#xff0c;所以也被称为胖客户端。 3.C/S结构的软件需要针对不同的操作系统开发…

QT之QML开发 锚点布局

QML中经常会用到锚点布局&#xff0c;本篇简单演示一下锚点布局的使用。 目录 1.锚点布局的说明 2.锚点布局三等分窗口 3.锚点布局拆分窗口 4.窗口拖动 5.完整代码 1.锚点布局的说明 2.锚点布局三等分窗口 main.qml import QtQuick 2.15 import QtQuick.Window 2.15 imp…

二叉树的具体原理及实现

文章目录 一.树的专业术语二.二叉树的原理三.常见的二叉树分类1.完全二叉树2.平衡二叉树3.二叉搜索树 四.二叉搜索树算法具体实现五.二叉搜索树具体实现代码 一.树的专业术语 首先先介绍树的专业术语 二.二叉树的原理 二叉搜索树&#xff08;Binary Search Tree&#xff0c…

Java判断时间间隔来决定任务执行

数据库中的任务记录表的某条记录&#xff0c;状态一直无法变更&#xff0c;导致整个任务进程卡住&#xff0c;看了代码如下&#xff0c; 正常情况是要走到这个else里面&#xff0c;执行dockingGroup.setStatus(JobStatus.FAILED.getCode());将任务状态改为失败的 &#xff0c;查…

Windows/Linux(命令、安装包和源码安装)平台各个版本QT详细安装教程

前言 本文章主要介绍了Windows/Linux平台下&#xff0c;QT4&#xff0c;QT5&#xff0c;QT6的安装步骤。为什么要把QT版本分开介绍呢&#xff0c;因为这三个版本&#xff0c;安装步骤都不一样。Windows平台&#xff0c;QT4的Qt Creator&#xff0c;QT库和编译器是分开的&#…

【奇思妙想】【节省磁盘空间】我有一些文件,我不想移动它们,但又想节省磁盘空间,该怎么做呢?

2023年9月17日&#xff0c;周日晚上 今天晚上整理电脑的磁盘空间时&#xff0c;无意间想到了这个事情 我有一个文件夹 这个文件夹里面有很多不常用的文件 但我不想把它们移走或删除&#xff0c;那我怎么才能从中节省磁盘空间呢&#xff1f; 很简单&#xff0c;把这些不常用的…

Maven常见面试题总结

Maven简介 Maven 是一个项目管理和整合工具。Maven 包含了一个项目对象模型 (Project Object Model)&#xff0c;一组标准集合&#xff0c;一个项目生命周期管理系统(Project Lifecycle Management System)&#xff0c;一个依赖管理系统(Dependency Management System)&#x…

JavaScript中的单线程(single-threaded)和异步编程的关系

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 单线程&#xff08;Single-Threaded&#xff09;与异步编程的关系⭐ 单线程的含义⭐ 阻塞与非阻塞⭐ 异步编程⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启…

RK3588 点亮imx586摄像头

一.硬件原理图 mipi摄像头硬件确认点&#xff1a; 1.供电&#xff1a;5V&#xff0c;2.8V&#xff0c;1.2V&#xff0c;1.8V&#xff0c;reset脚&#xff08;硬拉3.3&#xff0c;上电的时候从低到高&#xff09;&#xff0c;pwron脚外接 3.3V。 2,时钟&#xff1a;MCLKOUT是2…

leetcode数组必刷题——二分查找、移除元素、有序数组的平方、长度最小的子数组、螺旋矩阵、螺旋矩阵 II

文章目录 二分查找移除元素有序数组的平方长度最小的子数组螺旋矩阵螺旋矩阵II 二分查找 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0…

Retentive Network: A Successor to Transformer for Large Language Models

论文链接&#xff1a; https://arxiv.org/pdf/2307.08621.pdf 代码链接&#xff1a; https://github.com/microsoft/unilm/tree/master/retnet 引言 transformer的问题就是计算成本太高 RetNet使“不可能三角形”成为可能&#xff0c;同时实现了训练并行性&#xff0c;良好…

VBA技术资料MF57:VBA_自动创建PowerPoint演示文稿

【分享成果&#xff0c;随喜正能量】会因为有情绪而烦闷&#xff0c;也因为没控制情绪而懊悔。莫道幽人一事无&#xff0c;闲中尽有静工夫。情绪就像水&#xff0c;宜疏不宜堵。学会控制情绪&#xff0c;不能把情绪看得过重&#xff0c;也不能一味遏制情绪的产生。倾听所有声音…