(Я работаю над Mac Sierra, Python 2.7.12 и Pycharm IDE).

Я занимаюсь созданием приложения для сниффинга, чтобы извлекать избыточные копии форм отправки или другого трафика Ethernet. Это часть Unhackable Server Project.

Я уже начал проект с pyshark, но я мог получать только заголовки http, а не полезные данные, поэтому мне пришлось сменить тактику.

Одна из моих предыдущих публикаций была посвящена выполнению команд терминала через python. Причина, по которой мне это было нужно, заключалась в том, что я переключился с использования Pyshark для прослушивания моей сети на прямой переход к источнику; tshark. Я просто хотел сделать его более элегантным и при этом получить как можно больше информации.

import os


os.system("tshark  -T fields -e  data.data -e frame.time -w Eavesdrop_Data.pcap > Eavesdrop_Data.txt -F pcap -c 1000")

Мне нужно захватить пакеты, отправляемые на сервер.

Примечание: у вас могут возникнуть проблемы, если вы воспользуетесь им для сбора информации, которая вам не принадлежит.

Когда вы запускаете это, он сохраняет два файла в каталоге, файл Pcap и текстовый файл после захвата 1000 пакетов. Результатом является отметка времени и все данные, которые были захвачены.

Моя цель сейчас - следить за потоком TCP и извлекать файлы из пакетов.

Чтобы проверить пакеты, которые вы сохранили при написании этого кода, я рекомендую вам загрузить Wireshark и предоставить ему root-доступ, запустив это: /Applications/Wireshark.app/Contents/MacOS/Wireshark в вашем терминале.

Я использую эту статью из Codingsec.net в качестве руководства, но мои потребности немного проще и понятнее. Я также должен быть осторожен, потому что код, на который я ссылаюсь, написан на Python 3, а я использую Python 2.

Скачал scapy через терминал. Мне пришлось перезапустить PyCharm, чтобы он перезагрузил интерпретатор и заставил его импортировать в код.

Я начал просматривать справочный код и кое-что перенять.

import os
import pcapy as p
from scapy.all import *
a = " "
os.system("tshark  -T fields  -e frame.time -e  data.data -w Eavesdrop_Data.pcap > Eavesdrop_Data.txt -F pcap -c 1000")

data = "Eavesdrop_Data.pcap"
a = rdpcap(data)

Итак, это почерпнуто из вышеупомянутого кода. Это было просто, чтобы опустить палец на ногу и посмотреть, что именно делают разные модули.

Во-первых, мне пришлось повторно загрузить scapy, по какой-то причине он не загружался должным образом раньше, поэтому я удалил его и переустановил с помощью терминала. Затем мне пришлось загрузить Pcapy, который не мог работать без Dumbnet. Фу! Я целый день занимался поиском и установкой пакетов. Но вскоре у меня это заработало. Результат выглядел так:

Woot! Ошибок нет!

N.B. Я установил dumbnet на свой терминал из git.

Теперь я перешел к следующим нескольким строкам кода ссылки:

from scapy.all import *
a = " "
os.system("tshark  -T fields  -e frame.time -e  data.data -w Eavesdrop_Data.pcap > Eavesdrop_Data.txt -F pcap -c 1000")


data = "Eavesdrop_Data.pcap"
a = rdpcap(data)
sessions = a.sessions()
print sessions

и вывод:

Woot! По-прежнему нет ошибки. Мне нравится использовать команду печати, чтобы проверить, что происходит, и что данные остаются нетронутыми.

Код:

from scapy.all import *
a = " "
os.system("tshark  -T fields  -e frame.time -e  data.data -w Eavesdrop_Data.pcap > Eavesdrop_Data.txt -F pcap -c 1000")

data = "Eavesdrop_Data.pcap"
a = rdpcap(data)
sessions = a.sessions()
for session in sessions:
    print sessions

выход:

Вход:

from scapy.all import *
a = " "
#os.system("tshark  -T fields  -e frame.time -e  data.data -w Eavesdrop_Data.pcap > Eavesdrop_Data.txt -F pcap -c 1000")
#I commented out the t-shark so i could just reuse the same data

data = "Eavesdrop_Data.pcap"
a = rdpcap(data)
sessions = a.sessions()
for session in sessions:
    http_payload = ""
    for packet in sessions[session]:
        print packet

Теперь, на этом этапе я думаю, как мне убедиться, что я получаю полезную нагрузку от всех HTTP-запросов (не https), которые я получаю? Поэтому я изменил файл командной строки и добавил еще один под ним, чтобы проанализировать http-пакеты из файла, захваченного в первой командной строке. Затем я распечатал результат.

Код:

from scapy.all import *
data = "Eavesdrop_Data.pcap"
a = rdpcap(data)
os.system("tshark  -T fields -e _ws.col.Info -e http -e frame.time -e  "
          "data.data -w Eavesdrop_Data.pcap > Eavesdrop_Data.txt -c 1000")
