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.
@@ -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