一、前言

本篇我们会使用Python从HTTP流量中提取出图片文件,然后使用OpenCV对它们进行处理,尝试识别出带有人脸的图像,以此来锁定我们可能感兴趣的内容。至于流量数据包,可以百度搜索帅哥美女图片,有人脸的图片就会一抓一大把,再使用wireshark之类的工具来抓取等等,方式很多,就不介绍了。

对于这个需求,我们会分为两步来执行,第一步是从HTTP流量中提取图片,并将它们保存到磁盘上;第二步是分析每一张图片以判断里面是否会出现人脸,如果图片里面确实有人脸, 就用一个方框把其中的人脸圈出来,并单独保存(另存为)。

二、代码实例

代码中已有明确注释,因此不再做单独的逻辑解释。

第一段代码,提取图片,命名为:recapper.py

#!/usr/bin/python
#-*- coding:utf8 -*-

from scapy.all import *
import collections
import os
import re
import sys
import zlib

# 指定保存图片的目录和pcap文件的路径
OUTDIR = '/home/kali/D/pictures'
PCAPS = '/home/kali/D'
# Response命名元组,数据包头header,载荷payload
Response = collections.namedtuple('Response',['header','payload'])

# 获取数据包头(读取原始HTTP流量,将数据头单独切出来)
def get_header(payload):
    try:
        # 从数据包开头往下找两个连续的\r\n,把这整段数据切出来
        header_raw = payload[:payload.index(b'\r\n\r\n')+2]
    except ValueError:
        # 如果不存在就报异常,并输出符号“-”
        sys.stdout.write('-')
        sys.stdout.flush()
        return None
    # 如果没发生异常,就将HTTP头中的每一行以冒号分割,冒号左边是字段名,右边是字段值,然后存储在header字典中
    # 这里由于运行报错的原因(utf-8编码报错),使用了ISO-8859-1编码
    header = dict(re.findall(r'(?P<name>.*?): (?P<value>.*?)\r\n', header_raw.decode('ISO-8859-1')))
    # 如果HTTP头中没有名为Content-Type的字段,就返回None
    if 'Content-Type' not in header:
        return None
    return header

# 提取数据包内容,image是我们想提取的数据类型的名字
def extract_content(Response, content_name = 'image'):
    content, content_type = None, None
    # 含有图片的响应包,数据头的Content-Type会有image标识
    if content_name in Response.header['Content-Type']:
        # 将数据头中指定的实际数据类型保存下来
        content_type = Response.header['Content-Type'].split('/')[1]
        # 将HTTP头之后的全部数据保存下来
        content = Response.payload[Response.payload.index(b'\r\n\r\n')+4:]
        # 如果数据被gzip或deflate之类的工具压缩过,就调用zlib来解压
        # 这里使用Content-Encoding报错,因此使用Accept-Encoding
        if 'Accept-Encoding' in Response.header:
            # if Response.header['Content-Encoding'] == 'gzip':
            if Response.header['Accept-Encoding'] == 'gzip':
                content = zlib.decompress(Response.payload, zlib.MAX_WBITS | 32)
            elif Response.header['Accept-Encoding'] == 'deflate':
                content = zlib.decompress(Response.payload)
    return content, content_type

# 重构在数据包中出现过的图片
class Recapper:
    # 将要读取的pcap文件路径传给它
    def __init__(self,fname):
        pcap = rdpcap(fname)
        # TCP数据流保存到字典里
        self.sessions = pcap.sessions()
        self.responses = list()

    # get_responses的作用是从pcap文件中遍历读取响应数据
    def get_responses(self):
        # 遍历整个sessions字典中的每个会话
        for session in self.sessions:
            payload = b' '
            # 遍历每个会话中的每个数据包
            for packet in self.sessions[session]:
                try:
                    # 过滤数据,只处理发往80或者从80端口接收的数据
                    if packet[TCP].dport == 80 or packet[TCP].sport == 80:
                        # 把所有读取到的数据载荷做拼接
                        # 相当于wireshark中右键单击一个数据包,点击“Follow TCP Stream”
                        payload += bytes(packet[TCP].payload)
                except IndexError:
                    # 如果报错就打印一个“x”
                    sys.stdout.write('x')
                    sys.stdout.flush()
            # 如果payload有数据,就将其交给get_header函数解析
            if payload:
                header = get_header(payload)
                if header is None:
                    continue
                # 把构造出的response对象附加到responses列表中
                self.responses.append(Response(header=header,payload=payload))

    # write的作用是把在响应数据中找到的图片写入到输出目录里
    def write(self,content_name):
        # 遍历所有responses响应
        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'Writing {fname}')
                # 将内容写到一个文件里
                with open(fname, 'wb') as f:
                    f.write(content)

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

第二段代码,分析图片,命名为:detector.py

#!/usr/bin/python
#-*- coding:utf8 -*-

import cv2
import os

ROOT = '/home/kali/D/pictures'
FACES = '/home/kali/D/faces'
TRAIN = '/home/kali/D/training'

# 检查每张图片并确认里面是否有人脸,对于有人脸的图片
# 在人脸周围画一个方框,然后存储为另一张新图片
def detect(srcdir=ROOT, tgtdir=FACES, train_dir=TRAIN):
    for fname in os.listdir(srcdir):
        # 图片可能是“JPG”、“PNG”、“JPEG”等,需要一类一类去验证
        if not fname.upper().endswith('.JPEG'):
            continue
        fullname = os.path.join(srcdir, fname)
        newname = os.path.join(tgtdir,fname)
        # 使用OpenCV的计算视觉库cv2来读取图片
        img = cv2.imread(fullname)
        if img is None:
            continue
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        # 加载人脸检测器的xml文件配置
        # 下载地址:http://eclecti.cc/files/2008/03/haarcascade_frontalface_alt.xml
        training = os.path.join(train_dir, 'haarcascade_frontalface_alt.xml')
        # 创建cv2的面部检测对象
        cascade = cv2.CascadeClassifier(training)
        rects = cascade.detectMultiScale(gray, 1.3, 5)
        try:
            # 如果包含人脸,就返回一个长方形的坐标,对应图片的人脸区域
            if rects.any():
                print(f'Got a face {fname}')
                # 人脸区域坐标
                rects[:, 2:] += rects[:, :2]
        except AttributeError:
            continue

        for x1, y1, x2, y2 in rects:
            # 在人脸周围画一圈绿色方框
            cv2.rectangle(img,(x1, y1),(x2, y2),(127,256,0),2)
        # 将画了方框的新图片存储到指定目录中
        cv2.imwrite(newname, img)

if __name__ == '__main__':
    detect()

三、运行测试

首先安装OpenCV(人脸识别算法所需的文件):

apt-get install libopencv-dev python3-opencv python3-numpy python3-scipy

其次下载人脸识别算法的训练文件:

wget http://eclecti.cc/files/2008/03/haarcascade_frontalface_alt.xml

运行代码前,我们需要仔细阅读代码,尤其注意那几个文件路径,将对应的文件放到对应的文件夹中,或者自行修改代码中的文件路径。

运行代码:

python recapper.py

可见已经将数据流量包的图片提取到pictures文件夹中了。

python解析MySQL binlog python解析pfcp数据包_安全

之后运行代码:

python detector.py

可见已经将人脸图片单独存储在faces文件夹中了。

python解析MySQL binlog python解析pfcp数据包_HTTP_02


打开其中一个,可见已经额为人脸画了圈。

python解析MySQL binlog python解析pfcp数据包_python_03