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,406 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Native Library String Analyzer
|
|
4
|
+
Extracts and analyzes strings from .so files to find DEX/protection artifacts
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import zipfile
|
|
9
|
+
import re
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
import json
|
|
13
|
+
import argparse
|
|
14
|
+
|
|
15
|
+
class SOStringAnalyzer:
|
|
16
|
+
def __init__(self, verbose=False):
|
|
17
|
+
self.verbose = verbose
|
|
18
|
+
self.results = {}
|
|
19
|
+
|
|
20
|
+
def extract_strings(self, data, min_length=4, max_length=200):
|
|
21
|
+
"""Extract printable ASCII strings from binary data"""
|
|
22
|
+
strings = []
|
|
23
|
+
current_string = []
|
|
24
|
+
|
|
25
|
+
for byte in data:
|
|
26
|
+
if 32 <= byte < 127: # Printable ASCII range
|
|
27
|
+
current_string.append(chr(byte))
|
|
28
|
+
else:
|
|
29
|
+
if len(current_string) >= min_length:
|
|
30
|
+
string = ''.join(current_string)
|
|
31
|
+
if len(string) <= max_length: # Filter out extremely long strings
|
|
32
|
+
strings.append(string)
|
|
33
|
+
current_string = []
|
|
34
|
+
|
|
35
|
+
# Don't forget the last string
|
|
36
|
+
if len(current_string) >= min_length:
|
|
37
|
+
string = ''.join(current_string)
|
|
38
|
+
if len(string) <= max_length:
|
|
39
|
+
strings.append(string)
|
|
40
|
+
|
|
41
|
+
return strings
|
|
42
|
+
|
|
43
|
+
def categorize_strings(self, strings):
|
|
44
|
+
"""Categorize extracted strings by type and importance"""
|
|
45
|
+
categories = {
|
|
46
|
+
'dex_files': [], # .dex file references
|
|
47
|
+
'zip_files': [], # .zip file references
|
|
48
|
+
'jar_files': [], # .jar file references
|
|
49
|
+
'dat_files': [], # .dat file references
|
|
50
|
+
'apk_files': [], # .apk file references
|
|
51
|
+
'so_files': [], # .so file references
|
|
52
|
+
'code_cache_paths': [], # code_cache directory refs
|
|
53
|
+
'data_paths': [], # /data/data/ paths
|
|
54
|
+
'app_paths': [], # app-specific paths
|
|
55
|
+
'file_operations': [], # file I/O related strings
|
|
56
|
+
'encryption_keywords': [], # crypto-related keywords
|
|
57
|
+
'class_loaders': [], # ClassLoader references
|
|
58
|
+
'jni_methods': [], # JNI method names
|
|
59
|
+
'java_classes': [], # Java class references
|
|
60
|
+
'shell_keywords': [], # Shell/unpack keywords
|
|
61
|
+
'url_patterns': [], # URL-like patterns
|
|
62
|
+
'base64_like': [], # Base64-encoded looking strings
|
|
63
|
+
'hex_patterns': [], # Hexadecimal patterns
|
|
64
|
+
'package_names': [], # Android package names
|
|
65
|
+
'system_calls': [], # System call names
|
|
66
|
+
'library_calls': [], # Library function calls
|
|
67
|
+
'strings_of_interest': [] # Other suspicious strings
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for string in strings:
|
|
71
|
+
string_lower = string.lower()
|
|
72
|
+
|
|
73
|
+
# File extensions (HIGH PRIORITY)
|
|
74
|
+
if '.dex' in string_lower:
|
|
75
|
+
categories['dex_files'].append(string)
|
|
76
|
+
if '.zip' in string_lower:
|
|
77
|
+
categories['zip_files'].append(string)
|
|
78
|
+
if '.jar' in string_lower:
|
|
79
|
+
categories['jar_files'].append(string)
|
|
80
|
+
if '.dat' in string_lower:
|
|
81
|
+
categories['dat_files'].append(string)
|
|
82
|
+
if '.apk' in string_lower:
|
|
83
|
+
categories['apk_files'].append(string)
|
|
84
|
+
if '.so' in string_lower and '/' in string:
|
|
85
|
+
categories['so_files'].append(string)
|
|
86
|
+
|
|
87
|
+
# Path patterns (HIGH PRIORITY)
|
|
88
|
+
if 'code_cache' in string_lower or 'code-cache' in string_lower:
|
|
89
|
+
categories['code_cache_paths'].append(string)
|
|
90
|
+
if '/data/data/' in string_lower or '/data/app/' in string_lower:
|
|
91
|
+
categories['data_paths'].append(string)
|
|
92
|
+
if any(p in string_lower for p in ['/files/', '/cache/', '/app_dex/', '/app_', '/databases/']):
|
|
93
|
+
categories['app_paths'].append(string)
|
|
94
|
+
|
|
95
|
+
# File operations
|
|
96
|
+
if any(op in string_lower for op in ['fopen', 'fread', 'fwrite', 'fclose', 'open64', 'read', 'write', 'mkdir', 'rmdir']):
|
|
97
|
+
if len(string) < 50: # Avoid long false positives
|
|
98
|
+
categories['file_operations'].append(string)
|
|
99
|
+
|
|
100
|
+
# Encryption keywords (HIGH PRIORITY)
|
|
101
|
+
if any(kw in string_lower for kw in ['encrypt', 'decrypt', 'cipher', 'aes', 'des', 'rsa', 'crypto', 'md5', 'sha', 'key', 'iv', 'salt']):
|
|
102
|
+
if len(string) < 80:
|
|
103
|
+
categories['encryption_keywords'].append(string)
|
|
104
|
+
|
|
105
|
+
# ClassLoader patterns (HIGH PRIORITY)
|
|
106
|
+
if any(cl in string_lower for cl in ['classloader', 'dexclassloader', 'pathclassloader', 'basedexclassloader', 'inmemorydexclassloader']):
|
|
107
|
+
categories['class_loaders'].append(string)
|
|
108
|
+
|
|
109
|
+
# JNI methods
|
|
110
|
+
if string.startswith('JNI_') or string.startswith('Java_'):
|
|
111
|
+
categories['jni_methods'].append(string)
|
|
112
|
+
if any(jni in string for jni in ['JNIEnv', 'jmethodID', 'jclass', 'jobject', 'jstring']):
|
|
113
|
+
categories['jni_methods'].append(string)
|
|
114
|
+
|
|
115
|
+
# Java class patterns (com.*, android.*, etc.)
|
|
116
|
+
if re.match(r'^[a-z]+(\.[a-z][a-z0-9_]*)+', string):
|
|
117
|
+
if '/' not in string and len(string) < 100:
|
|
118
|
+
categories['java_classes'].append(string)
|
|
119
|
+
if 'dalvik/system/' in string_lower or 'android/app/' in string_lower:
|
|
120
|
+
categories['java_classes'].append(string)
|
|
121
|
+
|
|
122
|
+
# Shell/Protection keywords (HIGH PRIORITY)
|
|
123
|
+
if any(sh in string_lower for sh in ['shell', 'unpack', 'stub', 'proxy', 'wrapper', 'loader', 'attach', 'detach', 'hook']):
|
|
124
|
+
if len(string) < 60:
|
|
125
|
+
categories['shell_keywords'].append(string)
|
|
126
|
+
|
|
127
|
+
# URL patterns
|
|
128
|
+
if re.match(r'https?://', string_lower):
|
|
129
|
+
categories['url_patterns'].append(string)
|
|
130
|
+
|
|
131
|
+
# Base64-like patterns (long alphanumeric strings)
|
|
132
|
+
if len(string) > 20 and re.match(r'^[A-Za-z0-9+/=]+$', string):
|
|
133
|
+
if len(string) < 150:
|
|
134
|
+
categories['base64_like'].append(string)
|
|
135
|
+
|
|
136
|
+
# Hex patterns
|
|
137
|
+
if len(string) > 16 and re.match(r'^[0-9a-fA-F]+$', string):
|
|
138
|
+
if len(string) < 100:
|
|
139
|
+
categories['hex_patterns'].append(string)
|
|
140
|
+
|
|
141
|
+
# Package name patterns
|
|
142
|
+
if re.match(r'^com\.[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$', string_lower):
|
|
143
|
+
categories['package_names'].append(string)
|
|
144
|
+
|
|
145
|
+
# System calls
|
|
146
|
+
if any(sc in string for sc in ['syscall', 'ptrace', 'mmap', 'mprotect', 'dlopen', 'dlsym', 'pthread']):
|
|
147
|
+
if len(string) < 50:
|
|
148
|
+
categories['system_calls'].append(string)
|
|
149
|
+
|
|
150
|
+
# Library calls
|
|
151
|
+
if any(lc in string for lc in ['libc.so', 'libdl.so', 'liblog.so', 'libm.so', 'libz.so']):
|
|
152
|
+
categories['library_calls'].append(string)
|
|
153
|
+
|
|
154
|
+
# Remove empty categories and remove duplicates
|
|
155
|
+
result = {}
|
|
156
|
+
for category, items in categories.items():
|
|
157
|
+
if items:
|
|
158
|
+
result[category] = list(set(items)) # Remove duplicates
|
|
159
|
+
|
|
160
|
+
return result
|
|
161
|
+
|
|
162
|
+
def analyze_so_file(self, so_path, data):
|
|
163
|
+
"""Perform complete analysis on a single .so file"""
|
|
164
|
+
if self.verbose:
|
|
165
|
+
print(f"\n{'='*70}")
|
|
166
|
+
print(f"Analyzing: {so_path}")
|
|
167
|
+
print(f"Size: {len(data):,} bytes ({len(data)/1024:.2f} KB)")
|
|
168
|
+
print(f"{'='*70}")
|
|
169
|
+
|
|
170
|
+
# Extract strings
|
|
171
|
+
strings = self.extract_strings(data, min_length=4)
|
|
172
|
+
|
|
173
|
+
if self.verbose:
|
|
174
|
+
print(f"Extracted {len(strings)} strings")
|
|
175
|
+
|
|
176
|
+
# Categorize strings
|
|
177
|
+
categorized = self.categorize_strings(strings)
|
|
178
|
+
|
|
179
|
+
# Calculate statistics
|
|
180
|
+
stats = {
|
|
181
|
+
'total_strings': len(strings),
|
|
182
|
+
'categories_found': len(categorized),
|
|
183
|
+
'file_size_bytes': len(data),
|
|
184
|
+
'file_size_kb': len(data) / 1024
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
# Build result
|
|
188
|
+
result = {
|
|
189
|
+
'file': so_path,
|
|
190
|
+
'stats': stats,
|
|
191
|
+
'categories': categorized
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return result
|
|
195
|
+
|
|
196
|
+
def analyze_apk(self, apk_path, target_libs=None):
|
|
197
|
+
"""Analyze .so files from APK"""
|
|
198
|
+
if not Path(apk_path).exists():
|
|
199
|
+
print(f"Error: APK not found: {apk_path}", file=sys.stderr)
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
results = []
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
with zipfile.ZipFile(apk_path, 'r') as zf:
|
|
206
|
+
# Find all .so files
|
|
207
|
+
so_files = [f for f in zf.filelist if f.filename.endswith('.so')]
|
|
208
|
+
|
|
209
|
+
# Filter by target if specified
|
|
210
|
+
if target_libs:
|
|
211
|
+
so_files = [f for f in so_files if any(target in f.filename.lower() for target in target_libs)]
|
|
212
|
+
|
|
213
|
+
if not so_files:
|
|
214
|
+
print("No .so files found in APK", file=sys.stderr)
|
|
215
|
+
return None
|
|
216
|
+
|
|
217
|
+
print(f"\nFound {len(so_files)} .so file(s) to analyze")
|
|
218
|
+
|
|
219
|
+
for so_file in so_files:
|
|
220
|
+
try:
|
|
221
|
+
data = zf.read(so_file.filename)
|
|
222
|
+
result = self.analyze_so_file(so_file.filename, data)
|
|
223
|
+
results.append(result)
|
|
224
|
+
except Exception as e:
|
|
225
|
+
print(f"Error analyzing {so_file.filename}: {e}", file=sys.stderr)
|
|
226
|
+
|
|
227
|
+
except Exception as e:
|
|
228
|
+
print(f"Error reading APK: {e}", file=sys.stderr)
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
return results
|
|
232
|
+
|
|
233
|
+
def analyze_file(self, file_path):
|
|
234
|
+
"""Analyze a single .so file"""
|
|
235
|
+
if not Path(file_path).exists():
|
|
236
|
+
print(f"Error: File not found: {file_path}", file=sys.stderr)
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
with open(file_path, 'rb') as f:
|
|
241
|
+
data = f.read()
|
|
242
|
+
|
|
243
|
+
result = self.analyze_so_file(str(file_path), data)
|
|
244
|
+
return [result]
|
|
245
|
+
except Exception as e:
|
|
246
|
+
print(f"Error reading file: {e}", file=sys.stderr)
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
def print_results(self, results, show_all=False, limit=10):
|
|
250
|
+
"""Print analysis results in a readable format"""
|
|
251
|
+
if not results:
|
|
252
|
+
print("No results to display")
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
for result in results:
|
|
256
|
+
print(f"\n{'='*70}")
|
|
257
|
+
print(f"š¦ {result['file']}")
|
|
258
|
+
print(f"{'='*70}")
|
|
259
|
+
print(f"Size: {result['stats']['file_size_kb']:.2f} KB")
|
|
260
|
+
print(f"Total strings extracted: {result['stats']['total_strings']}")
|
|
261
|
+
print(f"Categories found: {result['stats']['categories_found']}\n")
|
|
262
|
+
|
|
263
|
+
categories = result['categories']
|
|
264
|
+
|
|
265
|
+
# Priority categories first
|
|
266
|
+
priority_categories = [
|
|
267
|
+
('dex_files', 'šÆ DEX Files', 'ā ļø CRITICAL'),
|
|
268
|
+
('zip_files', 'š ZIP Files', 'ā ļø CRITICAL'),
|
|
269
|
+
('jar_files', 'š¦ JAR Files', 'HIGH'),
|
|
270
|
+
('dat_files', 'š DAT Files', 'HIGH'),
|
|
271
|
+
('apk_files', 'š± APK Files', 'HIGH'),
|
|
272
|
+
('code_cache_paths', 'š Code Cache Paths', 'ā ļø CRITICAL'),
|
|
273
|
+
('data_paths', 'š Data Paths', 'HIGH'),
|
|
274
|
+
('encryption_keywords', 'š Encryption Keywords', 'HIGH'),
|
|
275
|
+
('class_loaders', 'š§ ClassLoaders', 'HIGH'),
|
|
276
|
+
('shell_keywords', 'š Shell/Protection Keywords', 'HIGH'),
|
|
277
|
+
]
|
|
278
|
+
|
|
279
|
+
# Show priority categories
|
|
280
|
+
for cat_key, cat_name, priority in priority_categories:
|
|
281
|
+
if cat_key in categories:
|
|
282
|
+
items = categories[cat_key]
|
|
283
|
+
print(f"\n{cat_name} [{priority}]: {len(items)} found")
|
|
284
|
+
display_limit = len(items) if show_all else min(limit, len(items))
|
|
285
|
+
for item in items[:display_limit]:
|
|
286
|
+
print(f" ⢠{item}")
|
|
287
|
+
if len(items) > display_limit:
|
|
288
|
+
print(f" ... and {len(items) - display_limit} more (use --all to show)")
|
|
289
|
+
|
|
290
|
+
# Other categories
|
|
291
|
+
other_categories = [
|
|
292
|
+
('jni_methods', 'āļø JNI Methods'),
|
|
293
|
+
('java_classes', 'ā Java Classes'),
|
|
294
|
+
('app_paths', 'š App Paths'),
|
|
295
|
+
('file_operations', 'š File Operations'),
|
|
296
|
+
('url_patterns', 'š URLs'),
|
|
297
|
+
('package_names', 'š¦ Package Names'),
|
|
298
|
+
('system_calls', 'ā” System Calls'),
|
|
299
|
+
('so_files', 'š SO Libraries'),
|
|
300
|
+
('base64_like', 'š¤ Base64-like Strings'),
|
|
301
|
+
('hex_patterns', 'š¢ Hex Patterns'),
|
|
302
|
+
]
|
|
303
|
+
|
|
304
|
+
if show_all or self.verbose:
|
|
305
|
+
for cat_key, cat_name in other_categories:
|
|
306
|
+
if cat_key in categories:
|
|
307
|
+
items = categories[cat_key]
|
|
308
|
+
print(f"\n{cat_name}: {len(items)} found")
|
|
309
|
+
display_limit = len(items) if show_all else min(5, len(items))
|
|
310
|
+
for item in items[:display_limit]:
|
|
311
|
+
print(f" ⢠{item}")
|
|
312
|
+
if len(items) > display_limit:
|
|
313
|
+
print(f" ... and {len(items) - display_limit} more")
|
|
314
|
+
|
|
315
|
+
def export_json(self, results, output_path):
|
|
316
|
+
"""Export results to JSON file"""
|
|
317
|
+
try:
|
|
318
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
319
|
+
json.dump(results, f, indent=2, ensure_ascii=False)
|
|
320
|
+
print(f"\nā
Results exported to: {output_path}")
|
|
321
|
+
except Exception as e:
|
|
322
|
+
print(f"Error exporting JSON: {e}", file=sys.stderr)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def main():
|
|
326
|
+
parser = argparse.ArgumentParser(
|
|
327
|
+
description='Native Library String Analyzer - Extract and analyze strings from .so files',
|
|
328
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
329
|
+
epilog="""
|
|
330
|
+
Examples:
|
|
331
|
+
# Analyze all .so files in APK
|
|
332
|
+
python3 so_string_analyzer.py app.apk
|
|
333
|
+
|
|
334
|
+
# Analyze specific libraries (DPT-shell)
|
|
335
|
+
python3 so_string_analyzer.py app.apk --target libdpt
|
|
336
|
+
|
|
337
|
+
# Analyze a single .so file
|
|
338
|
+
python3 so_string_analyzer.py lib/arm64-v8a/libdpt.so
|
|
339
|
+
|
|
340
|
+
# Show all strings (no limits)
|
|
341
|
+
python3 so_string_analyzer.py app.apk --all
|
|
342
|
+
|
|
343
|
+
# Export to JSON
|
|
344
|
+
python3 so_string_analyzer.py app.apk --json results.json
|
|
345
|
+
|
|
346
|
+
# Verbose output
|
|
347
|
+
python3 so_string_analyzer.py app.apk -v
|
|
348
|
+
"""
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
parser.add_argument('input', help='APK file or .so file to analyze')
|
|
352
|
+
parser.add_argument('--target', '-t', action='append', help='Target library name patterns (e.g., libdpt, libbangcle)')
|
|
353
|
+
parser.add_argument('--all', '-a', action='store_true', help='Show all strings (no limit)')
|
|
354
|
+
parser.add_argument('--limit', '-l', type=int, default=10, help='Limit items per category (default: 10)')
|
|
355
|
+
parser.add_argument('--json', '-j', help='Export results to JSON file')
|
|
356
|
+
parser.add_argument('--verbose', '-v', action='store_true', help='Verbose output')
|
|
357
|
+
parser.add_argument('--brief', '-b', action='store_true', help='Brief output (critical findings only)')
|
|
358
|
+
|
|
359
|
+
args = parser.parse_args()
|
|
360
|
+
|
|
361
|
+
analyzer = SOStringAnalyzer(verbose=args.verbose)
|
|
362
|
+
|
|
363
|
+
# Determine input type
|
|
364
|
+
input_path = Path(args.input)
|
|
365
|
+
|
|
366
|
+
if not input_path.exists():
|
|
367
|
+
print(f"Error: File not found: {args.input}", file=sys.stderr)
|
|
368
|
+
sys.exit(1)
|
|
369
|
+
|
|
370
|
+
# Analyze
|
|
371
|
+
if input_path.suffix.lower() == '.apk':
|
|
372
|
+
results = analyzer.analyze_apk(str(input_path), target_libs=args.target)
|
|
373
|
+
elif input_path.suffix.lower() == '.so':
|
|
374
|
+
results = analyzer.analyze_file(str(input_path))
|
|
375
|
+
else:
|
|
376
|
+
# Try to analyze as .so file anyway
|
|
377
|
+
results = analyzer.analyze_file(str(input_path))
|
|
378
|
+
|
|
379
|
+
if not results:
|
|
380
|
+
print("Analysis failed", file=sys.stderr)
|
|
381
|
+
sys.exit(1)
|
|
382
|
+
|
|
383
|
+
# Print results
|
|
384
|
+
if not args.brief:
|
|
385
|
+
analyzer.print_results(results, show_all=args.all, limit=args.limit)
|
|
386
|
+
else:
|
|
387
|
+
# Brief output - only critical findings
|
|
388
|
+
for result in results:
|
|
389
|
+
print(f"\n{result['file']}:")
|
|
390
|
+
critical_cats = ['dex_files', 'zip_files', 'code_cache_paths', 'encryption_keywords', 'shell_keywords']
|
|
391
|
+
for cat in critical_cats:
|
|
392
|
+
if cat in result['categories']:
|
|
393
|
+
items = result['categories'][cat]
|
|
394
|
+
print(f" {cat}: {len(items)} found")
|
|
395
|
+
for item in items[:3]:
|
|
396
|
+
print(f" - {item}")
|
|
397
|
+
|
|
398
|
+
# Export JSON if requested
|
|
399
|
+
if args.json:
|
|
400
|
+
analyzer.export_json(results, args.json)
|
|
401
|
+
|
|
402
|
+
print(f"\nā
Analysis complete!")
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
if __name__ == '__main__':
|
|
406
|
+
main()
|