os.system("tshark -r Eavesdrop_Data.pcap -Y http -w Eavesdrop_Data_http.pcap")
sessions = a.sessions()
i = 1
for session in sessions:
    http_payload = ""
    for packet in sessions[session]:
        print packet

На данный момент мы все еще используем только scapy.

Выход:

Я получил простой текст, который мне и нужен!

Итак, теперь я должен выяснить, как изолировать типы текстового / простого содержимого. Сначала я использовал функцию try and accept внутри цикла for, чтобы проверить, есть ли у http-пакетов TCP. Затем я сопоставил исходные порты (спорт) и порты назначения (dports) с портом 80. Порт 80 является портом прослушивания по умолчанию для http.

Теперь мой код выглядит так:

from scapy.all import *
data = "Eavesdrop_Data.pcap"
a = rdpcap(data)
#os.system("tshark  -T fields -e _ws.col.Info -e http -e frame.time -e  "
#          "data.data -w Eavesdrop_Data.pcap > Eavesdrop_Data.txt -c 1000")
os.system("tshark -r Eavesdrop_Data.pcap -Y http -w Eavesdrop_Data_http.pcap")
sessions = a.sessions()
i = 1
for session in sessions:
    http_payload = ""
    for packet in sessions[session]:
        try:
            if packet[TCP].dport == 80 or packet[TCP].sport == 80:
                print packet[TCP].payload
        except:
            pass

и я получаю это в своем терминале:

Это выглядит намного менее пугающим, чем раньше, и, как вы можете видеть, простой текст все еще там.

Мне нужно выяснить, как получить заголовок http, чтобы я мог просеивать типы контента. Если вы прокрутите вниз пример кода, вы увидите, что автор создал класс для обработки этого. Его класс ориентирован на образы.

Я начал с копирования и вставки его функции и адаптации ее для работы с текстом (везде, где написано изображение, измените его на текст). Вот как это выглядит:

import re


def HTTPHeaders(http_payload):
        try:
            #isolate headers
            headers_raw = http_payload[:http_payload.index("\r\n\r\n") + 2]
            regex = r"(?P<'name>.*?): (?P<value>.*?)\r\n'"
            headers =  dict(re.findall(regex, headers_raw))
        except:
            return None
        if 'Content-Type' not in headers:
            return None
        return headers

def extractText(headers, http_payload):
        text = None
        text_type = None
        if 'text' in headers['Content-Type']:
            text_type = headers['Content-Type'].split("/")[1]
            text = http_payload[http_payload.index("\r\n\r\n")+4:]
            return text,text_type

Это мой результат:

Нет
Нет
Нет
Нет
Нет
Нет
Нет
Нет
Нет
Нет

всю дорогу вниз.

Как мы видели ранее, на выходе должны быть заголовки. Я изолировал проблему от регулярного выражения в функции try. Пора узнать что-то новое… снова. Как лучше всего?

Покопавшись в Интернете, я нашел самый потрясающий api! Я смог протестировать регулярное выражение, приведенное в примере кода, с помощью headers_raw. А пока я вытащил код и просто сохранил весь необходимый мне код (избавился от функций text_type).

После этого я решил выяснить, что происходит. Я тестировал с операторами печати. Я проследил за данными, распечатал их на каждом шаге, каждом цикле, каждой функции и обнаружил, что проблема заключалась в строке headers_raw. Ошибка заключалась в том, что не удалось найти подстроку. При этом я удалил его и использовал часть кода, сгенерированного из https://regex101.com/ (переведенного на python 2.7).

Теперь это выглядит так:

import re
import zlib

def HTTPHeaders(http_payload):
    try:
        # isolate headers
        matches = re.finditer(r'\r\n\r\n', http_payload)

        for matchNum, match in enumerate(matches):
            matchNum = matchNum + 1
            headers_raw = http_payload[:match.end()]
            print headers_raw
        regex = ur"/?P<'name>.*?/: /?P<value>.*?/\n"
        headers = dict(re.findall(regex, headers_raw, re.UNICODE))
        return headers
    except:
        return None
    if 'Content-Type' not in headers:
        return None
    return headers

def extractText(headers, http_payload):
        text = None
        try:
            if 'text/html' in headers['Content-Type']:
                text = http_payload[http_payload.index("\n\n")+4:]
                try:
                    if "Content-Encoding" in headers.keys():
                        if headers['Content-Encoding'] == "gzip":
                            text = zlib.decompress(text)
                    elif headers['Content-Encoding'] == "deflate":
                        text = zlib.decompress(text)
                except: pass
        except:
            return None
        return text

Результат выглядит так:

Совпадение сработало отлично, но вскоре я обнаружил, что, хотя я решил свою проблему header_raw, что-то не так с регулярным выражением, которое анализирует заголовки в словарях.

