android-persistence 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- android_persistence/__init__.py +29 -0
- android_persistence/data_parser.py +325 -0
- android_persistence/defensive_mitigations.py +378 -0
- android_persistence/persistence_detector.py +441 -0
- android_persistence/report_generator.py +326 -0
- android_persistence/utils/__init__.py +3 -0
- android_persistence/utils/hex_analyzer.py +162 -0
- android_persistence/utils/logger.py +74 -0
- android_persistence/utils/manifest_parser.py +164 -0
- android_persistence/utils/signature_matcher.py +217 -0
- android_persistence-1.0.0.dist-info/METADATA +484 -0
- android_persistence-1.0.0.dist-info/RECORD +16 -0
- android_persistence-1.0.0.dist-info/WHEEL +5 -0
- android_persistence-1.0.0.dist-info/entry_points.txt +2 -0
- android_persistence-1.0.0.dist-info/licenses/LICENSE +21 -0
- android_persistence-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Report Generator - Generate comprehensive analysis reports in multiple formats.
|
|
3
|
+
|
|
4
|
+
Generates detailed findings reports in JSON, PDF, and HTML formats with
|
|
5
|
+
visualizations, statistics, and executive summaries.
|
|
6
|
+
|
|
7
|
+
Author: Security Research Team
|
|
8
|
+
License: Apache-2.0
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import List, Dict, Any, Optional
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from dataclasses import asdict
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from reportlab.lib import colors
|
|
19
|
+
from reportlab.lib.pagesizes import letter, A4
|
|
20
|
+
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak, Table, TableStyle
|
|
21
|
+
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
|
22
|
+
from reportlab.lib.units import inch
|
|
23
|
+
except ImportError:
|
|
24
|
+
SimpleDocTemplate = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ReportGenerator:
|
|
28
|
+
"""
|
|
29
|
+
Generate comprehensive analysis reports.
|
|
30
|
+
|
|
31
|
+
Supports multiple output formats:
|
|
32
|
+
- JSON: Machine-readable findings
|
|
33
|
+
- HTML: Interactive web-based reports
|
|
34
|
+
- PDF: Professional printable reports
|
|
35
|
+
|
|
36
|
+
Example:
|
|
37
|
+
>>> generator = ReportGenerator()
|
|
38
|
+
>>> generator.generate_report(findings, "report.json")
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, title: str = "Android Persistence Analysis Report"):
|
|
42
|
+
"""
|
|
43
|
+
Initialize report generator.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
title: Report title
|
|
47
|
+
"""
|
|
48
|
+
self.title = title
|
|
49
|
+
self.timestamp = datetime.now()
|
|
50
|
+
self.findings: List[Dict[str, Any]] = []
|
|
51
|
+
|
|
52
|
+
def add_findings(self, findings: List[Any]) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Add findings to report.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
findings: List of finding objects
|
|
58
|
+
"""
|
|
59
|
+
for finding in findings:
|
|
60
|
+
if hasattr(finding, 'to_dict'):
|
|
61
|
+
self.findings.append(finding.to_dict())
|
|
62
|
+
elif isinstance(finding, dict):
|
|
63
|
+
self.findings.append(finding)
|
|
64
|
+
|
|
65
|
+
def generate_json_report(self, output_path: str) -> bool:
|
|
66
|
+
"""
|
|
67
|
+
Generate JSON format report.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
output_path: Output file path
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
True if generation succeeded
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
report_data = {
|
|
77
|
+
"title": self.title,
|
|
78
|
+
"timestamp": self.timestamp.isoformat(),
|
|
79
|
+
"total_findings": len(self.findings),
|
|
80
|
+
"findings": self.findings,
|
|
81
|
+
"statistics": self._calculate_statistics(),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
with open(output_path, 'w') as f:
|
|
85
|
+
json.dump(report_data, f, indent=2, default=str)
|
|
86
|
+
|
|
87
|
+
print(f"JSON report generated: {output_path}")
|
|
88
|
+
return True
|
|
89
|
+
except Exception as e:
|
|
90
|
+
print(f"Error generating JSON report: {str(e)}")
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
def generate_html_report(self, output_path: str) -> bool:
|
|
94
|
+
"""
|
|
95
|
+
Generate HTML format report.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
output_path: Output file path
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
True if generation succeeded
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
html_content = self._generate_html_content()
|
|
105
|
+
|
|
106
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
107
|
+
f.write(html_content)
|
|
108
|
+
|
|
109
|
+
print(f"HTML report generated: {output_path}")
|
|
110
|
+
return True
|
|
111
|
+
except Exception as e:
|
|
112
|
+
print(f"Error generating HTML report: {str(e)}")
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
def generate_pdf_report(self, output_path: str) -> bool:
|
|
116
|
+
"""
|
|
117
|
+
Generate PDF format report.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
output_path: Output file path
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
True if generation succeeded
|
|
124
|
+
"""
|
|
125
|
+
if SimpleDocTemplate is None:
|
|
126
|
+
print("Error: reportlab not installed. Install with: pip install reportlab")
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
doc = SimpleDocTemplate(output_path, pagesize=letter)
|
|
131
|
+
story = []
|
|
132
|
+
styles = getSampleStyleSheet()
|
|
133
|
+
|
|
134
|
+
# Title
|
|
135
|
+
title_style = ParagraphStyle(
|
|
136
|
+
'CustomTitle',
|
|
137
|
+
parent=styles['Heading1'],
|
|
138
|
+
fontSize=24,
|
|
139
|
+
textColor=colors.HexColor('#FF0000'),
|
|
140
|
+
spaceAfter=30,
|
|
141
|
+
)
|
|
142
|
+
story.append(Paragraph(self.title, title_style))
|
|
143
|
+
story.append(Spacer(1, 0.3*inch))
|
|
144
|
+
|
|
145
|
+
# Summary
|
|
146
|
+
stats = self._calculate_statistics()
|
|
147
|
+
summary_text = f"""
|
|
148
|
+
<b>Report Generated:</b> {self.timestamp.strftime('%Y-%m-%d %H:%M:%S')}<br/>
|
|
149
|
+
<b>Total Findings:</b> {stats['total_findings']}<br/>
|
|
150
|
+
<b>Critical:</b> {stats['critical']} |
|
|
151
|
+
<b>High:</b> {stats['high']} |
|
|
152
|
+
<b>Medium:</b> {stats['medium']} |
|
|
153
|
+
<b>Low:</b> {stats['low']}
|
|
154
|
+
"""
|
|
155
|
+
story.append(Paragraph(summary_text, styles['Normal']))
|
|
156
|
+
story.append(Spacer(1, 0.3*inch))
|
|
157
|
+
|
|
158
|
+
# Findings table
|
|
159
|
+
if self.findings:
|
|
160
|
+
table_data = [['Type', 'Component', 'Severity', 'Confidence']]
|
|
161
|
+
for finding in self.findings[:20]: # Limit to 20 per page
|
|
162
|
+
table_data.append([
|
|
163
|
+
finding.get('persistence_type', 'Unknown'),
|
|
164
|
+
finding.get('component_name', 'N/A')[:20],
|
|
165
|
+
finding.get('severity', 'N/A'),
|
|
166
|
+
f"{finding.get('confidence', 0)}%",
|
|
167
|
+
])
|
|
168
|
+
|
|
169
|
+
table = Table(table_data)
|
|
170
|
+
table.setStyle(TableStyle([
|
|
171
|
+
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#FF0000')),
|
|
172
|
+
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
|
173
|
+
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
|
174
|
+
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
|
175
|
+
('FONTSIZE', (0, 0), (-1, 0), 12),
|
|
176
|
+
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
|
177
|
+
('GRID', (0, 0), (-1, -1), 1, colors.black),
|
|
178
|
+
]))
|
|
179
|
+
story.append(table)
|
|
180
|
+
|
|
181
|
+
doc.build(story)
|
|
182
|
+
print(f"PDF report generated: {output_path}")
|
|
183
|
+
return True
|
|
184
|
+
except Exception as e:
|
|
185
|
+
print(f"Error generating PDF report: {str(e)}")
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
def _generate_html_content(self) -> str:
|
|
189
|
+
"""Generate HTML report content."""
|
|
190
|
+
stats = self._calculate_statistics()
|
|
191
|
+
|
|
192
|
+
severity_colors = {
|
|
193
|
+
'CRITICAL': '#FF0000',
|
|
194
|
+
'HIGH': '#FF6600',
|
|
195
|
+
'MEDIUM': '#FFCC00',
|
|
196
|
+
'LOW': '#00CC00',
|
|
197
|
+
'INFO': '#0066FF',
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
html = f"""
|
|
201
|
+
<!DOCTYPE html>
|
|
202
|
+
<html>
|
|
203
|
+
<head>
|
|
204
|
+
<meta charset="UTF-8">
|
|
205
|
+
<title>{self.title}</title>
|
|
206
|
+
<style>
|
|
207
|
+
body {{ font-family: Arial, sans-serif; margin: 20px; background-color: #f5f5f5; }}
|
|
208
|
+
.header {{ background-color: #333; color: white; padding: 20px; border-radius: 5px; }}
|
|
209
|
+
.summary {{ background-color: white; padding: 20px; margin: 20px 0; border-radius: 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }}
|
|
210
|
+
.stats {{ display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin: 20px 0; }}
|
|
211
|
+
.stat-box {{ background-color: white; padding: 20px; border-radius: 5px; text-align: center; border-left: 5px solid #333; }}
|
|
212
|
+
.finding {{ background-color: white; padding: 15px; margin: 10px 0; border-radius: 5px; border-left: 5px solid #999; }}
|
|
213
|
+
.critical {{ border-left-color: {severity_colors['CRITICAL']}; }}
|
|
214
|
+
.high {{ border-left-color: {severity_colors['HIGH']}; }}
|
|
215
|
+
.medium {{ border-left-color: {severity_colors['MEDIUM']}; }}
|
|
216
|
+
.low {{ border-left-color: {severity_colors['LOW']}; }}
|
|
217
|
+
.severity {{ display: inline-block; padding: 5px 10px; border-radius: 3px; font-weight: bold; color: white; }}
|
|
218
|
+
table {{ width: 100%; border-collapse: collapse; background-color: white; margin: 20px 0; }}
|
|
219
|
+
th, td {{ padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }}
|
|
220
|
+
th {{ background-color: #333; color: white; }}
|
|
221
|
+
tr:hover {{ background-color: #f5f5f5; }}
|
|
222
|
+
</style>
|
|
223
|
+
</head>
|
|
224
|
+
<body>
|
|
225
|
+
<div class="header">
|
|
226
|
+
<h1>{self.title}</h1>
|
|
227
|
+
<p>Generated: {self.timestamp.strftime('%Y-%m-%d %H:%M:%S')}</p>
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<div class="summary">
|
|
231
|
+
<h2>Executive Summary</h2>
|
|
232
|
+
<p>This report presents findings from comprehensive Android APK persistence analysis.</p>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
<div class="stats">
|
|
236
|
+
<div class="stat-box">
|
|
237
|
+
<h3>{stats['total_findings']}</h3>
|
|
238
|
+
<p>Total Findings</p>
|
|
239
|
+
</div>
|
|
240
|
+
<div class="stat-box">
|
|
241
|
+
<h3 style="color: {severity_colors['CRITICAL']}">{stats['critical']}</h3>
|
|
242
|
+
<p>Critical</p>
|
|
243
|
+
</div>
|
|
244
|
+
<div class="stat-box">
|
|
245
|
+
<h3 style="color: {severity_colors['HIGH']}">{stats['high']}</h3>
|
|
246
|
+
<p>High</p>
|
|
247
|
+
</div>
|
|
248
|
+
<div class="stat-box">
|
|
249
|
+
<h3 style="color: {severity_colors['MEDIUM']}">{stats['medium']}</h3>
|
|
250
|
+
<p>Medium</p>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
<h2>Findings</h2>
|
|
255
|
+
<table>
|
|
256
|
+
<tr>
|
|
257
|
+
<th>Type</th>
|
|
258
|
+
<th>Component</th>
|
|
259
|
+
<th>Severity</th>
|
|
260
|
+
<th>Confidence</th>
|
|
261
|
+
<th>Description</th>
|
|
262
|
+
</tr>
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
for finding in self.findings:
|
|
266
|
+
severity = finding.get('severity', 'UNKNOWN')
|
|
267
|
+
severity_class = severity.lower()
|
|
268
|
+
color = severity_colors.get(severity, '#999999')
|
|
269
|
+
|
|
270
|
+
html += f"""
|
|
271
|
+
<tr>
|
|
272
|
+
<td>{finding.get('persistence_type', 'Unknown')}</td>
|
|
273
|
+
<td>{finding.get('component_name', 'N/A')}</td>
|
|
274
|
+
<td><span class="severity" style="background-color: {color}">{severity}</span></td>
|
|
275
|
+
<td>{finding.get('confidence', 0)}%</td>
|
|
276
|
+
<td>{finding.get('description', 'N/A')[:100]}...</td>
|
|
277
|
+
</tr>
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
html += """
|
|
281
|
+
</table>
|
|
282
|
+
|
|
283
|
+
<div class="summary">
|
|
284
|
+
<h2>Disclaimer</h2>
|
|
285
|
+
<p><strong>RESEARCH ONLY:</strong> This tool is designed for legitimate security research
|
|
286
|
+
and defensive analysis only. Unauthorized analysis of applications without permission may
|
|
287
|
+
violate laws and ethical standards. Use only on systems where you have proper authorization.</p>
|
|
288
|
+
</div>
|
|
289
|
+
</body>
|
|
290
|
+
</html>
|
|
291
|
+
"""
|
|
292
|
+
|
|
293
|
+
return html
|
|
294
|
+
|
|
295
|
+
def _calculate_statistics(self) -> Dict[str, Any]:
|
|
296
|
+
"""Calculate report statistics."""
|
|
297
|
+
stats = {
|
|
298
|
+
'total_findings': len(self.findings),
|
|
299
|
+
'critical': 0,
|
|
300
|
+
'high': 0,
|
|
301
|
+
'medium': 0,
|
|
302
|
+
'low': 0,
|
|
303
|
+
'info': 0,
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
for finding in self.findings:
|
|
307
|
+
severity = finding.get('severity', '').upper()
|
|
308
|
+
if severity in stats:
|
|
309
|
+
stats[severity] += 1
|
|
310
|
+
|
|
311
|
+
return stats
|
|
312
|
+
|
|
313
|
+
def print_summary(self) -> None:
|
|
314
|
+
"""Print summary to console."""
|
|
315
|
+
stats = self._calculate_statistics()
|
|
316
|
+
print("\n" + "="*70)
|
|
317
|
+
print("REPORT SUMMARY")
|
|
318
|
+
print("="*70)
|
|
319
|
+
print(f"Title: {self.title}")
|
|
320
|
+
print(f"Generated: {self.timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
321
|
+
print(f"\nTotal Findings: {stats['total_findings']}")
|
|
322
|
+
print(f" Critical: {stats['critical']}")
|
|
323
|
+
print(f" High: {stats['high']}")
|
|
324
|
+
print(f" Medium: {stats['medium']}")
|
|
325
|
+
print(f" Low: {stats['low']}")
|
|
326
|
+
print("="*70 + "\n")
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hex and Binary Analysis utilities for examining binary data from APK files.
|
|
3
|
+
|
|
4
|
+
Provides utilities for analyzing binary data, including hex dumps, pattern matching,
|
|
5
|
+
and binary format identification.
|
|
6
|
+
|
|
7
|
+
Author: Security Research Team
|
|
8
|
+
License: Apache-2.0
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import struct
|
|
12
|
+
from typing import List, Tuple, Optional
|
|
13
|
+
import re
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class HexAnalyzer:
|
|
17
|
+
"""Utility class for hex and binary data analysis."""
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def hex_dump(data: bytes, width: int = 16) -> str:
|
|
21
|
+
"""
|
|
22
|
+
Generate hex dump of binary data.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
data: Binary data to dump
|
|
26
|
+
width: Number of bytes per line
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Formatted hex dump string
|
|
30
|
+
"""
|
|
31
|
+
lines = []
|
|
32
|
+
for i in range(0, len(data), width):
|
|
33
|
+
chunk = data[i:i+width]
|
|
34
|
+
hex_part = ' '.join(f'{b:02x}' for b in chunk)
|
|
35
|
+
ascii_part = ''.join(chr(b) if 32 <= b < 127 else '.' for b in chunk)
|
|
36
|
+
lines.append(f'{i:08x} {hex_part:<{width*3}} {ascii_part}')
|
|
37
|
+
return '\n'.join(lines)
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def find_pattern(data: bytes, pattern: bytes, all_matches: bool = False) -> List[int]:
|
|
41
|
+
"""
|
|
42
|
+
Find pattern in binary data.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
data: Data to search
|
|
46
|
+
pattern: Pattern to find
|
|
47
|
+
all_matches: If True, return all matches; if False, return first
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
List of offsets where pattern is found
|
|
51
|
+
"""
|
|
52
|
+
matches = []
|
|
53
|
+
offset = 0
|
|
54
|
+
while True:
|
|
55
|
+
offset = data.find(pattern, offset)
|
|
56
|
+
if offset == -1:
|
|
57
|
+
break
|
|
58
|
+
matches.append(offset)
|
|
59
|
+
if not all_matches:
|
|
60
|
+
return matches
|
|
61
|
+
offset += 1
|
|
62
|
+
return matches
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def find_magic_bytes(data: bytes) -> List[Tuple[int, str, bytes]]:
|
|
66
|
+
"""
|
|
67
|
+
Identify common file format magic bytes in data.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
data: Data to analyze
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
List of (offset, type, bytes) tuples
|
|
74
|
+
"""
|
|
75
|
+
MAGIC_SIGNATURES = {
|
|
76
|
+
b'\x50\x4b\x03\x04': 'ZIP Archive',
|
|
77
|
+
b'\x50\x4b\x05\x06': 'ZIP End of Central Directory',
|
|
78
|
+
b'\x64\x65\x78\x0a': 'DEX File',
|
|
79
|
+
b'\x7fELF': 'ELF Binary',
|
|
80
|
+
b'\xca\xfe\xba\xbe': 'Java Class File',
|
|
81
|
+
b'\xff\xd8\xff': 'JPEG Image',
|
|
82
|
+
b'\x89PNG': 'PNG Image',
|
|
83
|
+
b'\x42\x4d': 'BMP Image',
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
matches = []
|
|
87
|
+
for sig, name in MAGIC_SIGNATURES.items():
|
|
88
|
+
offset = data.find(sig)
|
|
89
|
+
if offset != -1:
|
|
90
|
+
matches.append((offset, name, sig))
|
|
91
|
+
|
|
92
|
+
return sorted(matches, key=lambda x: x[0])
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def extract_strings(data: bytes, min_length: int = 4) -> List[str]:
|
|
96
|
+
"""
|
|
97
|
+
Extract ASCII strings from binary data.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
data: Binary data
|
|
101
|
+
min_length: Minimum string length
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
List of extracted strings
|
|
105
|
+
"""
|
|
106
|
+
strings = []
|
|
107
|
+
current = []
|
|
108
|
+
|
|
109
|
+
for byte in data:
|
|
110
|
+
if 32 <= byte < 127: # Printable ASCII
|
|
111
|
+
current.append(chr(byte))
|
|
112
|
+
else:
|
|
113
|
+
if len(current) >= min_length:
|
|
114
|
+
strings.append(''.join(current))
|
|
115
|
+
current = []
|
|
116
|
+
|
|
117
|
+
if len(current) >= min_length:
|
|
118
|
+
strings.append(''.join(current))
|
|
119
|
+
|
|
120
|
+
return strings
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def find_regex_pattern(data: bytes, pattern: str) -> List[Tuple[int, bytes]]:
|
|
124
|
+
"""
|
|
125
|
+
Find data matching regex pattern.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
data: Binary data
|
|
129
|
+
pattern: Regex pattern (will be applied to string representation)
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
List of (offset, match) tuples
|
|
133
|
+
"""
|
|
134
|
+
matches = []
|
|
135
|
+
regex = re.compile(pattern.encode() if isinstance(pattern, str) else pattern)
|
|
136
|
+
|
|
137
|
+
for match in regex.finditer(data):
|
|
138
|
+
matches.append((match.start(), match.group()))
|
|
139
|
+
|
|
140
|
+
return matches
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def xor_analysis(data: bytes, key_size: int = 1) -> List[Tuple[int, bytes]]:
|
|
144
|
+
"""
|
|
145
|
+
Perform XOR analysis on data for potential encryption/obfuscation.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
data: Data to analyze
|
|
149
|
+
key_size: Size of XOR key to try
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
List of (key, decrypted_sample) tuples
|
|
153
|
+
"""
|
|
154
|
+
results = []
|
|
155
|
+
|
|
156
|
+
if key_size == 1:
|
|
157
|
+
for key in range(256):
|
|
158
|
+
decrypted = bytes(b ^ key for b in data[:min(100, len(data))])
|
|
159
|
+
if decrypted.count(0) < len(decrypted) // 4: # Not too many nulls
|
|
160
|
+
results.append((bytes([key]), decrypted))
|
|
161
|
+
|
|
162
|
+
return results[:10] # Return top 10 results
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Logging utility module for consistent logging across the framework.
|
|
3
|
+
|
|
4
|
+
Provides configured loggers with standard formatting and multiple output handlers.
|
|
5
|
+
|
|
6
|
+
Author: Security Research Team
|
|
7
|
+
License: Apache-2.0
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LoggerConfig:
|
|
17
|
+
"""Configure and manage application logging."""
|
|
18
|
+
|
|
19
|
+
LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
20
|
+
DEBUG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s'
|
|
21
|
+
|
|
22
|
+
def __init__(self, log_dir: Optional[str] = None):
|
|
23
|
+
"""
|
|
24
|
+
Initialize logger configuration.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
log_dir: Optional directory to store log files
|
|
28
|
+
"""
|
|
29
|
+
self.log_dir = Path(log_dir) if log_dir else None
|
|
30
|
+
if self.log_dir:
|
|
31
|
+
self.log_dir.mkdir(parents=True, exist_ok=True)
|
|
32
|
+
|
|
33
|
+
def get_logger(
|
|
34
|
+
self,
|
|
35
|
+
name: str,
|
|
36
|
+
level: str = "INFO",
|
|
37
|
+
log_file: bool = False
|
|
38
|
+
) -> logging.Logger:
|
|
39
|
+
"""
|
|
40
|
+
Get configured logger.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
name: Logger name
|
|
44
|
+
level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
45
|
+
log_file: Whether to also log to file
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Configured logger instance
|
|
49
|
+
"""
|
|
50
|
+
logger = logging.getLogger(name)
|
|
51
|
+
logger.setLevel(getattr(logging, level))
|
|
52
|
+
|
|
53
|
+
# Remove existing handlers to avoid duplicates
|
|
54
|
+
logger.handlers.clear()
|
|
55
|
+
|
|
56
|
+
# Console handler
|
|
57
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
|
58
|
+
console_handler.setLevel(getattr(logging, level))
|
|
59
|
+
|
|
60
|
+
formatter = logging.Formatter(
|
|
61
|
+
self.DEBUG_FORMAT if level == "DEBUG" else self.LOG_FORMAT
|
|
62
|
+
)
|
|
63
|
+
console_handler.setFormatter(formatter)
|
|
64
|
+
logger.addHandler(console_handler)
|
|
65
|
+
|
|
66
|
+
# File handler (optional)
|
|
67
|
+
if log_file and self.log_dir:
|
|
68
|
+
file_path = self.log_dir / f"{name}.log"
|
|
69
|
+
file_handler = logging.FileHandler(file_path)
|
|
70
|
+
file_handler.setLevel(getattr(logging, level))
|
|
71
|
+
file_handler.setFormatter(formatter)
|
|
72
|
+
logger.addHandler(file_handler)
|
|
73
|
+
|
|
74
|
+
return logger
|