Files
volatility3/volatility/framework/layers/__init__.py
T

170 lines
6.7 KiB
Python

import bz2
import contextlib
import gzip
import hashlib
import logging
import lzma
import os
import ssl
import typing
import urllib.parse
import urllib.request
import zipfile
try:
import magic
except ImportError:
magic = None
try:
import smb.SMBHandler
except ImportError:
pass
from volatility import framework
from volatility.framework import constants, validity
from volatility.framework.layers import intel, lime, physical, segmented, vmware
vollog = logging.getLogger(__name__)
# TODO: Type-annotating the ResourceAccessor.open method is difficult because HTTPResponse is not actually an IO[Any] type
# fix this
class ResourceAccessor(object):
"""Object for openning URLs as files (downloading locally first if necessary)"""
def __init__(self,
progress_callback: typing.Optional[validity.ProgressCallback] = None,
context: typing.Optional[ssl.SSLContext] = None) -> None:
"""Creates a resource accessor
Note: context is an SSL context, not a volatility context
"""
self._progress_callback = progress_callback
self._context = context
self._cached_files = [] # type: typing.List[str]
self._handlers = list(framework.class_subclasses(urllib.request.BaseHandler))
vollog.log(constants.LOGLEVEL_VVV,
"Available URL handlers: {}".format(", ".join([x.__name__ for x in self._handlers])))
def open(self, url, mode = "rb"):
"""Returns a file-like object for a particular URL opened in mode"""
urllib.request.install_opener(urllib.request.build_opener(*self._handlers))
with contextlib.closing(urllib.request.urlopen(url, context = self._context)) as fp:
# Cache the file locally
parsed_url = urllib.parse.urlparse(url)
if parsed_url.scheme == 'file':
# ZipExtFiles (files in zips) cannot seek, so must be cached in order to use and/or decompress
curfile = urllib.request.urlopen(url, context = self._context)
else:
# TODO: find a way to check if we already have this file (look at http headers?)
block_size = 1028 * 8
temp_filename = os.path.join(constants.CACHE_PATH,
"data_" + hashlib.sha512(bytes(url, 'latin-1')).hexdigest())
if not temp_filename in self._cached_files or not os.path.exists(temp_filename):
vollog.info("Caching file at: {}".format(temp_filename))
try:
content_length = fp.info().get('Content-Length', -1)
except AttributeError:
# If our fp doesn't have an info member, carry on gracefully
content_length = -1
cache_file = open(temp_filename, "wb")
count = 0
while True:
block = fp.read(block_size)
count += len(block)
if not block:
break
if self._progress_callback:
self._progress_callback(count / max(count, int(content_length)),
"Reading file {}".format(url))
cache_file.write(block)
cache_file.close()
# Globally stash the file as cached this python session
self._cached_files += [temp_filename]
# Re-open the cache with a different mode
curfile = open(temp_filename, mode = "rb")
# Determine whether the file is a particular type of file, and if so, open it as such
IMPORTED_MAGIC = False
if not magic is None:
while True:
detected = None
try:
# Detect the content
detected = magic.detect_from_fobj(curfile)
IMPORTED_MAGIC = True
except AttributeError:
pass
except:
pass
if detected:
if detected.mime_type == 'application/x-xz':
curfile = lzma.LZMAFile(curfile, mode)
elif detected.mime_type == 'application/x-bzip2':
curfile = bz2.BZ2File(curfile, mode)
elif detected.mime_type == 'application/x-gzip':
curfile = gzip.GzipFile(fileobj = curfile, mode = mode)
else:
break
else:
break
# Read and rewind to ensure we're inside any compressed file layers
curfile.read(1)
curfile.seek(0)
if not IMPORTED_MAGIC:
# Somewhat of a hack, but prevents a hard dependency on the magic module
url_path = parsed_url.path
while True:
if url_path.endswith(".xz"):
curfile = lzma.LZMAFile(curfile, mode)
elif url_path.endswith(".bz2"):
curfile = bz2.BZ2File(curfile, mode)
elif url_path.endswith(".gz"):
curfile = gzip.GzipFile(fileobj = curfile, mode = mode)
else:
break
url_path = ".".join(url_path.split(".")[:-1])
# Fallback in case the file doesn't exist
if curfile is None:
raise ValueError("URL does not reference an openable file")
return curfile
class JarHandler(urllib.request.BaseHandler):
"""Handles the jar scheme for URIs
Reference used for the schema syntax:
http://docs.netkernel.org/book/view/book:mod:reference/doc:layer1:schemes:jar
Actual reference (found from https://www.w3.org/wiki/UriSchemes/jar) seemed not to return:
http://developer.java.sun.com/developer/onlineTraining/protocolhandlers/
"""
def default_open(self, req):
"""Handles the request if it's the jar scheme"""
if req.type == 'jar':
subscheme, remainder = req.full_url.split(":")[1], ":".join(req.full_url.split(":")[2:])
if subscheme != 'file':
vollog.log(constants.LOGLEVEL_VVV, "Unsupported jar subscheme {}".format(subscheme))
return None
zipsplit = remainder.split("!")
if len(zipsplit) != 2:
vollog.log(constants.LOGLEVEL_VVV,
"Path did not contain exactly one fragment indicator: {}".format(remainder))
return None
zippath, filepath = zipsplit
return zipfile.ZipFile(zippath).open(filepath)
return None