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,650 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Obfuscation Detection Module
4
+ Detects various obfuscation and protection mechanisms in Android APKs
5
+ including ProGuard, R8, DPT-Shell, DexProtector, Bangcle, etc.
6
+ Integrates with APKiD for enhanced detection.
7
+ """
8
+
9
+ import sys
10
+ import os
11
+ import json
12
+ import re
13
+ import subprocess
14
+ from pathlib import Path
15
+ from collections import Counter
16
+
17
+ # Import error logger
18
+ try:
19
+ from error_logger import setup_logger
20
+ except ImportError:
21
+ # Fallback if error_logger not available
22
+ class DummyLogger:
23
+ def info(self, msg): pass
24
+ def warning(self, msg): print(f"[WARNING] {msg}", file=sys.stderr)
25
+ def error(self, msg, detail=None): print(f"[ERROR] {msg}", file=sys.stderr)
26
+ def exception(self, msg, exc_info=True): print(f"[ERROR] {msg}", file=sys.stderr)
27
+
28
+ def setup_logger(name, log_file=None, verbose=False):
29
+ return DummyLogger()
30
+
31
+ class ObfuscationDetector:
32
+ def __init__(self, decompiled_dir, jadx_dir, apk_path=None):
33
+ self.decompiled_dir = Path(decompiled_dir) if decompiled_dir else None
34
+ self.jadx_dir = Path(jadx_dir) if jadx_dir else None
35
+ self.apk_path = apk_path
36
+ self.logger = setup_logger('detect_obfuscation')
37
+
38
+ self.results = {
39
+ 'type': 'none',
40
+ 'confidence': 0,
41
+ 'indicators': [],
42
+ 'protection_details': {},
43
+ 'apkid_results': {},
44
+ 'recommendations': []
45
+ }
46
+
47
+ def check_dex_files(self):
48
+ """Check DEX files for protection shells"""
49
+ indicators = []
50
+
51
+ if not self.decompiled_dir:
52
+ return indicators
53
+
54
+ # Look for shell DEX files
55
+ dex_files = list(self.decompiled_dir.rglob('*.dex'))
56
+
57
+ # Check for abnormal DEX structure
58
+ for dex_file in dex_files:
59
+ try:
60
+ with open(dex_file, 'rb') as f:
61
+ header = f.read(112) # DEX header is 112 bytes
62
+
63
+ # Check magic
64
+ if header[:4] != b'dex\n':
65
+ indicators.append(f'Invalid DEX magic in {dex_file.name}')
66
+
67
+ # Check for encryption markers
68
+ if b'DPT' in header or b'dpt' in header:
69
+ indicators.append('DPT-Shell signature detected')
70
+ self.results['type'] = 'dpt-shell'
71
+ self.results['confidence'] = 90
72
+
73
+ if b'BANGCLE' in header or b'bangcle' in header:
74
+ indicators.append('Bangcle signature detected')
75
+ self.results['type'] = 'bangcle'
76
+ self.results['confidence'] = 90
77
+
78
+ except Exception as e:
79
+ indicators.append(f'Error reading {dex_file.name}: {e}')
80
+
81
+ return indicators
82
+
83
+ def check_native_libs(self):
84
+ """Check for native protection libraries"""
85
+ indicators = []
86
+
87
+ if not self.decompiled_dir:
88
+ return indicators
89
+
90
+ lib_dir = self.decompiled_dir / 'lib'
91
+ if lib_dir.exists():
92
+ so_files = list(lib_dir.rglob('*.so'))
93
+
94
+ # Known protection library names
95
+ protection_libs = {
96
+ 'libdpt': 'DPT-Shell',
97
+ 'libexec': 'DexProtector',
98
+ 'libprotect': 'DexProtector',
99
+ 'libjiagu': 'Qihoo 360',
100
+ 'libbangcle': 'Bangcle',
101
+ 'libsecexe': 'SecNeo',
102
+ 'libshell': 'Generic Shell',
103
+ 'libmobisec': 'MobiSec',
104
+ 'libnqshield': 'NetQin',
105
+ 'libprotection': 'Generic Protection',
106
+ 'libapp': 'App Encryption',
107
+ 'libjiag': 'Qihoo 360',
108
+ 'libddog': 'Ijiami',
109
+ 'libAPKProtect': 'APK Protect',
110
+ 'libexecmain': 'Execmain Protection',
111
+ 'libtosprotection': 'Tencent Protection',
112
+ 'libshella': 'Shell Variant A',
113
+ 'libfake': 'Fake Dex Protection',
114
+ }
115
+
116
+ for so_file in so_files:
117
+ name = so_file.name.lower()
118
+ for lib_pattern, protector in protection_libs.items():
119
+ if lib_pattern in name:
120
+ indicators.append(f'Protection library detected: {so_file.name} ({protector})')
121
+ # Use most specific match
122
+ if 'dpt' in lib_pattern:
123
+ self.results['type'] = 'dpt-shell'
124
+ elif self.results['type'] == 'none':
125
+ self.results['type'] = protector.lower().replace(' ', '-')
126
+ self.results['confidence'] = max(self.results['confidence'], 90)
127
+
128
+ return indicators
129
+
130
+ def check_manifest_indicators(self):
131
+ """Check AndroidManifest for protection indicators"""
132
+ indicators = []
133
+
134
+ if not self.decompiled_dir:
135
+ return indicators
136
+
137
+ manifest = self.decompiled_dir / 'AndroidManifest.xml'
138
+ if manifest.exists():
139
+ try:
140
+ content = manifest.read_text(errors='ignore')
141
+
142
+ # Check for known protection application classes
143
+ protection_apps = {
144
+ 'com.jx.shell': 'DPT-Shell (jx.shell)',
145
+ 'com.jx': 'DPT-Shell variant',
146
+ 'JniBridge': 'JNI Bridge Protection (DPT)',
147
+ 'ProxyApplication': 'Proxy Application Shell (DPT)',
148
+ 'ProxyComponentFactory': 'Proxy Component Shell (DPT)',
149
+ 'com.secneo.apkwrapper': 'SecNeo',
150
+ 'com.bangcle.app': 'Bangcle',
151
+ 'com.qihoo.util': 'Qihoo 360',
152
+ 'com.shell.NativeApplication': 'Generic Shell',
153
+ 's.h.e.l.l': 'Obfuscated Shell',
154
+ 'com.stub.StubApp': 'Stub Application Shell',
155
+ 'com.wrapper.': 'Wrapper Shell',
156
+ 'StubApplication': 'Stub Shell',
157
+ 'com.metaapp': 'Meta Application Shell',
158
+ 'com.ali.mobisecenhance': 'Alibaba MobiSec',
159
+ 'com.tencent.StubShell': 'Tencent Shell',
160
+ 'com.baidu.protect': 'Baidu Protect',
161
+ 'com.qihoo360.repacker': 'Qihoo 360 Packer',
162
+ 'net.lingala.zip4j': 'Zip4j Encryption',
163
+ 'DPTApplication': 'DPT Shell',
164
+ 'com.dpt.': 'DPT Protection',
165
+ }
166
+
167
+ for pattern, protector in protection_apps.items():
168
+ if pattern in content:
169
+ indicators.append(f'Protection application class: {pattern} ({protector})')
170
+ # Specific handling for jx.shell/DPT variants
171
+ if any(x in pattern.lower() for x in ['jx', 'dpt', 'jnibridge', 'proxy']):
172
+ self.results['type'] = 'dpt-shell'
173
+ self.results['confidence'] = 98
174
+ self.results['protection_details']['shell_type'] = protector
175
+ elif self.results['type'] == 'none' or self.results['confidence'] < 80:
176
+ self.results['type'] = protector.lower().replace(' ', '-').replace('(', '').replace(')', '')
177
+ self.results['confidence'] = 85
178
+
179
+ except Exception as e:
180
+ indicators.append(f'Error reading manifest: {e}')
181
+
182
+ return indicators
183
+
184
+ def check_proguard_r8(self):
185
+ """Detect ProGuard/R8 obfuscation"""
186
+ indicators = []
187
+
188
+ if not self.jadx_dir or not self.jadx_dir.exists():
189
+ return indicators
190
+
191
+ # Sample Java files to check
192
+ java_files = list(self.jadx_dir.rglob('*.java'))[:50] # Check first 50
193
+
194
+ if not java_files:
195
+ return indicators
196
+
197
+ # Indicators of ProGuard/R8
198
+ obfuscated_patterns = {
199
+ 'short_class_names': 0, # Classes with single letter names
200
+ 'short_method_names': 0, # Methods like a(), b(), c()
201
+ 'short_field_names': 0, # Fields like a, b, c
202
+ 'package_flattening': 0, # All classes in root package
203
+ }
204
+
205
+ for java_file in java_files:
206
+ try:
207
+ content = java_file.read_text(errors='ignore')
208
+
209
+ # Check for short class names (a.java, b.java, etc.)
210
+ if len(java_file.stem) <= 2 and java_file.stem.isalpha():
211
+ obfuscated_patterns['short_class_names'] += 1
212
+
213
+ # Check for short method names
214
+ short_methods = re.findall(r'\s+[a-z]\s*\([^)]*\)\s*{', content)
215
+ obfuscated_patterns['short_method_names'] += len(short_methods)
216
+
217
+ # Check for short field names
218
+ short_fields = re.findall(r'^\s+(private|public|protected)?\s+\w+\s+[a-z];', content, re.MULTILINE)
219
+ obfuscated_patterns['short_field_names'] += len(short_fields)
220
+
221
+ # Check package structure
222
+ package_match = re.search(r'package\s+([\w.]+);', content)
223
+ if package_match:
224
+ package = package_match.group(1)
225
+ # If package has no dots, it's likely flattened
226
+ if '.' not in package or len(package) <= 3:
227
+ obfuscated_patterns['package_flattening'] += 1
228
+
229
+ except Exception as e:
230
+ continue
231
+
232
+ # Determine if ProGuard/R8 is used
233
+ total_indicators = sum(obfuscated_patterns.values())
234
+
235
+ if obfuscated_patterns['short_class_names'] > 10 or total_indicators > 30:
236
+ indicators.append('ProGuard/R8 obfuscation detected')
237
+ indicators.append(f" - Short class names: {obfuscated_patterns['short_class_names']}")
238
+ indicators.append(f" - Short method names: {obfuscated_patterns['short_method_names']}")
239
+ indicators.append(f" - Short field names: {obfuscated_patterns['short_field_names']}")
240
+
241
+ if self.results['type'] == 'none':
242
+ self.results['type'] = 'proguard'
243
+ self.results['confidence'] = min(70 + (total_indicators // 10), 95)
244
+
245
+ return indicators
246
+
247
+ def check_dpt_jx_shell_sources(self):
248
+ """Check Java sources for DPT/jx.shell specific patterns"""
249
+ indicators = []
250
+
251
+ if not self.jadx_dir or not self.jadx_dir.exists():
252
+ return indicators
253
+
254
+ # Look for jx.shell specific files
255
+ jx_shell_patterns = [
256
+ '**/jx/shell/*.java',
257
+ '**/com/jx/**/*.java',
258
+ '**/JniBridge.java',
259
+ '**/ProxyApplication.java',
260
+ '**/ProxyComponentFactory.java',
261
+ '**/DPTApplication.java',
262
+ ]
263
+
264
+ found_files = []
265
+ for pattern in jx_shell_patterns:
266
+ found_files.extend(self.jadx_dir.glob(pattern))
267
+
268
+ if found_files:
269
+ indicators.append(f'DPT-Shell/jx.shell source files detected: {len(found_files)} files')
270
+ for f in found_files[:10]: # Show first 10
271
+ indicators.append(f' - {f.relative_to(self.jadx_dir)}')
272
+
273
+ self.results['type'] = 'dpt-shell'
274
+ self.results['confidence'] = 99
275
+ self.results['protection_details']['detected_files'] = [str(f.relative_to(self.jadx_dir)) for f in found_files]
276
+ return indicators
277
+
278
+ # Check for DPT-shell code patterns in any Java file
279
+ java_files = list(self.jadx_dir.rglob('*.java'))[:200] # Increased to check more files
280
+
281
+ dpt_patterns = {
282
+ r'libdpt\.so': 'DPT native library',
283
+ r'JniBridge\b': 'JNI Bridge class',
284
+ r'ProxyApplication': 'Proxy Application',
285
+ r'ProxyComponentFactory': 'Proxy Component Factory',
286
+ r'com\.jx\.shell': 'jx.shell package',
287
+ r'attachBaseContext.*native': 'Native context attach',
288
+ r'System\.load.*libdpt': 'DPT library loading',
289
+ r'\.dex.*decrypt': 'DEX decryption',
290
+ r'\.dex.*unpack': 'DEX unpacking',
291
+ r'loadDex.*native': 'Native DEX loading',
292
+ r'getDexFromNative': 'Get DEX from native',
293
+ r'attachBaseContext.*classloader': 'Classloader manipulation',
294
+ }
295
+
296
+ matches = 0
297
+ matched_files = []
298
+ pattern_matches = Counter()
299
+
300
+ for java_file in java_files:
301
+ try:
302
+ content = java_file.read_text(errors='ignore')
303
+
304
+ for pattern, desc in dpt_patterns.items():
305
+ if re.search(pattern, content, re.IGNORECASE):
306
+ matches += 1
307
+ pattern_matches[desc] += 1
308
+ if java_file not in matched_files:
309
+ matched_files.append(java_file)
310
+
311
+ except Exception:
312
+ continue
313
+
314
+ if matches > 2: # Lowered threshold for better detection
315
+ indicators.append(f'DPT-Shell code patterns detected ({matches} matches in {len(matched_files)} files)')
316
+ for desc, count in pattern_matches.most_common(5):
317
+ indicators.append(f' - {desc}: {count} occurrence(s)')
318
+ self.results['type'] = 'dpt-shell'
319
+ self.results['confidence'] = max(self.results['confidence'], min(70 + matches * 5, 95))
320
+ self.results['protection_details']['pattern_matches'] = dict(pattern_matches)
321
+
322
+ return indicators
323
+
324
+ def run_apkid(self):
325
+ """Run APKiD for advanced obfuscation detection"""
326
+ indicators = []
327
+
328
+ if not self.apk_path or not Path(self.apk_path).exists():
329
+ return indicators
330
+
331
+ try:
332
+ # Get the directory where this script is located
333
+ script_dir = Path(__file__).parent.parent
334
+ apkid_dir = script_dir / 'apkid'
335
+
336
+ # Check if APKiD is available
337
+ if self.logger.info('APKiD not found, skipping enhanced detection'):
338
+ indicators.append('APKiD not found, skipping enhanced detection')
339
+ return indicators
340
+
341
+ # Run APKiD with JSON output
342
+ cmd = [
343
+ sys.executable, '-m', 'apkid.main',
344
+ '--json',
345
+ '--timeout', '60',
346
+ str(self.apk_path)
347
+ ]
348
+
349
+ # Set PYTHONPATH to include apkid directory
350
+ env = os.environ.copy()
351
+ env['PYTHONPATH'] = str(script_dir) + ':' + env.get('PYTHONPATH', '')
352
+
353
+ result = subprocess.run(
354
+ cmd,
355
+ capture_output=True,
356
+ text=True,
357
+ timeout=70,
358
+ env=env,
359
+ cwd=str(script_dir)
360
+ )
361
+
362
+ if result.returncode == 0 and result.stdout:
363
+ apkid_data = json.loads(result.stdout)
364
+ self.results['apkid_results'] = apkid_data
365
+
366
+ # Parse APKiD results
367
+ apk_name = Path(self.apk_path).name
368
+ if apk_name in apkid_data:
369
+ apk_data = apkid_data[apk_name]
370
+
371
+ # Check for compilers
372
+ if 'compiler' in apk_data:
373
+ for compiler_info in apk_data['compiler']:
374
+ indicators.append(f'APKiD - Compiler: {compiler_info}')
375
+
376
+ # Check for obfuscators
377
+ if 'obfuscator' in apk_data:
378
+ for obf_info in apk_data['obfuscator']:
379
+ indicators.append(f'APKiD - Obfuscator: {obf_info}')
380
+ if 'dpt' in obf_info.lower() or 'jx' in obf_info.lower():
381
+ self.results['type'] = 'dpt-shell'
382
+ self.results['confidence'] = 95
383
+
384
+ # Check for packers
385
+ if 'packer' in apk_data:
386
+ for packer_info in apk_data['packer']:
387
+ indicators.append(f'APKiD - Packer: {packer_info}')
388
+ if self.results['type'] == 'none':
389
+ self.results['type'] = 'packed'
390
+ self.results['confidence'] = 85
391
+
392
+ # Check for protectors
393
+ if 'protector' in apk_data:
394
+ for prot_info in apk_data['protector']:
395
+ indicators.append(f'APKiD - Protector: {prot_info}')
396
+ if 'dpt' in prot_info.lower():
397
+ self.results['type'] = 'dpt-shell'
398
+ self.results['confidence'] = 95
399
+
400
+ # Check for anti-VM
401
+ if 'anti_vm' in apk_data:
402
+ for antivm_info in apk_data['anti_vm']:
403
+ indicators.append(f'APKiD - Anti-VM: {antivm_info}')
404
+
405
+ # Check for abnormal features
406
+ if 'abnormal' in apk_data:
407
+ for abnormal_info in apk_data['abnormal']:
408
+ indicators.append(f'APKiD - Abnormal: {abnormal_info}')
409
+ else:
410
+ if result.stderr:
411
+ self.logger.warning(f'APKiD warning: {result.stderr[:200]}')
412
+
413
+ except subprocess.TimeoutExpired:
414
+ self.logger.warning('APKiD scan timed out')
415
+ indicators.append('APKiD scan timed out')
416
+ except json.JSONDecodeError as e:
417
+ self.logger.error('APKiD output parse error', str(e))
418
+ except Exception as e:
419
+ self.logger.exception('APKiD scan error')
420
+ indicators.append('APKiD scan error (check logs)')
421
+ indicators.append(f'APKiD scan error: {e}')
422
+
423
+ return indicators
424
+
425
+ def check_additional_obfuscation(self):
426
+ """Check for additional obfuscation techniques"""
427
+ indicators = []
428
+
429
+ if not self.jadx_dir or not self.jadx_dir.exists():
430
+ return indicators
431
+
432
+ java_files = list(self.jadx_dir.rglob('*.java'))[:100]
433
+
434
+ obf_patterns = {
435
+ 'control_flow': {
436
+ 'pattern': r'(goto|switch|if.*goto)',
437
+ 'desc': 'Control flow obfuscation',
438
+ 'count': 0
439
+ },
440
+ 'reflection': {
441
+ 'pattern': r'(Class\.forName|getDeclaredMethod|getDeclaredField|invoke)',
442
+ 'desc': 'Reflection-based obfuscation',
443
+ 'count': 0
444
+ },
445
+ 'dynamic_loading': {
446
+ 'pattern': r'(DexClassLoader|PathClassLoader|loadDex|loadClass)',
447
+ 'desc': 'Dynamic class loading',
448
+ 'count': 0
449
+ },
450
+ 'native_methods': {
451
+ 'pattern': r'native\s+\w+',
452
+ 'desc': 'Native method usage',
453
+ 'count': 0
454
+ },
455
+ 'string_concat': {
456
+ 'pattern': r'new\s+StringBuilder.*append',
457
+ 'desc': 'String concatenation obfuscation',
458
+ 'count': 0
459
+ },
460
+ }
461
+
462
+ for java_file in java_files:
463
+ try:
464
+ content = java_file.read_text(errors='ignore')
465
+
466
+ for key, info in obf_patterns.items():
467
+ matches = re.findall(info['pattern'], content, re.IGNORECASE)
468
+ info['count'] += len(matches)
469
+
470
+ except Exception:
471
+ continue
472
+
473
+ # Report findings
474
+ for key, info in obf_patterns.items():
475
+ if info['count'] > 20: # Threshold for significance
476
+ indicators.append(f"{info['desc']} detected ({info['count']} occurrences)")
477
+ self.results['protection_details'][key] = info['count']
478
+
479
+ return indicators
480
+
481
+ def check_string_encryption(self):
482
+ """Detect string encryption"""
483
+ indicators = []
484
+
485
+ if not self.jadx_dir or not self.jadx_dir.exists():
486
+ return indicators
487
+
488
+ java_files = list(self.jadx_dir.rglob('*.java'))[:30]
489
+
490
+ encrypted_string_patterns = [
491
+ r'decrypt\s*\(',
492
+ r'deobfuscate\s*\(',
493
+ r'base64\s*\(',
494
+ r'cipher\.doFinal',
495
+ r'Cipher\.getInstance',
496
+ r'SecretKeySpec',
497
+ ]
498
+
499
+ matches = 0
500
+ for java_file in java_files:
501
+ try:
502
+ content = java_file.read_text(errors='ignore')
503
+
504
+ for pattern in encrypted_string_patterns:
505
+ if re.search(pattern, content, re.IGNORECASE):
506
+ matches += 1
507
+
508
+ except Exception:
509
+ continue
510
+
511
+ if matches > 5:
512
+ indicators.append(f'String encryption detected ({matches} indicators)')
513
+ self.results['protection_details']['string_encryption'] = True
514
+
515
+ return indicators
516
+
517
+ def generate_recommendations(self):
518
+ """Generate recommendations based on detected protection"""
519
+
520
+ if self.results['type'] in ['dpt-shell', 'dpt-shell-jx.shell', 'dexprotector', 'bangcle', 'qihoo-360', 'generic-shell', 'ijiami', 'secneo']:
521
+ self.results['recommendations'] = [
522
+ 'DYNAMIC ANALYSIS REQUIRED - NATIVE PROTECTION DETECTED',
523
+ '1. Install APK on rooted Android device or emulator (Android 7-10 recommended)',
524
+ '2. Use Frida with frida-dexdump or similar tool',
525
+ '3. Run the application to trigger DEX unpacking',
526
+ '4. Dump decrypted DEX from memory:',
527
+ ' - frida-dexdump -U -f <package_name>',
528
+ ' - Or use frida -U -f <package_name> -l scripts/frida/frida_dex_dump.js',
529
+ '5. Analyze dumped DEX files with JADX/APKTool',
530
+ '6. Alternative: Use FART, DexHunter, or ZjDroid',
531
+ '7. Monitor native library loading with Frida hooks',
532
+ '8. Consider using Android Hooking/Instrumentation for API monitoring',
533
+ '',
534
+ 'SPECIFIC TOOLS FOR THIS PROTECTION:',
535
+ ]
536
+
537
+ if self.results['type'] == 'dpt-shell':
538
+ self.results['recommendations'].extend([
539
+ '- DPT-Shell unpacker tools',
540
+ '- Frida script: scripts/frida/frida_dex_dump.js (included)',
541
+ '- FRIDA command: frida -U -f com.package.name -l scripts/frida/frida_dex_dump.js --no-pause',
542
+ '- Monitor JniBridge and libdpt.so loading',
543
+ '- Hook ProxyApplication.attachBaseContext()',
544
+ ])
545
+ elif 'bangcle' in self.results['type']:
546
+ self.results['recommendations'].extend([
547
+ '- Bangcle unpacker',
548
+ '- ZjDroid for Bangcle',
549
+ ])
550
+ elif 'qihoo' in self.results['type']:
551
+ self.results['recommendations'].extend([
552
+ '- Qihoo 360 unpacker',
553
+ '- FART for Qihoo protection',
554
+ ])
555
+
556
+ elif self.results['type'] in ['proguard', 'r8']:
557
+ self.results['recommendations'] = [
558
+ 'STATIC ANALYSIS POSSIBLE (with limitations)',
559
+ '1. Use JADX with deobfuscation options enabled',
560
+ '2. Apply class/method renaming for better readability',
561
+ '3. Focus on analyzing:',
562
+ ' - Network communications',
563
+ ' - Cryptographic operations',
564
+ ' - Permission usage',
565
+ ' - Sensitive API calls',
566
+ '4. Use dynamic analysis for runtime behavior',
567
+ '5. Consider using JADX-GUI for manual analysis',
568
+ ]
569
+ else:
570
+ self.results['recommendations'] = [
571
+ 'STANDARD STATIC ANALYSIS APPLICABLE',
572
+ '1. Proceed with SAST tools',
573
+ '2. Manual code review of critical components',
574
+ '3. Check for security vulnerabilities',
575
+ '4. Analyze network traffic',
576
+ '5. Review permissions and components',
577
+ ]
578
+
579
+ def analyze(self):
580
+ """Run all detection checks"""
581
+
582
+ # Run APKiD first for comprehensive detection
583
+ self.results['indicators'].extend(self.run_apkid())
584
+
585
+ # Check for DEX-level protection
586
+ self.results['indicators'].extend(self.check_dex_files())
587
+
588
+ # Check for native libraries
589
+ self.results['indicators'].extend(self.check_native_libs())
590
+
591
+ # Check manifest (this can detect jx.shell directly)
592
+ self.results['indicators'].extend(self.check_manifest_indicators())
593
+
594
+ # Check Java sources for DPT/jx.shell patterns (priority check)
595
+ self.results['indicators'].extend(self.check_dpt_jx_shell_sources())
596
+
597
+ # Check for additional obfuscation techniques
598
+ self.results['indicators'].extend(self.check_additional_obfuscation())
599
+
600
+ # Check for ProGuard/R8 (only if no advanced protection detected)
601
+ if self.results['type'] == 'none':
602
+ self.results['indicators'].extend(self.check_proguard_r8())
603
+
604
+ # Check string encryption
605
+ self.results['indicators'].extend(self.check_string_encryption())
606
+
607
+ # Generate recommendations
608
+ self.generate_recommendations()
609
+
610
+ return self.results
611
+
612
+ def main():
613
+ if len(sys.argv) < 5:
614
+ print("Usage: detect_obfuscation.py <apk_path> <decompiled_dir> <jadx_dir> <output_json>")
615
+ sys.exit(1)
616
+
617
+ apk_path = sys.argv[1] if os.path.exists(sys.argv[1]) else None
618
+ decompiled_dir = sys.argv[2] if os.path.exists(sys.argv[2]) else None
619
+ jadx_dir = sys.argv[3] if os.path.exists(sys.argv[3]) else None
620
+ output_json = sys.argv[4]
621
+
622
+ detector = ObfuscationDetector(decompiled_dir, jadx_dir, apk_path)
623
+ results = detector.analyze()
624
+
625
+ # Save results
626
+ with open(output_json, 'w') as f:
627
+ json.dump(results, f, indent=2)
628
+
629
+ # Print summary
630
+ print(f"\n{'='*60}")
631
+ print(f"OBFUSCATION DETECTION RESULTS")
632
+ print(f"{'='*60}")
633
+ print(f"Type: {results['type'].upper()}")
634
+ print(f"Confidence: {results['confidence']}%")
635
+ print(f"\nIndicators Found: {len(results['indicators'])}")
636
+ for indicator in results['indicators']:
637
+ print(f" • {indicator}")
638
+
639
+ if results['apkid_results']:
640
+ print(f"\nAPKiD Enhanced Detection: Enabled")
641
+
642
+ if results['recommendations']:
643
+ print(f"\nRECOMMENDATIONS:")
644
+ for rec in results['recommendations']:
645
+ print(f" {rec}")
646
+
647
+ print(f"\n{'='*60}\n")
648
+
649
+ if __name__ == '__main__':
650
+ main()