APT_CyberCriminal_Campagin_.../2015/2015.10.05.Proactive_Threat_Identification/Prog/ioc-enrichment/rf_enrich.py
CyberMonitor 7cd6ba7319 go
2017-02-11 15:00:00 +08:00

129 lines
5.3 KiB
Python

""" Indicator of Compromise (IOC) enrichment script.
Prints resulting enrichment to stdout.
"""
import re
import sys
import json
import argparse
from RFAPI import RFAPI
ipv4_regexp = '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
ipv6_regexp = '((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?'
idn_regexp = '^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$'
hash_regexp = '(^[0-9a-f]{32}$)|(^[0-9a-f]{40}$)|(^[0-9a-f]{64}$)|(^[0-9a-f]{128}$)'
class RFEnricher(object):
pattern_to_ioc = {
'{0}|{1}'.format(ipv4_regexp, ipv6_regexp):
{
'data_group': 'EnrichIpAddress',
'function': 'enriched-ip-address',
'id_prefix': 'ip'
},
idn_regexp: {
'data_group': 'EnrichInternetDomainName',
'function': 'enriched-internet-domain-name',
'id_prefix': 'idn'
},
hash_regexp: {
'data_group': 'EnrichHash',
'function': 'enriched-hash',
'id_prefix': 'hash'
}
}
def __init__(self, token):
self.rfapi = RFAPI(token)
def enrich(self, iocs):
sys.stderr.write('Enriching {0} IOC(s)...\n'.format(len(iocs)))
enrichment = {}
for ioc in iocs:
for pattern, query_config in self.pattern_to_ioc.items():
if not re.match(pattern, ioc):
continue
sys.stderr.write('\tProcessing {0} : {1}... '.format(query_config['id_prefix'], ioc))
enrichment[ioc] = self.query_enrich_ioc(ioc, query_config)
sys.stderr.write('Done.\n')
break
else:
sys.stderr.write('Unable to match "{0}" with any supported IOC type.\n'.format(ioc))
return enrichment
def get_entity_id(self, id_prefix, name):
if id_prefix != 'hash':
return "{0}:{1}".format(id_prefix, name)
res = self.rfapi.query({
'entity': {
'name': name,
'type': 'Hash',
'limit': 1
}
})
if len(res.get('entities', [])) == 0:
return None
return res['entities'][0]
def query_enrich_ioc(self, text, query_config):
entity_id = self.get_entity_id(query_config['id_prefix'], text)
if not entity_id:
return "No enrichment available."
q = {
"cluster": {
"function": query_config['function'],
"attributes": [
{
"entity": {
"id": entity_id
}
}
],
"limit": 1,
"data_group": query_config['data_group']
},
"output": {
"inline_entities": True
}
}
res = self.rfapi.query(q)
if res['count']['events']['total'] == 0:
return "No enrichment available."
enr_data = res['events'][0]['stats']
enr_data['rf_link'] = 'https://www.recordedfuture.com/live/sc/entity/' + entity_id
return enr_data
def parse_args():
parser = argparse.ArgumentParser(description="Recorded Future indicator enrichment")
parser.add_argument('-f', action='store_true', default=False,
dest='is_file',
help='Read new-line separated IOCs from file')
parser.add_argument('file_or_ioc', action='store', help='IOC to enrich (or file containing IOCs if -f is supplied)')
parser.add_argument('-t', action="store", dest='token', default=None, help='Recorded Future API token. (default is read from the environment variable RECFUT_TOKEN)')
arg_res = parser.parse_args()
iocs = set()
if arg_res.is_file:
with open(arg_res.file_or_ioc) as f:
for l in f:
ioc = l.replace('\n', '').strip().lower()
if len(ioc) > 0:
iocs.add(ioc)
else:
iocs.add(arg_res.file_or_ioc.lower())
return iocs, arg_res.token
if __name__ == '__main__':
iocs, token = parse_args()
enrichment = RFEnricher(token).enrich(iocs)
print json.dumps(enrichment, None, indent=2)