# -*- coding: utf-8 -*-
"""A plugin that gather extension IDs from Chrome history browser."""
from __future__ import unicode_literals
import re
import requests
from plaso.analysis import interface
from plaso.analysis import logger
from plaso.analysis import manager
from plaso.containers import reports
[docs]class ChromeExtensionPlugin(interface.AnalysisPlugin):
"""Convert Chrome extension IDs into names, requires Internet connection."""
NAME = 'chrome_extension'
# Indicate that we can run this plugin during regular extraction.
ENABLE_IN_EXTRACTION = True
_TITLE_RE = re.compile(r'<title>([^<]+)</title>')
_WEB_STORE_URL = 'https://chrome.google.com/webstore/detail/{xid}?hl=en-US'
def __init__(self):
"""Initializes the Chrome extension analysis plugin."""
super(ChromeExtensionPlugin, self).__init__()
# Saved list of already looked up extensions.
self._extensions = {}
self._results = {}
# TODO: see if these can be moved to arguments passed to ExamineEvent
# or some kind of state object.
self._sep = None
def _GetChromeWebStorePage(self, extension_identifier):
"""Retrieves the page for the extension from the Chrome store website.
Args:
extension_identifier (str): Chrome extension identifier.
Returns:
str: page content or None.
"""
web_store_url = self._WEB_STORE_URL.format(xid=extension_identifier)
try:
response = requests.get(web_store_url)
except (requests.ConnectionError, requests.HTTPError) as exception:
logger.warning((
'[{0:s}] unable to retrieve URL: {1:s} with error: {2!s}').format(
self.NAME, web_store_url, exception))
return None
return response.text
def _GetPathSegmentSeparator(self, path):
"""Given a path give back the path separator as a best guess.
Args:
path (str): path.
Returns:
str: path segment separator.
"""
if path.startswith('\\') or path[1:].startswith(':\\'):
return '\\'
if path.startswith('/'):
return '/'
if '/' and '\\' in path:
# Let's count slashes and guess which one is the right one.
forward_count = len(path.split('/'))
backward_count = len(path.split('\\'))
if forward_count > backward_count:
return '/'
return '\\'
# Now we are sure there is only one type of separators yet
# the path does not start with one.
if '/' in path:
return '/'
return '\\'
def _GetTitleFromChromeWebStore(self, extension_identifier):
"""Retrieves the name of the extension from the Chrome store website.
Args:
extension_identifier (str): Chrome extension identifier.
Returns:
str: name of the extension or None.
"""
# Check if we have already looked this extension up.
if extension_identifier in self._extensions:
return self._extensions.get(extension_identifier)
page_content = self._GetChromeWebStorePage(extension_identifier)
if not page_content:
logger.warning(
'[{0:s}] no data returned for extension identifier: {1:s}'.format(
self.NAME, extension_identifier))
return None
first_line, _, _ = page_content.partition('\n')
match = self._TITLE_RE.search(first_line)
name = None
if match:
title = match.group(1)
if title.startswith('Chrome Web Store - '):
name = title[19:]
elif title.endswith('- Chrome Web Store'):
name = title[:-19]
if not name:
self._extensions[extension_identifier] = 'UNKNOWN'
return None
self._extensions[extension_identifier] = name
return name
[docs] def CompileReport(self, mediator):
"""Compiles an analysis report.
Args:
mediator (AnalysisMediator): mediates interactions between analysis
plugins and other components, such as storage and dfvfs.
Returns:
AnalysisReport: analysis report.
"""
lines_of_text = []
for user, extensions in sorted(self._results.items()):
lines_of_text.append(' == USER: {0:s} =='.format(user))
for extension, extension_identifier in sorted(extensions):
lines_of_text.append(' {0:s} [{1:s}]'.format(
extension, extension_identifier))
lines_of_text.append('')
lines_of_text.append('')
report_text = '\n'.join(lines_of_text)
analysis_report = reports.AnalysisReport(
plugin_name=self.NAME, text=report_text)
analysis_report.report_dict = self._results
return analysis_report
[docs] def ExamineEvent(self, mediator, event):
"""Analyzes an event.
Args:
mediator (AnalysisMediator): mediates interactions between analysis
plugins and other components, such as storage and dfvfs.
event (EventObject): event to examine.
"""
# Only interested in filesystem events.
if event.data_type != 'fs:stat':
return
filename = getattr(event, 'filename', None)
if not filename:
return
# Determine if we have a Chrome extension ID.
if 'chrome' not in filename.lower():
return
if not self._sep:
self._sep = self._GetPathSegmentSeparator(filename)
if '{0:s}Extensions{0:s}'.format(self._sep) not in filename:
return
# Now we have extension IDs, let's check if we've got the
# folder, nothing else.
paths = filename.split(self._sep)
if paths[-2] != 'Extensions':
return
extension_identifier = paths[-1]
if extension_identifier == 'Temp':
return
# Get the user and ID.
user = mediator.GetUsernameForPath(filename)
# We still want this information in here, so that we can
# manually deduce the username.
if not user:
if len(filename) > 25:
user = 'Not found ({0:s}...)'.format(filename[0:25])
else:
user = 'Not found ({0:s})'.format(filename)
extension_string = self._GetTitleFromChromeWebStore(extension_identifier)
if not extension_string:
extension_string = extension_identifier
self._results.setdefault(user, [])
if (extension_string, extension_identifier) not in self._results[user]:
self._results[user].append((extension_string, extension_identifier))
manager.AnalysisPluginManager.RegisterPlugin(ChromeExtensionPlugin)