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.
@@ -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,3 @@
1
+ """
2
+ Utility modules for Android persistence analysis.
3
+ """
@@ -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