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,734 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ DEX Payload Hunter
4
+ Finds encrypted/hidden DEX payloads in APK files
5
+ Predicts runtime unpacking locations for dynamic analysis
6
+ """
7
+
8
+ import sys
9
+ import os
10
+ import json
11
+ import zipfile
12
+ import struct
13
+ import math
14
+ from pathlib import Path
15
+ from collections import defaultdict
16
+
17
+ try:
18
+ from error_logger import setup_logger
19
+ except ImportError:
20
+ class DummyLogger:
21
+ def info(self, msg): pass
22
+ def warning(self, msg): print(f"[WARNING] {msg}", file=sys.stderr)
23
+ def error(self, msg, detail=None): print(f"[ERROR] {msg}", file=sys.stderr)
24
+ def setup_logger(name, log_file=None, verbose=False):
25
+ return DummyLogger()
26
+
27
+ class DEXPayloadHunter:
28
+ """Hunt for hidden/encrypted DEX payloads in APK"""
29
+
30
+ def __init__(self, apk_path):
31
+ self.apk_path = Path(apk_path)
32
+ self.logger = setup_logger('dex_payload_hunter')
33
+
34
+ self.results = {
35
+ 'stub_dex_detected': False,
36
+ 'encrypted_payloads': [],
37
+ 'suspicious_files': [],
38
+ 'likely_unpack_locations': [],
39
+ 'protection_indicators': [],
40
+ 'recommendations': []
41
+ }
42
+
43
+ def calculate_entropy(self, data):
44
+ """Calculate Shannon entropy of data"""
45
+ if len(data) == 0:
46
+ return 0
47
+
48
+ freq = defaultdict(int)
49
+ for byte in data:
50
+ freq[byte] += 1
51
+
52
+ entropy = 0
53
+ for count in freq.values():
54
+ p = count / len(data)
55
+ entropy -= p * math.log2(p)
56
+
57
+ return entropy
58
+
59
+ def is_dex_file(self, data):
60
+ """Check if data starts with DEX magic"""
61
+ if len(data) < 8:
62
+ return False
63
+ return data[:4] == b'dex\n'
64
+
65
+ def is_encrypted_dex(self, data):
66
+ """Detect if data might be encrypted DEX based on patterns"""
67
+ if len(data) < 112: # DEX header size
68
+ return False
69
+
70
+ # Check for common encryption markers
71
+ markers = [
72
+ b'ENCRYPTED',
73
+ b'ENCODED',
74
+ b'PACKED',
75
+ b'DPT',
76
+ b'SHELL',
77
+ b'PROTECTED'
78
+ ]
79
+
80
+ for marker in markers:
81
+ if marker in data[:200]: # Check first 200 bytes
82
+ return True
83
+
84
+ # Check entropy - encrypted data has high entropy
85
+ entropy = self.calculate_entropy(data[:min(10000, len(data))])
86
+ if entropy > 7.5:
87
+ return True
88
+
89
+ return False
90
+
91
+ def analyze_stub_dex(self, dex_path, dex_data):
92
+ """Analyze if DEX is a stub/loader"""
93
+ indicators = []
94
+ is_stub = False
95
+
96
+ # 1. Check size - stubs are usually small
97
+ size = len(dex_data)
98
+ if size < 100 * 1024: # < 100KB
99
+ indicators.append(f'Small DEX size: {size / 1024:.1f}KB (typical stub)')
100
+ is_stub = True
101
+
102
+ # 2. Check for DPT/protection strings in DEX
103
+ protection_strings = [
104
+ b'libdpt.so',
105
+ b'JniBridge',
106
+ b'ProxyApplication',
107
+ b'ProxyComponentFactory',
108
+ b'com/jx/shell',
109
+ b'attachBaseContext',
110
+ b'loadDex',
111
+ b'getDexFromNative',
112
+ b'unpack',
113
+ b'decrypt'
114
+ ]
115
+
116
+ found_strings = []
117
+ for ps in protection_strings:
118
+ if ps in dex_data:
119
+ found_strings.append(ps.decode('utf-8', errors='ignore'))
120
+ is_stub = True
121
+
122
+ if found_strings:
123
+ indicators.append(f'Protection strings found: {", ".join(found_strings)}')
124
+
125
+ # 3. Check method/class count (stubs have few methods)
126
+ try:
127
+ # Parse DEX header
128
+ header = struct.unpack('<8sIII', dex_data[0:24])
129
+ # Get method count from header
130
+ method_ids_off = struct.unpack('<I', dex_data[88:92])[0]
131
+ method_ids_size = struct.unpack('<I', dex_data[84:88])[0]
132
+
133
+ if method_ids_size < 500:
134
+ indicators.append(f'Low method count: {method_ids_size} (typical stub)')
135
+ is_stub = True
136
+ except:
137
+ pass
138
+
139
+ return is_stub, indicators
140
+
141
+ def find_encrypted_payloads(self):
142
+ """Find encrypted DEX payloads in APK"""
143
+ payloads = []
144
+
145
+ try:
146
+ with zipfile.ZipFile(self.apk_path, 'r') as zf:
147
+ for file_info in zf.filelist:
148
+ filename = file_info.filename
149
+
150
+ # Skip standard files
151
+ if filename.startswith('META-INF/') or filename.startswith('res/'):
152
+ continue
153
+
154
+ # Check suspicious locations
155
+ suspicious_patterns = [
156
+ 'assets/',
157
+ 'lib/',
158
+ '.dat',
159
+ '.bin',
160
+ '.jar',
161
+ '.dex',
162
+ '.so',
163
+ '.odex',
164
+ '.zip',
165
+ 'classes',
166
+ 'shell',
167
+ 'stub',
168
+ 'encrypted'
169
+ ]
170
+
171
+ is_suspicious = any(pattern in filename.lower() for pattern in suspicious_patterns)
172
+
173
+ if is_suspicious and file_info.file_size > 1024: # > 1KB
174
+ try:
175
+ data = zf.read(filename)
176
+ entropy = self.calculate_entropy(data[:min(10000, len(data))])
177
+
178
+ payload_info = {
179
+ 'location': filename,
180
+ 'size': file_info.file_size,
181
+ 'size_kb': file_info.file_size / 1024,
182
+ 'entropy': round(entropy, 2),
183
+ 'compressed': file_info.compress_type != 0,
184
+ 'indicators': []
185
+ }
186
+
187
+ # Analyze file
188
+ if self.is_dex_file(data):
189
+ payload_info['type'] = 'DEX'
190
+ payload_info['indicators'].append('Valid DEX header')
191
+ elif self.is_encrypted_dex(data):
192
+ payload_info['type'] = 'Encrypted DEX (suspected)'
193
+ payload_info['indicators'].append('Encryption markers or high entropy')
194
+ elif entropy > 7.5:
195
+ payload_info['type'] = 'Encrypted data'
196
+ payload_info['indicators'].append(f'High entropy: {entropy:.2f}')
197
+ elif entropy > 6.5:
198
+ payload_info['type'] = 'Compressed/Obfuscated'
199
+ payload_info['indicators'].append(f'Medium-high entropy: {entropy:.2f}')
200
+ else:
201
+ payload_info['type'] = 'Unknown'
202
+
203
+ # Check for specific protection patterns
204
+ if b'DPT' in data[:1000]:
205
+ payload_info['indicators'].append('DPT signature detected')
206
+ if b'BANGCLE' in data[:1000]:
207
+ payload_info['indicators'].append('Bangcle signature detected')
208
+
209
+ payloads.append(payload_info)
210
+
211
+ except Exception as e:
212
+ self.logger.error(f'Error analyzing {filename}', str(e))
213
+
214
+ except Exception as e:
215
+ self.logger.error('Error reading APK', str(e))
216
+
217
+ return payloads
218
+
219
+ def extract_strings_from_so(self, data, min_length=4):
220
+ """Extract printable strings from binary data"""
221
+ strings = []
222
+ current_string = []
223
+
224
+ for byte in data:
225
+ if 32 <= byte < 127: # Printable ASCII
226
+ current_string.append(chr(byte))
227
+ else:
228
+ if len(current_string) >= min_length:
229
+ strings.append(''.join(current_string))
230
+ current_string = []
231
+
232
+ # Don't forget last string
233
+ if len(current_string) >= min_length:
234
+ strings.append(''.join(current_string))
235
+
236
+ return strings
237
+
238
+ def analyze_dpt_shell_library(self, zf):
239
+ """Specifically analyze libdpt.so for DPT-shell protection artifacts"""
240
+ dpt_findings = []
241
+
242
+ # Find libdpt.so in any architecture or assets directory
243
+ libdpt_files = [f for f in zf.filelist if 'libdpt' in f.filename.lower() and f.filename.endswith('.so')]
244
+
245
+ if not libdpt_files:
246
+ return dpt_findings
247
+
248
+ for libdpt in libdpt_files:
249
+ try:
250
+ data = zf.read(libdpt.filename)
251
+
252
+ # Extract all strings from the library
253
+ all_strings = self.extract_strings_from_so(data, min_length=4)
254
+
255
+ # Enhanced suspicious pattern detection
256
+ found_artifacts = {
257
+ 'zip_files': [],
258
+ 'dex_files': [],
259
+ 'jar_files': [],
260
+ 'dat_files': [],
261
+ 'code_cache_refs': [],
262
+ 'file_paths': [],
263
+ 'encryption_refs': [],
264
+ 'shell_refs': [],
265
+ 'class_loader_refs': []
266
+ }
267
+
268
+ for string in all_strings:
269
+ string_lower = string.lower()
270
+
271
+ # CRITICAL: Check for .zip files (encrypted DEX container)
272
+ if '.zip' in string_lower and len(string) < 100:
273
+ if len(found_artifacts['zip_files']) < 10:
274
+ found_artifacts['zip_files'].append(string)
275
+
276
+ # CRITICAL: Check for .dex files
277
+ if '.dex' in string_lower and len(string) < 100:
278
+ if len(found_artifacts['dex_files']) < 10:
279
+ found_artifacts['dex_files'].append(string)
280
+
281
+ # Check for .jar files
282
+ if '.jar' in string_lower and len(string) < 100:
283
+ if len(found_artifacts['jar_files']) < 10:
284
+ found_artifacts['jar_files'].append(string)
285
+
286
+ # Check for .dat files
287
+ if '.dat' in string_lower and len(string) < 100:
288
+ if len(found_artifacts['dat_files']) < 10:
289
+ found_artifacts['dat_files'].append(string)
290
+
291
+ # CRITICAL: Check for code_cache path (where encrypted zip is stored)
292
+ if 'code_cache' in string_lower:
293
+ if len(found_artifacts['code_cache_refs']) < 10:
294
+ found_artifacts['code_cache_refs'].append(string)
295
+
296
+ # Check for file path patterns
297
+ if any(path in string_lower for path in ['/data/data/', '/files/', '/cache/', '/app_dex/']):
298
+ if len(found_artifacts['file_paths']) < 10:
299
+ found_artifacts['file_paths'].append(string)
300
+
301
+ # Check for encryption-related strings
302
+ if any(keyword in string_lower for keyword in ['encrypt', 'decrypt', 'cipher', 'aes', 'des', 'crypto']):
303
+ if len(found_artifacts['encryption_refs']) < 10:
304
+ found_artifacts['encryption_refs'].append(string)
305
+
306
+ # Check for shell/unpack/load related strings
307
+ if any(keyword in string_lower for keyword in ['shell', 'unpack', 'load', 'jni_', 'proxy', 'wrapper']):
308
+ if len(found_artifacts['shell_refs']) < 10:
309
+ found_artifacts['shell_refs'].append(string)
310
+
311
+ # Check for ClassLoader references
312
+ if any(keyword in string_lower for keyword in ['classloader', 'dexclassloader', 'basedexclassloader', 'pathclassloader']):
313
+ if len(found_artifacts['class_loader_refs']) < 10:
314
+ found_artifacts['class_loader_refs'].append(string)
315
+
316
+ # Remove empty categories
317
+ found_artifacts = {k: v for k, v in found_artifacts.items() if v}
318
+
319
+ if found_artifacts:
320
+ dpt_findings.append({
321
+ 'library': libdpt.filename,
322
+ 'finding': '🚨 DPT-Shell Protection Binary [CRITICAL]',
323
+ 'size': libdpt.file_size / 1024,
324
+ 'category': 'DPT-Shell Binary Code',
325
+ 'importance': 'CRITICAL',
326
+ 'artifacts': found_artifacts,
327
+ 'description': 'This is the DPT-Shell protection binary that handles DEX decryption and loading'
328
+ })
329
+
330
+ self.logger.info(f'🚨 DPT-shell CRITICAL binary detected: {libdpt.filename} with {len(found_artifacts)} artifact types')
331
+
332
+ except Exception as e:
333
+ self.logger.error(f'Error analyzing {libdpt.filename}', str(e))
334
+
335
+ return dpt_findings
336
+
337
+ def analyze_native_libraries(self):
338
+ """Check native libraries for embedded DEX and zip references"""
339
+ lib_findings = []
340
+
341
+ try:
342
+ with zipfile.ZipFile(self.apk_path, 'r') as zf:
343
+ # First, check specifically for DPT-shell
344
+ dpt_findings = self.analyze_dpt_shell_library(zf)
345
+ if dpt_findings:
346
+ lib_findings.extend(dpt_findings)
347
+
348
+ lib_files = [f for f in zf.filelist if f.filename.startswith('lib/') and f.filename.endswith('.so')]
349
+
350
+ for lib_file in lib_files:
351
+ try:
352
+ data = zf.read(lib_file.filename)
353
+
354
+ # Check for embedded DEX
355
+ dex_offset = data.find(b'dex\n')
356
+ if dex_offset != -1:
357
+ lib_findings.append({
358
+ 'library': lib_file.filename,
359
+ 'finding': 'Embedded DEX detected',
360
+ 'offset': dex_offset,
361
+ 'size': lib_file.file_size / 1024
362
+ })
363
+
364
+ # Check for ZIP file references (DPT-shell stores encrypted zip)
365
+ zip_patterns = [
366
+ rb'\.zip',
367
+ rb'\.jar',
368
+ rb'\.dat',
369
+ rb'code_cache',
370
+ rb'/files/',
371
+ rb'/app_dex/',
372
+ rb'encrypted',
373
+ rb'shell\.zip',
374
+ rb'dex\.zip',
375
+ rb'classes\.zip'
376
+ ]
377
+
378
+ zip_refs = []
379
+ for pattern in zip_patterns:
380
+ if pattern in data:
381
+ # Try to extract readable string around the reference
382
+ offset = data.find(pattern)
383
+ # Get 50 bytes before and after for context
384
+ start = max(0, offset - 50)
385
+ end = min(len(data), offset + 50)
386
+ context = data[start:end]
387
+ # Extract printable string
388
+ try:
389
+ readable = ''.join(chr(b) if 32 <= b < 127 else '.' for b in context)
390
+ zip_refs.append({
391
+ 'pattern': pattern.decode('utf-8', errors='ignore'),
392
+ 'offset': offset,
393
+ 'context': readable.strip('.')
394
+ })
395
+ except:
396
+ zip_refs.append({
397
+ 'pattern': pattern.decode('utf-8', errors='ignore'),
398
+ 'offset': offset
399
+ })
400
+
401
+ if zip_refs:
402
+ lib_findings.append({
403
+ 'library': lib_file.filename,
404
+ 'finding': 'ZIP/encrypted file references found',
405
+ 'size': lib_file.file_size / 1024,
406
+ 'references': zip_refs[:5] # Limit to 5 references
407
+ })
408
+
409
+ # Check for protection library patterns
410
+ protection_libs = {
411
+ 'libdpt': 'DPT-Shell',
412
+ 'libjiagu': 'Qihoo 360',
413
+ 'libbangcle': 'Bangcle',
414
+ 'libprotect': 'DexProtector',
415
+ 'libsecexe': 'SecNeo',
416
+ 'libshell': 'Generic Shell'
417
+ }
418
+
419
+ lib_name = lib_file.filename.lower()
420
+ for pattern, protector in protection_libs.items():
421
+ if pattern in lib_name:
422
+ lib_findings.append({
423
+ 'library': lib_file.filename,
424
+ 'finding': f'Protection library: {protector}',
425
+ 'size': lib_file.file_size / 1024
426
+ })
427
+
428
+ except Exception as e:
429
+ self.logger.error(f'Error analyzing {lib_file.filename}', str(e))
430
+
431
+ except Exception as e:
432
+ self.logger.error('Error analyzing native libraries', str(e))
433
+
434
+ return lib_findings
435
+
436
+ def predict_unpack_locations(self, package_name='<package>'):
437
+ """Predict where DEX will be unpacked at runtime"""
438
+ locations = [
439
+ {
440
+ 'path': f'/data/data/{package_name}/code_cache/',
441
+ 'description': 'Code cache directory (DPT-shell stores encrypted zip here)',
442
+ 'priority': 'HIGH'
443
+ },
444
+ {
445
+ 'path': f'/data/data/{package_name}/app_dex/',
446
+ 'description': 'Common DEX cache directory',
447
+ 'priority': 'HIGH'
448
+ },
449
+ {
450
+ 'path': f'/data/data/{package_name}/files/',
451
+ 'description': 'App files directory',
452
+ 'priority': 'HIGH'
453
+ },
454
+ {
455
+ 'path': f'/data/data/{package_name}/cache/',
456
+ 'description': 'App cache directory',
457
+ 'priority': 'MEDIUM'
458
+ },
459
+ {
460
+ 'path': f'/data/data/{package_name}/lib/',
461
+ 'description': 'Native libraries directory',
462
+ 'priority': 'MEDIUM'
463
+ },
464
+ {
465
+ 'path': f'/data/data/{package_name}/app_xxx/',
466
+ 'description': 'Custom app directory (xxx = variant name)',
467
+ 'priority': 'HIGH'
468
+ },
469
+ {
470
+ 'path': f'/data/data/{package_name}/databases/',
471
+ 'description': 'Sometimes used for hidden storage',
472
+ 'priority': 'LOW'
473
+ },
474
+ {
475
+ 'path': f'/sdcard/Android/data/{package_name}/',
476
+ 'description': 'External storage (less common)',
477
+ 'priority': 'LOW'
478
+ },
479
+ {
480
+ 'path': '/data/local/tmp/',
481
+ 'description': 'Temporary directory (requires root)',
482
+ 'priority': 'LOW'
483
+ }
484
+ ]
485
+
486
+ return locations
487
+
488
+ def analyze(self, package_name=None):
489
+ """Perform complete payload hunting analysis"""
490
+
491
+ self.logger.info(f'Analyzing APK: {self.apk_path.name}')
492
+
493
+ # 1. Check main DEX files
494
+ try:
495
+ with zipfile.ZipFile(self.apk_path, 'r') as zf:
496
+ dex_files = [f for f in zf.filelist if f.filename.startswith('classes') and f.filename.endswith('.dex')]
497
+
498
+ for dex_file in dex_files:
499
+ data = zf.read(dex_file.filename)
500
+ is_stub, indicators = self.analyze_stub_dex(dex_file.filename, data)
501
+
502
+ if is_stub:
503
+ self.results['stub_dex_detected'] = True
504
+ self.results['protection_indicators'].append({
505
+ 'file': dex_file.filename,
506
+ 'type': 'Stub DEX',
507
+ 'indicators': indicators
508
+ })
509
+
510
+ except Exception as e:
511
+ self.logger.error('Error analyzing DEX files', str(e))
512
+
513
+ # 2. Find encrypted payloads
514
+ self.results['encrypted_payloads'] = self.find_encrypted_payloads()
515
+
516
+ # 3. Analyze native libraries
517
+ lib_findings = self.analyze_native_libraries()
518
+ if lib_findings:
519
+ self.results['suspicious_files'].extend(lib_findings)
520
+
521
+ # 4. Predict unpack locations
522
+ self.results['likely_unpack_locations'] = self.predict_unpack_locations(package_name or '<package>')
523
+
524
+ # 5. Generate recommendations
525
+ self.generate_recommendations()
526
+
527
+ return self.results
528
+
529
+ def generate_recommendations(self):
530
+ """Generate recommendations based on findings"""
531
+ recs = []
532
+
533
+ if self.results['stub_dex_detected']:
534
+ recs.append({
535
+ 'priority': 'HIGH',
536
+ 'action': 'Dynamic Analysis Required',
537
+ 'details': 'Stub DEX detected - real DEX is encrypted/hidden and will be unpacked at runtime'
538
+ })
539
+
540
+ recs.append({
541
+ 'priority': 'HIGH',
542
+ 'action': 'Monitor Runtime Unpacking',
543
+ 'details': 'Use Frida hooks to intercept DexFile.loadDex, DexClassLoader, and file writes'
544
+ })
545
+
546
+ if self.results['encrypted_payloads']:
547
+ high_entropy = [p for p in self.results['encrypted_payloads'] if p.get('entropy', 0) > 7.5]
548
+ if high_entropy:
549
+ recs.append({
550
+ 'priority': 'HIGH',
551
+ 'action': 'Extract Encrypted Payloads',
552
+ 'details': f'Found {len(high_entropy)} high-entropy files that may contain encrypted DEX'
553
+ })
554
+
555
+ if self.results['suspicious_files']:
556
+ # Check if DPT-shell was detected
557
+ dpt_detected = any('DPT-Shell' in f.get('finding', '') for f in self.results['suspicious_files'])
558
+
559
+ if dpt_detected:
560
+ recs.append({
561
+ 'priority': 'HIGH',
562
+ 'action': 'DPT-Shell Protection Detected',
563
+ 'details': 'libdpt.so found with suspicious strings - check artifacts for encrypted zip filename and paths'
564
+ })
565
+
566
+ recs.append({
567
+ 'priority': 'MEDIUM',
568
+ 'action': 'Analyze Native Libraries',
569
+ 'details': 'Protection libraries detected - analyze for DEX loading mechanisms'
570
+ })
571
+
572
+ recs.append({
573
+ 'priority': 'HIGH',
574
+ 'action': 'Setup Runtime Monitoring',
575
+ 'details': 'Monitor predicted unpack locations during app execution'
576
+ })
577
+
578
+ recs.append({
579
+ 'priority': 'MEDIUM',
580
+ 'action': 'Use Frida DEX Dumper',
581
+ 'details': 'Run scripts/frida/frida_dex_dump.js to capture unpacked DEX from memory'
582
+ })
583
+
584
+ self.results['recommendations'] = recs
585
+
586
+ def main():
587
+ import argparse
588
+
589
+ parser = argparse.ArgumentParser(description='Hunt for encrypted DEX payloads in APK')
590
+ parser.add_argument('apk_path', help='Path to APK file')
591
+ parser.add_argument('output_json', help='Output JSON file')
592
+ parser.add_argument('package_name', nargs='?', default=None, help='Package name')
593
+ parser.add_argument('--brief', action='store_true', help='Brief console output')
594
+
595
+ args = parser.parse_args()
596
+
597
+ hunter = DEXPayloadHunter(args.apk_path)
598
+ results = hunter.analyze(args.package_name)
599
+
600
+ # Save results
601
+ with open(args.output_json, 'w') as f:
602
+ json.dump(results, f, indent=2)
603
+
604
+ # Print summary (brief or detailed)
605
+ if args.brief:
606
+ # Brief output - just key findings
607
+ if results['stub_dex_detected']:
608
+ print("āš ļø Stub DEX detected - real DEX is hidden", file=sys.stderr)
609
+ if results['encrypted_payloads']:
610
+ print(f"šŸ”’ Found {len(results['encrypted_payloads'])} encrypted payload(s)", file=sys.stderr)
611
+ if results['suspicious_files']:
612
+ so_with_refs = [f for f in results['suspicious_files'] if 'references' in f]
613
+ if so_with_refs:
614
+ print(f"šŸ” Found {len(so_with_refs)} .so file(s) with zip references", file=sys.stderr)
615
+ else:
616
+ # Detailed output
617
+ print(f"\n{'='*60}")
618
+ print(f"DEX PAYLOAD HUNTER RESULTS")
619
+ print(f"{'='*60}")
620
+ print(f"APK: {Path(args.apk_path).name}\n")
621
+
622
+ if results['stub_dex_detected']:
623
+ print("āš ļø STUB DEX DETECTED - Real DEX is hidden!")
624
+ for indicator in results['protection_indicators']:
625
+ print(f"\nšŸ“¦ {indicator['file']}:")
626
+ for ind in indicator['indicators']:
627
+ print(f" • {ind}")
628
+
629
+ if results['encrypted_payloads']:
630
+ print(f"\nšŸ”’ ENCRYPTED PAYLOADS FOUND: {len(results['encrypted_payloads'])}")
631
+ for payload in results['encrypted_payloads'][:5]: # Show first 5
632
+ print(f"\n šŸ“„ {payload['location']}")
633
+ print(f" Type: {payload['type']}")
634
+ print(f" Size: {payload['size_kb']:.1f} KB")
635
+ print(f" Entropy: {payload['entropy']}")
636
+ for ind in payload['indicators']:
637
+ print(f" • {ind}")
638
+
639
+ if results['suspicious_files']:
640
+ print(f"\nšŸ” SUSPICIOUS FILES: {len(results['suspicious_files'])}")
641
+ for sf in results['suspicious_files']:
642
+ # Highlight DPT-Shell binaries differently
643
+ if sf.get('category') == 'DPT-Shell Binary Code':
644
+ print(f"\n {sf['finding']}")
645
+ print(f" šŸ“‚ Location: {sf['library']}")
646
+ print(f" šŸ“ Size: {sf['size']:.2f} KB")
647
+ print(f" āš ļø Importance: {sf.get('importance', 'HIGH')}")
648
+ if sf.get('description'):
649
+ print(f" šŸ“ {sf['description']}")
650
+ else:
651
+ print(f" • {sf['library']}: {sf['finding']}")
652
+
653
+ # Show DPT-shell specific artifacts
654
+ if 'artifacts' in sf:
655
+ print(f" šŸ“‹ Artifacts Extracted:")
656
+ artifacts = sf['artifacts']
657
+
658
+ # CRITICAL: Show .dex files first
659
+ if 'dex_files' in artifacts:
660
+ print(f" šŸŽÆ DEX files ({len(artifacts['dex_files'])} found):")
661
+ for item in artifacts['dex_files'][:5]:
662
+ print(f" - {item}")
663
+
664
+ # CRITICAL: Show .zip files
665
+ if 'zip_files' in artifacts:
666
+ print(f" šŸ”’ ZIP files ({len(artifacts['zip_files'])} found):")
667
+ for item in artifacts['zip_files'][:5]:
668
+ print(f" - {item}")
669
+
670
+ # Show .jar files
671
+ if 'jar_files' in artifacts:
672
+ print(f" šŸ“¦ JAR files ({len(artifacts['jar_files'])} found):")
673
+ for item in artifacts['jar_files'][:3]:
674
+ print(f" - {item}")
675
+
676
+ # Show .dat files
677
+ if 'dat_files' in artifacts:
678
+ print(f" šŸ“„ DAT files ({len(artifacts['dat_files'])} found):")
679
+ for item in artifacts['dat_files'][:3]:
680
+ print(f" - {item}")
681
+
682
+ # CRITICAL: Show code_cache references
683
+ if 'code_cache_refs' in artifacts:
684
+ print(f" šŸ“ Code cache references:")
685
+ for item in artifacts['code_cache_refs'][:3]:
686
+ print(f" - {item}")
687
+
688
+ # Show file paths
689
+ if 'file_paths' in artifacts:
690
+ print(f" šŸ“‚ File paths ({len(artifacts['file_paths'])} found):")
691
+ for item in artifacts['file_paths'][:3]:
692
+ print(f" - {item}")
693
+
694
+ # Show encryption references
695
+ if 'encryption_refs' in artifacts:
696
+ print(f" šŸ” Encryption references:")
697
+ for item in artifacts['encryption_refs'][:3]:
698
+ print(f" - {item}")
699
+
700
+ # Show shell references
701
+ if 'shell_refs' in artifacts:
702
+ print(f" 🐚 Shell references ({len(artifacts['shell_refs'])} found):")
703
+ for item in artifacts['shell_refs'][:3]:
704
+ print(f" - {item}")
705
+
706
+ # Show ClassLoader references
707
+ if 'class_loader_refs' in artifacts:
708
+ print(f" šŸ”§ ClassLoader references:")
709
+ for item in artifacts['class_loader_refs'][:3]:
710
+ print(f" - {item}")
711
+
712
+ # Show general zip references (for non-DPT files)
713
+ elif 'references' in sf:
714
+ for ref in sf['references'][:3]: # Show first 3
715
+ print(f" - {ref.get('pattern', 'N/A')} at offset {ref.get('offset', 0)}")
716
+ if 'context' in ref:
717
+ print(f" Context: {ref['context'][:50]}")
718
+
719
+ print(f"\nšŸ“ PREDICTED UNPACK LOCATIONS:")
720
+ for loc in results['likely_unpack_locations']:
721
+ if loc['priority'] == 'HIGH':
722
+ print(f" šŸ”“ {loc['path']}")
723
+ print(f" {loc['description']}")
724
+
725
+ print(f"\nšŸ’” RECOMMENDATIONS:")
726
+ for rec in results['recommendations']:
727
+ priority_icon = 'šŸ”“' if rec['priority'] == 'HIGH' else '🟔'
728
+ print(f" {priority_icon} {rec['action']}")
729
+ print(f" {rec['details']}")
730
+
731
+ print(f"\n{'='*60}\n")
732
+
733
+ if __name__ == '__main__':
734
+ main()