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,412 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Comprehensive SAST Scanner for Android APK
4
+ Detects security vulnerabilities, malicious patterns, and suspicious behaviors
5
+ """
6
+
7
+ import sys
8
+ import os
9
+ import json
10
+ import re
11
+ from pathlib import Path
12
+ from collections import defaultdict
13
+ import argparse
14
+
15
+ class SASTScanner:
16
+ def __init__(self, apk_path, decompiled_dir, jadx_dir):
17
+ self.apk_path = apk_path
18
+ self.decompiled_dir = Path(decompiled_dir) if decompiled_dir and os.path.exists(decompiled_dir) else None
19
+ self.jadx_dir = Path(jadx_dir) if jadx_dir and os.path.exists(jadx_dir) else None
20
+
21
+ self.findings = {
22
+ 'critical': [],
23
+ 'high': [],
24
+ 'medium': [],
25
+ 'low': [],
26
+ 'info': [],
27
+ 'summary': {}
28
+ }
29
+
30
+ self.init_patterns()
31
+
32
+ def init_patterns(self):
33
+ """Initialize security patterns to search for"""
34
+
35
+ # Critical patterns
36
+ self.critical_patterns = {
37
+ 'root_detection_bypass': [
38
+ r'Superuser',
39
+ r'eu\.chainfire\.supersu',
40
+ r'/system/xbin/su',
41
+ r'/system/bin/su',
42
+ r'RootTools',
43
+ ],
44
+ 'crypto_wallet_targeting': [
45
+ r'im\.token\.app',
46
+ r'vip\.mytokenpocket',
47
+ r'io\.metamask',
48
+ r'com\.wallet\.crypto\.trustapp',
49
+ r'com\.tronlinkpro\.wallet',
50
+ r'com\.coinbase\.android',
51
+ r'mnemonic',
52
+ r'seed\s*phrase',
53
+ r'private\s*key',
54
+ r'wallet\s*password',
55
+ ],
56
+ 'c2_communication': [
57
+ r'wss?://[\w\-\.]+',
58
+ r'https?://[\w\-\.]+\.(top|xyz|tk|ml|ga|cf|gq)',
59
+ r'websocket',
60
+ r'socket\.io',
61
+ ],
62
+ 'data_exfiltration': [
63
+ r'contacts\s*cursor',
64
+ r'sms\s*cursor',
65
+ r'call\s*log',
66
+ r'accounts\s*manager',
67
+ r'getInstalledPackages',
68
+ r'TelephonyManager',
69
+ r'getDeviceId',
70
+ r'getLine1Number',
71
+ ],
72
+ 'remote_code_execution': [
73
+ r'DexClassLoader',
74
+ r'PathClassLoader',
75
+ r'loadClass',
76
+ r'\.dex',
77
+ r'Runtime\.exec',
78
+ r'ProcessBuilder',
79
+ ],
80
+ 'accessibility_abuse': [
81
+ r'AccessibilityService',
82
+ r'AccessibilityEvent',
83
+ r'performGlobalAction',
84
+ r'findAccessibilityNodeInfo',
85
+ ],
86
+ }
87
+
88
+ # High severity patterns
89
+ self.high_patterns = {
90
+ 'insecure_crypto': [
91
+ r'DES',
92
+ r'ECB',
93
+ r'MD5',
94
+ r'SHA1(?!SHA256)',
95
+ r'Random\(\)',
96
+ r'Cipher\.getInstance\("AES"\)', # Without mode
97
+ ],
98
+ 'hardcoded_secrets': [
99
+ r'password\s*=\s*["\'][\w]+["\']',
100
+ r'api[_\-]?key\s*=\s*["\'][\w]+["\']',
101
+ r'secret\s*=\s*["\'][\w]+["\']',
102
+ r'token\s*=\s*["\'][\w]{20,}["\']',
103
+ ],
104
+ 'insecure_network': [
105
+ r'setHostnameVerifier.*ALLOW_ALL',
106
+ r'TrustAllCertificates',
107
+ r'http://(?!localhost)',
108
+ r'trustAllHosts',
109
+ r'SSLSocketFactory',
110
+ ],
111
+ 'webview_vulnerabilities': [
112
+ r'setJavaScriptEnabled\(true\)',
113
+ r'addJavascriptInterface',
114
+ r'setAllowFileAccess\(true\)',
115
+ r'setAllowContentAccess\(true\)',
116
+ ],
117
+ 'dynamic_permission_requests': [
118
+ r'requestPermissions',
119
+ r'READ_SMS',
120
+ r'RECEIVE_SMS',
121
+ r'READ_CONTACTS',
122
+ r'READ_CALL_LOG',
123
+ r'SYSTEM_ALERT_WINDOW',
124
+ r'REQUEST_INSTALL_PACKAGES',
125
+ ],
126
+ }
127
+
128
+ # Medium severity patterns
129
+ self.medium_patterns = {
130
+ 'logging_sensitive_data': [
131
+ r'Log\.[dviwe]\(',
132
+ r'printStackTrace',
133
+ r'System\.out\.print',
134
+ ],
135
+ 'insecure_storage': [
136
+ r'MODE_WORLD_READABLE',
137
+ r'MODE_WORLD_WRITEABLE',
138
+ r'openFileOutput',
139
+ r'getSharedPreferences',
140
+ ],
141
+ 'intent_vulnerabilities': [
142
+ r'sendBroadcast',
143
+ r'startActivity',
144
+ r'startService',
145
+ r'getIntent\(\)',
146
+ r'PendingIntent',
147
+ ],
148
+ }
149
+
150
+ def add_finding(self, severity, category, title, description, file_path, line_number=None, code_snippet=None):
151
+ """Add a security finding"""
152
+ finding = {
153
+ 'category': category,
154
+ 'title': title,
155
+ 'description': description,
156
+ 'file': str(file_path),
157
+ 'severity': severity
158
+ }
159
+
160
+ if line_number:
161
+ finding['line'] = line_number
162
+
163
+ if code_snippet:
164
+ finding['code'] = code_snippet
165
+
166
+ self.findings[severity].append(finding)
167
+
168
+ def scan_smali_files(self):
169
+ """Scan SMALI files for security issues"""
170
+ if not self.decompiled_dir:
171
+ return
172
+
173
+ print("Scanning SMALI files...")
174
+
175
+ # Find all smali directories
176
+ smali_dirs = [d for d in self.decompiled_dir.iterdir() if d.is_dir() and d.name.startswith('smali')]
177
+
178
+ for smali_dir in smali_dirs:
179
+ smali_files = list(smali_dir.rglob('*.smali'))[:500] # Limit to avoid too long processing
180
+
181
+ for smali_file in smali_files:
182
+ try:
183
+ content = smali_file.read_text(errors='ignore')
184
+
185
+ # Check critical patterns
186
+ for category, patterns in self.critical_patterns.items():
187
+ for pattern in patterns:
188
+ matches = list(re.finditer(pattern, content, re.IGNORECASE))
189
+ if matches:
190
+ for match in matches[:3]: # Limit matches per file
191
+ line_num = content[:match.start()].count('\n') + 1
192
+ snippet = self.extract_snippet(content, match.start(), match.end())
193
+
194
+ self.add_finding(
195
+ 'critical',
196
+ category,
197
+ f'{category.replace("_", " ").title()} Detected',
198
+ f'Suspicious pattern found: {pattern}',
199
+ smali_file.relative_to(self.decompiled_dir),
200
+ line_num,
201
+ snippet
202
+ )
203
+
204
+ except Exception as e:
205
+ continue
206
+
207
+ def scan_java_files(self):
208
+ """Scan Java source files for security issues"""
209
+ if not self.jadx_dir or not self.jadx_dir.exists():
210
+ return
211
+
212
+ print("Scanning Java source files...")
213
+
214
+ java_files = list(self.jadx_dir.rglob('*.java'))[:500]
215
+
216
+ for java_file in java_files:
217
+ try:
218
+ content = java_file.read_text(errors='ignore')
219
+ lines = content.split('\n')
220
+
221
+ # Scan for all pattern categories
222
+ all_patterns = [
223
+ ('critical', self.critical_patterns),
224
+ ('high', self.high_patterns),
225
+ ('medium', self.medium_patterns)
226
+ ]
227
+
228
+ for severity, pattern_dict in all_patterns:
229
+ for category, patterns in pattern_dict.items():
230
+ for pattern in patterns:
231
+ matches = list(re.finditer(pattern, content, re.IGNORECASE))
232
+
233
+ if matches:
234
+ for match in matches[:5]: # Limit per file
235
+ line_num = content[:match.start()].count('\n') + 1
236
+ snippet = self.extract_snippet(content, match.start(), match.end(), context_lines=3)
237
+
238
+ self.add_finding(
239
+ severity,
240
+ category,
241
+ f'{category.replace("_", " ").title()}',
242
+ f'Pattern detected: {match.group(0)[:100]}',
243
+ java_file.relative_to(self.jadx_dir),
244
+ line_num,
245
+ snippet
246
+ )
247
+
248
+ except Exception as e:
249
+ continue
250
+
251
+ def scan_native_libraries(self):
252
+ """Scan native libraries for suspicious strings"""
253
+ if not self.decompiled_dir:
254
+ return
255
+
256
+ print("Scanning native libraries...")
257
+
258
+ lib_dir = self.decompiled_dir / 'lib'
259
+ if not lib_dir.exists():
260
+ return
261
+
262
+ so_files = list(lib_dir.rglob('*.so'))
263
+
264
+ suspicious_strings = [
265
+ 'curl',
266
+ 'wget',
267
+ 'http',
268
+ 'socket',
269
+ 'exec',
270
+ 'system',
271
+ 'popen',
272
+ 'decrypt',
273
+ 'encrypt',
274
+ ]
275
+
276
+ for so_file in so_files:
277
+ try:
278
+ # Read as binary and search for strings
279
+ with open(so_file, 'rb') as f:
280
+ content = f.read()
281
+
282
+ for susp_str in suspicious_strings:
283
+ if susp_str.encode() in content:
284
+ self.add_finding(
285
+ 'medium',
286
+ 'native_code',
287
+ 'Suspicious String in Native Library',
288
+ f'Found: {susp_str}',
289
+ so_file.relative_to(self.decompiled_dir),
290
+ code_snippet=f'String: {susp_str}'
291
+ )
292
+
293
+ except Exception:
294
+ continue
295
+
296
+ def scan_assets(self):
297
+ """Scan assets directory for suspicious files"""
298
+ if not self.decompiled_dir:
299
+ return
300
+
301
+ print("Scanning assets...")
302
+
303
+ assets_dir = self.decompiled_dir / 'assets'
304
+ if not assets_dir.exists():
305
+ return
306
+
307
+ # Look for suspicious files
308
+ suspicious_extensions = ['.dex', '.so', '.apk', '.jar', '.zip']
309
+
310
+ for asset_file in assets_dir.rglob('*'):
311
+ if asset_file.is_file():
312
+ # Check extension
313
+ if any(asset_file.suffix == ext for ext in suspicious_extensions):
314
+ self.add_finding(
315
+ 'high',
316
+ 'suspicious_asset',
317
+ 'Suspicious File in Assets',
318
+ f'Executable or archive file found in assets: {asset_file.suffix}',
319
+ asset_file.relative_to(self.decompiled_dir)
320
+ )
321
+
322
+ # Check for encoded/encrypted data
323
+ if asset_file.suffix in ['.bin', '.dat', '.enc']:
324
+ self.add_finding(
325
+ 'medium',
326
+ 'suspicious_asset',
327
+ 'Potential Encoded Data',
328
+ 'Binary or encoded file in assets',
329
+ asset_file.relative_to(self.decompiled_dir)
330
+ )
331
+
332
+ def extract_snippet(self, content, start, end, context_lines=2):
333
+ """Extract code snippet with context"""
334
+ lines = content.split('\n')
335
+ start_line = content[:start].count('\n')
336
+ end_line = content[:end].count('\n')
337
+
338
+ snippet_start = max(0, start_line - context_lines)
339
+ snippet_end = min(len(lines), end_line + context_lines + 1)
340
+
341
+ snippet_lines = lines[snippet_start:snippet_end]
342
+ return '\n'.join(snippet_lines)
343
+
344
+ def generate_summary(self):
345
+ """Generate summary statistics"""
346
+ self.findings['summary'] = {
347
+ 'total_findings': sum(len(self.findings[sev]) for sev in ['critical', 'high', 'medium', 'low', 'info']),
348
+ 'by_severity': {
349
+ 'critical': len(self.findings['critical']),
350
+ 'high': len(self.findings['high']),
351
+ 'medium': len(self.findings['medium']),
352
+ 'low': len(self.findings['low']),
353
+ 'info': len(self.findings['info']),
354
+ },
355
+ 'risk_score': self.calculate_risk_score()
356
+ }
357
+
358
+ def calculate_risk_score(self):
359
+ """Calculate overall risk score (0-100)"""
360
+ weights = {
361
+ 'critical': 10,
362
+ 'high': 5,
363
+ 'medium': 2,
364
+ 'low': 1,
365
+ 'info': 0
366
+ }
367
+
368
+ score = sum(
369
+ len(self.findings[sev]) * weight
370
+ for sev, weight in weights.items()
371
+ )
372
+
373
+ # Normalize to 0-100
374
+ return min(100, score)
375
+
376
+ def run(self):
377
+ """Run all scans"""
378
+ print("Starting SAST Analysis...")
379
+
380
+ self.scan_smali_files()
381
+ self.scan_java_files()
382
+ self.scan_native_libraries()
383
+ self.scan_assets()
384
+
385
+ self.generate_summary()
386
+
387
+ print(f"SAST Analysis Complete!")
388
+ print(f"Total Findings: {self.findings['summary']['total_findings']}")
389
+ print(f"Risk Score: {self.findings['summary']['risk_score']}/100")
390
+
391
+ return self.findings
392
+
393
+ def main():
394
+ parser = argparse.ArgumentParser(description='SAST Scanner for Android APK')
395
+ parser.add_argument('--apk', required=True, help='Path to APK file')
396
+ parser.add_argument('--decompiled', help='Path to decompiled directory')
397
+ parser.add_argument('--jadx', help='Path to JADX output directory')
398
+ parser.add_argument('--output', required=True, help='Output JSON file')
399
+
400
+ args = parser.parse_args()
401
+
402
+ scanner = SASTScanner(args.apk, args.decompiled, args.jadx)
403
+ findings = scanner.run()
404
+
405
+ # Save results
406
+ with open(args.output, 'w') as f:
407
+ json.dump(findings, f, indent=2)
408
+
409
+ print(f"\nResults saved to: {args.output}")
410
+
411
+ if __name__ == '__main__':
412
+ main()