PcapFileExtractor

This automation extracts all possible files from a PCAP file.

python · PCAP Analysis

Source

import hashlib
import subprocess
import tempfile

import magic
from CommonServerPython import *  # noqa: E402 lgtm [py/polluting-import]

INCLUSIVE: str = "inclusive"
EXCLUSIVE: str = "exclusive"


class HashCalculator:
    """Class to calculate hashes."""

    @staticmethod
    def _calculate_by_chunks(hash_calculator, file_path: str) -> str:
        """Calculates hashes by chunks

        Args:
            hash_calculator: A hashlib object
            file_path: File path to calculate hashes

        Returns:
            Hash string.
        """
        with open(file_path, "rb") as f:
            for chunk in iter(lambda: f.read(4096), b""):
                hash_calculator.update(chunk)
        return hash_calculator.hexdigest()

    @classmethod
    def md5(cls, file_path: str) -> str:
        """Calculate MD5 hash of file.

        Args:
            file_path: A path of file to calculate.

        Returns:
            A MD5 hash
        """
        return cls._calculate_by_chunks(hashlib.md5(), file_path)  # nosec

    @classmethod
    def sha1(cls, file_path: str) -> str:
        """Calculate SHA1 hash of file.

        Args:
            file_path: A path of file to calculate.

        Returns:
            A SHA1 hash
        """
        return cls._calculate_by_chunks(hashlib.sha1(), file_path)  # nosec

    @classmethod
    def sha256(cls, file_path: str) -> str:
        """Calculate SHA256 hash of file.

        Args:
            file_path: A path of file to calculate.

        Returns:
            A SHA256 hash
        """
        return cls._calculate_by_chunks(hashlib.sha256(), file_path)


def get_file_path_from_id(entry_id: str) -> tuple[str, str]:
    """Gets a file path and name from entry_id.

    Args:
        entry_id: ID of the file from context.

    Returns:
        file path, name of file
    """
    file_obj = demisto.getFilePath(entry_id)
    return file_obj.get("path"), file_obj.get("name")


def run_command(args: list, stdout=subprocess.PIPE, stderr=subprocess.PIPE):
    """Running a process

    Args:
        args: args as will be passed to Popen
        stdout: STDOUT pipe
        stderr: STDERR pipe

    Raises:
        DemistoException if returncode is different than 0
    """
    process = subprocess.Popen(args, stdout=stdout, stderr=stderr)
    stdout_data, stderr_data = process.communicate()
    if process.returncode != 0:
        raise DemistoException(f"Error returned from tshark command: {process.returncode}\n {stderr_data!r}")


def filter_files(
    root: str,
    files: list[str],
    types: set[str] | None = None,
    extensions: set[str] | None = None,
    inclusive_or_exclusive: str | None = None,
) -> list[str]:
    """Filtering files by its MIME type and file extension.

    Args:
        root: file's root
        files: files to filter
        types: types to filter by.
        extensions: extensions to filter by.
        inclusive_or_exclusive: should extensions/types set be inclusive or exclusive

    Returns:
        Filtered file list.
    """
    # strip `.` from extension
    if extensions is not None:
        extensions = {extension.split(".")[-1] for extension in extensions}  # type: ignore
    else:
        extensions = set()
    if types is None:
        types = set()
    magic_mime = magic.Magic(mime=True)
    new_files = files.copy()
    # MIME Types
    if types:
        for file in files:
            # types list supplied,
            mime_type = magic_mime.from_file(os.path.join(root, file))
            # Inclusive types, take only the types in the list.
            if inclusive_or_exclusive == INCLUSIVE and mime_type not in types:
                new_files.remove(file)
            # Exclusive types, don't take those files.
            elif inclusive_or_exclusive == EXCLUSIVE and mime_type in types:
                new_files.remove(file)
    # Extensions
    if extensions:
        for file in files:
            # Get file extension without a leading point.
            f_ext = os.path.splitext(file)[1].split(".")[-1]
            # Inclusive extensions, take only the types in the list.
            if inclusive_or_exclusive == INCLUSIVE and f_ext not in extensions:
                new_files.remove(file)
            # Exclude extensions, don't take those files.
            elif inclusive_or_exclusive == EXCLUSIVE and f_ext in extensions:
                new_files.remove(file)
    return new_files


