-
Notifications
You must be signed in to change notification settings - Fork 149
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #77 from obsidianforensics/unfurl-for-interpretation
Add plugin to run Unfurl across Local Storage values
- Loading branch information
Showing
3 changed files
with
97 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
__all__ = ['chrome_extensions', 'generic_timestamps', 'google_analytics', | ||
'google_searches', 'load_balancer_cookies', 'quantcast_cookies', | ||
'query_string_parser', 'time_discrepancy_finder'] | ||
'query_string_parser', 'time_discrepancy_finder', 'unfurl_interpretation'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
################################################################################################### | ||
# | ||
# unfurl_interpretation.py | ||
# Run storage values through Unfurl to (hopefully) make some more clear. | ||
# | ||
# Plugin Author: Ryan Benson (ryan@dfir.blog) | ||
# | ||
################################################################################################### | ||
|
||
from unfurl import core | ||
import unfurl | ||
import logging | ||
# Disable most Unfurl logs, as we're about to shove a lot of garbage at it | ||
# and don't want to swamp the Hindsight log. | ||
unfurl.log.setLevel(logging.CRITICAL) | ||
|
||
# Config | ||
friendlyName = "Unfurl" | ||
description = "Run storage values through Unfurl" | ||
artifactTypes = ["local storage"] # Artifacts that this plugin processes | ||
remoteLookups = 1 # if this plugin will query online sources/databases | ||
browser = "Chrome" # browsers that the plugin applies to | ||
browserVersion = 1 # browser versions that the plugin applies to | ||
version = "20210307" # version of the plugin (use the date) | ||
parsedItems = 0 # count of items that the plugin parsed; initialized to 0 | ||
|
||
|
||
def plugin(target_browser): | ||
|
||
# Setting up our return variable | ||
global parsedItems | ||
parsedItems = 0 | ||
|
||
for item in target_browser.parsed_storage: | ||
# If the item isn't of a type we want to parse, go to the next one. | ||
if item.row_type not in artifactTypes: | ||
continue | ||
|
||
# If the item already has an interpretation don't replace it. | ||
if item.interpretation is not None: | ||
continue | ||
|
||
# Otherwise, try to parse the item's value with Unfurl | ||
try: | ||
u = core.Unfurl() | ||
u.add_to_queue(data_type='url', key=None, value=item.value) | ||
u.parse_queue() | ||
u_json = u.generate_json() | ||
|
||
# Many varieties of exceptions are expected here, as we're shoving | ||
# all kinds of data into Unfurl, many of types it isn't designed | ||
# to handle. That's fine; keep moving. | ||
except: | ||
continue | ||
|
||
# Case where Unfurl was not able to parse anything meaningful from input | ||
if u_json['summary'] == {}: | ||
continue | ||
|
||
# Case where the Unfurl graph is just two nodes; first is just the input again. | ||
# Display the second as the interpretation in a more compact form. | ||
if len(u_json['nodes']) == 2: | ||
item.interpretation = f"{u_json['nodes'][1]['label']}" | ||
|
||
# Try to get a description of the transform Unfurl did | ||
desc = u_json['nodes'][1].get('title', None) | ||
if not desc: | ||
desc = u_json['edges'][0].get('title', None) | ||
if desc: | ||
item.interpretation += f' ({desc})' | ||
|
||
item.interpretation += f' [Unfurl]' | ||
|
||
# Cases for UUIDs | ||
elif len(u_json['nodes']) == 3 and u_json['nodes'][2]['label'].startswith('Version 4 UUID'): | ||
item.interpretation = 'Value is a Version 4 UUID (randomly generated)' | ||
|
||
elif len(u_json['nodes']) == 3 and u_json['nodes'][2]['label'].startswith('Version 5 UUID'): | ||
item.interpretation = 'Value is a Version 5 UUID (generated based on a namespace and a name, ' \ | ||
'which are combined and hashed using SHA-1)' | ||
|
||
elif len(u_json['nodes']) == 6 and u_json['nodes'][2]['label'].startswith('Version 1 UUID'): | ||
item.interpretation = f"{u_json['nodes'][5]['label']} (Time Generated); " \ | ||
f"{u_json['nodes'][4]['label']} (MAC address); " \ | ||
f"Value is a Version 1 UUID (based on time and MAC address) [Unfurl]" | ||
|
||
# Lastly, the generic Unfurl case. Stick the whole "ASCII-art" tree into the Interpretation field. | ||
else: | ||
item.interpretation = f"{u.generate_text_tree()} \n[Unfurl]" | ||
|
||
parsedItems += 1 | ||
|
||
# Return a count parsed items | ||
return f'{parsedItems} values parsed' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,4 +6,5 @@ xlsxwriter>=1.2.9 | |
bottle>=0.12.18 | ||
setuptools~=51.0.0 | ||
puremagic~=1.10 | ||
argparse~=1.4.0 | ||
argparse~=1.4.0 | ||
dfir-unfurl |