class UploadSchema(schema.FileSchema): """Extends `FileSchema`.""" args = fields.Dict(required=False, default={}, missing={}) extract = fields.Bool(missing=False) name = fields.Str(missing=None) # Override name password = fields.Str(missing=None)
class CommandSchema(Schema): """The command schema. This is the base schema for the command document stored within the mongo database. Note: Scales are allowed to embed additional information into this document but it will be ignored. """ _id = fields.ObjectId(load_only=True) _output_id = fields.ObjectId(load_only=True, missing=None) # GridFS sha256_digest = fields.Str(required=True) scale = fields.Str(required=True) command = fields.Str(required=True) args = fields.Dict(default={}, missing={}) asynchronous = fields.Boolean(default=False) timeout = fields.Int(default=600) format = fields.Str(type=enums.Format, missing=enums.Format.JSON) output = fields.Raw(dump_only=True, default=None, missing=None) status = fields.Str(type=enums.Status, missing=enums.Status.PENDING, default=enums.Status.PENDING) timestamp = fields.DateTime("%Y-%m-%dT%H:%M:%S.%f") start_time = fields.DateTime("%Y-%m-%dT%H:%M:%S.%f") end_time = fields.DateTime("%Y-%m-%dT%H:%M:%S.%f")
class InterfaceSchema(schema.Schema): """Extends `Schema`. Create schema for interface requests. """ args = fields.Dict(required=False, default={}, missing={}) command = fields.Str(required=True) format = fields.Str(type=enums.Format, missing=enums.Format.JSON) sha256_digest = fields.Str(required=True) type = fields.Str(type=enums.InterfaceType, missing=enums.InterfaceType.PULL)
class CommandsSchema(schema.Schema): """Extends `Schema`. Defines the valid schema for post request. """ args = fields.Dict(required=False, default={}, missing={}) command = fields.Str(required=True) format = fields.Str(type=enums.Format, missing=enums.Format.JSON) sha256_digests = fields.List(fields.Str(), required=True) scale = fields.Str(required=True) timeout = fields.Int(required=False)
class NoteSchema(Schema): """The note schema. This is the schema for the note document stored within the mongo database. """ _id = fields.ObjectId(load_only=True) sha256_digest = fields.Str(required=True) body = fields.Str(required=True) timestamp = fields.DateTime("%Y-%m-%dT%H:%M:%S.%f") updated_time = fields.DateTime("%Y-%m-%dT%H:%M:%S.%f")
class StoreHandler(snake_handler.SnakeHandler): """Extends `SnakeHandler`.""" @tornadoparser.use_args({ # filter[field]: str 'file_type': fields.Enum(type=enums.FileType, required=False, missing=None), 'from': fields.Int(required=False, missing=0), 'limit': fields.Int(required=False, missing=10), 'operator': fields.Str(required=False, missing='and'), 'order': fields.Int(required=False, missing=-1), 'sort': fields.Str(required=False, missing=None) }) async def get(self, data): documents = [] filter_ = self.create_filter(self.request.arguments, data['operator']) if filter_: filter_ = {'$and': [filter_]} if data['file_type']: filter_['$and'] += [{'file_type': data['file_type']}] elif data['file_type']: filter_ = {'file_type': data['file_type']} # NOTE: With async (motor) there is no count() on cursor so we have to work around that total = await db.async_file_collection.db.files.count_documents( filter_ if filter_ else {}) cursor = db.async_file_collection.select_all(filter_, data['order'], data['sort'], data['limit'], data['from']) while await cursor.fetch_next: documents += [cursor.next_object()] documents = schema.FileSchema(many=True).dump( schema.FileSchema(many=True).load(documents)) self.jsonify({'samples': documents, 'total': total}) self.finish()
class FilesHandler(snake_handler.SnakeHandler): """Extends `SnakeHandler`.""" @tornadoparser.use_args({ 'limit': fields.Str(required=False), 'operator': fields.Str(required=False, missing='and'), 'order': fields.Int(required=False, missing=-1), 'sort': fields.Str(required=False) }) async def get(self, data): documents = [] sort = None if 'sort' in data.keys(): sort = data['sort'] filter_ = self.create_filter(self.request.arguments, data['operator']) if filter_: filter_ = {'$and': [{'file_type': enums.FileType.FILE}, filter_]} else: filter_ = {'file_type': enums.FileType.FILE} cursor = db.async_file_collection.select_all(filter_, data['order'], sort) index = 0 while await cursor.fetch_next: if 'limit' in data.keys(): if index >= int(data['limit']): break index += 1 documents += [cursor.next_object()] documents = schema.FileSchema(many=True).dump( schema.FileSchema(many=True).load(documents)) self.jsonify({'files': documents}) self.finish()
class Commands(scale.Commands): def _decompile(self, kwargs): # NOTE: Using kwargs is lazy but it just makes life easier! # Online if self.decomp: try: decompilation = self.decomp.start_decompilation(**kwargs) decompilation.wait_until_finished() except exceptions.RetdecError as err: raise error.CommandError("retdec-python error: {}".format(err)) return decompilation.get_hll_code() # Local else: with tempfile.NamedTemporaryFile('rb') as fp: cmd = [ path.join(self.retdec_dir, 'bin/retdec-decompiler.sh'), '-o', fp.name ] if 'sel_decomp_funcs' in kwargs: cmd += ['--select-functions', kwargs['sel_decomp_funcs']] if 'sel_decomp_ranges' in kwargs: cmd += ['--select-ranges', kwargs['sel_decomp_ranges']] cmd += [kwargs['input_file']] proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if proc.returncode: raise error.CommandError("retdec error: {}".format( proc.stderr.decode())) return fp.read().decode() def check(self): self.decomp = None self.retdec_dir = None if not shutil.which('radare2'): raise error.CommandError("binary 'radare2' not found") if config.scale_configs['retdec']['online']: if not config.scale_configs['retdec']['api_key']: raise error.CommandError( "config variable 'api_key' has not been set and is required to query the online retdec" ) self.decomp = decompiler.Decompiler( api_key=config.scale_configs['retdec']['api_key']) else: if not config.scale_configs['retdec']['retdec_dir']: raise error.CommandError( "config variable 'retdec_dir' has not been set and is required to use a local retdec instance" ) self.retdec_dir = config.scale_configs['retdec']['retdec_dir'] @scale.command({ 'args': { 'address_range': fields.Str(), 'function_name': fields.Str(), 'mode': fields.Str(default='bin', missing='bin') }, 'info': 'decompile a function with retdec' }) def decompile(self, args, file, opts): kwargs = {'input_file': file.file_path} name = '' # Check the mode and ensure the options if args['mode'] == 'bin': if 'address_range' in args and args['address_range'] == '': del args['address_range'] if 'function_name' in args and args['function_name'] == '': del args['function_name'] if 'address_range' not in args and 'function_name' not in args: raise error.CommandError( "'address_range' or 'function_name' must be set") if 'address_range' in args and 'function_name' in args: raise error.CommandError( "'address_range' and 'function_name' are mutually exclusive" ) if 'address_range' in args: name = '{}'.format(args['address_range'].strip()) kwargs['sel_decomp_ranges'] = name.strip() elif 'function_name' in args: name = '{}'.format(args['function_name'].strip()) kwargs['sel_decomp_funcs'] = name.strip() else: raise error.CommandError( "incorrect mode specified '{}' the following are supported: 'bin'" .format(args['mode'])) return {'code': self._decompile(kwargs), 'name': name} def decompile_markdown(self, json): output = md.h3(json['name']) output += md.code(json['code'], lang='c') return output @scale.command({'info': 'returns a list of functions using radare2'}) def functions(self, args, file, opts): r2 = r2pipe.open(file.file_path, ['-2']) # pylint: disable=invalid-name output = {} output['exports'] = r2.cmdj('iEj') output['functions'] = [] r2.cmd('aaa') funcs = r2.cmdj('aflj') if funcs: for i in funcs: i['address_range'] = '0x%08x-0x%08x' % ( i['offset'], i['offset'] + i['size']) output['functions'] += [i] return output def functions_markdown(self, json): output = md.h3('Exports') output += md.table_header(('Virtual Address', 'Size', 'Type', 'Name')) if not json['exports']: output += md.table_row(('-', '-', '-', '-')) else: for row in json['exports']: output += md.table_row( ('0x%08x' % row['vaddr'], '%u' % row['size'], row['type'], md.bold(row['name']))) output += md.newline() output += md.h3('Functions') output += md.table_header(('Address Range', 'Offset', 'Size', 'Name')) if not json['functions']: output += md.table_row(('-', '-', '-')) else: for row in json['functions']: output += md.table_row( (md.bold(row['address_range']), '0x%08x' % row['offset'], '%u' % row['size'], row['name'])) return output
def arguments(self): return {'url': fields.Str(required=True)}
class Interface(scale.Interface): def check(self): if CUCKOO_API is None or CUCKOO_API == '': raise error.InterfaceError( "config variable 'cuckoo_api' has not been set") @scale.pull({'info': 'summary of scores for the sample'}) def info(self, args, file, opts): try: j = requests.get(CUCKOO_API + '/files/view/sha256/' + file.sha256_digest, verify=VERIFY).json() except requests.exceptions.RequestException: raise error.InterfaceError("failed to connect to Cuckoo") if 'sample' not in j: raise error.InterfaceWarning( "file has never been submitted to Cuckoo") s_id = j['sample']['id'] r = requests.get(CUCKOO_API + '/tasks/list', verify=VERIFY) if not r.status_code == requests.codes.ok: # pylint: disable=no-member return "No reports, sample must be pending/running", "pending" j = r.json() output = [] for t in j['tasks']: if t['sample_id'] == s_id: r = requests.get(CUCKOO_API + '/tasks/report/' + str(t['id']), verify=VERIFY) if r.status_code == requests.codes.ok: # pylint: disable=no-member j = r.json() output += [{ 'score': j['info']['score'], 'name': j['info']['machine']['name'] }] if not output: return error.InterfaceWarning("no information available!") return {'info': output} def info_markdown(self, json): output = md.table_header(('Machine', 'Score')) for j in json['info']: score = j['score'] if score > 5: s = "%red " + str(score) + " %" elif score > 3: s = "%yellow " + str(score) + " %" else: s = str(score) output += md.table_row((j['name'], s)) return output @scale.pull({ 'args': { 'id': fields.Str(required=True) }, 'info': 'view report summary' }) def report(self, args, file, opts): # TODO: Hash match! try: r = requests.get(CUCKOO_API + '/tasks/report/' + args['id'], verify=VERIFY) except requests.exceptions.RequestException: raise error.InterfaceError("failed to connect to Cuckoo") if not r.status_code == requests.codes.ok: # pylint: disable=no-member return "No task for given id" j = r.json() output = { 'score': j['info']['score'], 'platform': j['info']['platform'], 'analysis': { 'category': j['info']['category'], 'started': j['info']['started'], 'ended': j['info']['ended'], 'duration': j['info']['duration'] }, 'machine': { 'name': j['info']['machine']['name'], 'manager': j['info']['machine']['manager'] }, 'signatures': [{ 'severity': x['severity'], 'description': x['description'] } for x in j['signatures']] } return output def report_markdown(self, json): output = md.h4('General') output += md.paragraph(md.bold('Score: ') + str(json['score'])) output += md.cr() output += md.paragraph(md.bold('Platform: ') + json['platform']) output += md.h4('Analysis') output += md.table_header(('Category', 'Started', 'Ended', 'Duration')) output += md.table_row( (json['analysis']['category'], str(json['analysis']['started']), str(json['analysis']['ended']), str(json['analysis']['duration']))) output += md.h4('Machines') output += md.table_header(('Name', 'Manager')) output += md.table_row( (json['machine']['name'], json['machine']['manager'])) output += md.h4('Signatures') output += md.table_header(('Severity', 'Description')) for s in json['signatures']: if s['severity'] > 2: output += md.table_row( ('%red ' + str(s['severity']) + ' %', s['description'])) elif s['severity'] > 1: output += md.table_row( ('%orange ' + str(s['severity']) + ' %', s['description'])) else: output += md.table_row( ('%blue ' + str(s['severity']) + ' %', s['description'])) return output @scale.pull({'info': 'view reports for sample'}) def reports(self, args, file, opts): try: j = requests.get(CUCKOO_API + '/files/view/sha256/' + file.sha256_digest, verify=VERIFY).json() except requests.exceptions.RequestException: raise error.InterfaceError("failed to connect to Cuckoo") if 'sample' not in j: raise error.InterfaceWarning( "file has never been submitted to Cuckoo") s_id = j['sample']['id'] r = requests.get(CUCKOO_API + '/tasks/list', verify=VERIFY) if not r.status_code == requests.codes.ok: # pylint: disable=no-member return "No reports, sample must be pending/running", "pending" j = r.json() output = {'reports': []} for t in j['tasks']: if t['sample_id'] == s_id: output['reports'] += [{ 'id': str(t['id']), 'url': config.scale_configs['cuckoo']['cuckoo_url'] + str(t['id']), 'timestamp': str(t['added_on']), 'status': str(t['status']) }] return output def reports_markdown(self, json): output = md.table_header(('ID', 'URL', 'Timestamp', 'Status')) for r in json['reports']: output += md.table_row( (r['id'], r['url'], r['timestamp'], r['status'])) return output @scale.push({ 'args': { 'machine': fields.Str(required=False), 'priority': fields.Int(required=False), 'timeout': fields.Int(required=False) }, 'info': 'submit sample to cuckoo' }) def submit(self, args, file, opts): document = db.file_collection.select(file.sha256_digest) with open(file.file_path, "rb") as f: try: r = requests.post(CUCKOO_API + '/tasks/create/file', files={"file": (document['name'], f)}, verify=VERIFY) except requests.exceptions.RequestException: raise error.InterfaceError("failed to connect to Cuckoo") if not r.status_code == requests.codes.ok: # pylint: disable=no-member raise error.InterfaceError('failed to submit sample to Cuckoo') j = r.json() if not j["task_id"]: raise error.InterfaceError('failed to submit sample to Cuckoo') return j
class Commands(scale.Commands): # pylint: disable=too-many-lines, too-many-public-methods def check(self): if not shutil.which('rekall'): raise error.CommandError("binary 'rekall' not found") # Generic run a command, useful for most commands def run_command(self, file, args, vol_cmd): if isinstance(vol_cmd, list): cmd = ['rekall', '-f', file.file_path] cmd += vol_cmd else: cmd = ['rekall', '-f', file.file_path, '%s' % (vol_cmd)] if config.scale_configs['rekall']['repository_path']: cmd += [ '--repository_path', config.scale_configs['rekall']['repository_path'] ] if config.scale_configs['rekall']['cache_dir']: cmd += ['--cache_dir', config.scale_configs['rekall']['cache_dir']] env = environ.copy() if 'http_proxy' in config.snake_config.keys(): env['http_proxy'] = config.snake_config['http_proxy'] env['HTTP_PROXY'] = config.snake_config['http_proxy'] env['https_proxy'] = config.snake_config['https_proxy'] env['HTTPS_PROXY'] = config.snake_config['https_proxy'] proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) if proc.returncode != 0: raise error.CommandError(proc.stderr) return str(proc.stdout, encoding="utf-8") @scale.command({ 'args': { 'hive_offset': fields.Str(required=True), }, 'info': 'prints out a hive' }) def hivedump(self, args, file, opts): cmd = [ 'rekall', '-f', file.file_path, 'hivedump', '--hive-offset', '%s' % (args['hive_offset']) ] if config.scale_configs['rekall']['repository_path']: cmd += [ '--repository_path', config.scale_configs['rekall']['repository_path'] ] if config.scale_configs['rekall']['cache_dir']: cmd += ['--cache_dir', config.scale_configs['rekall']['cache_dir']] env = environ.copy() if 'http_proxy' in config.snake_config.keys(): env['http_proxy'] = config.snake_config['http_proxy'] env['HTTP_PROXY'] = config.snake_config['http_proxy'] env['https_proxy'] = config.snake_config['https_proxy'] env['HTTPS_PROXY'] = config.snake_config['https_proxy'] proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) if proc.returncode != 0: raise error.CommandError(proc.stderr) return {'hivedump': str(proc.stdout, encoding="utf-8")} def hivedump_plaintext(self, json): return json['hivedump'] @scale.command({'info': 'scan for possible _KDDEBUGGER_DATA64 structures'}) def kdbgscan(self, args, file, opts): return {'kdbgscan': self.run_command(file, args, 'kdbgscan')} def kdbgscan_plaintext(self, json): return json['kdbgscan'] @scale.command({'info': 'list overview information about this image'}) def imageinfo(self, args, file, opts): return {'imageinfo': self.run_command(file, args, 'imageinfo')} def imageinfo_plaintext(self, json): return json['imageinfo'] @scale.command({ 'args': { 'offset': fields.Str(required=True), 'profile': fields.Str(required=False) }, 'info': 'a plugin to analyze a memory location' }) def analyze_struct(self, args, file, opts): return { 'analyze_struct': self.run_command(file, args, 'analyze_struct') } def analyze_struct_plaintext(self, json): return json['analyze_struct'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print session and window station atom tables' }) def atoms(self, args, file, opts): return {'atoms': self.run_command(file, args, 'atoms')} def atoms_plaintext(self, json): return json['atoms'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'pool scanner for _RTL_ATOM_TABLE' }) def atomscan(self, args, file, opts): return {'atomscan': self.run_command(file, args, 'atomscan')} def atomscan_plaintext(self, json): return json['atomscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'enumerate callback routines' }) def callbacks(self, args, file, opts): return {'callback': self.run_command(file, args, 'callbacks')} def callbacks_plaintext(self, json): return json['callback'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'a cc plugin for windows' }) def cc(self, args, file, opts): return {'cc': self.run_command(file, args, 'cc')} def cc_plaintext(self, json): return json['cc'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'checks a pe file mapped into memory for hooks' }) def check_pehooks(self, args, file, opts): return {'check_pehook': self.run_command(file, args, 'check_pehooks')} def check_pehooks_plaintext(self, json): return json['check_pehooks'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'extract the contents of the windows clipboard' }) def clipboard(self, args, file, opts): return {'clipboard': self.run_command(file, args, 'clipboard')} def clipboard_plaintext(self, json): return json['clipboard'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'extract command history by scanning for _COMMAND_HISTORY' }) def cmdscan(self, args, file, opts): return {'cmdscan': self.run_command(file, args, 'cmdscan')} def cmdscan_plaintext(self, json): return json['cmdscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print list of open connections [Windows XP and 2003 Only]' }) def connections(self, args, file, opts): return {'connection': self.run_command(file, args, 'connections')} def connections_plaintext(self, json): return json['connection'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'pool scanner for tcp connections' }) def connscan(self, args, file, opts): return {'connscan': self.run_command(file, args, 'connscan')} def connscan_plaintext(self, json): return json['connscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'enumerate command consoles' }) def consoles(self, args, file, opts): return {'consoles': self.run_command(file, args, 'consoles')} def consoles_plaintext(self, json): return json['consoles'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print information on each desktop' }) def desktops(self, args, file, opts): return {'desktops': self.run_command(file, args, 'desktops')} def desktops_plaintext(self, json): return json['desktops'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'show device tree' }) def devicetree(self, args, file, opts): return {'devicetree': self.run_command(file, args, 'devicetree')} def devicetree_plaintext(self, json): return json['devicetree'] @scale.command({ 'args': { 'offset': fields.Str(required=False), 'profile': fields.Str(required=False) }, 'info': 'disassemble the given offset' }) def dis(self, args, file, opts): return {'dis': self.run_command(file, args, 'dis')} def dis_plaintext(self, json): return json['dis'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'prints a list of dll modules mapped into each process' }) def dlllist(self, args, file, opts): return {'dlllist': self.run_command(file, args, 'dlllist')} def dlllist_plaintext(self, json): return json['dlllist'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'dump the windows DNS resolver cache' }) def dns_cache(self, args, file, opts): return {'dns_cache': self.run_command(file, args, 'dns_cache')} def dns_cache_plaintext(self, json): return json['dns_cache'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'driver IRP hook detection' }) def driverirp(self, args, file, opts): return {'driverirp': self.run_command(file, args, 'driverirp')} def driverirp_plaintext(self, json): return json['driverirp'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'scan for driver objects _DRIVER_OBJECT' }) def driverscan(self, args, file, opts): return {'driverscan': self.run_command(file, args, 'driverscan')} def driverscan_plaintext(self, json): return json['driverscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'scans the physical memory for DTB values' }) def dtbscan(self, args, file, opts): return {'dtbscan': self.run_command(file, args, 'dtbscan')} def dtbscan_plaintext(self, json): return json['dtbscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print details on windows event hooks' }) def eventhooks(self, args, file, opts): return {'eventhook': self.run_command(file, args, 'eventhooks')} def eventhooks_plaintext(self, json): return json['eventhooks'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'extract Windows Event Logs (XP/2003 only)' }) def evtlogs(self, args, file, opts): return {'evtlog': self.run_command(file, args, 'evtlogs')} def evtlogs_plaintext(self, json): return json['evtlogs'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'scan Physical memory for _FILE_OBJECT pool allocations' }) def filescan(self, args, file, opts): return {'filescan': self.run_command(file, args, 'filescan')} def filescan_plaintext(self, json): return json['filescan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'a plugin to search for the Directory Table Base for windows system' }) def find_dtb(self, args, file, opts): return {'find_dtb': self.run_command(file, args, 'find_dtb')} def find_dtb_plaintext(self, json): return json['find_dtb'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'none' }) def fls(self, args, file, opts): return {'fls': self.run_command(file, args, 'fls')} def fls_plaintext(self, json): return json['fls'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'dump the USER handle type information' }) def gahti(self, args, file, opts): return {'gahti': self.run_command(file, args, 'gahti')} def gahti_plaintext(self, json): return json['gahti'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'get the names of services in the Registry and return Calculated SID' }) def getservicesids(self, args, file, opts): return { 'getservicesid': self.run_command(file, args, 'getservicesids') } def getservicesids_plaintext(self, json): return json['getservicesids'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print list of open handles for each process' }) def handles(self, args, file, opts): return {'handles': self.run_command(file, args, 'handles')} def handles_plaintext(self, json): return json['handles'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print list of registry hives on the system' }) def hives(self, args, file, opts): return {'hives': self.run_command(file, args, 'hives')} def hives_plaintext(self, json): return json['hives'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'detect EAT hooks in process and kernel memory' }) def hooks_eat(self, args, file, opts): return {'hooks_eat': self.run_command(file, args, 'hooks_eat')} def hooks_eat_plaintext(self, json): return json['hooks_eat'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'detect IAT/EAT hooks in process and kernel memory' }) def hooks_iat(self, args, file, opts): return {'hooks_iat': self.run_command(file, args, 'hooks_iat')} def hooks_iat_plaintext(self, json): return json['hooks_iat'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'detect API hooks in process and kernel memory' }) def hooks_inline(self, args, file, opts): return self.run_command(file, args, 'hooks_inline') @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'scan for calls to imported functions' }) def impscan(self, args, file, opts): return {'impscan': self.run_command(file, args, 'impscan')} def impscan_plaintext(self, json): return json['impscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'a plugin to print all KPCR blocks' }) def kpcr(self, args, file, opts): return {'kpcr': self.run_command(file, args, 'kpcr')} def kpcr_plaintext(self, json): return json['kpcr'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'detect unlinked DLLs' }) def ldrmodules(self, args, file, opts): return {'ldrmodule': self.run_command(file, args, 'ldrmodules')} def ldrmodules_plaintext(self, json): return json['ldrmodules'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'find hidden and injected code' }) def malfind(self, args, file, opts): return {'malfind': self.run_command(file, args, 'malfind')} def malfind_plaintext(self, json): return json['malfind'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'calculates the memory regions mapped by a process' }) def memmap(self, args, file, opts): return {'memmap': self.run_command(file, args, 'memmap')} def memmap_plaintext(self, json): return json['memmap'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'list desktop and thread window message hooks' }) def messagehooks(self, args, file, opts): return {'messagehook': self.run_command(file, args, 'messagehooks')} def messagehooks_plaintext(self, json): return json['messagehooks'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'extract and decrypt passwords from the LSA Security Service' }) def mimikatz(self, args, file, opts): return {'mimikatz': self.run_command(file, args, 'mimikatz')} def mimikatz_plaintext(self, json): return json['mimikatz'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'scan Physical memory for _LDR_DATA_TABLE_ENTRY objects' }) def modscan(self, args, file, opts): return {'modscan': self.run_command(file, args, 'modscan')} def modscan_plaintext(self, json): return json['modscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print list of loaded modules' }) def modules(self, args, file, opts): return {'modules': self.run_command(file, args, 'modules')} def modules_plaintext(self, json): return json['modules'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'scan for mutant objects _KMUTANT' }) def mutantscan(self, args, file, opts): return {'mutantscan': self.run_command(file, args, 'mutantscan')} def mutantscan_plaintext(self, json): return json['mutantscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'scan a Vista, 2008 or Windows 7 image for connections and sockets' }) def netscan(self, args, file, opts): return {'netscan': self.run_command(file, args, 'netscan')} def netscan_plaintext(self, json): return json['netscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print the active network connections' }) def netstat(self, args, file, opts): return {'netstat': self.run_command(file, args, 'netstat')} def netstat_plaintext(self, json): return json['netstat'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'visualize the kernel object tree' }) def object_tree(self, args, file, opts): return {'object_tree': self.run_command(file, args, 'object_tree')} def object_tree_plaintext(self, json): return json['object_tree'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'displays all object Types on the system' }) def object_types(self, args, file, opts): return {'object_types': self.run_command(file, args, 'object_types')} def object_types_plaintext(self, json): return json['object_types'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'report all the active pagefiles' }) def pagefiles(self, args, file, opts): return {'pagefiles': self.run_command(file, args, 'pagefiles')} def pagefiles_plaintext(self, json): return json['pagefiles'] @scale.command({ 'args': { 'offset': fields.Str(required=True), 'profile': fields.Str(required=False) }, 'info': 'resolves a physical address to a virtual addrress in a process' }) def pas2vas(self, args, file, opts): return {'pas2vas': self.run_command(file, args, 'pas2vas')} def pas2vas_plaintext(self, json): return json['pas2vas'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print information about a PE binary' }) def peinfo(self, args, file, opts): return {'peinfo': self.run_command(file, args, 'peinfo')} def peinfo_plaintext(self, json): return json['peinfo'] @scale.command({ 'args': { 'pfn': fields.Str(required=True), 'profile': fields.Str(required=False) }, 'info': 'prints information about an address from the PFN database' }) def pfn(self, args, file, opts): return {'pfn': self.run_command(file, args, 'pfn')} def pfn_plaintext(self, json): return json['pfn'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'prints the boot physical memory map' }) def phys_map(self, args, file, opts): return {'phys_map': self.run_command(file, args, 'phys_map')} def phys_map_plaintext(self, json): return json['phys_map'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'enumerate pool tag usage statistics' }) def pool_tracker(self, args, file, opts): return {'pool_tracker': self.run_command(file, args, 'pool_tracker')} def pool_tracker_plaintext(self, json): return json['pool_tracker'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'prints information about system pools' }) def pools(self, args, file, opts): return {'pools': self.run_command(file, args, 'pools')} def pools_plaintext(self, json): return json['pools'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print a registry key, and its subkeys and values' }) def printkey(self, args, file, opts): return {'printkey': self.run_command(file, args, 'printkey')} def printkey_plaintext(self, json): return json['printkey'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'prints process privileges' }) def privileges(self, args, file, opts): return {'privileges': self.run_command(file, args, 'privileges')} def privileges_plaintext(self, json): return json['privileges'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'dump detailed information about a running process' }) def procinfo(self, args, file, opts): return {'procinfo': self.run_command(file, args, 'procinfo')} def procinfo_plaintext(self, json): return json['procinfo'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'list processes for windows' }) def pslist(self, args, file, opts): return {'pslist': self.run_command(file, args, 'pslist')} def pslist_plaintext(self, json): return json['pslist'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'scan Physical memory for _EPROCESS pool allocations' }) def psscan(self, args, file, opts): return {'psscan': self.run_command(file, args, 'psscan')} def psscan_plaintext(self, json): return json['psscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print process list as a tree' }) def pstree(self, args, file, opts): return {'pstree': self.run_command(file, args, 'pstree')} def pstree_plaintext(self, json): return json['pstree'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'find hidden processes with various process listings' }) def psxview(self, args, file, opts): return {'psxview': self.run_command(file, args, 'psxview')} def psxview_plaintext(self, json): return json['psxview'] @scale.command({ 'args': { 'offset': fields.Str(required=True), 'profile': fields.Str(required=False) }, 'info': 'converts a physical address to a virtual address' }) def ptov(self, args, file, opts): return {'ptov': self.run_command(file, args, 'psxview')} def ptov_plaintext(self, json): return json['ptov'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'scan all physical memory and report page owners' }) def rammap(self, args, file, opts): return {'rammap': self.run_command(file, args, 'rammap')} def rammap_plaintext(self, json): return json['rammap'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'enumerate all services' }) def services(self, args, file, opts): return {'services': self.run_command(file, args, 'services')} def services_plaintext(self, json): return json['services'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'list details on _MM_SESSION_SPACE (user logon sessions)' }) def sessions(self, args, file, opts): return {'sessions': self.run_command(file, args, 'sessions')} def sessions_plaintext(self, json): return json['sessions'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'dump RSA private and public SSL keys from the physical address space' }) def simple_certscan(self, args, file, opts): return { 'simple_certscan': self.run_command(file, args, 'simple_certscan') } def simple_certscan_plaintext(self, json): return json['simple_certscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print list of open sockets [Windows XP only]' }) def sockets(self, args, file, opts): return {'sockets': self.run_command(file, args, 'sockets')} def sockets_plaintext(self, json): return json['sockets'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'enumerate the SSDT' }) def ssdt(self, args, file, opts): return {'ssdt': self.run_command(file, args, 'ssdt')} def ssdt_plaintext(self, json): return json['ssdt'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'scan for Windows services' }) def svcscan(self, args, file, opts): return {'svcscan': self.run_command(file, args, 'svcscan')} def svcscan_plaintext(self, json): return json['svcscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'scan for symbolic link objects' }) def symlinkscan(self, args, file, opts): return {'symlinkscan': self.run_command(file, args, 'symlinkscan')} def symlinkscan_plaintext(self, json): return json['symlinkscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'scan physical memory for _ETHREAD objects' }) def thrdscan(self, args, file, opts): return {'thrdscan': self.run_command(file, args, 'thrdscan')} def thrdscan_plaintext(self, json): return json['thrdscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'enumerate threads' }) def threads(self, args, file, opts): return {'threads': self.run_command(file, args, 'threads')} def threads_plaintext(self, json): return json['threads'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print kernel timers and associated module DPCs' }) def timers(self, args, file, opts): return {'timers': self.run_command(file, args, 'timers')} def timers_plaintext(self, json): return json['timers'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'return current time, as known to the kernel' }) def times(self, args, file, opts): return {'times': self.run_command(file, args, 'times')} def times_plaintext(self, json): return json['times'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print the SIDs owning each process token' }) def tokens(self, args, file, opts): return {'tokens': self.run_command(file, args, 'tokens')} def tokens_plaintext(self, json): return json['tokens'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print list of recently unloaded modules' }) def unloaded_modules(self, args, file, opts): return { 'unloaded_modules': self.run_command(file, args, 'unloaded_modules') } def unloaded_modules_plaintext(self, json): return json['unloaded_modules'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print userassist registry keys and information' }) def userassist(self, args, file, opts): return {'userassist': self.run_command(file, args, 'userassist')} def userassist_plaintext(self, json): return json['userassist'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'dump the USER handle tables' }) def userhandles(self, args, file, opts): return {'userhandles': self.run_command(file, args, 'userhandles')} def userhandles_plaintext(self, json): return json['userhandles'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'enumerate all users of this system' }) def users(self, args, file, opts): return {'users': self.run_command(file, args, 'users')} def users_plaintext(self, json): return json['users'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'enumerate all blocks cached in the cache manager' }) def vacbs(self, args, file, opts): return {'vacbs': self.run_command(file, args, 'vacbs')} def vacbs_plaintext(self, json): return json['vacbs'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'concise dump of the VAD' }) def vad(self, args, file, opts): return {'vad': self.run_command(file, args, 'vad')} def vad_plaintext(self, json): return json['vad'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'inspect each page in the VAD and report its status' }) def vadmap(self, args, file, opts): return {'vadmap': self.run_command(file, args, 'vadmap')} def vadmap_plaintext(self, json): return json['vadmap'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'try to determine the versions for all kernel drivers' }) def version_modules(self, args, file, opts): return { 'version_modules': self.run_command(file, args, 'version_modules') } def version_modules_plaintext(self, json): return json['version_modules'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'prints the Windows Kernel Virtual Address Map' }) def virt_map(self, args, file, opts): return {'virt_map': self.run_command(file, args, 'virt_map')} def virt_map_plaintext(self, json): return json['virt_map'] @scale.command({ 'args': { 'name': fields.Str(required=True), 'profile': fields.Str(required=False) }, 'info': 'prints information about the virtual to physical translation' }) def vtop(self, args, file, opts): return {'vtop': self.run_command(file, args, 'vtop')} def vtop_plaintext(self, json): return json['vtop'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'automatically detect win32k struct layout' }) def win32k_autodetect(self, args, file, opts): return { 'win32k_autodetect': self.run_command(file, args, 'win32k_autodetect') } def win32k_autodetect_plaintext(self, json): return json['win32k_autodetect']
class UploadFileSchema(schema.FileSchema): """Extends `FileSchema`.""" name = fields.Str(required=False) # Override extract = fields.Bool(missing=False) password = fields.Str(missing=None)
class Commands(scale.Commands): # pylint: disable=too-many-lines, too-many-public-methods def check(self): self.vol = None if config.scale_configs['volatility']['vol_path']: if path.exists(config.scale_configs['volatility']['vol_path']): self.vol = config.scale_configs['volatility']['vol_path'] else: raise error.CommandError( "binary 'vol.py' not found - 'vol_path' not set") if not self.vol: raise error.CommandError("binary 'vol.py' not found") def get_profile(self, file): document = db.file_collection.select(file.sha256_digest) if 'profile' not in document: self.imageinfo(None, file.sha256_digest) # pylint: disable=no-value-for-parameter document = db.file_collection.select(file.sha256_digest) if 'profile' not in document: raise error.CommandError( 'Unable to automatically determine profile!') return document['profile'] # Generic run a command, useful for most commands def run_command(self, file, args, vol_cmd): cmd = [self.vol, '-f', file.file_path, '%s' % (vol_cmd)] if 'profile' in args and args['profile'] != '': cmd += ['--profile', args['profile']] else: profile = self.get_profile(file) cmd += ['--profile', profile] proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if proc.returncode != 0: raise error.CommandError(proc.stderr) return str(proc.stdout, encoding="utf-8") @scale.command({ 'args': { 'hive_offset': fields.Str(required=True), 'profile': fields.Str(required=False) }, 'info': 'prints out a hive' }) def hivedump(self, args, file, opts): cmd = [ self.vol, '-f', file.file_path, 'hivedump', '--hive-offset', '%s' % (args['hive_offset']) ] if 'profile' in args and args['profile'] != '': cmd += ['--profile', args['profile']] else: profile = self.get_profile(file) cmd += ['--profile', profile] proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if proc.returncode != 0: raise error.CommandError(proc.stderr) return {'hivedump': str(proc.stdout, encoding="utf-8")} def hivedump_plaintext(self, json): return json['hivedump'] @scale.command({'info': 'search for and dump potential KDBG values'}) def kdbgscan(self, args, file, opts): proc = subprocess.run([self.vol, '-f', file.file_path, 'kdbgscan'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) if proc.returncode != 0: raise error.CommandError(proc.stderr) return {'kdbgscan': str(proc.stdout, encoding="utf-8")} def kdbgscan_plaintext(self, json): return json['kdbgscan'] @scale.autorun @scale.command({'info': 'identify information for the image'}) def imageinfo(self, args, file, opts): proc = subprocess.run([self.vol, '-f', file.file_path, 'imageinfo'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) if proc.returncode != 0: raise error.CommandError(proc.stderr) output = str(proc.stdout, encoding="utf-8") # Try and extract profile try: prof = output.split('\n')[0].split(':')[1] if 'suggestion' not in prof: if ',' in prof: prof = prof.split(',')[0] data = {'profile': prof.strip()} if not db.file_collection.update(file.sha256_digest, data): raise error.MongoError( 'Error adding profile into file document %s' % file.sha256_digest) except Exception: # noqa pylint: disable=broad-except pass return {'imageinfo': output} def imageinfo_plaintext(self, json): return json['imageinfo'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print AmCache information' }) def amcache(self, args, file, opts): return {'amcache': self.run_command(file, args, 'amcache')} def amcache_plaintext(self, json): return json['amcache'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'detect API hooks in process and kernel memory' }) def apihooks(self, args, file, opts): return {'apihooks': self.run_command(file, args, 'apihooks')} def apihooks_plaintext(self, json): return json['apihooks'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print session and window station atom tables' }) def atoms(self, args, file, opts): return {'atoms': self.run_command(file, args, 'atoms')} def atoms_plaintext(self, json): return json['atoms'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'pool scanner for atom tables' }) def atomscan(self, args, file, opts): return {'atomscan': self.run_command(file, args, 'atomscan')} def atomscan_plaintext(self, json): return json['atomscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': r'prints out the Audit Policies from HKLM\SECURITY\Policy\PolAdtEv' }) def auditpol(self, args, file, opts): return {'auditpol': self.run_command(file, args, 'auditpol')} def auditpol_plaintext(self, json): return json['auditpol'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'dump the big page pools using BigPagePoolScanner' }) def bigpools(self, args, file, opts): return {'bigpools': self.run_command(file, args, 'bigpools')} def bigpools_plaintext(self, json): return json['bigpools'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'reads the keyboard buffer from Real Mode memory' }) def bioskbd(self, args, file, opts): return {'bioskbd': self.run_command(file, args, 'bioskbd')} def bioskbd_plaintext(self, json): return json['bioskbd'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'dumps cached domain hashes from memory' }) def cachedump(self, args, file, opts): return {'cachedump': self.run_command(file, args, 'cachedump')} def cachedump_plaintext(self, json): return json['cachedump'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print system-wide notification routines' }) def callbacks(self, args, file, opts): return {'callbacks': self.run_command(file, args, 'callbacks')} def callbacks_plaintext(self, json): return json['callbacks'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'extract the contents of the windows clipboard' }) def clipboard(self, args, file, opts): return {'clipboard': self.run_command(file, args, 'clipboard')} def clipboard_plaintext(self, json): return json['clipboard'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'display process command-line arguments' }) def cmdline(self, args, file, opts): return {'cmdline': self.run_command(file, args, 'cmdline')} def cmdline_plaintext(self, json): return json['cmdline'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'extract command history by scanning for _COMMAND_HISTORY' }) def cmdscan(self, args, file, opts): return {'cmdscan': self.run_command(file, args, 'cmdscan')} def cmdscan_plaintext(self, json): return json['cmdscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print list of open connections [Windows XP and 2003 Only]' }) def connections(self, args, file, opts): return {'connections': self.run_command(file, args, 'connections')} def connections_plaintext(self, json): return json['connections'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'pool scanner for tcp connections' }) def connscan(self, args, file, opts): return {'connscan': self.run_command(file, args, 'connscan')} def connscan_plaintext(self, json): return json['connscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'extract command history by scanning for _CONSOLE_INFORMATION' }) def consoles(self, args, file, opts): return {'consoles': self.run_command(file, args, 'consoles')} def consoles_plaintext(self, json): return json['consoles'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'dump crash-dump information' }) def crashinfo(self, args, file, opts): return {'crashinfo': self.run_command(file, args, 'crashinfo')} def crashinfo_plaintext(self, json): return json['crashinfo'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'poolscaner for tagDESKTOP (desktops)' }) def deskscan(self, args, file, opts): return {'deskscan': self.run_command(file, args, 'deskscan')} def deskscan_plaintext(self, json): return json['deskscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'show device tree' }) def devicetree(self, args, file, opts): return {'devicetree': self.run_command(file, args, 'devicetree')} def devicetree_plaintext(self, json): return json['devicetree'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print list of loaded dlls for each process' }) def dlllist(self, args, file, opts): return {'dlllist': self.run_command(file, args, 'dlllist')} def dlllist_plaintext(self, json): return json['dlllist'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'driver IRP hook detection' }) def driverirp(self, args, file, opts): return {'driverirp': self.run_command(file, args, 'driverirp')} def driverirp_plaintext(self, json): return json['driverirp'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'associate driver objects to kernel modules' }) def drivermodule(self, args, file, opts): return {'drivermodule': self.run_command(file, args, 'drivermodule')} def drivermodule_plaintext(self, json): return json['drivermodule'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'pool scanner for driver objects' }) def driverscan(self, args, file, opts): return {'driverscan': self.run_command(file, args, 'driverscan')} def driverscan_plaintext(self, json): return json['driverscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'dump RSA private and public SSL keys' }) def dumpcerts(self, args, file, opts): return {'dumpcerts': self.run_command(file, args, 'dumpcerts')} def dumpcerts_plaintext(self, json): return json['dumpcerts'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'displays information about Edit controls. (Listbox experimental.)' }) def editbox(self, args, file, opts): return {'editbox': self.run_command(file, args, 'editbox')} def editbox_plaintext(self, json): return json['editbox'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'display process environment variables' }) def envars(self, args, file, opts): return {'envars': self.run_command(file, args, 'envars')} def envars_plaintext(self, json): return json['envars'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print details on windows event hooks' }) def eventhooks(self, args, file, opts): return {'eventhooks': self.run_command(file, args, 'eventhooks')} def eventhooks_plaintext(self, json): return json['eventhooks'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'extract Windows Event Logs (XP/2003 only)' }) def evtlogs(self, args, file, opts): return {'evtlogs': self.run_command(file, args, 'evtlogs')} def evtlogs_plaintext(self, json): return json['evtlogs'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'pool scanner for file objects' }) def filescan(self, args, file, opts): return {'filescan': self.run_command(file, args, 'filescan')} def filescan_plaintext(self, json): return json['filescan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'dump the USER handle type information' }) def gahti(self, args, file, opts): return {'gahti': self.run_command(file, args, 'gahti')} def gahti_plaintext(self, json): return json['gahti'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print installed GDI timers and callbacks' }) def gditimers(self, args, file, opts): return {'gditimers': self.run_command(file, args, 'gditimers')} def gditimers_plaintext(self, json): return json['gditimers'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'display Global Descriptor Table' }) def gdt(self, args, file, opts): return {'gdt': self.run_command(file, args, 'gdt')} def gdt_plaintext(self, json): return json['gdt'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'get the names of services in the Registry and return Calculated SID' }) def getservicesids(self, args, file, opts): return { 'getservicesids': self.run_command(file, args, 'getservicesids') } def getservicesids_plaintext(self, json): return json['getservicesids'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print the SIDs owning each process' }) def getsids(self, args, file, opts): return {'getsids': self.run_command(file, args, 'getsids')} def getsids_plaintext(self, json): return json['getsids'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print list of open handles for each process' }) def handles(self, args, file, opts): return {'handles': self.run_command(file, args, 'handles')} def handles_plaintext(self, json): return json['handles'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'dumps passwords hashes (LM/NTLM) from memory' }) def hashdump(self, args, file, opts): return {'hashdump': self.run_command(file, args, 'hashdump')} def hashdump_plaintext(self, json): return json['hashdump'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'dump hibernation file information' }) def hibinfo(self, args, file, opts): return {'hibinfo': self.run_command(file, args, 'hibinfo')} def hibinfo_plaintext(self, json): return json['hibinfo'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print list of registry hives' }) def hivelist(self, args, file, opts): return {'hivelist': self.run_command(file, args, 'hivelist')} def hivelist_plaintext(self, json): return json['hivelist'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'pool scanner for registry hives' }) def hivescan(self, args, file, opts): return {'hivescan': self.run_command(file, args, 'hivescan')} def hivescan_plaintext(self, json): return json['hivescan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'display Interrupt Descriptor Table' }) def idt(self, args, file, opts): return {'idt': self.run_command(file, args, 'idt')} def idt_plaintext(self, json): return json['idt'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'reconstruct Internet Explorer cache / history' }) def iehistory(self, args, file, opts): return {'iehistory': self.run_command(file, args, 'iehistory')} def iehistory_plaintext(self, json): return json['iehistory'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'scan for calls to imported functions' }) def impscan(self, args, file, opts): return {'impscan': self.run_command(file, args, 'impscan')} def impscan_plaintext(self, json): return json['impscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print process job link information' }) def joblinks(self, args, file, opts): return {'joblinks': self.run_command(file, args, 'joblinks')} def joblinks_plaintext(self, json): return json['joblinks'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'search for and dump potential KPCR values' }) def kpcrscan(self, args, file, opts): return {'kpcrscan': self.run_command(file, args, 'kpcrscan')} def kpcrscan_plaintext(self, json): return json['kpcrscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'detect unlinked DLLs' }) def ldrmodules(self, args, file, opts): return {'ldrmodules': self.run_command(file, args, 'ldrmodules')} def ldrmodules_plaintext(self, json): return json['ldrmodules'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'dump (decrypted) LSA secrets from the registry' }) def lsadump(self, args, file, opts): return {'lsadump': self.run_command(file, args, 'lsadump')} def lsadump_plaintext(self, json): return json['lsadump'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'dump Mach-O file format information' }) def machoinfo(self, args, file, opts): return {'machoinfo': self.run_command(file, args, 'machoinfo')} def machoinfo_plaintext(self, json): return json['machoinfo'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'find hidden and injected code' }) def malfind(self, args, file, opts): return {'malfind': self.run_command(file, args, 'malfind')} def malfind_plaintext(self, json): return json['malfind'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'scans for and parses potential Master Boot Records (MBRs)' }) def mbrparser(self, args, file, opts): return {'mbrparser': self.run_command(file, args, 'mbrparser')} def mbrparser_plaintext(self, json): return json['mbrparser'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print the memory map' }) def memmap(self, args, file, opts): return {'memmap': self.run_command(file, args, 'memmap')} def memmap_plaintext(self, json): return json['memmap'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'list desktop and thread window message hooks' }) def messagehooks(self, args, file, opts): return {'messagehooks': self.run_command(file, args, 'messagehooks')} def messagehooks_plaintext(self, json): return json['messagehooks'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'scans for and parses potential MFT entries' }) def mftparser(self, args, file, opts): return {'mftparser': self.run_command(file, args, 'mftparser')} def mftparser_plaintext(self, json): return json['mftparser'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'pool scanner for kernel modules' }) def modscan(self, args, file, opts): return {'modscan': self.run_command(file, args, 'modscan')} def modscan_plaintext(self, json): return json['modscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print list of loaded modules' }) def modules(self, args, file, opts): return {'modules': self.run_command(file, args, 'modules')} def modules_plaintext(self, json): return json['modules'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'pool scanner for mutex objects' }) def mutantscan(self, args, file, opts): return {'mutantscan': self.run_command(file, args, 'mutantscan')} def mutantscan_plaintext(self, json): return json['mutantscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'scan a vista (or later) image for connections and sockets' }) def netscan(self, args, file, opts): return {'netscan': self.run_command(file, args, 'netscan')} def netscan_plaintext(self, json): return json['netscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'list currently displayed notepad text' }) def notepad(self, args, file, opts): return {'notepad': self.run_command(file, args, 'notepad')} def notepad_plaintext(self, json): return json['notepad'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'scan for Windows object type objects' }) def objtypescan(self, args, file, opts): return {'objtypescan': self.run_command(file, args, 'objtypescan')} def objtypescan_plaintext(self, json): return json['objtypescan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'display process privileges' }) def privs(self, args, file, opts): return {'privs': self.run_command(file, args, 'privs')} def privs_plaintext(self, json): return json['privs'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print all running processes by following the EPROCESS lists' }) def pslist(self, args, file, opts): return {'pslist': self.run_command(file, args, 'pslist')} def pslist_plaintext(self, json): return json['pslist'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'pool scanner for process objects' }) def psscan(self, args, file, opts): return {'psscan': self.run_command(file, args, 'psscan')} def psscan_plaintext(self, json): return json['psscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print process list as a tree' }) def pstree(self, args, file, opts): return {'pstree': self.run_command(file, args, 'pstree')} def pstree_plaintext(self, json): return json['pstree'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'find hidden processes with various process listings' }) def psxview(self, args, file, opts): return {'psxview': self.run_command(file, args, 'psxview')} def psxview_plaintext(self, json): return json['psxview'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'dump Qemu information' }) def qemuinfo(self, args, file, opts): return {'qemuinfo': self.run_command(file, args, 'qemuinfo')} def qemuinfo_plaintext(self, json): return json['qemuinfo'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'list Windows services (ala Plugx)' }) def servicediff(self, args, file, opts): return {'servicediff': self.run_command(file, args, 'servicediff')} def servicediff_plaintext(self, json): return json['servicediff'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'list details on _MM_SESSION_SPACE (user logon sessions)' }) def sessions(self, args, file, opts): return {'sessions': self.run_command(file, args, 'sessions')} def sessions_plaintext(self, json): return json['sessions'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'prints ShellBags info' }) def shellbags(self, args, file, opts): return {'shellbags': self.run_command(file, args, 'shellbags')} def shellbags_plaintext(self, json): return json['shellbags'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'parses the Application Compatibility Shim Cache registry key' }) def shimcache(self, args, file, opts): return {'shimcache': self.run_command(file, args, 'shimcache')} def shimcache_plaintext(self, json): return json['shimcache'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print ShutdownTime of machine from registry' }) def shutdowntime(self, args, file, opts): return {'shutdowntime': self.run_command(file, args, 'shutdowntime')} def shutdowntime_plaintext(self, json): return json['shutdowntime'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print list of open sockets' }) def sockets(self, args, file, opts): return {'sockets': self.run_command(file, args, 'sockets')} def sockets_plaintext(self, json): return json['sockets'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'pool scanner for tcp socket objects' }) def sockscan(self, args, file, opts): return {'sockscan': self.run_command(file, args, 'sockscan')} def sockscan_plaintext(self, json): return json['sockscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'display SSDT entries' }) def ssdt(self, args, file, opts): return {'ssdt': self.run_command(file, args, 'ssdt')} def ssdt_plaintext(self, json): return json['ssdt'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'scan for Windows services' }) def svcscan(self, args, file, opts): return {'svcscan': self.run_command(file, args, 'svcscan')} def svcscan_plaintext(self, json): return json['svcscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'pool scanner for symlink objects' }) def symlinkscan(self, args, file, opts): return {'symlinkscan': self.run_command(file, args, 'symlinkscan')} def symlinkscan_plaintext(self, json): return json['symlinkscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'pool scanner for thread objects' }) def thrdscan(self, args, file, opts): return {'thrdscan': self.run_command(file, args, 'thrdscan')} def thrdscan_plaintext(self, json): return json['thrdscan'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'investigate _ETHREAD and _KTHREADs' }) def threads(self, args, file, opts): return {'threads': self.run_command(file, args, 'threads')} def threads_plaintext(self, json): return json['threads'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print kernel timers and associated module DPCs' }) def timers(self, args, file, opts): return {'timers': self.run_command(file, args, 'timers')} def timers_plaintext(self, json): return json['timers'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'recover TrueCrypt 7.1a Master Keys' }) def truecryptmaster(self, args, file, opts): return { 'truecryptmaster': self.run_command(file, args, 'truecryptmaster') } def truecryptmaster_plaintext(self, json): return json['truecryptmaster'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'TrueCrypt Cached Passphrase Finder' }) def truecryptpassphrase(self, args, file, opts): return { 'truecryptpassphrase': self.run_command(file, args, 'truecryptpassphrase') } def truecryptpassphrase_plaintext(self, json): return json['truecryptpassphrase'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'TrueCrypt Summary' }) def truecryptsummary(self, args, file, opts): return { 'truecryptsummary': self.run_command(file, args, 'truecryptsummary') } def truecryptsummary_plaintext(self, json): return json['truecryptsummary'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print list of unloaded modules' }) def unloadedmodules(self, args, file, opts): return { 'unloadedmodules': self.run_command(file, args, 'unloadedmodules') } def unloadedmodules_plaintext(self, json): return json['unloadedmodules'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print userassist registry keys and information' }) def userassist(self, args, file, opts): return {'userassist': self.run_command(file, args, 'userassist')} def userassist_plaintext(self, json): return json['userassist'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'dump the USER handle tables' }) def userhandles(self, args, file, opts): return {'userhandles': self.run_command(file, args, 'userhandles')} def userhandles_plaintext(self, json): return json['userhandles'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'dump the VAD info' }) def vadinfo(self, args, file, opts): return {'vadinfo': self.run_command(file, args, 'vadinfo')} def vadinfo_plaintext(self, json): return json['vadinfo'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'walk the VAD tree and display in tree format' }) def vadtree(self, args, file, opts): return {'vadtree': self.run_command(file, args, 'vadtree')} def vadtree_plaintext(self, json): return json['vadtree'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'walk the VAD tree' }) def vadwalk(self, args, file, opts): return {'vadwalk': self.run_command(file, args, 'vadwalk')} def vadwalk_plaintext(self, json): return json['vadwalk'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'dump virtualbox information' }) def vboxinfo(self, args, file, opts): return {'vboxinfo': self.run_command(file, args, 'vboxinfo')} def vboxinfo_plaintext(self, json): return json['vboxinfo'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'prints out the version information from PE images' }) def verinfo(self, args, file, opts): return {'verinfo': self.run_command(file, args, 'verinfo')} def verinfo_plaintext(self, json): return json['verinfo'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'dump VMware VMSS/VMSN information' }) def vmwareinfo(self, args, file, opts): return {'vmwareinfo': self.run_command(file, args, 'vmwareinfo')} def vmwareinfo_plaintext(self, json): return json['vmwareinfo'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print Desktop Windows (verbose details)' }) def windows(self, args, file, opts): return {'windows': self.run_command(file, args, 'windows')} def windows_plaintext(self, json): return json['windows'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'print Z-Order Desktop Windows Tree' }) def wintree(self, args, file, opts): return {'wintree': self.run_command(file, args, 'wintree')} def wintree_plaintext(self, json): return json['wintree'] @scale.command({ 'args': { 'profile': fields.Str(required=False) }, 'info': 'pool scanner for window stations' }) def wndscan(self, args, file, opts): return {'wndscan': self.run_command(file, args, 'wndscan')} def wndscan_plaintext(self, json): return json['wndscan']
class Commands(scale.Commands): def check(self): rule_files = [] # Check rules_path if not RULES_PATH or RULES_PATH == '': raise error.CommandError("config variable 'rules_path' not set") if not os.path.exists(RULES_PATH): raise error.CommandError( "'rules_path': '{}' does not exist".format(RULES_PATH)) if not os.listdir(RULES_PATH): raise error.CommandError( "'rules_path': '{}' is empty".format(RULES_PATH)) # Loop through the rules folder and append all of the yar[a] files for dir_path, _dirs, files in os.walk(RULES_PATH): for f in files: if f.endswith('.yar') or f.endswith('.yara'): rule_files.append(os.path.join(dir_path, f)) # Compile each of the new yara files for rule_file in rule_files: compiled_rule = os.path.join( RULES_PATH, os.path.splitext(os.path.basename(rule_file))[0] + '.yarac') if not os.path.exists(compiled_rule): try: rule = yara.compile(rule_file) rule.save(compiled_rule) except Exception as err: # pylint: disable=broad-except # TODO: Raising a CommandWarning breaks the module load, hence hacking in app_log direct warning temporarily app_log.warning( 'error with yara module when compiling rule: %s', err) # raise CommandWarning('error with yara module when compiling rule: %s' % err) @scale.command({'info': 'show a list of the yara rule'}) def rules(self, args, file, opts): output = [] # Loop through the compiled rules for _root, _dirs, files in os.walk(RULES_PATH): for f in files: if f.endswith('.yara') or f.endswith('.yar'): output += [os.path.join(RULES_PATH, f)] return output def rules_plaintext(self, json): return '\n'.join(json) @scale.command({ 'args': { 'rule': fields.Str(required=False) }, 'info': 'scan a file with the available yara rules. Allows for only a single rule to be passed' }) def scan(self, args, file, opts): # pylint: disable=too-many-locals, too-many-branches compiled_rule_files = [] output = [] if 'rule' in args and args['rule'] != '': rule = args['rule'] rule = rule.strip('.yar').strip('.yara') rule += '.yarac' path = os.path.join(RULES_PATH, rule) if os.path.exists(path): compiled_rule_files.append(path) else: raise error.CommandError('rule file does not exist') else: for _root, _dirs, files in os.walk(RULES_PATH): for f in files: if f.endswith('.yarac'): compiled_rule_files.append(os.path.join(RULES_PATH, f)) # Load each of the compiled yara files for compiled_rule_file in compiled_rule_files: try: rules = yara.load(compiled_rule_file) matches = rules.match(file.file_path) except Exception: # noqa pylint: disable=broad-except continue # Skip if no rule matches if not matches: continue # If the rule index doesn't exist we are likely using the yara plugin and not yara-python if matches[0].rule is None: raise error.CommandWarning( 'incorrect yara python plugin installed') # Loop through each match and append to output for match in matches: try: if match.rule in config.scale_configs['yara'][ 'blacklisted_rules']: continue except Exception: # noqa pylint: disable=broad-except continue # Strings matches are stored as byte arrays, and whilst they can be converted to utf-8 strings, # in the case of hex values these are converted to ASCII which is not the desired output. # e.g: # b'This program cannot be run in DOS mo' = 'This program cannot be run in DOS mo' # b'\x40\x410x42' = @A0x42 output += [{ 'file': str(os.path.basename(compiled_rule_file)), 'rule': str(match.rule), 'hits': [{ 'hit': str(x[2])[2:-1], 'offset': str(x[0]) } for x in match.strings], 'description': str(match.meta['description']) if 'description' in match.meta else '', 'author': str(match.meta['author']) if 'author' in match.meta else '', }] return output def scan_markdown(self, json): output = md.table_header( ['File', 'Rule', 'String', 'Offset', 'Description', 'Author']) for r in json: # pylint: disable=invalid-name output += md.table_row([ md.sanitize(r['file']), md.bold(md.sanitize(r['rule'])), md.code(md.sanitize(r['hits'][0]['hit']), inline=True) if r['hits'] else '', md.code(md.sanitize(r['hits'][0]['offset']), inline=True) if r['hits'] else '', md.sanitize(r['description']), md.sanitize(r['author']) ]) for hit in r['hits'][1:]: output += md.table_row( ('', '', md.code(md.sanitize(hit['hit']), inline=True), md.code(md.sanitize(hit['offset']), inline=True), '', '')) if not json: output += md.table_row(('-', '-', '-', '-', '-')) return output
class FileSchema(Schema): """The file schema. This is the schema for the file document stored within the mongo database. """ not_blank = marshmallow.validate.Length(min=1, error='Field cannot be blank') _id = fields.ObjectId(load_only=True) file_type = fields.Enum(required=True, type=enums.FileType, missing=enums.FileType.FILE) name = fields.Str(required=True, validate=not_blank) sha256_digest = fields.Str() description = fields.Str() tags = fields.Str() magic = fields.Str() mime = fields.Str() size = fields.Int() timestamp = fields.DateTime("%Y-%m-%dT%H:%M:%S.%f") submission_type = fields.Str(validate=not_blank, default="unknown") parents = fields.Dict(values=fields.List(fields.Str(validate=not_blank)), keys=fields.Str(validate=not_blank), default={}) children = fields.Dict(values=fields.List(fields.Str(validate=not_blank)), keys=fields.Str(validate=not_blank), default={})
class Commands(scale.Commands): # pylint: disable=too-many-public-methods def check(self): strings = shutil.which('radare2') if not strings: raise error.CommandWarning("binary 'radare2' not found") return @scale.command({ 'args': { 'offset': fields.Str(required=True), 'magic_bytes': fields.Str(default=None, missing=None), 'patch': fields.Bool(default=True, missing=True), 'size': fields.Str(required=True), }, 'info': 'this function will carve binaries out of MDMP files' }) def binary_carver(self, args, file, opts): sample = {} with tempfile.TemporaryDirectory(dir=path.abspath( path.expanduser( config.snake_config['cache_dir']))) as temp_dir: # Try and carve file_path = r2_bin_carver.carve(file.file_path, temp_dir, args['offset'], args['size'], args['magic_bytes']) if not file_path: raise error.CommandError('failed to carve binary') if args['patch']: if not r2_bin_carver.patch(file_path): raise error.CommandError( 'failed to patch binary, not a valid pe file') # Get file name document = db.file_collection.select(file.sha256_digest) if not document: raise error.SnakeError("failed to get sample's metadata") # Create schema and save name = '{}.{}'.format(document['name'], args['offset']) file_schema = schema.FileSchema().load({ 'name': name, 'description': 'extracted with radare2 script r2_bin_carver.py' }) new_file = fs.FileStorage() new_file.create(file_path) sample = submitter.submit(file_schema, enums.FileType.FILE, new_file, file, NAME) sample = schema.FileSchema().dump(schema.FileSchema().load( sample)) # Required to clean the above return sample def binary_carver_markdown(self, json): output = md.table_header(('Name', 'SHA256 Digest', 'File Type')) output += md.table_row( (json['name'], md.url( json['sha256_digest'], '/#/{}/{}'.format(json['file_type'], json['sha256_digest'])), json['file_type'])) if not json.keys(): output += md.table_row(('-', '-', '-')) return output @scale.command({ 'args': { 'bits': fields.Str(default='32', missing='32'), 'technique': fields.Str(required=True) }, 'info': 'scan shellcode for hashed functions' }) def hash_function_decoder(self, args, file, opts): # Validate if args['bits'] not in ['32', '64']: raise error.CommandError( 'invalid bits provided, currently supported: 32, 64') if args['technique'] not in r2_hash_func_decoder.TECHNIQUES: raise error.CommandError( 'invalid technique provided, currently supported: {}'.format( r2_hash_func_decoder.TECHNIQUES)) scale_dir = path.dirname(__file__) func_decoder = path.join(scale_dir, 'scripts/r2_hash_func_decoder.py') hash_db = path.join(scale_dir, 'scripts/r2_hash_func_decoder.db') # Get the output proc = subprocess.run([ 'python3', '{}'.format(func_decoder), 'analyse', '-f', '{}'.format( file.file_path), '-d', '{}'.format(hash_db), '{}'.format( args['technique']) ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) if proc.returncode: raise error.CommandError('failed to execute script') return {'analysis': str(proc.stdout, encoding='utf-8')} def hash_function_decoder_plaintext(self, json): return json['analysis']
class CommandHandler(snake_handler.SnakeHandler): """Extends `SnakeHandler`.""" @tornadoparser.use_args({ # 'args': fields.Dict(required=False, default={}, missing={}), 'command': fields.Str(required=True), 'format': fields.Str(type=enums.Format, missing=enums.Format.JSON), 'output': fields.Bool(required=False, default=True, missing=True), 'scale': fields.Str(required=True), 'sha256_digest': fields.Str(required=True) }) async def get(self, data): # NOTE: Tornado/Marshmallow does not like Dict in args, will have to parse manually # TODO: Use marshmallow validation if 'args' in self.request.arguments and self.request.arguments['args']: data['args'] = json.loads(self.request.arguments['args'][0]) else: data['args'] = {} document = await db.async_command_collection.select( data['sha256_digest'], data['scale'], data['command'], data['args']) if not document: self.write_warning("no output for given data", 404, data) self.finish() return if document['status'] == enums.Status.ERROR: self.write_warning("%s" % document['output'], 404, data) self.finish() return document = schema.CommandSchema().load(document) output = None if document['_output_id']: output = await db.async_command_output_collection.get( document['_output_id']) try: scale = scale_manager.get_scale(data['scale']) commands = scale_manager.get_component( scale, enums.ScaleComponent.COMMANDS) if data['output']: document['output'] = commands.snake.format( data['format'], document['command'], output) document['format'] = data['format'] except (SnakeError, TypeError) as err: self.write_warning("%s" % err, 404, data) self.finish() return document = schema.CommandSchema().dump(document) self.jsonify({'command': document}) self.finish() @tornadoparser.use_args({ 'args': fields.Dict(required=False, default={}, missing={}), 'asynchronous': fields.Bool(required=False), 'command': fields.Str(required=True), 'format': fields.Str(type=enums.Format, missing=enums.Format.JSON), 'scale': fields.Str(required=True), 'sha256_digest': fields.Str(required=True), 'timeout': fields.Int(required=False) }) async def post(self, data): # Check that there is a file for this hash document = await db.async_file_collection.select(data['sha256_digest']) if not document: self.write_warning("no sample for given data", 404, data) self.finish() return # Check scale support try: scale = scale_manager.get_scale(data['scale'], document['file_type']) commands = scale_manager.get_component( scale, enums.ScaleComponent.COMMANDS) cmd = commands.snake.command(data['command']) except SnakeError as err: self.write_warning("%s" % err, 404, data) self.finish() return # Validate arguments as to not waste users time, yes this is also done on execution result, args = validate_args(cmd, data['args']) if not result: self.write_warning(args, 422, data) self.finish() return data['args'] = args # Queue command try: document = await route_support.queue_command(data) except SnakeError as err: self.write_warning("%s" % err, 500, data) self.finish() return document = schema.CommandSchema().load(document) output = None if document['_output_id']: output = await db.async_command_output_collection.get( document['_output_id']) try: document['output'] = commands.snake.format(data['format'], document['command'], output) document['format'] = data['format'] except SnakeError as err: self.write_warning("%s" % err, 404, data) self.finish() return # Dump and finish document = schema.CommandSchema().dump(document) self.jsonify({"command": document}) self.finish()
class NotesHandler(snake_handler.SnakeHandler): """Extends `SnakeHandler`.""" @tornadoparser.use_args({ 'sha256_digest': fields.Str(required=False), }) async def get(self, data): documents = [] if 'sha256_digest' in data.keys(): cursor = db.async_note_collection.select_many( data['sha256_digest']) while await cursor.fetch_next: documents += [cursor.next_object()] else: cursor = db.async_note_collection.select_all() while await cursor.fetch_next: documents += [cursor.next_object()] documents = schema.NoteSchema(many=True).dump( schema.NoteSchema(many=True).load(documents)) self.jsonify({'notes': documents}) self.finish() @tornadoparser.use_args(schema.NoteSchema(many=True)) async def post(self, data): if data == []: self.write_warning("note - no request body found", 422) self.finish() return # Check that there is a file for each hash missing = [] for i in data: document = await db.async_file_collection.select(i['sha256_digest'] ) if not document: missing += [i] if missing: self.write_warning("note - no sample for given data", 404, missing) self.finish() return # Check that there is a note for each hash exists = [] for i in data: document = await db.async_note_collection.select(i['sha256_digest'] ) if document: exists += [ schema.NoteSchema().dump( schema.NoteSchema().load(document)) ] if exists: self.write_warning("note - note already exists for given data", 409, exists) self.finish() return documents = [] timestamp = datetime.utcnow() for i in data: i['timestamp'] = timestamp i = schema.NoteSchema().dump(i) await db.async_note_collection.insert(i) documents += [ await db.async_note_collection.select(i['sha256_digest']) ] documents = schema.NoteSchema(many=True).dump( schema.NoteSchema(many=True).load(documents)) self.jsonify({'notes': documents}) self.finish()