From db5cf10474eae80a24c49e1d361a344dfb0c865a Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Wed, 8 Nov 2017 16:15:40 +0200 Subject: [PATCH 1/4] integrate get_symbols in windows backend --- nitro/backends/windows/backend.py | 110 ++++++++++++++++++++++++------ 1 file changed, 91 insertions(+), 19 deletions(-) diff --git a/nitro/backends/windows/backend.py b/nitro/backends/windows/backend.py index 4ccd5ae..f3ab1e3 100644 --- a/nitro/backends/windows/backend.py +++ b/nitro/backends/windows/backend.py @@ -5,9 +5,13 @@ import subprocess import shutil import json - +from io import StringIO +from collections import defaultdict +from rekall import session +from rekall import plugins from tempfile import NamedTemporaryFile, TemporaryDirectory + import libvirt from nitro.event import SyscallDirection @@ -16,8 +20,6 @@ from nitro.backends.backend import Backend from nitro.backends.windows.arguments import WindowsArgumentMap -GETSYMBOLS_SCRIPT = 'get_symbols.py' - class WindowsBackend(Backend): __slots__ = ( @@ -38,7 +40,10 @@ def __init__(self, domain, libvmi, listener, syscall_filtering=True): # create on syscall stack per vcpu self.syscall_stack = tuple([] for _ in range(self.nb_vcpu)) self.sdt = None - self.load_symbols() + symbols = self.get_symbols() + self.load_symbols(symbols) + # save symbols + self.symbols = symbols # get offsets self.tasks_offset = self.libvmi.get_offset("win_tasks") @@ -46,7 +51,7 @@ def __init__(self, domain, libvmi, listener, syscall_filtering=True): self.processes = {} - def load_symbols(self): + def get_symbols(self): # we need to put the ram dump in our own directory # because otherwise it will be created in /tmp # and later owned by root @@ -62,18 +67,34 @@ def load_symbols(self): flags = libvirt.VIR_DUMP_MEMORY_ONLY dumpformat = libvirt.VIR_DOMAIN_CORE_DUMP_FORMAT_RAW self.domain.coreDumpWithFormat(ram_dump.name, dumpformat, flags) - # build symbols.py absolute path - script_dir = os.path.dirname(os.path.realpath(__file__)) - symbols_script_path = os.path.join(script_dir, - GETSYMBOLS_SCRIPT) - # call rekall on ram dump - logging.info('Extracting symbols with Rekall') - python2 = shutil.which('python2') - symbols_process = [python2, symbols_script_path, ram_dump.name] - output = subprocess.check_output(symbols_process) - logging.info('Loading symbols') - # load output as json - symbols = json.loads(output.decode('utf-8')) + home = os.getenv('HOME') + # we need to make sure the directory exists otherwise rekall will complain + # when we specify it in the profile_path + local_cache_path = os.path.join(home, '.rekall_cache') + try: + os.makedirs(local_cache_path) + except OSError: # already exists + pass + + s = session.Session( + filename=ram_dump.name, + autodetect=["rsds"], + logger=logging.getLogger(), + autodetect_build_local='none', + format='data', + profile_path=[ + local_cache_path, + "http://profiles.rekall-forensic.com" + ]) + + symbols = {} + output = StringIO.StringIO() + s.RunPlugin("ssdt", output=output) + symbols['syscall_table'] = json.loads(output.getvalue()) + symbols['offsets'] = self.get_offsets(s) + return symbols + + def load_symbols(self, symbols): # load ssdt entries nt_ssdt = {'ServiceTable': {}, 'ArgumentTable': {}} win32k_ssdt = {'ServiceTable': {}, 'ArgumentTable': {}} @@ -92,8 +113,59 @@ def load_symbols(self): # add entry to our current ssdt cur_ssdt[entry] = full_name logging.debug('Add SSDT entry [%s] -> %s', entry, full_name) - # save rekall symbols - self.symbols = symbols + + + def get_offsets(self, session): + offsets = defaultdict(dict) + + offsets['KPROCESS'][ + 'DirectoryTableBase'] = session.profile.get_obj_offset('_KPROCESS', + 'DirectoryTableBase') + + offsets['EPROCESS'][ + 'ActiveProcessLinks'] = session.profile.get_obj_offset('_EPROCESS', + 'ActiveProcessLinks') + + offsets['EPROCESS']['ImageFileName'] = session.profile.get_obj_offset( + '_EPROCESS', + 'ImageFileName') + + offsets['EPROCESS']['UniqueProcessId'] = session.profile.get_obj_offset( + '_EPROCESS', + 'UniqueProcessId') + + offsets['EPROCESS']['InheritedFromUniqueProcessId'] = \ + session.profile.get_obj_offset('_EPROCESS', + 'InheritedFromUniqueProcessId') + + offsets['EPROCESS']['Wow64Process'] = \ + session.profile.get_obj_offset('_EPROCESS', 'Wow64Process') + + offsets['EPROCESS']['CreateTime'] = \ + session.profile.get_obj_offset('_EPROCESS', 'CreateTime') + + offsets['EPROCESS']['SeAuditProcessCreationInfo'] = \ + session.profile.get_obj_offset('_EPROCESS', + 'SeAuditProcessCreationInfo') + + offsets['SE_AUDIT_PROCESS_CREATION_INFO']['ImageFileName'] = \ + session.profile.get_obj_offset('_SE_AUDIT_PROCESS_CREATION_INFO', + 'ImageFileName') + + offsets['OBJECT_NAME_INFORMATION']['Name'] = \ + session.profile.get_obj_offset('_OBJECT_NAME_INFORMATION', 'Name') + + offsets['EPROCESS']['Peb'] = session.profile.get_obj_offset('_EPROCESS', + 'Peb') + + offsets['PEB']['ProcessParameters'] = \ + session.profile.get_obj_offset('_PEB', 'ProcessParameters') + + offsets['RTL_USER_PROCESS_PARAMETERS']['CommandLine'] = \ + session.profile.get_obj_offset('_RTL_USER_PROCESS_PARAMETERS', + 'CommandLine') + + return offsets def process_event(self, event): # invalidate libvmi cache From 983ec75682307d3568c03d55d90751bcbd64a1dd Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Wed, 8 Nov 2017 16:16:00 +0200 Subject: [PATCH 2/4] delete get_symbols.py --- nitro/backends/windows/get_symbols.py | 101 -------------------------- 1 file changed, 101 deletions(-) delete mode 100755 nitro/backends/windows/get_symbols.py diff --git a/nitro/backends/windows/get_symbols.py b/nitro/backends/windows/get_symbols.py deleted file mode 100755 index 1018303..0000000 --- a/nitro/backends/windows/get_symbols.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env python2 - -""" - -Usage: - symbols.py - -Options: - -h --help Show this screen. - -""" - -import os -import logging -import StringIO -import json -from collections import defaultdict - -# logging.basicConfig(level=logging.DEBUG) - - -from docopt import docopt -from rekall import session -from rekall import plugins - -def main(args): - ram_dump = args[''] - home = os.getenv('HOME') - # we need to make sure the directory exists otherwise rekall will complain - # when we specify it in the profile_path - local_cache_path = os.path.join(home, '.rekall_cache') - try: - os.makedirs(local_cache_path) - except OSError: # already exists - pass - - s = session.Session( - filename=ram_dump, - autodetect=["rsds"], - logger=logging.getLogger(), - autodetect_build_local='none', - format='data', - profile_path=[ - local_cache_path, - "http://profiles.rekall-forensic.com" - ]) - - symbols = {} - output = StringIO.StringIO() - s.RunPlugin("ssdt", output=output) - symbols['syscall_table'] = json.loads(output.getvalue()) - symbols['offsets'] = get_offsets(s) - - print(json.dumps(symbols)) - - -def get_offsets(session): - offsets = defaultdict(dict) - - offsets['KPROCESS']['DirectoryTableBase'] = session.profile.get_obj_offset('_KPROCESS', - 'DirectoryTableBase') - - offsets['EPROCESS']['ActiveProcessLinks'] = session.profile.get_obj_offset('_EPROCESS', - 'ActiveProcessLinks') - - offsets['EPROCESS']['ImageFileName'] = session.profile.get_obj_offset('_EPROCESS', - 'ImageFileName') - - offsets['EPROCESS']['UniqueProcessId'] = session.profile.get_obj_offset('_EPROCESS', - 'UniqueProcessId') - - offsets['EPROCESS']['InheritedFromUniqueProcessId'] = \ - session.profile.get_obj_offset('_EPROCESS', 'InheritedFromUniqueProcessId') - - offsets['EPROCESS']['Wow64Process'] = \ - session.profile.get_obj_offset('_EPROCESS', 'Wow64Process') - - offsets['EPROCESS']['CreateTime'] = \ - session.profile.get_obj_offset('_EPROCESS', 'CreateTime') - - offsets['EPROCESS']['SeAuditProcessCreationInfo'] = \ - session.profile.get_obj_offset('_EPROCESS', 'SeAuditProcessCreationInfo') - - offsets['SE_AUDIT_PROCESS_CREATION_INFO']['ImageFileName'] = \ - session.profile.get_obj_offset('_SE_AUDIT_PROCESS_CREATION_INFO', 'ImageFileName') - - offsets['OBJECT_NAME_INFORMATION']['Name'] = \ - session.profile.get_obj_offset('_OBJECT_NAME_INFORMATION', 'Name') - - offsets['EPROCESS']['Peb'] = session.profile.get_obj_offset('_EPROCESS', 'Peb') - - offsets['PEB']['ProcessParameters'] = \ - session.profile.get_obj_offset('_PEB', 'ProcessParameters') - - offsets['RTL_USER_PROCESS_PARAMETERS']['CommandLine'] = \ - session.profile.get_obj_offset('_RTL_USER_PROCESS_PARAMETERS', 'CommandLine') - - return offsets - -if __name__ == '__main__': - main(docopt(__doc__)) From cf5f9832803525b389afa68469ac4d9881cfa629 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Wed, 8 Nov 2017 16:16:22 +0200 Subject: [PATCH 3/4] remove useless imports in windows backend --- nitro/backends/windows/backend.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nitro/backends/windows/backend.py b/nitro/backends/windows/backend.py index f3ab1e3..b034fcb 100644 --- a/nitro/backends/windows/backend.py +++ b/nitro/backends/windows/backend.py @@ -2,8 +2,6 @@ import re import stat import os -import subprocess -import shutil import json from io import StringIO from collections import defaultdict From 2ecc09a3cfd8114b317ee149fc3d265fffd184cf Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Wed, 8 Nov 2017 16:18:38 +0200 Subject: [PATCH 4/4] update README --- README.md | 10 +++++----- nitro/backends/windows/backend.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2042e0f..a9a1f7a 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,10 @@ It will receive the events generated by KVM and display them. - `python 3` - `docopt` - `libvirt` -- `ioctl-opt Python 3` -- `cffi Python3` (optional) +- `ioctl-opt` +- `cffi` (optional) - `libvmi` (optional) -- `rekall` (optional) +- `rekall >= 1.7.1` (optional) # Setup @@ -136,7 +136,7 @@ informations, such as: ## Rekall -`Rekall` is used in `symbols.py` to extract the syscall table from +`Rekall` is used to extract the syscall table from the memory dump. Unfortunately, `Rekall` is not available as a Debian package. @@ -144,7 +144,7 @@ For now you will have to install it system-wide with `pip`. ~~~ $ sudo pip3 install --upgrade setuptools pip wheel -$ sudo pip3 install rekall +$ sudo pip3 install rekall-agent rekall ~~~ ## libvmi diff --git a/nitro/backends/windows/backend.py b/nitro/backends/windows/backend.py index b034fcb..ebba463 100644 --- a/nitro/backends/windows/backend.py +++ b/nitro/backends/windows/backend.py @@ -86,7 +86,7 @@ def get_symbols(self): ]) symbols = {} - output = StringIO.StringIO() + output = StringIO() s.RunPlugin("ssdt", output=output) symbols['syscall_table'] = json.loads(output.getvalue()) symbols['offsets'] = self.get_offsets(s)