android-persistence 1.0.0__py3-none-any.whl
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.
- android_persistence/__init__.py +29 -0
- android_persistence/data_parser.py +325 -0
- android_persistence/defensive_mitigations.py +378 -0
- android_persistence/persistence_detector.py +441 -0
- android_persistence/report_generator.py +326 -0
- android_persistence/utils/__init__.py +3 -0
- android_persistence/utils/hex_analyzer.py +162 -0
- android_persistence/utils/logger.py +74 -0
- android_persistence/utils/manifest_parser.py +164 -0
- android_persistence/utils/signature_matcher.py +217 -0
- android_persistence-1.0.0.dist-info/METADATA +484 -0
- android_persistence-1.0.0.dist-info/RECORD +16 -0
- android_persistence-1.0.0.dist-info/WHEEL +5 -0
- android_persistence-1.0.0.dist-info/entry_points.txt +2 -0
- android_persistence-1.0.0.dist-info/licenses/LICENSE +21 -0
- android_persistence-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Android Persistence Research Framework - Core analysis package.
|
|
3
|
+
|
|
4
|
+
This package provides comprehensive tools for analyzing Android persistence
|
|
5
|
+
mechanisms, including detection, analysis, and mitigation strategies.
|
|
6
|
+
|
|
7
|
+
Version: 1.0.0
|
|
8
|
+
License: MIT
|
|
9
|
+
Author: Zyekh Abdul Qadir Jailani
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__version__ = "1.0.0"
|
|
13
|
+
__author__ = "Zyekh Abdul Qadir Jailani"
|
|
14
|
+
|
|
15
|
+
from .persistence_detector import PersistenceDetector, PersistenceFinding, PersistenceType, SeverityLevel
|
|
16
|
+
from .defensive_mitigations import MitigationStrategies
|
|
17
|
+
from .data_parser import DataParser, APKMetadata
|
|
18
|
+
from .report_generator import ReportGenerator
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"PersistenceDetector",
|
|
22
|
+
"PersistenceFinding",
|
|
23
|
+
"PersistenceType",
|
|
24
|
+
"SeverityLevel",
|
|
25
|
+
"MitigationStrategies",
|
|
26
|
+
"DataParser",
|
|
27
|
+
"APKMetadata",
|
|
28
|
+
"ReportGenerator",
|
|
29
|
+
]
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data Parser - Binary and APK data extraction and parsing utilities.
|
|
3
|
+
|
|
4
|
+
This module provides comprehensive parsing capabilities for Android APK files,
|
|
5
|
+
including ZIP archive inspection, resource extraction, and binary format handling.
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- APK file structure parsing
|
|
9
|
+
- Resource extraction (strings, assets, etc.)
|
|
10
|
+
- Binary format identification
|
|
11
|
+
- Manifest XML parsing and extraction
|
|
12
|
+
- Certificate chain extraction
|
|
13
|
+
|
|
14
|
+
Author: Security Research Team
|
|
15
|
+
License: Apache-2.0
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import zipfile
|
|
19
|
+
import struct
|
|
20
|
+
import io
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Dict, List, Optional, Tuple, Any
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
import logging
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class APKMetadata:
|
|
29
|
+
"""Metadata extracted from APK file."""
|
|
30
|
+
filename: str
|
|
31
|
+
size: int
|
|
32
|
+
file_count: int
|
|
33
|
+
dex_count: int
|
|
34
|
+
lib_count: int
|
|
35
|
+
resource_count: int
|
|
36
|
+
cert_sha256: Optional[str] = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DataParser:
|
|
40
|
+
"""
|
|
41
|
+
Main data parsing engine for APK analysis.
|
|
42
|
+
|
|
43
|
+
Handles extraction and parsing of various data formats found in APK files,
|
|
44
|
+
including DEX bytecode, XML manifests, and resource files.
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
>>> parser = DataParser("sample.apk")
|
|
48
|
+
>>> metadata = parser.get_metadata()
|
|
49
|
+
>>> manifest = parser.extract_manifest()
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self, apk_path: str):
|
|
53
|
+
"""
|
|
54
|
+
Initialize data parser.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
apk_path: Path to APK file
|
|
58
|
+
"""
|
|
59
|
+
self.apk_path = Path(apk_path)
|
|
60
|
+
self.logger = logging.getLogger(__name__)
|
|
61
|
+
self.apk_zip = None
|
|
62
|
+
self.metadata = None
|
|
63
|
+
|
|
64
|
+
def open(self) -> bool:
|
|
65
|
+
"""
|
|
66
|
+
Open and validate APK file.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
True if APK is valid and opened successfully
|
|
70
|
+
"""
|
|
71
|
+
try:
|
|
72
|
+
self.apk_zip = zipfile.ZipFile(self.apk_path, 'r')
|
|
73
|
+
self.logger.info(f"Opened APK: {self.apk_path.name}")
|
|
74
|
+
return True
|
|
75
|
+
except zipfile.BadZipFile:
|
|
76
|
+
self.logger.error("Invalid APK file format")
|
|
77
|
+
return False
|
|
78
|
+
except Exception as e:
|
|
79
|
+
self.logger.error(f"Error opening APK: {str(e)}")
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
def close(self) -> None:
|
|
83
|
+
"""Close APK file."""
|
|
84
|
+
if self.apk_zip:
|
|
85
|
+
self.apk_zip.close()
|
|
86
|
+
|
|
87
|
+
def get_metadata(self) -> Optional[APKMetadata]:
|
|
88
|
+
"""
|
|
89
|
+
Extract APK metadata.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
APKMetadata object with file information
|
|
93
|
+
"""
|
|
94
|
+
if not self.apk_zip:
|
|
95
|
+
self.open()
|
|
96
|
+
|
|
97
|
+
if not self.apk_zip:
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
file_list = self.apk_zip.namelist()
|
|
102
|
+
dex_files = [f for f in file_list if f.endswith('.dex')]
|
|
103
|
+
lib_files = [f for f in file_list if f.startswith('lib/')]
|
|
104
|
+
resource_files = [f for f in file_list if f.startswith('res/')]
|
|
105
|
+
|
|
106
|
+
metadata = APKMetadata(
|
|
107
|
+
filename=self.apk_path.name,
|
|
108
|
+
size=self.apk_path.stat().st_size,
|
|
109
|
+
file_count=len(file_list),
|
|
110
|
+
dex_count=len(dex_files),
|
|
111
|
+
lib_count=len(lib_files),
|
|
112
|
+
resource_count=len(resource_files),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
self.metadata = metadata
|
|
116
|
+
return metadata
|
|
117
|
+
except Exception as e:
|
|
118
|
+
self.logger.error(f"Error extracting metadata: {str(e)}")
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
def list_files(self, prefix: str = None) -> List[str]:
|
|
122
|
+
"""
|
|
123
|
+
List files in APK.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
prefix: Optional prefix to filter files
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
List of file paths
|
|
130
|
+
"""
|
|
131
|
+
if not self.apk_zip:
|
|
132
|
+
self.open()
|
|
133
|
+
|
|
134
|
+
if not self.apk_zip:
|
|
135
|
+
return []
|
|
136
|
+
|
|
137
|
+
all_files = self.apk_zip.namelist()
|
|
138
|
+
if prefix:
|
|
139
|
+
return [f for f in all_files if f.startswith(prefix)]
|
|
140
|
+
return all_files
|
|
141
|
+
|
|
142
|
+
def extract_manifest(self) -> Optional[bytes]:
|
|
143
|
+
"""
|
|
144
|
+
Extract AndroidManifest.xml (binary format).
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Raw manifest bytes or None if not found
|
|
148
|
+
"""
|
|
149
|
+
if not self.apk_zip:
|
|
150
|
+
self.open()
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
manifest_data = self.apk_zip.read('AndroidManifest.xml')
|
|
154
|
+
self.logger.debug(f"Extracted AndroidManifest.xml ({len(manifest_data)} bytes)")
|
|
155
|
+
return manifest_data
|
|
156
|
+
except KeyError:
|
|
157
|
+
self.logger.warning("AndroidManifest.xml not found in APK")
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
def extract_dex_files(self, output_dir: str = None) -> List[bytes]:
|
|
161
|
+
"""
|
|
162
|
+
Extract all DEX files from APK.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
output_dir: Optional directory to save DEX files
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
List of DEX file contents
|
|
169
|
+
"""
|
|
170
|
+
if not self.apk_zip:
|
|
171
|
+
self.open()
|
|
172
|
+
|
|
173
|
+
dex_files = []
|
|
174
|
+
try:
|
|
175
|
+
for file_info in self.apk_zip.filelist:
|
|
176
|
+
if file_info.filename.endswith('.dex'):
|
|
177
|
+
dex_data = self.apk_zip.read(file_info.filename)
|
|
178
|
+
dex_files.append(dex_data)
|
|
179
|
+
|
|
180
|
+
if output_dir:
|
|
181
|
+
output_path = Path(output_dir) / file_info.filename
|
|
182
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
183
|
+
with open(output_path, 'wb') as f:
|
|
184
|
+
f.write(dex_data)
|
|
185
|
+
self.logger.debug(f"Saved {file_info.filename}")
|
|
186
|
+
|
|
187
|
+
self.logger.info(f"Extracted {len(dex_files)} DEX files")
|
|
188
|
+
return dex_files
|
|
189
|
+
except Exception as e:
|
|
190
|
+
self.logger.error(f"Error extracting DEX files: {str(e)}")
|
|
191
|
+
return []
|
|
192
|
+
|
|
193
|
+
def extract_resources(self, output_dir: str = None) -> Dict[str, bytes]:
|
|
194
|
+
"""
|
|
195
|
+
Extract resource files from APK.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
output_dir: Optional directory to save resources
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Dictionary mapping resource paths to content
|
|
202
|
+
"""
|
|
203
|
+
if not self.apk_zip:
|
|
204
|
+
self.open()
|
|
205
|
+
|
|
206
|
+
resources = {}
|
|
207
|
+
try:
|
|
208
|
+
for file_info in self.apk_zip.filelist:
|
|
209
|
+
if file_info.filename.startswith('res/'):
|
|
210
|
+
resource_data = self.apk_zip.read(file_info.filename)
|
|
211
|
+
resources[file_info.filename] = resource_data
|
|
212
|
+
|
|
213
|
+
if output_dir:
|
|
214
|
+
output_path = Path(output_dir) / file_info.filename
|
|
215
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
216
|
+
with open(output_path, 'wb') as f:
|
|
217
|
+
f.write(resource_data)
|
|
218
|
+
|
|
219
|
+
self.logger.info(f"Extracted {len(resources)} resource files")
|
|
220
|
+
return resources
|
|
221
|
+
except Exception as e:
|
|
222
|
+
self.logger.error(f"Error extracting resources: {str(e)}")
|
|
223
|
+
return {}
|
|
224
|
+
|
|
225
|
+
def extract_native_libs(self, output_dir: str = None) -> Dict[str, bytes]:
|
|
226
|
+
"""
|
|
227
|
+
Extract native libraries (SO files).
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
output_dir: Optional directory to save libraries
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Dictionary mapping library paths to content
|
|
234
|
+
"""
|
|
235
|
+
if not self.apk_zip:
|
|
236
|
+
self.open()
|
|
237
|
+
|
|
238
|
+
libraries = {}
|
|
239
|
+
try:
|
|
240
|
+
for file_info in self.apk_zip.filelist:
|
|
241
|
+
if file_info.filename.startswith('lib/') and file_info.filename.endswith('.so'):
|
|
242
|
+
lib_data = self.apk_zip.read(file_info.filename)
|
|
243
|
+
libraries[file_info.filename] = lib_data
|
|
244
|
+
|
|
245
|
+
if output_dir:
|
|
246
|
+
output_path = Path(output_dir) / file_info.filename
|
|
247
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
248
|
+
with open(output_path, 'wb') as f:
|
|
249
|
+
f.write(lib_data)
|
|
250
|
+
|
|
251
|
+
self.logger.info(f"Extracted {len(libraries)} native libraries")
|
|
252
|
+
return libraries
|
|
253
|
+
except Exception as e:
|
|
254
|
+
self.logger.error(f"Error extracting native libraries: {str(e)}")
|
|
255
|
+
return {}
|
|
256
|
+
|
|
257
|
+
def get_certificates(self) -> List[Dict[str, Any]]:
|
|
258
|
+
"""
|
|
259
|
+
Extract certificate information from APK.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
List of certificate dictionaries
|
|
263
|
+
"""
|
|
264
|
+
if not self.apk_zip:
|
|
265
|
+
self.open()
|
|
266
|
+
|
|
267
|
+
certificates = []
|
|
268
|
+
try:
|
|
269
|
+
cert_files = [f for f in self.apk_zip.namelist()
|
|
270
|
+
if f.startswith('META-INF/') and f.endswith('.RSA')]
|
|
271
|
+
|
|
272
|
+
for cert_file in cert_files:
|
|
273
|
+
cert_data = self.apk_zip.read(cert_file)
|
|
274
|
+
certificates.append({
|
|
275
|
+
'filename': cert_file,
|
|
276
|
+
'size': len(cert_data),
|
|
277
|
+
'data': cert_data[:32], # First 32 bytes for identification
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
self.logger.debug(f"Found {len(certificates)} certificate files")
|
|
281
|
+
return certificates
|
|
282
|
+
except Exception as e:
|
|
283
|
+
self.logger.error(f"Error extracting certificates: {str(e)}")
|
|
284
|
+
return []
|
|
285
|
+
|
|
286
|
+
def get_apk_size_breakdown(self) -> Dict[str, int]:
|
|
287
|
+
"""
|
|
288
|
+
Get breakdown of APK size by component type.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
Dictionary with size information per component
|
|
292
|
+
"""
|
|
293
|
+
if not self.apk_zip:
|
|
294
|
+
self.open()
|
|
295
|
+
|
|
296
|
+
breakdown = {
|
|
297
|
+
'dex': 0,
|
|
298
|
+
'resources': 0,
|
|
299
|
+
'libraries': 0,
|
|
300
|
+
'manifest': 0,
|
|
301
|
+
'metadata': 0,
|
|
302
|
+
'other': 0,
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
try:
|
|
306
|
+
for file_info in self.apk_zip.filelist:
|
|
307
|
+
size = file_info.file_size
|
|
308
|
+
|
|
309
|
+
if file_info.filename.endswith('.dex'):
|
|
310
|
+
breakdown['dex'] += size
|
|
311
|
+
elif file_info.filename.startswith('res/'):
|
|
312
|
+
breakdown['resources'] += size
|
|
313
|
+
elif file_info.filename.startswith('lib/'):
|
|
314
|
+
breakdown['libraries'] += size
|
|
315
|
+
elif file_info.filename == 'AndroidManifest.xml':
|
|
316
|
+
breakdown['manifest'] += size
|
|
317
|
+
elif file_info.filename.startswith('META-INF/'):
|
|
318
|
+
breakdown['metadata'] += size
|
|
319
|
+
else:
|
|
320
|
+
breakdown['other'] += size
|
|
321
|
+
|
|
322
|
+
return breakdown
|
|
323
|
+
except Exception as e:
|
|
324
|
+
self.logger.error(f"Error calculating size breakdown: {str(e)}")
|
|
325
|
+
return breakdown
|