anais-apk-forensic 1.0.0

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.
Files changed (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +249 -0
  3. package/anais.sh +669 -0
  4. package/analysis_tools/__pycache__/apk_basic_info.cpython-313.pyc +0 -0
  5. package/analysis_tools/__pycache__/apk_basic_info.cpython-314.pyc +0 -0
  6. package/analysis_tools/__pycache__/check_zip_encryption.cpython-313.pyc +0 -0
  7. package/analysis_tools/__pycache__/check_zip_encryption.cpython-314.pyc +0 -0
  8. package/analysis_tools/__pycache__/detect_obfuscation.cpython-313.pyc +0 -0
  9. package/analysis_tools/__pycache__/detect_obfuscation.cpython-314.pyc +0 -0
  10. package/analysis_tools/__pycache__/dex_payload_hunter.cpython-314.pyc +0 -0
  11. package/analysis_tools/__pycache__/entropy_analyzer.cpython-314.pyc +0 -0
  12. package/analysis_tools/__pycache__/error_logger.cpython-313.pyc +0 -0
  13. package/analysis_tools/__pycache__/error_logger.cpython-314.pyc +0 -0
  14. package/analysis_tools/__pycache__/find_encrypted_payload.cpython-314.pyc +0 -0
  15. package/analysis_tools/__pycache__/fix_apk_headers.cpython-313.pyc +0 -0
  16. package/analysis_tools/__pycache__/fix_apk_headers.cpython-314.pyc +0 -0
  17. package/analysis_tools/__pycache__/manifest_analyzer.cpython-313.pyc +0 -0
  18. package/analysis_tools/__pycache__/manifest_analyzer.cpython-314.pyc +0 -0
  19. package/analysis_tools/__pycache__/network_analyzer.cpython-313.pyc +0 -0
  20. package/analysis_tools/__pycache__/network_analyzer.cpython-314.pyc +0 -0
  21. package/analysis_tools/__pycache__/report_generator.cpython-313.pyc +0 -0
  22. package/analysis_tools/__pycache__/report_generator.cpython-314.pyc +0 -0
  23. package/analysis_tools/__pycache__/report_generator_modular.cpython-314.pyc +0 -0
  24. package/analysis_tools/__pycache__/sast_scanner.cpython-313.pyc +0 -0
  25. package/analysis_tools/__pycache__/sast_scanner.cpython-314.pyc +0 -0
  26. package/analysis_tools/__pycache__/so_string_analyzer.cpython-314.pyc +0 -0
  27. package/analysis_tools/__pycache__/yara_enhanced_analyzer.cpython-314.pyc +0 -0
  28. package/analysis_tools/__pycache__/yara_results_processor.cpython-314.pyc +0 -0
  29. package/analysis_tools/apk_basic_info.py +85 -0
  30. package/analysis_tools/check_zip_encryption.py +142 -0
  31. package/analysis_tools/detect_obfuscation.py +650 -0
  32. package/analysis_tools/dex_payload_hunter.py +734 -0
  33. package/analysis_tools/entropy_analyzer.py +335 -0
  34. package/analysis_tools/error_logger.py +75 -0
  35. package/analysis_tools/find_encrypted_payload.py +485 -0
  36. package/analysis_tools/fix_apk_headers.py +154 -0
  37. package/analysis_tools/manifest_analyzer.py +214 -0
  38. package/analysis_tools/network_analyzer.py +287 -0
  39. package/analysis_tools/report_generator.py +506 -0
  40. package/analysis_tools/report_generator_modular.py +885 -0
  41. package/analysis_tools/sast_scanner.py +412 -0
  42. package/analysis_tools/so_string_analyzer.py +406 -0
  43. package/analysis_tools/yara_enhanced_analyzer.py +330 -0
  44. package/analysis_tools/yara_results_processor.py +368 -0
  45. package/analyzer_config.json +113 -0
  46. package/apkid/__init__.py +32 -0
  47. package/apkid/__pycache__/__init__.cpython-313.pyc +0 -0
  48. package/apkid/__pycache__/__init__.cpython-314.pyc +0 -0
  49. package/apkid/__pycache__/apkid.cpython-313.pyc +0 -0
  50. package/apkid/__pycache__/apkid.cpython-314.pyc +0 -0
  51. package/apkid/__pycache__/main.cpython-313.pyc +0 -0
  52. package/apkid/__pycache__/main.cpython-314.pyc +0 -0
  53. package/apkid/__pycache__/output.cpython-313.pyc +0 -0
  54. package/apkid/__pycache__/rules.cpython-313.pyc +0 -0
  55. package/apkid/apkid.py +266 -0
  56. package/apkid/main.py +98 -0
  57. package/apkid/output.py +177 -0
  58. package/apkid/rules/apk/common.yara +68 -0
  59. package/apkid/rules/apk/obfuscators.yara +118 -0
  60. package/apkid/rules/apk/packers.yara +1197 -0
  61. package/apkid/rules/apk/protectors.yara +301 -0
  62. package/apkid/rules/dex/abnormal.yara +104 -0
  63. package/apkid/rules/dex/anti-vm.yara +568 -0
  64. package/apkid/rules/dex/common.yara +60 -0
  65. package/apkid/rules/dex/compilers.yara +434 -0
  66. package/apkid/rules/dex/obfuscators.yara +602 -0
  67. package/apkid/rules/dex/packers.yara +761 -0
  68. package/apkid/rules/dex/protectors.yara +520 -0
  69. package/apkid/rules/dll/common.yara +38 -0
  70. package/apkid/rules/dll/obfuscators.yara +43 -0
  71. package/apkid/rules/elf/anti-vm.yara +43 -0
  72. package/apkid/rules/elf/common.yara +54 -0
  73. package/apkid/rules/elf/obfuscators.yara +991 -0
  74. package/apkid/rules/elf/packers.yara +1128 -0
  75. package/apkid/rules/elf/protectors.yara +794 -0
  76. package/apkid/rules/res/common.yara +43 -0
  77. package/apkid/rules/res/obfuscators.yara +46 -0
  78. package/apkid/rules/res/protectors.yara +46 -0
  79. package/apkid/rules.py +77 -0
  80. package/bin/anais +3 -0
  81. package/dist/cli.js +82 -0
  82. package/dist/index.js +123 -0
  83. package/dist/types/index.js +2 -0
  84. package/dist/utils/index.js +21 -0
  85. package/dist/utils/output.js +44 -0
  86. package/dist/utils/paths.js +107 -0
  87. package/docs/ARCHITECTURE.txt +353 -0
  88. package/docs/Workflow and Reference.md +445 -0
  89. package/package.json +70 -0
  90. package/rules/yara_general_rules.yar +323 -0
  91. package/scripts/dynamic_analysis_helper.sh +334 -0
  92. package/scripts/frida/dpt_dex_dumper.js +145 -0
  93. package/scripts/frida/frida_dex_dump.js +145 -0
  94. package/scripts/frida/frida_hooks.js +437 -0
  95. package/scripts/frida/frida_websocket_extractor.js +154 -0
  96. package/scripts/setup.sh +206 -0
  97. package/scripts/validate_framework.sh +224 -0
  98. package/src/cli.ts +91 -0
  99. package/src/index.ts +123 -0
  100. package/src/types/index.ts +44 -0
  101. package/src/utils/index.ts +6 -0
  102. package/src/utils/output.ts +50 -0
  103. package/src/utils/paths.ts +72 -0
  104. package/tsconfig.json +14 -0
@@ -0,0 +1,885 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Modular Report Generator
4
+ Generates split reports with main index and separate detail files
5
+ """
6
+
7
+ import sys
8
+ import json
9
+ import argparse
10
+ from pathlib import Path
11
+ from datetime import datetime
12
+
13
+ class ModularReportGenerator:
14
+ def __init__(self, workspace, temp_dir, apk_name, sha256):
15
+ self.workspace = Path(workspace)
16
+ self.temp_dir = Path(temp_dir)
17
+ self.apk_name = apk_name
18
+ self.sha256 = sha256
19
+ self.timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
20
+ self.report_dir = self.workspace / 'reports'
21
+ self.report_dir.mkdir(exist_ok=True)
22
+
23
+ # Load all analysis results
24
+ self.load_results()
25
+
26
+ def load_results(self):
27
+ """Load all JSON results from temp directory"""
28
+ self.results = {}
29
+
30
+ result_files = {
31
+ 'basic_info': 'basic_info.json',
32
+ 'encryption_check': 'encryption_check.json',
33
+ 'obfuscation': 'obfuscation_report.json',
34
+ 'dex_payload': 'dex_payload_report.json',
35
+ 'so_strings': 'so_strings.json',
36
+ 'entropy': 'entropy_analysis.json',
37
+ 'yara': 'yara_enhanced.json',
38
+ 'sast': 'sast_findings.json',
39
+ 'manifest': 'manifest_findings.json',
40
+ 'network': 'network_findings.json',
41
+ }
42
+
43
+ for key, filename in result_files.items():
44
+ filepath = self.temp_dir / filename
45
+ if filepath.exists():
46
+ try:
47
+ with open(filepath, 'r') as f:
48
+ self.results[key] = json.load(f)
49
+ except Exception as e:
50
+ self.results[key] = {'error': str(e)}
51
+ else:
52
+ self.results[key] = {}
53
+
54
+ def generate_main_report(self, output_file):
55
+ """Generate main index report with links to detailed reports"""
56
+
57
+ md = []
58
+
59
+ # Header
60
+ md.append("# Android Malware Analysis Report\n")
61
+ md.append(f"**Generated:** {self.timestamp}\n")
62
+ md.append(f"**APK:** `{self.apk_name}`\n")
63
+ md.append(f"**SHA256:** `{self.sha256}`\n")
64
+ md.append("\n---\n")
65
+
66
+ # Quick Navigation
67
+ md.append("\n## 📑 Quick Navigation\n")
68
+ md.append("- [Executive Summary](#executive-summary)")
69
+ md.append("- [APK Information](#apk-information)")
70
+ md.append("- [Protection & Obfuscation](#protection-obfuscation)")
71
+ md.append("- 🔗 [Security Findings (SAST)](./sast_findings.md)")
72
+ md.append("- 🔗 [AndroidManifest Analysis](./manifest_analysis.md)")
73
+ md.append("- 🔗 [Network Analysis](./network_analysis.md)")
74
+ md.append("- 🔗 [Entropy Analysis](./entropy_analysis.md) - **Encrypted Payload Detection**")
75
+ md.append("- 🔗 [YARA Scan Results](./yara_results.md)")
76
+ md.append("- 🔗 [Detailed Recommendations](./recommendations.md)")
77
+ md.append("")
78
+
79
+ # Executive Summary
80
+ md.append("\n## 📊 Executive Summary {#executive-summary}\n")
81
+ self._add_executive_summary(md)
82
+
83
+ # APK Information
84
+ md.append("\n## 📱 APK Information {#apk-information}\n")
85
+ self._add_apk_info(md)
86
+
87
+ # Protection & Obfuscation
88
+ md.append("\n## 🔒 Protection & Obfuscation Analysis {#protection-obfuscation}\n")
89
+ self._add_obfuscation_info(md)
90
+
91
+ # Summary sections with links to details
92
+ md.append("\n## 📊 Analysis Results Summary\n")
93
+ md.append(self._get_findings_summary())
94
+
95
+ md.append("\n### For Detailed Analysis:\n")
96
+ md.append("- 📄 **[Security Findings (SAST)](./sast_findings.md)** - Complete vulnerability analysis")
97
+ md.append("- 📄 **[Manifest Analysis](./manifest_analysis.md)** - Permission and component security")
98
+ md.append("- 📄 **[Network Analysis](./network_analysis.md)** - Network artifacts and C2 indicators")
99
+ md.append("- 📄 **[Entropy Analysis](./entropy_analysis.md)** - Encrypted payload detection & visualization")
100
+ md.append("- 📄 **[YARA Results](./yara_results.md)** - Malware pattern matching with detailed categorization")
101
+ md.append("- 📄 **[Recommendations](./recommendations.md)** - Actionable security guidance")
102
+ md.append("")
103
+
104
+ # Write main report
105
+ with open(output_file, 'w') as f:
106
+ f.write('\n'.join(md))
107
+
108
+ # Generate detailed reports
109
+ self._generate_sast_report()
110
+ self._generate_manifest_report()
111
+ self._generate_network_report()
112
+ self._generate_entropy_report()
113
+ self._generate_yara_report()
114
+ self._generate_recommendations_report()
115
+
116
+ def _get_findings_summary(self):
117
+ """Get summary of findings"""
118
+ lines = []
119
+
120
+ # SAST Summary
121
+ sast = self.results.get('sast', {}).get('summary', {})
122
+ if sast:
123
+ lines.append(f"\n**🔴 Security Findings:** {sast.get('total_findings', 0)} issues found")
124
+ lines.append(f"- Critical: {sast.get('critical', 0)}")
125
+ lines.append(f"- High: {sast.get('high', 0)}")
126
+ lines.append(f"- Medium: {sast.get('medium', 0)}")
127
+ lines.append(f"- Low: {sast.get('low', 0)}")
128
+
129
+ # Manifest Summary
130
+ manifest = self.results.get('manifest', {})
131
+ if manifest:
132
+ dangerous_perms = len(manifest.get('dangerous_permissions', []))
133
+ exported_components = len(manifest.get('exported_components', []))
134
+ lines.append(f"\n**📋 Manifest Issues:**")
135
+ lines.append(f"- Dangerous permissions: {dangerous_perms}")
136
+ lines.append(f"- Exported components: {exported_components}")
137
+
138
+ # Network Summary
139
+ network = self.results.get('network', {})
140
+ if network:
141
+ urls = len(network.get('urls', []))
142
+ suspicious = len(network.get('suspicious_urls', []))
143
+ lines.append(f"\n**🌐 Network Artifacts:**")
144
+ lines.append(f"- URLs found: {urls}")
145
+ lines.append(f"- Suspicious URLs: {suspicious}")
146
+
147
+ # Entropy Summary
148
+ entropy = self.results.get('entropy', {})
149
+ if entropy and 'summary' in entropy:
150
+ summary = entropy['summary']
151
+ lines.append(f"\n**📊 Entropy Analysis:**")
152
+ lines.append(f"- Highly suspicious files: {len(summary.get('highly_suspicious', []))}")
153
+ lines.append(f"- Suspicious files: {len(summary.get('suspicious', []))}")
154
+
155
+ # YARA Summary
156
+ yara = self.results.get('yara', {})
157
+ if yara and 'statistics' in yara:
158
+ stats = yara['statistics']
159
+ lines.append(f"\n**🎯 YARA Matches:**")
160
+ lines.append(f"- Total matches: {stats.get('total_matches', 0)}")
161
+ by_sev = stats.get('by_severity', {})
162
+ if by_sev.get('CRITICAL', 0) > 0:
163
+ lines.append(f"- 🔴 Critical: {by_sev['CRITICAL']}")
164
+ if by_sev.get('HIGH', 0) > 0:
165
+ lines.append(f"- 🟠 High: {by_sev['HIGH']}")
166
+
167
+ return '\n'.join(lines)
168
+
169
+ def _generate_sast_report(self):
170
+ """Generate detailed SAST findings report"""
171
+ output_file = self.report_dir / 'sast_findings.md'
172
+
173
+ md = []
174
+ md.append("# 🚨 Security Findings (SAST)\n")
175
+ md.append(f"**Analysis Date:** {self.timestamp}\n")
176
+ md.append(f"**APK:** {self.apk_name}\n")
177
+ md.append("\n[← Back to Main Report](./report.md)\n")
178
+ md.append("\n---\n")
179
+
180
+ # Add SAST findings
181
+ self._add_sast_findings(md)
182
+
183
+ with open(output_file, 'w') as f:
184
+ f.write('\n'.join(md))
185
+
186
+ def _generate_manifest_report(self):
187
+ """Generate detailed manifest analysis report"""
188
+ output_file = self.report_dir / 'manifest_analysis.md'
189
+
190
+ md = []
191
+ md.append("# 📋 AndroidManifest Analysis\n")
192
+ md.append(f"**Analysis Date:** {self.timestamp}\n")
193
+ md.append(f"**APK:** {self.apk_name}\n")
194
+ md.append("\n[← Back to Main Report](./report.md)\n")
195
+ md.append("\n---\n")
196
+
197
+ # Add manifest findings
198
+ self._add_manifest_findings(md)
199
+
200
+ with open(output_file, 'w') as f:
201
+ f.write('\n'.join(md))
202
+
203
+ def _generate_network_report(self):
204
+ """Generate detailed network analysis report"""
205
+ output_file = self.report_dir / 'network_analysis.md'
206
+
207
+ md = []
208
+ md.append("# 🌐 Network Analysis\n")
209
+ md.append(f"**Analysis Date:** {self.timestamp}\n")
210
+ md.append(f"**APK:** {self.apk_name}\n")
211
+ md.append("\n[← Back to Main Report](./report.md)\n")
212
+ md.append("\n---\n")
213
+
214
+ # Add network findings
215
+ self._add_network_findings(md)
216
+
217
+ with open(output_file, 'w') as f:
218
+ f.write('\n'.join(md))
219
+
220
+ def _generate_entropy_report(self):
221
+ """Generate detailed entropy analysis report"""
222
+ output_file = self.report_dir / 'entropy_analysis.md'
223
+
224
+ md = []
225
+ md.append("# 📊 Entropy Analysis - Encrypted Payload Detection\n")
226
+ md.append(f"**Analysis Date:** {self.timestamp}\n")
227
+ md.append(f"**APK:** {self.apk_name}\n")
228
+ md.append("\n[← Back to Main Report](./report.md)\n")
229
+ md.append("\n---\n")
230
+
231
+ # Add entropy findings
232
+ self._add_entropy_findings(md)
233
+
234
+ with open(output_file, 'w') as f:
235
+ f.write('\n'.join(md))
236
+
237
+ def _generate_yara_report(self):
238
+ """Generate detailed YARA results report"""
239
+ output_file = self.report_dir / 'yara_results.md'
240
+
241
+ md = []
242
+ md.append("# 🎯 YARA Scan Results\n")
243
+ md.append(f"**Analysis Date:** {self.timestamp}\n")
244
+ md.append(f"**APK:** {self.apk_name}\n")
245
+ md.append("\n[← Back to Main Report](./report.md)\n")
246
+ md.append("\n---\n")
247
+
248
+ # Add YARA results
249
+ self._add_yara_results(md)
250
+
251
+ with open(output_file, 'w') as f:
252
+ f.write('\n'.join(md))
253
+
254
+ def _generate_recommendations_report(self):
255
+ """Generate detailed recommendations report"""
256
+ output_file = self.report_dir / 'recommendations.md'
257
+
258
+ md = []
259
+ md.append("# 💡 Security Recommendations\n")
260
+ md.append(f"**Analysis Date:** {self.timestamp}\n")
261
+ md.append(f"**APK:** {self.apk_name}\n")
262
+ md.append("\n[← Back to Main Report](./report.md)\n")
263
+ md.append("\n---\n")
264
+
265
+ # Add recommendations
266
+ self._add_recommendations(md)
267
+
268
+ with open(output_file, 'w') as f:
269
+ f.write('\n'.join(md))
270
+
271
+ def _add_executive_summary(self, md):
272
+ """Add executive summary"""
273
+
274
+ # Calculate risk level
275
+ risk_score = self.results.get('sast', {}).get('summary', {}).get('risk_score', 0)
276
+
277
+ if risk_score >= 80:
278
+ risk_level = "🔴 **CRITICAL**"
279
+ elif risk_score >= 60:
280
+ risk_level = "🟠 **HIGH**"
281
+ elif risk_score >= 40:
282
+ risk_level = "🟡 **MEDIUM**"
283
+ elif risk_score >= 20:
284
+ risk_level = "🟢 **LOW**"
285
+ else:
286
+ risk_level = "⚪ **MINIMAL**"
287
+
288
+ md.append(f"**Overall Risk Level:** {risk_level} (Score: {risk_score}/100)\n")
289
+
290
+ obf_type = self.results.get('obfuscation', {}).get('type', 'none')
291
+ md.append(f"- **Obfuscation Type:** `{obf_type.upper()}`")
292
+
293
+ total_findings = self.results.get('sast', {}).get('summary', {}).get('total_findings', 0)
294
+ critical_findings = self.results.get('sast', {}).get('summary', {}).get('critical', 0)
295
+ md.append(f"- **Total Security Findings:** {total_findings}")
296
+ md.append(f"- **Critical Issues:** {critical_findings}")
297
+ md.append("")
298
+
299
+ def _add_apk_info(self, md):
300
+ """Add APK information"""
301
+ basic_info = self.results.get('basic_info', {})
302
+
303
+ md.append("| Property | Value |")
304
+ md.append("|----------|-------|")
305
+ md.append(f"| Package Name | `{basic_info.get('package_name', 'N/A')}` |")
306
+ md.append(f"| App Name | {basic_info.get('app_name', 'N/A')} |")
307
+ md.append(f"| Version | {basic_info.get('version_name', 'N/A')} ({basic_info.get('version_code', 'N/A')}) |")
308
+ md.append(f"| Min SDK | {basic_info.get('min_sdk', 'N/A')} |")
309
+ md.append(f"| Target SDK | {basic_info.get('target_sdk', 'N/A')} |")
310
+ md.append(f"| Signed | {basic_info.get('is_signed', False)} |")
311
+ md.append(f"| Permissions | {len(basic_info.get('permissions', []))} |")
312
+ md.append(f"| Activities | {len(basic_info.get('activities', []))} |")
313
+ md.append(f"| Services | {len(basic_info.get('services', []))} |")
314
+ md.append(f"| Receivers | {len(basic_info.get('receivers', []))} |")
315
+ md.append("")
316
+
317
+ def _add_obfuscation_info(self, md):
318
+ """Add obfuscation detection info"""
319
+ obf = self.results.get('obfuscation', {})
320
+ dex_payload = self.results.get('dex_payload', {})
321
+ so_strings = self.results.get('so_strings', [])
322
+
323
+ obf_type = obf.get('type', 'none')
324
+ confidence = obf.get('confidence', 0)
325
+
326
+ md.append(f"**Type:** `{obf_type.upper()}`")
327
+ md.append(f"**Confidence:** {confidence}%\n")
328
+
329
+ # DEX Payload Analysis
330
+ if dex_payload:
331
+ stub_detected = dex_payload.get('stub_dex_detected', False)
332
+ encrypted_payloads = dex_payload.get('encrypted_payloads', [])
333
+
334
+ if stub_detected:
335
+ md.append("### 🚨 Stub DEX Detected\n")
336
+ md.append("⚠️ **Real DEX is NOT in the APK** - it will be unpacked at runtime\n")
337
+
338
+ for indicator in dex_payload.get('protection_indicators', []):
339
+ md.append(f"**{indicator.get('file')}:** {indicator.get('type')}")
340
+ for ind in indicator.get('indicators', []):
341
+ md.append(f"- {ind}")
342
+ md.append("")
343
+
344
+ # Protection Library String Analysis
345
+ if so_strings and len(so_strings) > 0:
346
+ md.append("### 🔍 Protection Library Analysis\n")
347
+ md.append("**Native libraries analyzed for protection artifacts:**\n")
348
+
349
+ for lib_result in so_strings:
350
+ lib_file = lib_result.get('file', 'Unknown')
351
+ stats = lib_result.get('stats', {})
352
+ categories = lib_result.get('categories', {})
353
+
354
+ md.append(f"\n#### 📦 `{lib_file}`\n")
355
+ md.append(f"- **Size:** {stats.get('file_size_kb', 0):.2f} KB")
356
+ md.append(f"- **Strings Extracted:** {stats.get('total_strings', 0)}")
357
+ md.append(f"- **Categories Found:** {stats.get('categories_found', 0)}\n")
358
+
359
+ # Show critical findings
360
+ critical_cats = {
361
+ 'dex_files': '🎯 DEX Files',
362
+ 'zip_files': '🔒 ZIP Files (Encrypted Containers)',
363
+ 'code_cache_paths': '📁 Code Cache Paths',
364
+ 'encryption_keywords': '🔐 Encryption Keywords',
365
+ 'class_loaders': '🔧 ClassLoaders',
366
+ 'shell_keywords': '🐚 Shell/Protection Keywords'
367
+ }
368
+
369
+ for cat_key, cat_name in critical_cats.items():
370
+ if cat_key in categories:
371
+ items = categories[cat_key]
372
+ md.append(f"**{cat_name}:** {len(items)} found")
373
+ for item in items[:5]: # Show first 5
374
+ md.append(f" - `{item}`")
375
+ if len(items) > 5:
376
+ md.append(f" - ... and {len(items) - 5} more")
377
+ md.append("")
378
+ md.append("")
379
+
380
+ if encrypted_payloads:
381
+ md.append(f"### 🔒 Encrypted Payloads Found: {len(encrypted_payloads)}\n")
382
+ for payload in encrypted_payloads[:5]: # Show first 5
383
+ md.append(f"**{payload['location']}**")
384
+ md.append(f"- Type: {payload['type']}")
385
+ md.append(f"- Size: {payload['size_kb']:.1f} KB")
386
+ md.append(f"- Entropy: {payload['entropy']}")
387
+ for ind in payload.get('indicators', []):
388
+ md.append(f"- {ind}")
389
+ md.append("")
390
+
391
+ if len(encrypted_payloads) > 5:
392
+ md.append(f"*...and {len(encrypted_payloads) - 5} more encrypted payloads*\n")
393
+
394
+ # Likely unpack locations
395
+ unpack_locs = dex_payload.get('likely_unpack_locations', [])
396
+ if unpack_locs and (stub_detected or encrypted_payloads):
397
+ md.append("### 📍 Predicted Runtime Unpack Locations\n")
398
+ md.append("Monitor these paths during dynamic analysis:\n")
399
+ for loc in unpack_locs[:5]:
400
+ if loc['priority'] == 'HIGH':
401
+ md.append(f"- 🔴 `{loc['path']}`")
402
+ md.append(f" {loc['description']}")
403
+ md.append("")
404
+
405
+ indicators = obf.get('indicators', [])
406
+ if indicators:
407
+ md.append("### Detection Indicators\n")
408
+ for ind in indicators[:10]: # Show first 10
409
+ md.append(f"- {ind}")
410
+ if len(indicators) > 10:
411
+ md.append(f"\n*...and {len(indicators) - 10} more indicators*")
412
+ md.append("")
413
+
414
+ # APKiD results if available
415
+ apkid = obf.get('apkid_results', {})
416
+ if apkid:
417
+ md.append("### APKiD Enhanced Detection\n")
418
+ md.append("✅ APKiD analysis completed\n")
419
+
420
+ md.append("### Analysis Recommendations\n")
421
+
422
+ # DEX payload recommendations
423
+ if dex_payload:
424
+ for rec in dex_payload.get('recommendations', [])[:3]:
425
+ priority_icon = '🔴' if rec.get('priority') == 'HIGH' else '🟡'
426
+ md.append(f"{priority_icon} **{rec.get('action')}**")
427
+ md.append(f" {rec.get('details')} ")
428
+ md.append("")
429
+
430
+ # Obfuscation recommendations
431
+ recs = obf.get('recommendations', [])
432
+ for rec in recs:
433
+ md.append(f"{rec} ")
434
+ md.append("")
435
+
436
+ def _add_sast_findings(self, md):
437
+ """Add SAST findings (full details)"""
438
+ sast = self.results.get('sast', {})
439
+ summary = sast.get('summary', {})
440
+
441
+ md.append("### Summary\n")
442
+ md.append("| Severity | Count |")
443
+ md.append("|----------|-------|")
444
+ total_findings = 0
445
+ for severity in ['critical', 'high', 'medium', 'low']:
446
+ count = summary.get('by_severity', {}).get(severity, 0)
447
+ total_findings += count
448
+ emoji = {'critical': '🔴', 'high': '🟠', 'medium': '🟡', 'low': '🔵'}
449
+ md.append(f"| {emoji[severity]} {severity.title()} | {count} |")
450
+ md.append("")
451
+
452
+ md.append(f"**Total Findings:** {total_findings}\n")
453
+
454
+ if total_findings == 0:
455
+ md.append("✅ No security issues found\n")
456
+ return
457
+
458
+ # Group findings by severity - read directly from arrays
459
+ for severity in ['critical', 'high', 'medium', 'low']:
460
+ findings = sast.get(severity, [])
461
+ if not findings:
462
+ continue
463
+
464
+ icons = {'critical': '🔴', 'high': '🟠', 'medium': '🟡', 'low': '🔵'}
465
+ md.append(f"\n## {icons[severity]} {severity.upper()} Severity Findings\n")
466
+
467
+ # Group by category
468
+ by_category = {}
469
+ for finding in findings:
470
+ cat = finding.get('category', 'other')
471
+ if cat not in by_category:
472
+ by_category[cat] = []
473
+ by_category[cat].append(finding)
474
+
475
+ for category, cat_findings in by_category.items():
476
+ md.append(f"\n### {category.replace('_', ' ').title()} ({len(cat_findings)} issues)\n")
477
+
478
+ for i, finding in enumerate(cat_findings, 1):
479
+ md.append(f"**{i}. {finding.get('title', 'Security Issue')}**")
480
+ md.append(f"- **Description:** {finding.get('description', 'N/A')}")
481
+ md.append(f"- **File:** `{finding.get('file', 'N/A')}`")
482
+ if finding.get('line'):
483
+ md.append(f"- **Line:** {finding['line']}")
484
+ md.append("")
485
+
486
+ code = finding.get('code', '')
487
+ if code:
488
+ md.append("```java")
489
+ md.append(code)
490
+ md.append("```\n")
491
+
492
+ def _add_manifest_findings(self, md):
493
+ """Add manifest analysis findings"""
494
+ manifest = self.results.get('manifest', {})
495
+ findings = manifest.get('findings', [])
496
+
497
+ if not findings:
498
+ md.append("✅ No significant issues found in AndroidManifest.xml\n")
499
+ return
500
+
501
+ md.append(f"**Total Issues:** {len(findings)}\n")
502
+
503
+ # Group by severity
504
+ by_severity = {'critical': [], 'high': [], 'medium': [], 'low': []}
505
+ for finding in findings:
506
+ sev = finding.get('severity', 'low')
507
+ by_severity[sev].append(finding)
508
+
509
+ for severity in ['critical', 'high', 'medium']:
510
+ if not by_severity[severity]:
511
+ continue
512
+
513
+ emoji = {'critical': '🔴', 'high': '🟠', 'medium': '🟡'}
514
+ md.append(f"\n## {emoji[severity]} {severity.title()} Issues ({len(by_severity[severity])})\n")
515
+
516
+ for i, finding in enumerate(by_severity[severity], 1):
517
+ md.append(f"**{i}. {finding.get('title', 'Unknown')}**")
518
+ md.append(f"- **Description:** {finding.get('description', 'N/A')}")
519
+ if finding.get('permission'):
520
+ md.append(f"- **Permission:** `{finding['permission']}`")
521
+ if finding.get('component'):
522
+ md.append(f"- **Component:** `{finding['component']}`")
523
+ md.append("")
524
+
525
+ def _add_network_findings(self, md):
526
+ """Add network analysis findings"""
527
+ network = self.results.get('network', {})
528
+
529
+ urls = network.get('urls', [])
530
+ domains = network.get('domains', [])
531
+ ips = network.get('ips', [])
532
+ suspicious_urls = network.get('suspicious_urls', [])
533
+
534
+ if suspicious_urls:
535
+ md.append("### 🚨 Suspicious URLs\n")
536
+ for url in suspicious_urls: # Show all
537
+ md.append(f"- `{url}`")
538
+ md.append("")
539
+
540
+ if urls:
541
+ md.append(f"### URLs Found ({len(urls)})\n")
542
+ for url in urls: # Show all
543
+ md.append(f"- `{url}`")
544
+ md.append("")
545
+
546
+ if domains:
547
+ md.append(f"### Unique Domains ({len(domains)})\n")
548
+ for domain in list(domains): # Show all
549
+ md.append(f"- `{domain}`")
550
+ md.append("")
551
+
552
+ if ips:
553
+ md.append(f"### IP Addresses ({len(ips)})\n")
554
+ for ip in ips:
555
+ md.append(f"- `{ip}`")
556
+ md.append("")
557
+
558
+ def _add_entropy_findings(self, md):
559
+ """Add entropy analysis findings"""
560
+ entropy = self.results.get('entropy', {})
561
+
562
+ if not entropy or 'files' not in entropy:
563
+ md.append("⚠️ Entropy analysis data not available\n")
564
+ return
565
+
566
+ summary = entropy.get('summary', {})
567
+ files = entropy.get('files', [])
568
+
569
+ md.append("## Overview\n")
570
+ md.append("Entropy analysis identifies encrypted or heavily obfuscated regions in APK files.")
571
+ md.append("High entropy (>7.0) typically indicates encryption, while low entropy (<5.0) suggests plaintext.\n")
572
+ md.append(f"**Files Analyzed:** {summary.get('total_files', len(files))}\n")
573
+
574
+ # Highly suspicious files
575
+ highly_suspicious = summary.get('highly_suspicious', [])
576
+ if highly_suspicious:
577
+ md.append(f"\n## 🔴 Highly Suspicious Files ({len(highly_suspicious)})\n")
578
+ md.append("These files show characteristics of strong encryption - likely encrypted payloads.\n")
579
+
580
+ for file_entry in highly_suspicious:
581
+ filename = file_entry['filename']
582
+ entropy_val = file_entry['entropy']
583
+ ratio = file_entry['high_entropy_ratio']
584
+
585
+ md.append(f"\n### `{filename}`\n")
586
+ md.append(f"- **Overall Entropy:** {entropy_val:.2f}/8.0")
587
+ md.append(f"- **High Entropy Ratio:** {ratio:.1%}")
588
+ md.append(f"- **Assessment:** Likely encrypted payload - dynamic analysis required\n")
589
+
590
+ # Find and display chart if available
591
+ file_data = next((f for f in files if f.get('filename') == filename), None)
592
+ if file_data and 'chart' in file_data:
593
+ md.append("**Entropy Distribution:**\n")
594
+ md.append("```")
595
+ md.append(file_data['chart'])
596
+ md.append("```\n")
597
+
598
+ if file_data and 'assessment' in file_data:
599
+ md.append(f"**Recommendation:** {file_data['assessment'].get('recommendation', 'N/A')}\n")
600
+
601
+ # Suspicious files
602
+ suspicious = summary.get('suspicious', [])
603
+ if suspicious:
604
+ md.append(f"\n## 🟡 Suspicious Files ({len(suspicious)})\n")
605
+ md.append("These files contain elevated entropy regions - may contain compressed or partially encrypted data.\n")
606
+
607
+ for file_entry in suspicious:
608
+ filename = file_entry['filename']
609
+ entropy_val = file_entry['entropy']
610
+ ratio = file_entry['high_entropy_ratio']
611
+
612
+ md.append(f"\n### `{filename}`\n")
613
+ md.append(f"- **Overall Entropy:** {entropy_val:.2f}/8.0")
614
+ md.append(f"- **High Entropy Ratio:** {ratio:.1%}\n")
615
+
616
+ # Normal files
617
+ normal = summary.get('normal', [])
618
+ if normal:
619
+ md.append(f"\n## 🟢 Normal Files ({len(normal)})\n")
620
+ md.append("These files show low entropy - mostly plaintext or uncompressed code.\n")
621
+
622
+ md.append("\n## Interpretation Guide\n")
623
+ md.append("| Entropy Range | Interpretation |")
624
+ md.append("|--------------|----------------|")
625
+ md.append("| 7.5 - 8.0 | Heavily encrypted - likely encrypted payload |")
626
+ md.append("| 7.0 - 7.5 | Strong encryption/compression detected |")
627
+ md.append("| 6.0 - 7.0 | Moderate obfuscation or compression |")
628
+ md.append("| 5.0 - 6.0 | Some obfuscation, mostly normal code |")
629
+ md.append("| 0.0 - 5.0 | Low entropy - plaintext or uncompressed |")
630
+ md.append("")
631
+
632
+ def _add_yara_results(self, md):
633
+ """Add YARA scan results with detailed rule information"""
634
+ yara_data = self.results.get('yara', {})
635
+
636
+ # If no enhanced results, try to parse raw YARA files
637
+ if not yara_data or 'summary' not in yara_data:
638
+ md.append("## Overview\n")
639
+ md.append("⚠️ Enhanced YARA analysis not available. Showing raw results:\n")
640
+ self._add_raw_yara_results(md)
641
+ return
642
+
643
+ summary = yara_data.get('summary', {})
644
+ findings = yara_data.get('findings', [])
645
+ rules_checked = yara_data.get('rules_checked', [])
646
+ categories = yara_data.get('categories', {})
647
+ scan_details = yara_data.get('scan_details', {})
648
+
649
+ # Summary section
650
+ md.append("## Overview\n")
651
+ md.append(f"- **Rules Checked:** {summary.get('total_rules_checked', 0)}")
652
+ md.append(f"- **Rules Matched:** {summary.get('rules_matched', 0)}/{summary.get('total_rules_checked', 0)}")
653
+ md.append(f"- **Total Matches:** {summary.get('total_matches', 0)}")
654
+ md.append(f"- **Files Scanned:** {summary.get('files_scanned', 0)}\n")
655
+
656
+ # Scan coverage
657
+ md.append("### Scan Coverage\n")
658
+ md.append("| Scan Type | Files | Matches |")
659
+ md.append("|-----------|-------|---------|")
660
+ for scan_name, scan_info in scan_details.items():
661
+ scan_label = scan_name.replace('_scan', '').replace('_', ' ').title()
662
+ files = len(scan_info.get('files', []))
663
+ matches = scan_info.get('matches', 0)
664
+ md.append(f"| {scan_label} | {files} | {matches} |")
665
+ md.append("")
666
+
667
+ # Severity breakdown
668
+ md.append("### Severity Distribution\n")
669
+ md.append("| Severity | Count |")
670
+ md.append("|----------|-------|")
671
+ critical = summary.get('critical_findings', 0)
672
+ high = summary.get('high_severity', 0)
673
+ medium = summary.get('medium_severity', 0)
674
+ low = summary.get('low_severity', 0)
675
+
676
+ if critical > 0:
677
+ md.append(f"| 🔴 CRITICAL | {critical} |")
678
+ if high > 0:
679
+ md.append(f"| 🟠 HIGH | {high} |")
680
+ if medium > 0:
681
+ md.append(f"| 🟡 MEDIUM | {medium} |")
682
+ if low > 0:
683
+ md.append(f"| 🔵 LOW | {low} |")
684
+ md.append("")
685
+
686
+ # Rules checked table
687
+ if rules_checked:
688
+ md.append("\n## All Rules Checked\n")
689
+ md.append("Complete list of YARA rules that were executed during the scan.\n")
690
+ md.append("| Rule Name | Category | Severity | Status | Matches |")
691
+ md.append("|-----------|----------|----------|--------|---------|")
692
+
693
+ for rule in rules_checked:
694
+ name = rule['name']
695
+ category = rule['category']
696
+ severity = rule['severity']
697
+ matched = "✓ Matched" if rule['matched'] else "✗ No Match"
698
+ match_count = rule['match_count']
699
+
700
+ # Add emoji based on status
701
+ emoji = "🔍"
702
+ if rule['matched']:
703
+ if severity == 'CRITICAL':
704
+ emoji = "🔴"
705
+ elif severity == 'HIGH':
706
+ emoji = "🟠"
707
+ elif severity == 'MEDIUM':
708
+ emoji = "🟡"
709
+ else:
710
+ emoji = "🔵"
711
+
712
+ md.append(f"| {emoji} `{name}` | {category} | {severity} | {matched} | {match_count} |")
713
+ md.append("")
714
+
715
+ # Detailed findings by severity
716
+ if findings:
717
+ md.append("\n## Detailed Findings\n")
718
+
719
+ for severity in ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW']:
720
+ severity_findings = [f for f in findings if f['severity'] == severity]
721
+ if not severity_findings:
722
+ continue
723
+
724
+ emoji = {'CRITICAL': '🔴', 'HIGH': '🟠', 'MEDIUM': '🟡', 'LOW': '🔵'}
725
+ md.append(f"\n### {emoji[severity]} {severity} Severity ({len(severity_findings)} matches)\n")
726
+
727
+ # Group by rule
728
+ from collections import defaultdict
729
+ by_rule = defaultdict(list)
730
+ for finding in severity_findings:
731
+ by_rule[finding['rule_name']].append(finding)
732
+
733
+ for rule_name, rule_findings in by_rule.items():
734
+ md.append(f"\n#### `{rule_name}` ({len(rule_findings)} occurrences)\n")
735
+
736
+ # Rule details
737
+ first = rule_findings[0]
738
+ md.append(f"**Category:** {first['category']}")
739
+ md.append(f"**Description:** {first['description']}")
740
+ md.append(f"**Recommendation:** {first['recommendation']}\n")
741
+
742
+ md.append("**Affected Files:**\n")
743
+ for i, finding in enumerate(rule_findings, 1):
744
+ file_path = finding['file']
745
+ scan_type = finding['scan_type']
746
+
747
+ md.append(f"{i}. `{file_path}` _(from {scan_type} scan)_")
748
+
749
+ # Show matched strings if available
750
+ matched_strings = finding.get('matched_strings', [])
751
+ if matched_strings:
752
+ md.append(f" - **Matched Patterns:** {len(matched_strings)}")
753
+ for j, string_match in enumerate(matched_strings[:3], 1):
754
+ offset = string_match['offset']
755
+ identifier = string_match['identifier']
756
+ content = string_match['content'][:60]
757
+ md.append(f" {j}. `{identifier}` at {offset}: `{content}...`")
758
+ if len(matched_strings) > 3:
759
+ md.append(f" ... and {len(matched_strings) - 3} more")
760
+ md.append("")
761
+
762
+ md.append("---\n")
763
+
764
+ def _add_raw_yara_results(self, md):
765
+ """Fallback method to display raw YARA scan results"""
766
+ yara_files = [
767
+ ('yara_apk_results.txt', 'APK Scan'),
768
+ ('yara_decompiled_results.txt', 'Decompiled Files'),
769
+ ('yara_jadx_results.txt', 'JADX Sources'),
770
+ ]
771
+
772
+ has_results = False
773
+ for filename, title in yara_files:
774
+ filepath = self.temp_dir / filename
775
+ if filepath.exists():
776
+ try:
777
+ content = filepath.read_text(errors='ignore')
778
+ if content.strip():
779
+ has_results = True
780
+ md.append(f"\n### {title}\n")
781
+ md.append("```")
782
+ lines = content.strip().split('\n')
783
+ for line in lines[:100]: # Limit to 100 lines per file
784
+ md.append(line)
785
+ if len(lines) > 100:
786
+ md.append(f"\n... ({len(lines) - 100} more lines)")
787
+ md.append("```\n")
788
+ except Exception as e:
789
+ md.append(f"Error reading {filename}: {e}\n")
790
+
791
+ if not has_results:
792
+ md.append("✅ No YARA matches found\n")
793
+
794
+ def _add_recommendations(self, md):
795
+ """Add security recommendations"""
796
+
797
+ obf = self.results.get('obfuscation', {})
798
+ sast = self.results.get('sast', {})
799
+ entropy = self.results.get('entropy', {})
800
+ yara = self.results.get('yara', {})
801
+
802
+ md.append("### Based on Protection Type\n")
803
+ obf_recs = obf.get('recommendations', [])
804
+ for rec in obf_recs:
805
+ md.append(f"{rec} ")
806
+ md.append("")
807
+
808
+ # Entropy-based recommendations
809
+ if entropy and entropy.get('summary', {}).get('highly_suspicious'):
810
+ md.append("### Based on Entropy Analysis\n")
811
+ md.append("🔴 **High entropy files detected:**")
812
+ md.append("1. These files are likely encrypted and cannot be analyzed statically")
813
+ md.append("2. **REQUIRED:** Dynamic analysis to capture unpacked DEX files")
814
+ md.append("3. Install APK on rooted device/emulator")
815
+ md.append("4. Use Frida scripts to dump DEX from memory at runtime")
816
+ md.append("5. Monitor file system for unpacked payloads\n")
817
+
818
+ md.append("### Based on SAST Findings\n")
819
+ critical_count = sast.get('summary', {}).get('critical', 0)
820
+ high_count = sast.get('summary', {}).get('high', 0)
821
+
822
+ if critical_count > 0:
823
+ md.append("1. **URGENT:** Address all critical severity findings immediately")
824
+ md.append("2. **PRIORITY:** Review and fix high severity issues")
825
+ elif high_count > 0:
826
+ md.append("1. **PRIORITY:** Review and fix high severity issues")
827
+
828
+ md.append("3. **Code Review:** Conduct thorough manual code review")
829
+ md.append("4. **Penetration Testing:** Perform dynamic analysis and penetration testing")
830
+ md.append("")
831
+
832
+ md.append("### General Security Recommendations\n")
833
+ md.append("1. **Dynamic Analysis:** Run the app in a controlled environment with Frida hooks")
834
+ md.append("2. **Network Monitoring:** Capture and analyze network traffic")
835
+ md.append("3. **Behavioral Analysis:** Monitor file system, IPC, and system calls")
836
+ md.append("4. **Memory Dump:** Extract and analyze runtime memory")
837
+ md.append("5. **API Hooking:** Hook sensitive APIs to understand behavior")
838
+ md.append("")
839
+
840
+ def generate_json(self, output_file):
841
+ """Generate JSON report (complete data)"""
842
+
843
+ report_data = {
844
+ 'metadata': {
845
+ 'generated': self.timestamp,
846
+ 'apk_name': self.apk_name,
847
+ 'sha256': self.sha256,
848
+ },
849
+ 'results': self.results
850
+ }
851
+
852
+ with open(output_file, 'w') as f:
853
+ json.dump(report_data, f, indent=2)
854
+
855
+ def main():
856
+ parser = argparse.ArgumentParser(description='Generate modular malware analysis reports')
857
+ parser.add_argument('workspace', help='Analysis workspace directory')
858
+ parser.add_argument('temp_dir', help='Temporary directory with analysis results')
859
+ parser.add_argument('apk_name', help='APK filename')
860
+ parser.add_argument('sha256', help='APK SHA256 hash')
861
+ parser.add_argument('--output-md', default='report.md', help='Main markdown report output')
862
+ parser.add_argument('--output-json', default='report.json', help='JSON report output')
863
+
864
+ args = parser.parse_args()
865
+
866
+ generator = ModularReportGenerator(
867
+ args.workspace,
868
+ args.temp_dir,
869
+ args.apk_name,
870
+ args.sha256
871
+ )
872
+
873
+ # Generate modular markdown reports
874
+ output_md = Path(args.workspace) / 'reports' / args.output_md
875
+ generator.generate_main_report(output_md)
876
+ print(f"✓ Main report: {output_md}")
877
+ print(f"✓ Detailed reports in: {Path(args.workspace) / 'reports'}")
878
+
879
+ # Generate JSON report
880
+ output_json = Path(args.workspace) / 'reports' / args.output_json
881
+ generator.generate_json(output_json)
882
+ print(f"✓ JSON report: {output_json}")
883
+
884
+ if __name__ == '__main__':
885
+ main()