Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Analyzers for Valhalla and Thunderstorm #943

Merged
merged 7 commits into from
Feb 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions analyzers/Splunk/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
splunk-sdk
cortexutils
23 changes: 23 additions & 0 deletions analyzers/Thunderstorm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
### Thunderstorm

The Thunderstorm analyzer submits a file sample to a local or public THOR Thunderstorm service and processes the scan result

#### Requirements

- [ThunderstormAPI](https://github.com/NextronSystems/thunderstormAPI)

#### Scope

[THOR Thunderstorm](https://www.nextron-systems.com/thor-thunderstorm/) is a web service version of the well-known scanner THOR. THOR focuses on APTs, hacking activity, traces of hacking activity and file anomalies like obfuscation techniques, suspicious PE packers or PE header anomalies.

#### Matches

The reports contain useful meta data and a list of matching rules. Each rule links to a related public report or states that the rules was based on internal research.

The reports include a total score and sub scores defined in the matching YARA rules.

The score and level indicate the criticality of the finding.

#### Access to Thunderstorm

THOR Thunderstorm is a high-speed, multi-threaded, caching scan service that is licensed and installed on-premise on the Linux system of your choice. Nextron systems offers access to test systems with the FQDN thunderstorm.nextron-systems.com [on request](https://www.nextron-systems.com/get-started/).
70 changes: 70 additions & 0 deletions analyzers/Thunderstorm/Thunderstorm_ScanSample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"name": "THOR_Thunderstorm_ScanSample",
"version": "0.3.1",
"author": "Florian Roth",
"url": "https://github.com/NextronSystems/Cortex-Analyzers",
"license": "AGPL-V3",
"description": "Submits sample to an on-premise THOR Thunderstorm web service and processes the scan result",
"dataTypeList": ["file"],
"command": "Thunderstorm/thunderstorm.py",
"baseConfig": "Thunderstorm",
"configurationItems": [
{
"name": "thunderstorm_server",
"description": "Thunderstorm Server",
"type": "string",
"multi": false,
"required": true,
"defaultValue": "thunderstorm.nextron-systems.com"
},
{
"name": "thunderstorm_port",
"description": "Thunderstorm Port",
"type": "number",
"multi": false,
"required": true,
"defaultValue": 8080
},
{
"name": "thunderstorm_source",
"description": "Source System",
"type": "string",
"multi": false,
"required": false,
"defaultValue": "cortex-analyzer"
},
{
"name": "thunderstorm_ssl",
"description": "Use an SSL encrypted HTTP connection",
"type": "boolean",
"multi": false,
"required": false,
"defaultValue": false
},
{
"name": "thunderstorm_ssl_verify",
"description": "Verify the SSL certificate of the remote service",
"type": "boolean",
"multi": false,
"required": false,
"defaultValue": false
}
],
"registration_required": true,
"subscription_required": true,
"free_subscription": false,
"service_homepage": "https://www.nextron-systems.com/thor-thunderstorm/",
"service_logo": {"path":"assets/thor_thunderstorm_logo.png", "caption": "logo"},
"screenshots": [
{"path":"assets/THOR_Thunderstorm_ScanSample_long.png",
"caption":"THOR Thunderstorm long report sample"
},
{
"path": "assets/THOR_Thunderstorm_ScanSample_short.png",
"caption:":"THOR Thunderstorm short report sample"
},
{"path":"assets/THOR_Thunderstorm_ScanSample_raw.png",
"caption":"THOR Thunderstorm raw JSON"
}
]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions analyzers/Thunderstorm/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
cortexutils
requests
thunderstormAPI
99 changes: 99 additions & 0 deletions analyzers/Thunderstorm/thunderstorm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
# encoding: utf-8

import os

from thunderstormAPI.thunderstorm import ThunderstormAPI
from cortexutils.analyzer import Analyzer


class ThunderstormAnalyzer(Analyzer):

def __init__(self):
Analyzer.__init__(self)
self.thunderstorm_server = self.get_param('config.thunderstorm_server', None, 'THOR Thunderstorm server has not been configured')
self.thunderstorm_port = self.get_param('config.thunderstorm_port', 8080)
self.thunderstorm_source = self.get_param('config.thunderstorm_source', 'cortex-analyzer')
self.thunderstorm_ssl = self.get_param('config.thunderstorm_ssl', False)
self.thunderstorm_verify_ssl = self.get_param('config.thunderstorm_ssl_verify', False)

self.thorapi = ThunderstormAPI(
host=self.thunderstorm_server,
port=int(self.thunderstorm_port),
source=self.thunderstorm_source,
use_ssl=self.thunderstorm_ssl,
verify_ssl=self.thunderstorm_verify_ssl)

def check_response(self, response):
if len(response) > 0:
if type(response) is not list:
self.error('Bad response : ' + str(response))
results = response[0]
else:
results = {}
return results

def summary(self, raw):
taxonomies = []
level = "info"
namespace = "THUNDERSTORM"
predicate = "GetScanResult"
value = "no matches"

result = raw
if len(result) > 0:
# A single match automatically makes it suspicious
level = "suspicious"
# Get THOR's level
thor_level = result['level']
# If that is 'Alert', then increase the level to malicious
if thor_level == "Alert":
level = "malicious"

# Get all matches that add a sub score to the total score
match_reasons = []
yara_matches = 0
other_matches = 0
total_score = 0
for match in result['matches']:
# Fix /tmp/ folder finding caused by Cortex file upload
if "suspicious apt directory" in match['reason'].lower():
# ignore this match
continue
# Add sub score to total score
if 'subscore' in match:
total_score += int(match['subscore'])
# YARA rule match
if 'rulename' in match:
match_reasons.append(match['rulename'])
yara_matches += 1
else:
other_matches += 1

# Combine all rule names to a value
if len(match_reasons) > 0:
if len(match_reasons) < 4:
value = ", ".join(match_reasons)
else:
value = "[%d of different rule matches]" % len(match_reasons)

# Add match type to the result set
result['yara_matches'] = yara_matches
result['other_matches'] = other_matches

taxonomies.append(self.build_taxonomy(level, namespace, predicate, value))
return {"taxonomies": taxonomies}

def run(self):
if self.data_type == 'file':
data = self.get_param('file', None, 'File is missing')
if os.path.exists(data):
self.report(self.check_response(self.thorapi.scan(data)))
else:
self.error("File '%s' not found" % data)
else:
self.error('Invalid data type')


if __name__ == '__main__':
ThunderstormAnalyzer().run()
28 changes: 28 additions & 0 deletions analyzers/Valhalla/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
### Valhalla

The Valhalla analyzer queries the Valhalla YARA rule databased and retrieves the matching YARA rules.

#### Requirements

- [ValhallaAPI](https://github.com/NextronSystems/valhallaAPI)

#### Scope

The result contains all matching YARA rules including

- Nextron's rules in the [public rule repository](https://github.com/Neo23x0/signature-base/)
- Nextron's rules sold in the form of the [YARA rule feed](https://www.nextron-systems.com/valhalla/)

The result does not contain matches with YARA rules

- submitted by 3rd parties into the [public rule repository](https://github.com/Neo23x0/signature-base/) due to legal restrictions
- rules that are tagged as confidential and can therefore only be used in Nextron's scanner [THOR](https://www.nextron-systems.com/thor/)
- rules that require external variables and can therefore only be used in Nextron's scanner [THOR](https://www.nextron-systems.com/thor/)

The database contains YARA rule matches on samples submitted to Virustotal and Nextron's internal sample matching, which accounts for less than 1% of the matches within that database. The database does not contain information on samples that have not been transmitted to Virustotal.

#### Matches

The matches in the long report link to rule info pages that contain more information, like other matching samples, a report or public source in which the sample from which that rule was derived has been mentioned.

They also include the Antivirus detection rate at the moment of the first submission to Virustotal, which gives a good indication of the overall coverage.
34 changes: 34 additions & 0 deletions analyzers/Valhalla/Valhalla_GetMatches.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "Valhalla_GetRuleMatches",
"version": "0.3.1",
"author": "Florian Roth",
"url": "https://github.com/NextronSystems/Cortex-Analyzers",
"license": "AGPL-V3",
"description": "Gets matching YARA rules for a given sample SHA256 hash",
"dataTypeList": ["hash"],
"command": "Valhalla/valhalla.py",
"baseConfig": "Valhalla",
"configurationItems": [
{
"name": "key",
"description": "API key for Valhalla",
"type": "string",
"multi": false,
"required": true,
"defaultValue": "1111111111111111111111111111111111111111111111111111111111111111"
}
],
"registration_required": false,
"subscription_required": false,
"free_subscription": false,
"service_homepage": "https://valhalla.nextron-systems.com",
"service_logo": {"path":"assets/Valhalla_logo.png", "caption": "logo"},
"screenshots": [
{"path":"assets/Valhalla_GetMatches_short.png",
"caption":"Valhalla Get Hashes short report sample"
},
{
"path": "assets/Valhalla_GetMatches_long.png",
"caption:":"Valhalla Get Hashes long report sample"
}]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added analyzers/Valhalla/assets/Valhalla_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions analyzers/Valhalla/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
cortexutils
requests
valhallaAPI
84 changes: 84 additions & 0 deletions analyzers/Valhalla/valhalla.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
# encoding: utf-8

from valhallaAPI.valhalla import ValhallaAPI
from cortexutils.analyzer import Analyzer


class ValhallaAnalyzer(Analyzer):

def __init__(self):
Analyzer.__init__(self)
self.valhalla_key = self.get_param('config.key', None, 'Missing Valhalla API key')
self.polling_interval = self.get_param('config.polling_interval', 60)
self.v = ValhallaAPI(api_key=self.valhalla_key)

def check_response(self, response):
if type(response) is not dict:
self.error('Bad response : ' + str(response))
status = response.get('status', 'not set')
if status == 'error':
self.error('Query failed: %s Message: %s' % (str(status), response.get('message', 'not set')))
results = response
return results

def summary(self, raw):
taxonomies = []
level = "info"
namespace = "VALHALLA"
predicate = "GetMatches"
value = "not set"

# Get status and result set
status = raw.get('status', 'not set')
results = raw.get('results', [])

# Status handling
if status == "error":
status = raw.get('message', 'not set')
value = status
if status == "empty":
value = "no matches found"

# If a single matching YARA rule could be found, then set suspicious
if len(results) > 0:
level = "suspicious"

# Match handling
av_matches = []
avg_av_matches = 0
matching_rules = []
for match in results:
# Add rule to list
matching_rules.append(match["rulename"])
# Sum up all AV matches
if 'positives' in match:
if isinstance(match['positives'], int):
av_matches.append(match['positives'])
# Calculate average AV detection rate
if len(av_matches) > 0:
avg_av_matches = sum(av_matches)/len(av_matches)
# If AV engines also came to the conclusion that this is malicious, then mark it as malicious
if avg_av_matches > 10:
level = "malicious"

# Compose a list of all matching YARA rules for the value field
if len(matching_rules) > 0:
value = ", ".join(matching_rules)

taxonomies.append(self.build_taxonomy(level, namespace, predicate, value))
return {"taxonomies": taxonomies}

def run(self):
if self.data_type == 'hash':
data = self.get_param('data', None, 'Data is missing')
if len(data) == 64:
self.report(self.check_response(self.v.get_hash_info(data)))
else:
self.report({'status': 'error', 'message': 'hash is not SHA256', 'results': []})
else:
self.error('Invalid data type')


if __name__ == '__main__':
ValhallaAnalyzer().run()
Loading