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,485 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Encrypted Payload Locator
4
+ Finds the actual source location of encrypted DEX payloads within the APK
5
+ before they are unpacked to code_cache at runtime.
6
+ """
7
+
8
+ import sys
9
+ import zipfile
10
+ import struct
11
+ from pathlib import Path
12
+ import argparse
13
+ import json
14
+
15
+ class EncryptedPayloadLocator:
16
+ def __init__(self, apk_path, target_filename=None):
17
+ self.apk_path = apk_path
18
+ self.target_filename = target_filename
19
+ self.results = {
20
+ 'found_payloads': [],
21
+ 'zip_signatures': [],
22
+ 'embedded_in_so': [],
23
+ 'obfuscated_assets': [],
24
+ 'suspicious_files': []
25
+ }
26
+
27
+ def search_by_magic_bytes(self, zf):
28
+ """Search for ZIP magic bytes (PK signature) in all files"""
29
+ print("\n🔍 Searching for ZIP magic bytes (PK signatures)...")
30
+
31
+ # ZIP file signatures
32
+ ZIP_SIGNATURES = [
33
+ b'PK\x03\x04', # Local file header
34
+ b'PK\x05\x06', # End of central directory
35
+ b'PK\x01\x02', # Central directory file header
36
+ ]
37
+
38
+ for file_info in zf.filelist:
39
+ try:
40
+ data = zf.read(file_info.filename)
41
+
42
+ # Check for ZIP signatures
43
+ for sig in ZIP_SIGNATURES:
44
+ offset = 0
45
+ while True:
46
+ offset = data.find(sig, offset)
47
+ if offset == -1:
48
+ break
49
+
50
+ # Found a signature - check if it's a valid ZIP
51
+ if sig == b'PK\x03\x04': # Local file header
52
+ # Try to read ZIP header
53
+ try:
54
+ if offset + 30 <= len(data):
55
+ header = data[offset:offset+30]
56
+ # Parse local file header
57
+ signature, version, flags, method, mod_time, mod_date, crc32, comp_size, uncomp_size, fname_len, extra_len = struct.unpack('<IHHHHIIIHH', header)
58
+
59
+ if fname_len > 0 and fname_len < 256: # Reasonable filename length
60
+ # Extract filename
61
+ if offset + 30 + fname_len <= len(data):
62
+ zip_fname = data[offset+30:offset+30+fname_len].decode('utf-8', errors='ignore')
63
+
64
+ # Check if this matches our target
65
+ match_target = False
66
+ if self.target_filename and self.target_filename.lower() in zip_fname.lower():
67
+ match_target = True
68
+
69
+ result = {
70
+ 'location': file_info.filename,
71
+ 'offset': offset,
72
+ 'embedded_filename': zip_fname,
73
+ 'compressed_size': comp_size,
74
+ 'uncompressed_size': uncomp_size,
75
+ 'compression_method': method,
76
+ 'matches_target': match_target
77
+ }
78
+
79
+ self.results['zip_signatures'].append(result)
80
+
81
+ if match_target:
82
+ print(f"\n✅ FOUND TARGET: {self.target_filename}")
83
+ print(f" Location: {file_info.filename}")
84
+ print(f" Offset: 0x{offset:08x} ({offset} bytes)")
85
+ print(f" Embedded filename: {zip_fname}")
86
+ print(f" Compressed: {comp_size} bytes")
87
+ print(f" Uncompressed: {uncomp_size} bytes")
88
+ except Exception as e:
89
+ pass
90
+
91
+ offset += 1
92
+
93
+ except Exception as e:
94
+ continue
95
+
96
+ def search_obfuscated_assets(self, zf):
97
+ """Search assets folder for obfuscated files that might be the payload"""
98
+ print("\n🔍 Searching assets folder for obfuscated files...")
99
+
100
+ asset_files = [f for f in zf.filelist if f.filename.startswith('assets/')]
101
+
102
+ for asset in asset_files:
103
+ try:
104
+ data = zf.read(asset.filename)
105
+
106
+ # Check if it's actually a ZIP file (starts with PK)
107
+ if data[:2] == b'PK':
108
+ is_valid_zip = True
109
+ embedded_files = []
110
+
111
+ # Try to read as ZIP
112
+ try:
113
+ import io
114
+ with zipfile.ZipFile(io.BytesIO(data)) as inner_zip:
115
+ embedded_files = [f.filename for f in inner_zip.filelist]
116
+
117
+ # Check if any embedded file contains .dex
118
+ has_dex = any('.dex' in f.lower() for f in embedded_files)
119
+
120
+ result = {
121
+ 'location': asset.filename,
122
+ 'size': len(data),
123
+ 'is_zip': True,
124
+ 'embedded_files': embedded_files,
125
+ 'contains_dex': has_dex,
126
+ 'potential_payload': has_dex
127
+ }
128
+
129
+ self.results['obfuscated_assets'].append(result)
130
+
131
+ print(f"\n📦 Found ZIP in assets: {asset.filename}")
132
+ print(f" Size: {len(data)} bytes")
133
+ print(f" Contains {len(embedded_files)} file(s)")
134
+ if has_dex:
135
+ print(f" ⚠️ Contains DEX files!")
136
+ print(f" Files: {', '.join(embedded_files[:5])}")
137
+
138
+ except zipfile.BadZipFile:
139
+ # Not a valid ZIP despite PK signature
140
+ result = {
141
+ 'location': asset.filename,
142
+ 'size': len(data),
143
+ 'is_zip': False,
144
+ 'has_pk_signature': True,
145
+ 'potential_encrypted': True
146
+ }
147
+ self.results['suspicious_files'].append(result)
148
+ print(f"\n🔒 Suspicious file: {asset.filename}")
149
+ print(f" Has PK signature but not a valid ZIP (likely encrypted)")
150
+ print(f" Size: {len(data)} bytes")
151
+
152
+ # Check for files with suspicious names or no extension
153
+ elif '.' not in Path(asset.filename).name or len(Path(asset.filename).name) > 20:
154
+ # Check entropy (high entropy = encrypted/compressed)
155
+ entropy = self.calculate_entropy(data[:min(1024, len(data))])
156
+
157
+ if entropy > 7.0: # High entropy
158
+ result = {
159
+ 'location': asset.filename,
160
+ 'size': len(data),
161
+ 'entropy': entropy,
162
+ 'suspicious': True
163
+ }
164
+ self.results['suspicious_files'].append(result)
165
+ print(f"\n⚠️ High entropy file: {asset.filename}")
166
+ print(f" Size: {len(data)} bytes")
167
+ print(f" Entropy: {entropy:.2f} (likely encrypted)")
168
+
169
+ except Exception as e:
170
+ continue
171
+
172
+ def search_in_native_libs(self, zf):
173
+ """Search for embedded payloads in .so files"""
174
+ print("\n🔍 Searching native libraries for embedded payloads...")
175
+
176
+ so_files = [f for f in zf.filelist if f.filename.endswith('.so')]
177
+
178
+ for so_file in so_files:
179
+ try:
180
+ data = zf.read(so_file.filename)
181
+
182
+ # Search for PK signatures
183
+ pk_offsets = []
184
+ offset = 0
185
+ while True:
186
+ offset = data.find(b'PK\x03\x04', offset)
187
+ if offset == -1:
188
+ break
189
+ pk_offsets.append(offset)
190
+ offset += 1
191
+
192
+ if pk_offsets:
193
+ # Found embedded ZIP
194
+ for pk_offset in pk_offsets:
195
+ # Try to extract the embedded ZIP
196
+ try:
197
+ # Find the end of the ZIP (look for end of central directory signature)
198
+ end_offset = data.find(b'PK\x05\x06', pk_offset)
199
+ if end_offset != -1:
200
+ # Extract ZIP data
201
+ end_offset += 22 # Size of end of central directory record
202
+ zip_data = data[pk_offset:end_offset]
203
+
204
+ # Try to read it
205
+ import io
206
+ try:
207
+ with zipfile.ZipFile(io.BytesIO(zip_data)) as embedded_zip:
208
+ embedded_files = [f.filename for f in embedded_zip.filelist]
209
+
210
+ result = {
211
+ 'location': so_file.filename,
212
+ 'offset': pk_offset,
213
+ 'size': len(zip_data),
214
+ 'embedded_files': embedded_files,
215
+ 'contains_dex': any('.dex' in f.lower() for f in embedded_files)
216
+ }
217
+
218
+ self.results['embedded_in_so'].append(result)
219
+
220
+ print(f"\n📦 Found embedded ZIP in: {so_file.filename}")
221
+ print(f" Offset: 0x{pk_offset:08x} ({pk_offset} bytes)")
222
+ print(f" Size: {len(zip_data)} bytes")
223
+ print(f" Files: {', '.join(embedded_files)}")
224
+
225
+ except zipfile.BadZipFile:
226
+ pass
227
+ except Exception as e:
228
+ continue
229
+
230
+ except Exception as e:
231
+ continue
232
+
233
+ def search_by_name_pattern(self, zf):
234
+ """Search for files matching the target filename pattern"""
235
+ if not self.target_filename:
236
+ return
237
+
238
+ print(f"\n🔍 Searching for files matching: {self.target_filename}")
239
+
240
+ # Also search where the filename is referenced (in .so files)
241
+ target_bytes = self.target_filename.encode()
242
+
243
+ # Find all .so files that reference this filename
244
+ so_references = []
245
+ for file_info in zf.filelist:
246
+ if file_info.filename.endswith('.so'):
247
+ try:
248
+ data = zf.read(file_info.filename)
249
+ if target_bytes in data:
250
+ offset = data.find(target_bytes)
251
+ so_references.append({
252
+ 'so_file': file_info.filename,
253
+ 'offset': offset
254
+ })
255
+ print(f"\n📌 Found reference in: {file_info.filename}")
256
+ print(f" Offset: 0x{offset:08x}")
257
+ except:
258
+ continue
259
+
260
+ # If found in .so files, the actual encrypted data is likely in assets
261
+ if so_references:
262
+ print(f"\n💡 The filename '{self.target_filename}' is used at RUNTIME")
263
+ print(f" The ENCRYPTED source is likely in assets/ with an obfuscated name")
264
+ print(f"\n🔍 Checking for large suspicious assets...")
265
+
266
+ # Find large files in assets (potential encrypted payloads)
267
+ large_assets = []
268
+ for file_info in zf.filelist:
269
+ if file_info.filename.startswith('assets/') and file_info.file_size > 1_000_000: # > 1MB
270
+ # Check if it has weird name
271
+ filename = Path(file_info.filename).name
272
+ is_suspicious = (
273
+ '.' not in filename or # No extension
274
+ filename.count(filename[0]) > len(filename) // 2 or # Repetitive chars
275
+ not any(c.isalnum() and c.islower() for c in filename) # No lowercase letters
276
+ )
277
+
278
+ if is_suspicious:
279
+ large_assets.append(file_info)
280
+
281
+ data = zf.read(file_info.filename)
282
+ entropy = self.calculate_entropy(data[:min(4096, len(data))])
283
+
284
+ result = {
285
+ 'location': file_info.filename,
286
+ 'size': len(data),
287
+ 'compressed_size': file_info.compress_size,
288
+ 'entropy': entropy,
289
+ 'likely_encrypted': entropy > 5.0,
290
+ 'target_runtime_name': self.target_filename,
291
+ 'referenced_in': [ref['so_file'] for ref in so_references]
292
+ }
293
+
294
+ self.results['found_payloads'].append(result)
295
+
296
+ print(f"\n✅ POTENTIAL SOURCE: {file_info.filename}")
297
+ print(f" Size: {len(data):,} bytes ({len(data)/1024/1024:.2f} MB)")
298
+ print(f" Entropy: {entropy:.2f}")
299
+ if entropy > 5.0:
300
+ print(f" ⚠️ High entropy - likely encrypted!")
301
+ print(f" Runtime name: {self.target_filename}")
302
+ print(f" Referenced in: {', '.join(ref['so_file'] for ref in so_references)}")
303
+
304
+ # Original search for exact filename
305
+ # Extract base name without extension
306
+ target_base = self.target_filename.replace('.zip', '').replace('.jar', '').replace('.dat', '')
307
+
308
+ for file_info in zf.filelist:
309
+ # Check if filename contains target pattern
310
+ if target_base.lower() in file_info.filename.lower():
311
+ data = zf.read(file_info.filename)
312
+
313
+ result = {
314
+ 'location': file_info.filename,
315
+ 'size': len(data),
316
+ 'compressed_size': file_info.compress_size,
317
+ 'exact_match': file_info.filename.endswith(self.target_filename)
318
+ }
319
+
320
+ self.results['found_payloads'].append(result)
321
+
322
+ print(f"\n✅ FOUND: {file_info.filename}")
323
+ print(f" Size: {len(data)} bytes")
324
+ print(f" Compressed: {file_info.compress_size} bytes")
325
+
326
+ # Check if it's a valid ZIP
327
+ if data[:2] == b'PK':
328
+ print(f" ✓ Valid ZIP signature")
329
+
330
+ # Try to list contents
331
+ try:
332
+ import io
333
+ with zipfile.ZipFile(io.BytesIO(data)) as inner_zip:
334
+ files = [f.filename for f in inner_zip.filelist]
335
+ print(f" Contains: {', '.join(files)}")
336
+ except:
337
+ print(f" ⚠️ Has PK signature but cannot read (encrypted?)")
338
+ else:
339
+ entropy = self.calculate_entropy(data[:min(1024, len(data))])
340
+ print(f" Entropy: {entropy:.2f}")
341
+
342
+ def calculate_entropy(self, data):
343
+ """Calculate Shannon entropy of data"""
344
+ if not data:
345
+ return 0
346
+
347
+ import math
348
+ from collections import Counter
349
+
350
+ counter = Counter(data)
351
+ length = len(data)
352
+ entropy = 0
353
+
354
+ for count in counter.values():
355
+ p = count / length
356
+ entropy -= p * math.log2(p)
357
+
358
+ return entropy
359
+
360
+ def extract_payload(self, output_path, location):
361
+ """Extract the found payload to a file"""
362
+ print(f"\n💾 Extracting payload from: {location}")
363
+
364
+ try:
365
+ with zipfile.ZipFile(self.apk_path, 'r') as zf:
366
+ data = zf.read(location)
367
+
368
+ with open(output_path, 'wb') as f:
369
+ f.write(data)
370
+
371
+ print(f"✅ Extracted to: {output_path}")
372
+ print(f" Size: {len(data)} bytes")
373
+
374
+ # Try to analyze
375
+ if data[:2] == b'PK':
376
+ print(f" Type: ZIP archive")
377
+ try:
378
+ import io
379
+ with zipfile.ZipFile(io.BytesIO(data)) as inner_zip:
380
+ files = [f.filename for f in inner_zip.filelist]
381
+ print(f" Contains {len(files)} file(s):")
382
+ for f in files:
383
+ print(f" • {f}")
384
+ except:
385
+ print(f" ⚠️ Encrypted or corrupted ZIP")
386
+ else:
387
+ entropy = self.calculate_entropy(data[:min(1024, len(data))])
388
+ print(f" Entropy: {entropy:.2f}")
389
+ if entropy > 7.5:
390
+ print(f" Likely encrypted")
391
+
392
+ return True
393
+
394
+ except Exception as e:
395
+ print(f"❌ Error: {e}")
396
+ return False
397
+
398
+ def search_all(self):
399
+ """Perform all searches"""
400
+ print(f"\n{'='*70}")
401
+ print(f"🔍 ENCRYPTED PAYLOAD LOCATOR")
402
+ print(f"{'='*70}")
403
+ print(f"APK: {self.apk_path}")
404
+ if self.target_filename:
405
+ print(f"Target: {self.target_filename}")
406
+ print(f"{'='*70}")
407
+
408
+ with zipfile.ZipFile(self.apk_path, 'r') as zf:
409
+ # Search by name pattern first
410
+ if self.target_filename:
411
+ self.search_by_name_pattern(zf)
412
+
413
+ # Search in assets
414
+ self.search_obfuscated_assets(zf)
415
+
416
+ # Search in native libraries
417
+ self.search_in_native_libs(zf)
418
+
419
+ # Search by magic bytes (comprehensive but slow)
420
+ # self.search_by_magic_bytes(zf) # Uncomment for deep search
421
+
422
+ return self.results
423
+
424
+
425
+ def main():
426
+ parser = argparse.ArgumentParser(
427
+ description='Find encrypted DEX payload source location in APK',
428
+ formatter_class=argparse.RawDescriptionHelpFormatter,
429
+ epilog="""
430
+ Examples:
431
+ # Find specific encrypted zip file
432
+ python3 find_encrypted_payload.py app.apk --target i11111i111.zip
433
+
434
+ # Search for all suspicious files
435
+ python3 find_encrypted_payload.py app.apk
436
+
437
+ # Extract found payload
438
+ python3 find_encrypted_payload.py app.apk --target i11111i111.zip --extract payload.zip
439
+
440
+ # Export results to JSON
441
+ python3 find_encrypted_payload.py app.apk --json results.json
442
+ """
443
+ )
444
+
445
+ parser.add_argument('apk', help='APK file to analyze')
446
+ parser.add_argument('--target', '-t', help='Target filename to search for (e.g., i11111i111.zip)')
447
+ parser.add_argument('--extract', '-e', help='Extract found payload to this path')
448
+ parser.add_argument('--json', '-j', help='Export results to JSON file')
449
+
450
+ args = parser.parse_args()
451
+
452
+ if not Path(args.apk).exists():
453
+ print(f"Error: APK not found: {args.apk}", file=sys.stderr)
454
+ sys.exit(1)
455
+
456
+ # Create locator
457
+ locator = EncryptedPayloadLocator(args.apk, args.target)
458
+
459
+ # Search
460
+ results = locator.search_all()
461
+
462
+ # Summary
463
+ print(f"\n{'='*70}")
464
+ print(f"📊 SEARCH SUMMARY")
465
+ print(f"{'='*70}")
466
+ print(f"Direct matches: {len(results['found_payloads'])}")
467
+ print(f"Obfuscated assets: {len(results['obfuscated_assets'])}")
468
+ print(f"Embedded in .so: {len(results['embedded_in_so'])}")
469
+ print(f"Suspicious files: {len(results['suspicious_files'])}")
470
+ print(f"{'='*70}\n")
471
+
472
+ # Extract if requested
473
+ if args.extract and results['found_payloads']:
474
+ location = results['found_payloads'][0]['location']
475
+ locator.extract_payload(args.extract, location)
476
+
477
+ # Export JSON
478
+ if args.json:
479
+ with open(args.json, 'w') as f:
480
+ json.dump(results, f, indent=2)
481
+ print(f"✅ Results exported to: {args.json}")
482
+
483
+
484
+ if __name__ == '__main__':
485
+ main()
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ APK Header Fixer
4
+ Attempts to fix corrupted or manipulated ZIP headers in APK files
5
+ """
6
+
7
+ import sys
8
+ import struct
9
+ import shutil
10
+ from pathlib import Path
11
+
12
+ class APKHeaderFixer:
13
+ def __init__(self, input_apk, output_apk):
14
+ self.input_apk = Path(input_apk)
15
+ self.output_apk = Path(output_apk)
16
+
17
+ def fix_local_file_headers(self, data):
18
+ """Remove encryption flags from local file headers"""
19
+ output = bytearray(data)
20
+ offset = 0
21
+
22
+ while offset < len(output) - 30:
23
+ # Look for local file header signature (PK\x03\x04)
24
+ if output[offset:offset+4] == b'PK\x03\x04':
25
+ # Get flag bytes at offset + 6
26
+ flags = struct.unpack('<H', output[offset+6:offset+8])[0]
27
+
28
+ # Clear encryption bit (bit 0)
29
+ new_flags = flags & ~0x0001
30
+
31
+ # Clear data descriptor bit (bit 3) if needed
32
+ new_flags = new_flags & ~0x0008
33
+
34
+ # Update flags
35
+ output[offset+6:offset+8] = struct.pack('<H', new_flags)
36
+
37
+ # Get filename and extra field lengths
38
+ filename_len = struct.unpack('<H', output[offset+26:offset+28])[0]
39
+ extra_len = struct.unpack('<H', output[offset+28:offset+30])[0]
40
+
41
+ # Move to next header
42
+ offset += 30 + filename_len + extra_len
43
+ else:
44
+ offset += 1
45
+
46
+ return bytes(output)
47
+
48
+ def fix_central_directory_headers(self, data):
49
+ """Remove encryption flags from central directory headers"""
50
+ output = bytearray(data)
51
+ offset = 0
52
+
53
+ while offset < len(output) - 46:
54
+ # Look for central directory header signature (PK\x01\x02)
55
+ if output[offset:offset+4] == b'PK\x01\x02':
56
+ # Get flag bytes at offset + 8
57
+ flags = struct.unpack('<H', output[offset+8:offset+10])[0]
58
+
59
+ # Clear encryption bit (bit 0)
60
+ new_flags = flags & ~0x0001
61
+
62
+ # Clear data descriptor bit (bit 3) if needed
63
+ new_flags = new_flags & ~0x0008
64
+
65
+ # Update flags
66
+ output[offset+8:offset+10] = struct.pack('<H', new_flags)
67
+
68
+ # Get lengths
69
+ filename_len = struct.unpack('<H', output[offset+28:offset+30])[0]
70
+ extra_len = struct.unpack('<H', output[offset+30:offset+32])[0]
71
+ comment_len = struct.unpack('<H', output[offset+32:offset+34])[0]
72
+
73
+ # Move to next header
74
+ offset += 46 + filename_len + extra_len + comment_len
75
+ else:
76
+ offset += 1
77
+
78
+ return bytes(output)
79
+
80
+ def remove_encryption_header(self, data):
81
+ """Remove encryption header if present"""
82
+ # Some protectors add fake encryption headers
83
+ # Look for and remove them
84
+ output = bytearray(data)
85
+
86
+ # Search for encryption header patterns
87
+ # This is heuristic-based and may need adjustment
88
+ patterns = [
89
+ b'\x09\x99\x07', # Common encryption header
90
+ b'\x09\x99\x08',
91
+ ]
92
+
93
+ for pattern in patterns:
94
+ idx = output.find(pattern)
95
+ if idx != -1:
96
+ print(f"Found potential encryption header at offset {idx}")
97
+ # Remove the pattern (careful with this)
98
+ # output = output[:idx] + output[idx+len(pattern):]
99
+
100
+ return bytes(output)
101
+
102
+ def fix_apk(self):
103
+ """Main fixing routine"""
104
+ try:
105
+ # Read entire APK
106
+ with open(self.input_apk, 'rb') as f:
107
+ data = f.read()
108
+
109
+ print(f"Original APK size: {len(data)} bytes")
110
+
111
+ # Apply fixes
112
+ print("Fixing local file headers...")
113
+ data = self.fix_local_file_headers(data)
114
+
115
+ print("Fixing central directory headers...")
116
+ data = self.fix_central_directory_headers(data)
117
+
118
+ print("Checking for encryption headers...")
119
+ data = self.remove_encryption_header(data)
120
+
121
+ # Write fixed APK
122
+ with open(self.output_apk, 'wb') as f:
123
+ f.write(data)
124
+
125
+ print(f"Fixed APK size: {len(data)} bytes")
126
+ print(f"Fixed APK saved to: {self.output_apk}")
127
+
128
+ return True
129
+
130
+ except Exception as e:
131
+ print(f"Error fixing APK: {e}")
132
+ import traceback
133
+ traceback.print_exc()
134
+ return False
135
+
136
+ def main():
137
+ if len(sys.argv) < 3:
138
+ print("Usage: fix_apk_headers.py <input_apk> <output_apk>")
139
+ sys.exit(1)
140
+
141
+ input_apk = sys.argv[1]
142
+ output_apk = sys.argv[2]
143
+
144
+ fixer = APKHeaderFixer(input_apk, output_apk)
145
+
146
+ if fixer.fix_apk():
147
+ print("APK headers fixed successfully!")
148
+ print("Try decompiling the fixed APK now.")
149
+ else:
150
+ print("Failed to fix APK headers.")
151
+ sys.exit(1)
152
+
153
+ if __name__ == '__main__':
154
+ main()