def upload_files(
    file_path: str,
    dir_path: str,
    types: set[str] | None = None,
    extensions: set[str] | None = None,
    inclusive_or_exclusive: str | None = None,
    wpa_pwd: str | None = None,
    rsa_path: str | None = None,
    limit: int = 5,
) -> CommandResults | str:
    """Extracts files and delivers it to CortexSOAR

    Args:
        file_path: the path to the PCAP file
        dir_path: dir path for the files
        types: types to filter by.
        extensions: extensions to filter by.
        inclusive_or_exclusive: should types/extensions set be inclusive or exclusive
        wpa_pwd: password to the file (if WPA-PWD protected)
        rsa_path: path to a private key file (if TLS encrypted)
        limit: maximum files to extract (default 5)

    Returns:
        Extracted files to download

    """
    assert not (types and extensions), "Provide only types or extensions, not both."
    command = [
        "tshark",
        "-2",
        "-r",
        f"{file_path}",
        "--export-objects",
        f"http,{dir_path}",
        "--export-objects",
        f"smb,{dir_path}",
        "--export-objects",
        f"imf,{dir_path}",
        "--export-objects",
        f"tftp,{dir_path}",
        "--export-objects",
        f"dicom,{dir_path}",
    ]
    # If WPA-PWD protected
    if wpa_pwd:
        command.extend(["-o", "wlan.enable_decryption:TRUE", "-o", f'uat:80211_keys:"wpa-pwd","{wpa_pwd}"'])
    # If need to decrypt the file using a RSA key
    if rsa_path:
        command.extend(["-o", f'uat:rsa_keys:"{rsa_path}",""'])

    run_command(command)

    context = []
    for root, _, files in os.walk(dir_path):
        # Limit the files list to the limit provided by the user
        files = files[:limit]
        if not files:
            return "No files found."
        # Filter files
        files = filter_files(root, files, types=types, extensions=extensions, inclusive_or_exclusive=inclusive_or_exclusive)
        for file in files:
            file_path = os.path.join(root, file)
            file_name = os.path.join(file)

            with open(file_path, "rb") as file_stream:
                data = file_stream.read()
                demisto.results(fileResult(file_name, data))

            context.append(
                {
                    "FileMD5": HashCalculator.md5(file_path),
                    "FileSHA1": HashCalculator.sha1(file_path),
                    "FileSHA256": HashCalculator.sha256(file_path),
                    "FileName": file_name,
                    "FileSize": os.path.getsize(file_path),
                    "FileExtension": os.path.splitext(file_name)[1],
                }
            )

        readable_output = tableToMarkdown("Pcap Extracted Files", [{"name": file_name} for file_name in files])

        results = CommandResults(
            outputs_prefix="PcapExtractedFiles", outputs_key_field="FileMD5", outputs=context, readable_output=readable_output
        )
        return results
    return "No files found in path."


def main(
    entry_id: str,
    wpa_password: str | None = None,
    rsa_decrypt_key_entry_id: str | None = None,
    types: str | None = None,
    inclusive_or_exclusive: str | None = "inclusive",
    extensions: str | None = None,
    limit: str = "5",
):
    """Exports a PCAP file and returns them to the context.

    Args:
        entry_id: Entry ID of the PCAP file
        wpa_password: password for WPA-PWD protected files. <password> or <host>:<password>
        rsa_decrypt_key_entry_id: Entry ID of a RSA key.
        types: A CSV list of types.
        extensions: A CSV list of extensions.
        inclusive_or_exclusive: Should types/extensions be inclusive or exclusive
        limit: Maximum of files to export from PCAP file

    Raises:
        SystemExit if error occurred.
    """
    with tempfile.TemporaryDirectory() as dir_path:
        try:
            file_path, file_name = get_file_path_from_id(entry_id)
            cert_path, _ = get_file_path_from_id(rsa_decrypt_key_entry_id) if rsa_decrypt_key_entry_id else (None, None)
            return_results(
                upload_files(
                    file_path,
                    dir_path,
                    types=set(argToList(types)),
                    extensions=set(argToList(extensions)),
                    inclusive_or_exclusive=inclusive_or_exclusive,
                    wpa_pwd=wpa_password,
                    rsa_path=cert_path,
                    limit=int(limit) if limit else 5,
                )
            )
        except Exception as e:
            return_error(f"Failed to execute PcapFileExtractor. Error: {e!s}")


if __name__ in ("__builtin__", "builtins", "__main__"):
    main(**demisto.args())

README

This automation extracts all possible files from a PCAP file.

Script Data


Name Description
Script Type python3
Tags pcap, file, Utility
Cortex XSOAR Version 5.0.0

Inputs


Argument Name Description
entry_id The EntryID of the PCAP file to extract the files from.

Outputs


Path Description Type
PcapExtractedFiles.FileName File name. String
PcapExtractedFiles.FileSize File size. Number
PcapExtractedFiles.FileMD5 The MD5 hash of the file. String
PcapExtractedFiles.FileSHA1 The SHA1 hash of the file. String
PcapExtractedFiles.FileSHA256 The SHA256 hash of the file. String
PcapExtractedFiles.FileExtension The extension of the file. String
File.Size The size of the file in bytes. Number
File.SHA1 The SHA1 hash of the file. String
File.SHA256 The SHA256 hash of the file. String
File.SHA512 The SHA512 hash of the file. String
File.Name The full file name. String
File.SSDeep The ssdeep hash of the file. String
File.EntryID The ID for locating the file in the War Room. String
File.Info The file information. String
File.Type The file type. String
File.MD5 The MD5 hash of the file. String
File.Extension The file extension, for example: ‘txt’. String