Опять же, я прослезился, потому что понятия не имею о регулярных выражениях. Я закатал рукава и принялся за работу.

Это будет намного сложнее, чем просто найти заголовки. Я провел больше исследований, искал в Google и искал Stackoverflow, а также Regex101 в поисках решения. Regex101 имеет библиотеку регулярных выражений, созданных пользователями. В поисках json я нашел это: (?: [\ N] {0,1}) (\ w +) (?: \ * = \ *) ([^ \ N] *) (?: [\ N] { 0,1})

Когда я вставил его в свой код как есть, он дал мне следующее:

Ой! Это должна быть словарная форма:

Если вы внимательно посмотрите на вывод, вы увидите, что помимо этого есть некоторые мошеннические значения \ r, регулярное выражение вырезало много информации. Если мы добавим \ r к нашему \ n в регулярном выражении следующим образом: «(?: [\ R \ n] {0,1}) (\ w +) (?: \ * = \ *) ([^ \ r \ n] *) (?: [\ r \ n] {0,1}) ”

и наш результат выглядит так:

Это приближается….

Еще одна корректировка, которую мне нужно было сделать, заключалась в замене знака равенства на двоеточие, чтобы адаптировать регулярное выражение к формату моего ввода (в оригинале было = для обозначения пар ключ-значение).

Этот:

(?:[\r\n]{0,1})(\w+)(?:\ *:\ *)([^\r\n]*)(?:[\r\n]{0,1})

Дает мне это:

К этому времени я немного понимаю, как работает регулярное выражение, поэтому, когда я понял, что код отбрасывает первую часть всего, что переносится через дефис, я знал, что мне нужно избежать дефиса и добавить \ w +, чтобы добавить слово перед дефисом.

regex = ur"(?:[\r\n]{0,1})(\w+\-\w+)(?:\ *:\ *)([^\r\n]*)(?:[\r\n]{0,1})"

что дало мне это:

Если вы посмотрите на вывод, то заметите, что теперь он игнорирует все, что не содержит дефисов. Поэтому мне нужно было добавить «или», чтобы захватить отсутствующие комбинации ключ / значение (или обозначены знаком |).

regex = ur"(?:[\r\n]{0,1})(\w+\-\w+|\w+)(?:\ *:\ *)([^\r\n]*)(?:[\r\n]{0,1})"

Woooot! Это не в порядке, но для меня это не имеет значения. На этом этапе я вернулся, чтобы посмотреть, будет ли работать исходный синтаксический анализ headers_raw, потому что цикл for для сопоставления казался немного громоздким. Это сработало!

В конце концов, мой код состоял из двух файлов Python. Они здесь:

Eavesdrop.py

import os
from scapy.all import *

from getHTTPHeaders import HTTPHeaders, extractText
data = "Eavesdrop_Data.pcap"
a = rdpcap(data)
os.system("tshark  -T fields -e _ws.col.Info -e http -e frame.time -e  "
          "data.data -w Eavesdrop_Data.pcap > Eavesdrop_Data.txt -c     1000")


sessions = a.sessions()
carved_texts = 1
for session in sessions:
    http_payload = ""
    for packet in sessions[session]:
        try:
            if packet[TCP].dport == 80 or packet[TCP].sport == 80:
                http_payload += str(packet[TCP].payload)
        except:
            pass
        headers = HTTPHeaders(http_payload)
    if headers is None:
        continue
    text = extractText(headers,http_payload)
    if text is not None:
         print (text)

getHTTPHeaders.py

import re
import zlib

def HTTPHeaders(http_payload):
    try:
        # isolate headers
        headers_raw = http_payload[:http_payload.index("\r\n\r\n") + 2]
        regex = ur"(?:[\r\n]{0,1})(\w+\-\w+|\w+)(?:\ *:\ *)([^\r\n]*)(?:[\r\n]{0,1})"
        headers = dict(re.findall(regex, headers_raw, re.UNICODE))
        print headers
        return headers
    except:
        return None
    if 'Content-Type' not in headers:
        return None
    return headers

def extractText(headers, http_payload):
        text = None
        try:
            if 'text/plain' in headers['Content-Type']:
                text = http_payload[http_payload.index("\r\n\r\n")+4:]
                try:
                    if "Accept-Encoding" in headers.keys():
                        if headers['Accept-Encoding'] == "gzip":
                            text = zlib.decompress(text,  16+zlib.MAX_WBITS)
                    elif headers['Content-Encoding'] == "deflate":
                        text = zlib.decompress(text)
                except: pass
        except:
            return None
        return text

В данных есть только одна полезная нагрузка в виде простого текста…. и код его нашел!

Это было испытанием терпения и любовью. Уже несколько месяцев пытаюсь решить эту проблему. Имейте в виду, что когда я начинал, у меня не было опыта сетевого программирования. Надеюсь, это кому-то поможет. Если у вас есть предложения, прокомментируйте или найдите меня на моем Github