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.
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/anais.sh +669 -0
- package/analysis_tools/__pycache__/apk_basic_info.cpython-313.pyc +0 -0
- package/analysis_tools/__pycache__/apk_basic_info.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/check_zip_encryption.cpython-313.pyc +0 -0
- package/analysis_tools/__pycache__/check_zip_encryption.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/detect_obfuscation.cpython-313.pyc +0 -0
- package/analysis_tools/__pycache__/detect_obfuscation.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/dex_payload_hunter.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/entropy_analyzer.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/error_logger.cpython-313.pyc +0 -0
- package/analysis_tools/__pycache__/error_logger.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/find_encrypted_payload.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/fix_apk_headers.cpython-313.pyc +0 -0
- package/analysis_tools/__pycache__/fix_apk_headers.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/manifest_analyzer.cpython-313.pyc +0 -0
- package/analysis_tools/__pycache__/manifest_analyzer.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/network_analyzer.cpython-313.pyc +0 -0
- package/analysis_tools/__pycache__/network_analyzer.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/report_generator.cpython-313.pyc +0 -0
- package/analysis_tools/__pycache__/report_generator.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/report_generator_modular.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/sast_scanner.cpython-313.pyc +0 -0
- package/analysis_tools/__pycache__/sast_scanner.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/so_string_analyzer.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/yara_enhanced_analyzer.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/yara_results_processor.cpython-314.pyc +0 -0
- package/analysis_tools/apk_basic_info.py +85 -0
- package/analysis_tools/check_zip_encryption.py +142 -0
- package/analysis_tools/detect_obfuscation.py +650 -0
- package/analysis_tools/dex_payload_hunter.py +734 -0
- package/analysis_tools/entropy_analyzer.py +335 -0
- package/analysis_tools/error_logger.py +75 -0
- package/analysis_tools/find_encrypted_payload.py +485 -0
- package/analysis_tools/fix_apk_headers.py +154 -0
- package/analysis_tools/manifest_analyzer.py +214 -0
- package/analysis_tools/network_analyzer.py +287 -0
- package/analysis_tools/report_generator.py +506 -0
- package/analysis_tools/report_generator_modular.py +885 -0
- package/analysis_tools/sast_scanner.py +412 -0
- package/analysis_tools/so_string_analyzer.py +406 -0
- package/analysis_tools/yara_enhanced_analyzer.py +330 -0
- package/analysis_tools/yara_results_processor.py +368 -0
- package/analyzer_config.json +113 -0
- package/apkid/__init__.py +32 -0
- package/apkid/__pycache__/__init__.cpython-313.pyc +0 -0
- package/apkid/__pycache__/__init__.cpython-314.pyc +0 -0
- package/apkid/__pycache__/apkid.cpython-313.pyc +0 -0
- package/apkid/__pycache__/apkid.cpython-314.pyc +0 -0
- package/apkid/__pycache__/main.cpython-313.pyc +0 -0
- package/apkid/__pycache__/main.cpython-314.pyc +0 -0
- package/apkid/__pycache__/output.cpython-313.pyc +0 -0
- package/apkid/__pycache__/rules.cpython-313.pyc +0 -0
- package/apkid/apkid.py +266 -0
- package/apkid/main.py +98 -0
- package/apkid/output.py +177 -0
- package/apkid/rules/apk/common.yara +68 -0
- package/apkid/rules/apk/obfuscators.yara +118 -0
- package/apkid/rules/apk/packers.yara +1197 -0
- package/apkid/rules/apk/protectors.yara +301 -0
- package/apkid/rules/dex/abnormal.yara +104 -0
- package/apkid/rules/dex/anti-vm.yara +568 -0
- package/apkid/rules/dex/common.yara +60 -0
- package/apkid/rules/dex/compilers.yara +434 -0
- package/apkid/rules/dex/obfuscators.yara +602 -0
- package/apkid/rules/dex/packers.yara +761 -0
- package/apkid/rules/dex/protectors.yara +520 -0
- package/apkid/rules/dll/common.yara +38 -0
- package/apkid/rules/dll/obfuscators.yara +43 -0
- package/apkid/rules/elf/anti-vm.yara +43 -0
- package/apkid/rules/elf/common.yara +54 -0
- package/apkid/rules/elf/obfuscators.yara +991 -0
- package/apkid/rules/elf/packers.yara +1128 -0
- package/apkid/rules/elf/protectors.yara +794 -0
- package/apkid/rules/res/common.yara +43 -0
- package/apkid/rules/res/obfuscators.yara +46 -0
- package/apkid/rules/res/protectors.yara +46 -0
- package/apkid/rules.py +77 -0
- package/bin/anais +3 -0
- package/dist/cli.js +82 -0
- package/dist/index.js +123 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/index.js +21 -0
- package/dist/utils/output.js +44 -0
- package/dist/utils/paths.js +107 -0
- package/docs/ARCHITECTURE.txt +353 -0
- package/docs/Workflow and Reference.md +445 -0
- package/package.json +70 -0
- package/rules/yara_general_rules.yar +323 -0
- package/scripts/dynamic_analysis_helper.sh +334 -0
- package/scripts/frida/dpt_dex_dumper.js +145 -0
- package/scripts/frida/frida_dex_dump.js +145 -0
- package/scripts/frida/frida_hooks.js +437 -0
- package/scripts/frida/frida_websocket_extractor.js +154 -0
- package/scripts/setup.sh +206 -0
- package/scripts/validate_framework.sh +224 -0
- package/src/cli.ts +91 -0
- package/src/index.ts +123 -0
- package/src/types/index.ts +44 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/output.ts +50 -0
- package/src/utils/paths.ts +72 -0
- 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()
|