diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fcd647..0573f6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [v1.6.0] - 2025-01-26 +### Added +- New Static analyzer Stringnalyzer implementation + +### Fixed +- Refactoring health check implementation to use configuration file settings + ## [v1.5.1] - 2025-01-25 ### Added - Support for executing payloads with custom command-line arguments @@ -15,7 +22,7 @@ All notable changes to this project will be documented in this file. ## [v1.5.0] - 2025-01-11 ### Added -- LitterBox RedEdr Scanner implementation +- New Dynamic analyzer RedEdr Scanner implementation - Added LICENSE file ### Changed diff --git a/Config/config.yaml b/Config/config.yaml index c5dc3af..c4f918e 100644 --- a/Config/config.yaml +++ b/Config/config.yaml @@ -36,6 +36,12 @@ analysis: command: "{tool_path} -m -r -f {file_path}" timeout: 120 + stringnalyzer: + enabled: true + tool_path: ".\\Scanners\\Stringnalyzer\\Stringnalyzer.exe " + command: "{tool_path} --wide -f {file_path}" + timeout: 120 + dynamic: yara: enabled: true diff --git a/README.md b/README.md index c9e0b7b..3e8d20a 100644 --- a/README.md +++ b/README.md @@ -58,20 +58,20 @@ Features include: - Injection technique analysis - Sleep pattern monitoring - Collect Windows telemetry via ETW -- PE integrity verification ## Integrated Tools ### Static Analysis Suite - [YARA](https://github.com/elastic/protections-artifacts/tree/main/yara) - Pattern matching and signature detection - [CheckPlz](https://github.com/BlackSnufkin/CheckPlz) - AV detection testing +- [Stringnalyzer](https://github.com/BlackSnufkin/Rusty-Playground/Stringnalyzer) - Payload Strings analyzer (Note: May produce false positives) ### Dynamic Analysis Suite - [YARA](https://github.com/elastic/protections-artifacts/tree/main/yara) (memory scanning) - Runtime pattern detection - [PE-Sieve](https://github.com/hasherezade/pe-sieve) - Detecting and dumping in-memory malware implants and advanced process injection techniques - [Moneta](https://github.com/forrest-orr/moneta) - Usermode memory analysis tool to detect malware IOCs - [Patriot](https://github.com/BlackSnufkin/patriot) - Detecting various kinds of in-memory stealth techniques -- [RedEdr](https://github.com/dobin/RedEdr) - Collect Windows telemetry via ETW providers +- [RedEdr](https://github.com/dobin/RedEdr) - Collect Windows telemetry via ETW providers (Note: See issue #6) - [Hunt-Sleeping-Beacons](https://github.com/thefLink/Hunt-Sleeping-Beacons) - Beacon behavior analysis ## Web Endpoint Reference diff --git a/Scanners/Stringnalyzer/Stringnalyzer.exe b/Scanners/Stringnalyzer/Stringnalyzer.exe new file mode 100644 index 0000000..ea53316 Binary files /dev/null and b/Scanners/Stringnalyzer/Stringnalyzer.exe differ diff --git a/app/analyzers/manager.py b/app/analyzers/manager.py index 0c0961d..fa33b02 100644 --- a/app/analyzers/manager.py +++ b/app/analyzers/manager.py @@ -1,6 +1,5 @@ # app/analyzers/manager.py - import logging import subprocess import time @@ -12,6 +11,7 @@ from abc import ABC, abstractmethod # Import analyzers from .static.yara_analyzer import YaraStaticAnalyzer from .static.checkplz_analyzer import CheckPlzAnalyzer +from .static.stringnalyzer_analyzer import StringsAnalyzer from .dynamic.yara_analyzer import YaraDynamicAnalyzer from .dynamic.pe_sieve_analyzer import PESieveAnalyzer from .dynamic.moneta_analyzer import MonetaAnalyzer @@ -32,7 +32,8 @@ class AnalysisManager: # Define analyzer mappings STATIC_ANALYZERS = { 'yara': YaraStaticAnalyzer, - 'checkplz': CheckPlzAnalyzer + 'checkplz': CheckPlzAnalyzer, + 'stringnalyzer': StringsAnalyzer } DYNAMIC_ANALYZERS = { diff --git a/app/analyzers/static/stringnalyzer_analyzer.py b/app/analyzers/static/stringnalyzer_analyzer.py new file mode 100644 index 0000000..6a7d7e2 --- /dev/null +++ b/app/analyzers/static/stringnalyzer_analyzer.py @@ -0,0 +1,102 @@ +import subprocess +import json +import os +from .base import StaticAnalyzer + +class StringsAnalyzer(StaticAnalyzer): + def analyze(self, file_path): + """ + Analyzes a file using strings analysis tool specified in the config. + """ + try: + tool_config = self.config['analysis']['static']['stringnalyzer'] + command = tool_config['command'].format( + tool_path=os.path.abspath(tool_config['tool_path']), + file_path=os.path.abspath(file_path) + ) + + process = subprocess.Popen( + command, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + cwd=os.path.dirname(os.path.abspath(tool_config['tool_path'])) + ) + + stdout, stderr = process.communicate(timeout=tool_config.get('timeout', 300)) + + # Parse the JSON output + results = self._parse_output(stdout) + + self.results = { + 'status': 'completed' if process.returncode == 0 else 'failed', + 'scan_info': { + 'target': file_path, + 'tool': 'Stringnalyzer' + }, + 'findings': results, + 'errors': stderr if stderr else None + } + + except Exception as e: + self.results = { + 'status': 'error', + 'error': str(e) + } + + def _parse_output(self, output): + """ + Parse the strings tool JSON output into structured data. + Returns a dictionary containing the parsed results. + """ + try: + # Try to parse the JSON output + results = json.loads(output) + + # Ensure all expected fields are present, initialize if missing + default_fields = { + 'file_path': None, + 'total_strings': 0, + 'all_strings': [], + 'found_error_messages': [], + 'found_functions': [], + 'found_url': [], + 'found_dll': [], + 'found_ip': [], + 'found_path': [], + 'found_file': [], + 'found_commands': [], + 'found_suspicious_strings': [], + 'found_suspicious_functions': [], + 'found_network_indicators': [], + 'found_registry_keys': [], + 'found_interesting_strings': [], + 'found_file_operations': [], + 'found_emails': [], + 'found_domains': [] + } + + # Update default fields with actual results + for key in default_fields: + if key not in results: + results[key] = default_fields[key] + + return results + + except json.JSONDecodeError as e: + return { + 'error': f'Failed to parse JSON output: {str(e)}', + **default_fields + } + except Exception as e: + return { + 'error': f'Unexpected error parsing output: {str(e)}', + **default_fields + } + + def cleanup(self): + """ + Cleanup any temporary files or processes. + """ + pass \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index 7428ffe..337c4ab 100644 --- a/app/routes.py +++ b/app/routes.py @@ -724,7 +724,7 @@ def register_routes(app): analysis_config = config.get('analysis', {}) issues = [] - # Check upload folder accessibility + # Simple upload folder check upload_folder = upload_config.get('upload_folder') if not upload_folder: app.logger.warning("Upload folder path is not configured.") @@ -732,9 +732,6 @@ def register_routes(app): elif not os.path.isdir(upload_folder): app.logger.warning(f"Upload folder does not exist: {upload_folder}") issues.append(f"Upload folder does not exist: {upload_folder}") - elif not os.access(upload_folder, os.W_OK): - app.logger.warning(f"Upload folder is not writable: {upload_folder}") - issues.append(f"Upload folder is not writable: {upload_folder}") # Check static and dynamic analysis tools def check_analysis_tool(section, tool_name): @@ -746,29 +743,45 @@ def register_routes(app): issues.append(f"{tool_name}: tool path not configured") elif not os.path.isfile(tool_path): issues.append(f"{tool_name}: tool not found at {tool_path}") - elif not os.access(tool_path, os.X_OK): - issues.append(f"{tool_name}: tool not executable at {tool_path}") + rules_path = tool_config.get('rules_path') if rules_path and not os.path.isfile(rules_path): issues.append(f"{tool_name}: rules not found at {rules_path}") - for tool in ['yara', 'checkplz']: - check_analysis_tool(analysis_config.get('static', {}), tool) + # Get tools from config instead of hardcoding + static_section = analysis_config.get('static', {}) + dynamic_section = analysis_config.get('dynamic', {}) - for tool in ['yara', 'pe_sieve', 'moneta', 'patriot', 'hsb', 'rededr']: - check_analysis_tool(analysis_config.get('dynamic', {}), tool) + # Check all configured static tools + for tool_name in static_section.keys(): + check_analysis_tool(static_section, tool_name) + + # Check all configured dynamic tools + for tool_name in dynamic_section.keys(): + check_analysis_tool(dynamic_section, tool_name) + + # Get all enabled tools for configuration response + static_tools = { + tool: static_section.get(tool, {}).get('enabled', False) + for tool in static_section.keys() + } + + dynamic_tools = { + tool: dynamic_section.get(tool, {}).get('enabled', False) + for tool in dynamic_section.keys() + } status = 'ok' if not issues else 'degraded' app.logger.debug(f"Health check completed. Status: {status}") - + return jsonify({ 'status': status, 'timestamp': datetime.datetime.now().isoformat(), - 'upload_folder_accessible': os.path.isdir(upload_folder) and os.access(upload_folder, os.W_OK) if upload_folder else False, + 'upload_folder_accessible': os.path.isdir(upload_folder) if upload_folder else False, 'issues': issues, 'configuration': { - 'static_analysis': {tool: analysis_config.get('static', {}).get(tool, {}).get('enabled', False) for tool in ['yara', 'threatcheck']}, - 'dynamic_analysis': {tool: analysis_config.get('dynamic', {}).get(tool, {}).get('enabled', False) for tool in ['yara', 'pe_sieve', 'moneta']} + 'static_analysis': static_tools, + 'dynamic_analysis': dynamic_tools } }), 200 if status == 'ok' else 503 diff --git a/app/static/js/results.js b/app/static/js/results.js index b9f78a4..e88652e 100644 --- a/app/static/js/results.js +++ b/app/static/js/results.js @@ -250,6 +250,32 @@ function formatBytes(bytes) { } +function renderSection(title, items) { + if (!items || items.length === 0) return ''; + + const displayItems = items.slice(0, 25); + const remainingCount = items.length - 25; + + return ` +
+
+ ${title} (${items.length}) +
+
+ ${displayItems.map(item => ` +
+ ${item} +
+ `).join('')} + ${remainingCount > 0 ? ` +
+ ... and ${remainingCount} more items +
+ ` : ''} +
+
`; +} + // Tools Registry Object (keeping reference to tools) const tools = { yara: { @@ -421,6 +447,214 @@ const tools = { }, }, + checkplz: { + element: document.getElementById('threatCheckResults'), + statsElement: document.getElementById('threatCheckStats'), + render: (results) => { + if (results.status === 'error') { + tools.checkplz.element.innerHTML = ` +
+
+ + + + ${results.error} +
+
`; + return; + } + + const findings = results.findings || {}; + const scanResults = findings.scan_results || {}; + const isClean = !findings.initial_threat && !scanResults.detection_offset; + + // Stats Section + tools.checkplz.statsElement.innerHTML = ` +
+
+
Status
+
+ ${isClean ? 'Clean' : (findings.initial_threat || 'Unknown Threat')} +
+
+
+
Scan Duration
+
+ ${typeof scanResults.scan_duration === 'number' ? scanResults.scan_duration.toFixed(3) + 's' : 'N/A'} +
+
+
+
Search Iterations
+
+ ${scanResults.search_iterations || 'N/A'} +
+
+
`; + + let html = ''; + + // File Information Section + html += ` +
+
File Information
+
+
+
File Path
+
+ ${scanResults.file_path || 'N/A'} +
+
+
+
File Size
+
+ ${scanResults.file_size || 'N/A'} +
+
+
+
`; + + // Clean State Message + if (isClean) { + html += ` +
+ + + + No threats detected - File is clean + Security scan completed successfully +
`; + } else { + // Detection Details Section + if (scanResults.detection_offset) { + html += ` +
+
+ + + + Threat Detection Details +
+
+
+
Detection Offset
+
${scanResults.detection_offset}
+
+
+
Relative Location
+
${scanResults.relative_location}
+
+
+
Final Threat Detection
+
${scanResults.final_threat_detection}
+
+
+
`; + } + + // Hex Dump Section (only shown when threats are detected) + if (scanResults.hex_dump) { + html += ` +
+
+ Showing ±128 bytes around detection point + +
+
${scanResults.hex_dump}
+
`; + } + } + + tools.checkplz.element.innerHTML = html; + } + }, + + stringnalyzer: { + element: document.getElementById('StringnalyzerResults'), + statsElement: document.getElementById('StringnalyzerStats'), + render: (results) => { + if (results.status === 'error') { + tools.stringnalyzer.element.innerHTML = ` +
+
+ + + + ${results.error} +
+
`; + return; + } + + const findings = results.findings || {}; + + // Stats Section with Download Button + tools.stringnalyzer.statsElement.innerHTML = ` +
+
+
File Path
+
+ ${findings.file_path || 'N/A'} +
+
+
+
Total Strings
+
+ ${findings.total_strings || 0} +
+
+
`; + + // Attach click event to the Download Button + const downloadButton = document.getElementById('downloadResultsBtn'); + if (downloadButton) { + downloadButton.addEventListener('click', () => { + const filePath = results.findings.file_path || 'unknown'; + const fullFileName = filePath.split('\\').pop().split('/').pop(); // Get file name from path + const actualFileName = fullFileName.split('_').pop(); // Get everything after last underscore + const downloadName = `stringnalyzer_${actualFileName.replace('.exe', '')}.json`; + + const blob = new Blob([JSON.stringify(results, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = downloadName; + a.click(); + URL.revokeObjectURL(url); + }); + } + + let html = ''; + + // All Findings Sections + html += ` +
+ ${renderSection('Suspicious Strings', findings.found_suspicious_strings)} + ${renderSection('Functions Referenced', findings.found_suspicious_functions)} + ${renderSection('URLs Found', findings.found_url)} + ${renderSection('DLLs Referenced', findings.found_dll)} + ${renderSection('IP Addresses', findings.found_ip)} + ${renderSection('Paths Found', findings.found_path)} + ${renderSection('Files Referenced', findings.found_file)} + ${renderSection('Commands Found', findings.found_commands)} + ${renderSection('Functions', findings.found_functions)} + ${renderSection('Error Messages', findings.found_error_messages)} + ${renderSection('Network Indicators', findings.found_network_indicators)} + ${renderSection('Registry Keys', findings.found_registry_keys)} + ${renderSection('File Operations', findings.found_file_operations)} + ${renderSection('Email Addresses', findings.found_emails)} + ${renderSection('Domains', findings.found_domains)} + ${renderSection('Interesting Strings', findings.found_interesting_strings)} +
`; + + tools.stringnalyzer.element.innerHTML = html; + } + }, + pe_sieve: { element: document.getElementById('peSieveResults'), render: (results) => { @@ -686,132 +920,6 @@ const tools = { } }, - checkplz: { - element: document.getElementById('threatCheckResults'), - statsElement: document.getElementById('threatCheckStats'), - render: (results) => { - if (results.status === 'error') { - tools.checkplz.element.innerHTML = ` -
-
- - - - ${results.error} -
-
`; - return; - } - - const findings = results.findings || {}; - const scanResults = findings.scan_results || {}; - const isClean = !findings.initial_threat && !scanResults.detection_offset; - - // Stats Section - tools.checkplz.statsElement.innerHTML = ` -
-
-
Status
-
- ${isClean ? 'Clean' : (findings.initial_threat || 'Unknown Threat')} -
-
-
-
Scan Duration
-
- ${typeof scanResults.scan_duration === 'number' ? scanResults.scan_duration.toFixed(3) + 's' : 'N/A'} -
-
-
-
Search Iterations
-
- ${scanResults.search_iterations || 'N/A'} -
-
-
`; - - let html = ''; - - // File Information Section - html += ` -
-
File Information
-
-
-
File Path
-
- ${scanResults.file_path || 'N/A'} -
-
-
-
File Size
-
- ${scanResults.file_size || 'N/A'} -
-
-
-
`; - - // Clean State Message - if (isClean) { - html += ` -
- - - - No threats detected - File is clean - Security scan completed successfully -
`; - } else { - // Detection Details Section - if (scanResults.detection_offset) { - html += ` -
-
- - - - Threat Detection Details -
-
-
-
Detection Offset
-
${scanResults.detection_offset}
-
-
-
Relative Location
-
${scanResults.relative_location}
-
-
-
Final Threat Detection
-
${scanResults.final_threat_detection}
-
-
-
`; - } - - // Hex Dump Section (only shown when threats are detected) - if (scanResults.hex_dump) { - html += ` -
-
- Showing ±128 bytes around detection point - -
-
${scanResults.hex_dump}
-
`; - } - } - - tools.checkplz.element.innerHTML = html; - } - }, - patriot: { element: document.getElementById('patriotResults'), statsElement: document.getElementById('patriotStats'), diff --git a/app/templates/results.html b/app/templates/results.html index e4f0550..19fdf0b 100644 --- a/app/templates/results.html +++ b/app/templates/results.html @@ -121,7 +121,6 @@ -
@@ -137,6 +136,9 @@ + {% else %}
+
+
+

Stringnalyzer Scan Results

+ +
+

Payload String Analyzer

+
+
+
{% else %}