From ba1ce88901bfb86f6b053c49e28c6afe9ed6e236 Mon Sep 17 00:00:00 2001 From: geoffrey Date: Sat, 22 Jun 2024 14:34:22 +0200 Subject: [PATCH] First commit --- .gitignore | 2 + README.md | 15 +++ config | 2 + config.py | 19 ++++ dnsinformations.py | 63 +++++++++++ emailchecker.py | 7 ++ hashing.py | 38 +++++++ main.py | 259 +++++++++++++++++++++++++++++++++++++++++++++ reports.py | 157 +++++++++++++++++++++++++++ requirements.txt | 10 ++ vt.py | 114 ++++++++++++++++++++ 11 files changed, 686 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 config create mode 100644 config.py create mode 100644 dnsinformations.py create mode 100644 emailchecker.py create mode 100644 hashing.py create mode 100644 main.py create mode 100644 reports.py create mode 100644 requirements.txt create mode 100644 vt.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..86e22b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.**.swp +__pycache__/** diff --git a/README.md b/README.md new file mode 100644 index 0000000..919d7f6 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Introduction +This tool can help SOC analyst to identify any threat + +# Implementation +First, you should create a virtualenv: + +``` +$ virtualenv ~/venv/baoSOC +$ source ~/venv/baoSOC/bin/activate +``` + +And install all packages the tool need: +``` +$ pip install -r requirements.txt +``` diff --git a/config b/config new file mode 100644 index 0000000..848508c --- /dev/null +++ b/config @@ -0,0 +1,2 @@ +api_key_vt: f4c451920a7e41ec344e16e6d36a1b7951bf23a8d224b796cb08301e65bf3114 +api_key_emailrep: foo diff --git a/config.py b/config.py new file mode 100644 index 0000000..77679ff --- /dev/null +++ b/config.py @@ -0,0 +1,19 @@ +#!/usr/bin/venv python +# -*- coding: utf-8 -*- + +VT_ATTRIBUTES_MAPPING = { + 'asn': 'str', + 'as_owner': 'int', + 'continent': 'str', + 'country': 'str', + 'last_analysis_date': 'date', + 'regional_internet_registry': 'str', + 'network': 'str', + 'ip': 'str' +} + +DNS_QUERIES_TYPE = { + 'A': 'address', + 'MX': ['exchange', 'preference'], + 'TXT': 'strings', +} diff --git a/dnsinformations.py b/dnsinformations.py new file mode 100644 index 0000000..3f352e3 --- /dev/null +++ b/dnsinformations.py @@ -0,0 +1,63 @@ +#!/usr/bin/venv python +# -*- coding: utf-8 -*- + +import whois +import dns.resolver +import dns.name +from config import DNS_QUERIES_TYPE + + +class DNSInformations: + def __init__(self, api_key, fqdn): + self._fqdn = fqdn + + def checkDomainExist(self): + try: + res_query = dns.resolver.resolve(self._fqdn, 'NS') + except dns.resolver.NoAnswer: + return False + except dns.resolver.NXDOMAIN: + return False + return True + + def whois(self): + report = dict() + w = whois.whois(self._fqdn) + report['domain_name'] = w.domain_name + report['expiration_date'] = w.expiration_date + report['creation_date'] = w.creation_date + report['updated_date'] = w.updated_date + report['ns'] = w.name_servers + report['admin_name'] = w.admin_name + report['registrar'] = w.registrar + return report + + def resolver(self): + report = dict() + + for t in DNS_QUERIES_TYPE.keys(): + report[t] = self._resolving(self._fqdn, t, DNS_QUERIES_TYPE[t]) + return report + + def _resolving(self, fqdn, t, attr): + report = list() + try: + res_query = dns.resolver.resolve(fqdn, t) + for rdata in res_query: + if isinstance(attr, list): + data = dict() + for a in attr: + data[a] = getattr(rdata, a) + report.append(data) + else: + report.append({ + attr: getattr(rdata, attr) + }) + except dns.resolver.NoAnswer: + pass + except dns.resolver.NXDOMAIN: + report.append({'error': 'Domain not exist'}) + except dns.resolver.NoNameservers: + report.append({'error': 'Domain not exist'}) + return report + diff --git a/emailchecker.py b/emailchecker.py new file mode 100644 index 0000000..b420616 --- /dev/null +++ b/emailchecker.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 + +class EmailChecker: + def __init__(self, key): + self.headers = { + 'Key': key, + } diff --git a/hashing.py b/hashing.py new file mode 100644 index 0000000..efa5063 --- /dev/null +++ b/hashing.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 + +import hashlib + + +class Hash: + def __init__(self): + pass + + def hashMd5(self, f): + md5 = hashlib.md5() + with open(f, "rb") as data: + md5.update(data.read()) + return md5.hexdigest() + + def hashSha1(self, f): + sha1 = hashlib.sha1() + with open(f, "rb") as data: + sha1.update(data.read()) + return sha1.hexdigest() + + def hashSha256(self, f): + sha256 = hashlib.sha256() + with open(f, "rb") as data: + sha256.update(data.read()) + return sha256.hexdigest() + + def hashSha384(self, f): + sha384 = hashlib.sha384() + with open(f, "rb") as data: + sha384.update(data.read()) + return sha384.hexdigest() + + def hashSha512(self, f): + sha512 = hashlib.sha512() + with open(f, "rb") as data: + sha512.update(data.read()) + return sha512.hexdigest() diff --git a/main.py b/main.py new file mode 100644 index 0000000..dd2ad74 --- /dev/null +++ b/main.py @@ -0,0 +1,259 @@ +#!/usr/bin/venv python +# -*- coding: utf-8 -*- + +from argparse import ArgumentParser +from config import VT_ATTRIBUTES_MAPPING +from vt import VT +from dnsinformations import DNSInformations as DNS +import ipaddress +from datetime import datetime +from hashing import Hash +from os.path import exists + + +def checkArguments(): + parser = ArgumentParser(description="baoSOC") + parser.add_argument('-c', '--config', help='Config file') + parser.add_argument('--dns', help='Get domain name information', action="store_true") + # For dns command + parser.add_argument('--domain', help='Get domain name information') + parser.add_argument('--host', help='Get domain name information') + parser.add_argument('--ip', help='Get IP information') + # For hash command + parser.add_argument('--hashfile', help='Hash file', action='store_true') + parser.add_argument('--scanvt', help='If specified, scan the hash with VirusTotal', action='store_true') + parser.add_argument('--md5', help='Hash file') + parser.add_argument('--sha1', help='Hash file') + parser.add_argument('--sha256', help='Hash file') + parser.add_argument('--sha384', help='Hash file') + parser.add_argument('--sha512', help='Hash file') + parser.add_argument('--hash', help='Get information about the hash') + + return parser.parse_args() + +def usage(): + print("------------------------------") + print("| baoSOC |") + print("------------------------------\n") + print("A tool for SOC analyst\n") + print("Usage: main.py [COMMAND]") + print("-c PATH, --config PATH\t\tConfig file - mandatory") + print("--hashfile\t\t\tHash the file and check in VirusTotal") + print("--hash HASH\t\tAnalyse the hash from VirusTotal") + print("--dns \t\t\t\tGet information regarding the domain with whois and VirusTotal") + print("--email\t\t\t\tGet informations about an email and check if has been compromised") + + print("\n--dns command:") + print("\t --domain FQDN\t\tScan and get domain information") + print("\t --host HOST\t\tScan and get host information") + print("\t --ip IP\t\tScan and get IP information") + + print("\n--hashfile command:") + print("\t --md5 FILE\t\tGet the MD5 hash of the file") + print("\t --sha1 FILE\t\tGet the SHA256 of the file") + print("\t --sha256 FILE\t\tGet the SHA256 of the file") + print("\t --sha384 FILE\t\tGet the SHA384 of the file") + print("\t --sha512 FILE\t\tGet the SHA512 of the file") + +def mainMenu(): + print("\n baoSOC ") + print(" What would you like to do? ") + print("\n OPTION 1: Sanitise URL For emails ") + print(" OPTION 2: Decoders (PP, URL, SafeLinks) ") + print(" OPTION 3: Reputation Checker") + print(" OPTION 4: DNS Tools") + print(" OPTION 5: Hashing Function") + print(" OPTION 6: Phishing Analysis") + print(" OPTION 7: URL scan") + print(" OPTION 9: Extras") + print(" OPTION 0: Exit Tool") + +def readConfigFile(config): + """ + This function read the config file + """ + data = {} + try: + with open(config, 'r') as f: + lines = f.readlines() + + # Split each line into te dictionary + for line in lines: + l = line.split(":") + lineParsed = l[1].replace(" ", "") + lineParsed = lineParsed.replace("\n", "") + data[l[0]] = lineParsed + + except FileNotFoundError: + return None + return data + +def main(): + args = checkArguments() + + if not args.config: + usage() + exit(1); + + # Read the config file + config = readConfigFile(args.config) + if config is None: + print("Failed to read the config file") + exit(0) + + report = dict() + + # Analyse DNS + if args.dns: + if args.domain: + _parsingDomain(config, args.domain, report) + if args.host: + pass + if args.ip: + _parsingIP(config, args.ip, report) + # Analyse hash file + if args.hashfile: + h = Hash() + dispatcher = { + "MD5": h.hashMd5, + "SHA1": h.hashSha1, + "SHA256": h.hashSha256, + "SHA384": h.hashSha384, + "SHA512": h.hashSha512, + } + if args.md5: + hashType = "MD5" + filename = args.md5 + if args.sha1: + hashType = "SHA1" + filename = args.sha1 + if args.sha256: + hashType = "SHA256" + filename = args.sha256 + if args.sha384: + hashType = "SHA384" + filename = args.sha384 + if args.sha512: + hashType = "SHA512" + filename = args.sha512 + + if not exists(filename): + print(f"File {filename} do not exist") + else: + res = dispatcher[hashType](filename) + print(f"{hashType} hash: {res}") + + if args.scanvt: + _parsingHash(config, res, report) + + # Analyse the hash + if args.hash: + _parsingHash(config, args.hash, report) + +def _parsingHash(config, h, report): + report = dict() + vt = VT(config['api_key_vt']) + + vt.getRateFromHash(h, report) + print("\n----------------------") + print("| VirusTotal |") + print("----------------------") + + if 'error' in report: + print("Error, can not find any informations") + return + + # File + print("\nFile information:") + f = report['results']['file'] + print(f"\tMD5: {f['md5']}") + print(f"\tSHA1: {f['sha1']}") + print(f"\tMagic: {f['magic']}") + print(f"\tFile type: {f['filetype']}") + print(f"\tSize: {f['size']}") + print(f"\tExtension: {f['extension']}") + print(f"\tFirst seen: {f['first_seen']}") + print(f"\tLast analysis: {f['last_analysis']}") + + # Vendors + vendors = report['results']['vendors'] + print("\nVendors:") + print(f"\tTotal vendors: {vendors['total_vendors']}") + print(f"\tCategorized as malicious: {vendors['total_malicious']}\n") + for key in vendors['result']: + print(f"\t{key}: {vendors['result'][key]}") + +def _parsingIP(config, ip, report): + # Check if it's an IP address + vt = VT(config['api_key_vt']) + try: + ip_obj = ipaddress.ip_address(ip) + except ValueError: + print("The IP is not valid") + return + + report['ip'] = dict() + report['ip'] = vt.getIPVirusTotal(ip) + + if 'error' not in report['ip']: + print("----------------------------") + print("| IP Informations |") + print("----------------------------") + for vt in VT_ATTRIBUTES_MAPPING.keys(): + try: + vtAttributes = VT_ATTRIBUTES_MAPPING[vt] + if 'date' in vtAttributes: + value = datetime.fromtimestamp(int(report['ip'][vt])) + else: + value = report['ip'][vt] + print(f"{vt}: {value}") + except KeyError: + print(f"Cannot find the key {vt}") + +def _parsingDomain(config, fqdn, report): + vt = VT(config['api_key_vt']) + dns = DNS(config['api_key_vt'], fqdn) + + # Check if domain exist + if not dns.checkDomainExist(): + print(f"The domain {fqdn} do not exist") + return + + print("----------------------") + print("| resolving |") + print("----------------------") + report['resolving'] = dns.resolver() + for key in report['resolving'].keys(): + print(f"{key}: ") + for entry in report['resolving'][key]: + for subkey in entry.keys(): + value = entry[subkey] + if isinstance(value, bytes): + value = value.decode() + print(f"\t{subkey}: {value}") + + print("\n----------------------") + print("| whois |") + print("----------------------") + report['whois'] = dns.whois() + report_whois = report['whois'] + for key in report_whois.keys(): + if isinstance(report_whois[key], list): + print(f"{key}:") + for value in report_whois[key]: + print(f"\t{value}") + else: + print(f"{key}: {report_whois[key]}") + + print("\n----------------------") + print("| VirusTotal |") + print("----------------------") + report['vt'] = dict() + vt.getDomainReport(fqdn, report['vt']) + report_vt = report['vt'] + for key in report_vt: + print(f"{key}: {report_vt[key]}") + + +if __name__ == "__main__": + main() diff --git a/reports.py b/reports.py new file mode 100644 index 0000000..3188c7b --- /dev/null +++ b/reports.py @@ -0,0 +1,157 @@ +#!/usr/bin/venv python +# -*- coding: utf-8 -*- + +from datetime import datetime +from os import mkdir +from config import VT_ATTRIBUTES_MAPPING +import jinja2 + + +def generateHtmlReport(data): + env = jinja2.Environment( + loader=jinja2.FileSystemLoader("reports/templates"), + autoescape=jinja2.select_autoescape() + ) + + _queriesReport(data['queries'], env) + _graphicsReport(data['graphics'], env) + _vtReport(data['vt'], env) + _dnsTunnelingReports(data['dnstunneling'], env) + +def _indexReport(): + pass + +def _queriesReport(queries, env): + """ + This function generate the report for queries + """ + today = getTodayDate() + dataJinja2 = dict() + dataJinja2['title'] = 'Queries' + dataJinja2['year'] = '2023' + dataJinja2['queries'] = queries + + tmpl = env.get_template('queries.html.j2') + + render = tmpl.render(data=dataJinja2) + + with open(f"reports/{today}/reports_queries.html", "w") as f: + f.write(render) + +def _graphicsReport(graphics, env): + today = getTodayDate() + + dataJinja2 = dict() + dataJinja2['title'] = 'Graphics' + dataJinja2['year'] = '2023' + dataJinja2['graphics'] = graphics + + tmpl = env.get_template('graphics.html.j2') + + render = tmpl.render(data=dataJinja2) + + with open(f"reports/{today}/reports_graphics.html", "w") as f: + f.write(render) + +def _vtReport(vt, env): + today = getTodayDate() + # For testing + #vt = list() + #vt.append({ + # 'ip': '1.2.3.4', + # 'asn': 3215, + # 'as_owner': 'Orange', + # 'continent': 'EU', + # 'country': 'FR', + # 'last_analysis_date': 1686839532, + # 'regional_internet_registry': 'RIPE NCC', + # 'network': '1.2.3.0/24' + #}) + #vt.append({ + # 'ip': '2.2.2.1', + # 'asn': 3215, + # 'as_owner': 'Orange', + # 'continent': 'EU', + # 'country': 'FR', + # 'last_analysis_date': 1686839532, + # 'regional_internet_registry': 'RIPE NCC', + # 'network': '2.2.2.0/24' + #}) + #vt.append({ + # 'ip': '3.3.3.1', + # 'asn': 3215, + # 'as_owner': 'Orange', + # 'continent': 'EU', + # 'country': 'FR', + # 'last_analysis_date': 1686839532, + # 'regional_internet_registry': 'RIPE NCC', + # 'network': '3.3.3.0/24' + #}) + + dataJinja2 = dict() + dataJinja2['title'] = 'VirusTotal' + dataJinja2['year'] = '2023' + dataJinja2['vt'] = list() + + tmpl = env.get_template('vt.html.j2') + + body = str() + + for entry in vt: + vtEntry = dict() + if 'error' not in entry: + for vt in VT_ATTRIBUTES_MAPPING.keys(): + try: + vtAttributes = VT_ATTRIBUTES_MAPPING[vt] + if 'date' in vtAttributes: + value = datetime.fromtimestamp(int(entry[vt])) + else: + value = entry[vt] + vtEntry[vt] = value + except KeyError: + pass + dataJinja2['vt'].append({ + 'ip': entry['ip'], + 'data': vtEntry + }) + + render = tmpl.render(data=dataJinja2) + + with open(f"reports/{today}/reports_vt.html", "w") as f: + f.write(render) + +def _dnsTunnelingReports(dnstunneling, env): + today = getTodayDate() + + dataJinja2 = dict() + dataJinja2['title'] = 'DNS Tunneling' + dataJinja2['year'] = '2023' + dataJinja2['dnstunneling'] = dnstunneling + + tmpl = env.get_template('dnsTunneling.html.j2') + + render = tmpl.render(data=dataJinja2) + + with open(f"reports/{today}/reports_dns_tunneling.html", "w") as f: + f.write(render) + +def createReportsDirectory(): + """ + This function will create the reports directory + Return the report name or None if failed + """ + today = getTodayDate() + name = f"reports/{today}" + try: + mkdir(name) + except FileExistsError: + print("Reports directory already created") + return name + return name + +def getTodayDate(): + """ + This function genrate the today datetime at this format: + year_month_day + """ + return datetime.now().isoformat()[0:10].replace("-", "_") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d5168c2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +pip +scapy +ArgumentParser +matplotlib +numpy +vt-py +requests +jinja2 +python-whois +git+https://github.com/rthalley/dnspython.git diff --git a/vt.py b/vt.py new file mode 100644 index 0000000..702a72f --- /dev/null +++ b/vt.py @@ -0,0 +1,114 @@ +#!/ur/bin/env python3 + +import requests +from config import VT_ATTRIBUTES_MAPPING +from datetime import datetime + + +class VT: + def __init__(self, api_key): + self._url = "https://www.virustotal.com/api/v3" + self._headers = { + 'x-apikey': api_key, + } + + def getIPVirusTotal(self, ip): + res = requests.get( + f"{self._url}/ip_addresses/{ip}", + headers=self._headers + ).json() + + data = dict() + data['ip'] = ip + + if 'error' in res: + report.append({ + 'error': res['error']['message'], + 'ip': ip + }) + return + + vt = res['data']['attributes'] + for entry in VT_ATTRIBUTES_MAPPING.keys(): + if entry in vt: + try: + data[entry] = vt[entry] + except KeyError: + data[entry] = 'Unknown' + return data + + def getDomainReport(self, fqdn, report): + res = requests.get( + f"{self._url}/domains/{fqdn}", + headers=self._headers + ) + + js = res.json() + report['reputation'] = js['data']['attributes']['reputation'] + report['last_update'] = \ + datetime.fromtimestamp(js['data']['attributes']['last_update_date']) + + # Get number of security vendors + report['total_vendors'] = 0 + report['clean'] = 0 + report['unrated'] = 0 + report['malicious'] = 0 + vendors = js['data']['attributes']['last_analysis_results'] + + for entry in vendors: + report['total_vendors'] += 1 + if vendors[entry]['result'] == 'clean': + report['clean'] += 1 + elif vendors[entry]['result'] == 'unrated': + report['unrated'] += 1 + elif vendors[entry]['result'] == 'malicious': + report['malicious'] += 1 + + def getRateFromHash(self, h, report): + headers = self._headers + + res = requests.get( + f"{self._url}/files/{h}", + headers=self._headers + ).json() + + if 'error' in res: + report["error"] = "Can not find the result" + return + + attributes = res['data']['attributes'] + report['results'] = dict() + report['results']['file'] = dict() + report['results']['file']['magic'] = attributes['magic'] + report['results']['file']['sha1'] = attributes['sha1'] + report['results']['file']['md5'] = attributes['md5'] + report['results']['file']['filetype'] = attributes['detectiteasy']['filetype'] + report['results']['file']['size'] = attributes['size'] + report['results']['file']['extension'] = attributes['type_extension'] + try: + report['results']['file']['first_seen'] = \ + datetime.fromtimestamp(attributes['first_seen_itw_date']) + except KeyError: + report['results']['file']['first_seen'] = "Unknown" + + report['results']['file']['last_analysis'] = \ + datetime.fromtimestamp(attributes['last_analysis_date']) + + # Identify vendors + report['results']['vendors'] = dict() + report['results']['vendors']['total_vendors'] = 0 + report['results']['vendors']['total_malicious'] = 0 + report['results']['vendors']['result'] = dict() + report['results']['vendors']['result']['undetected'] = 0 + results = res['data']['attributes']['last_analysis_results'] + for entry in results: + report['results']['vendors']['total_vendors'] += 1 + if results[entry]['category'] == 'undetected': + report['results']['vendors']['result']['undetected'] += 1 + else: + result = results[entry]['result'] + if result not in report['results']['vendors']['result']: + report['results']['vendors']['result'][result] = 0 + report['results']['vendors']['result'][result] += 1 + report['results']['vendors']['total_malicious'] += 1 +