DiskAnalyzer 0.0.1__tar.gz

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,485 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ ###################
5
+ # This package implements multiples libraries and tools to parse, analyze
6
+ # and extract informations from disk on the live system.
7
+ # Copyright (C) 2025 DiskAnalyzer
8
+
9
+ # This program is free software: you can redistribute it and/or modify
10
+ # it under the terms of the GNU General Public License as published by
11
+ # the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+
14
+ # This program is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU General Public License for more details.
18
+
19
+ # You should have received a copy of the GNU General Public License
20
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
21
+ ###################
22
+
23
+ """
24
+ This package implements multiples libraries and tools to parse, analyze
25
+ and extract informations from disk on the live system.
26
+ """
27
+
28
+ __version__ = "0.0.1"
29
+ __author__ = "Maurice Lambert"
30
+ __author_email__ = "mauricelambert434@gmail.com"
31
+ __maintainer__ = "Maurice Lambert"
32
+ __maintainer_email__ = "mauricelambert434@gmail.com"
33
+ __description__ = """
34
+ This package implements multiples libraries and tools to parse, analyze
35
+ and extract informations from disk on the live system.
36
+ """
37
+ __url__ = "https://github.com/mauricelambert/DiskAnalyzer"
38
+
39
+ __all__ = ["GPTHeader", "GPTPartitionEntry", "MBRHeader", "MBRPartitionEntry", "Partition", "disk_parsing", "get_main_partition"]
40
+
41
+ __license__ = "GPL-3.0 License"
42
+ __copyright__ = """
43
+ DiskAnalyzer Copyright (C) 2025 Maurice Lambert
44
+ This program comes with ABSOLUTELY NO WARRANTY.
45
+ This is free software, and you are welcome to redistribute it
46
+ under certain conditions.
47
+ """
48
+ copyright = __copyright__
49
+ license = __license__
50
+
51
+ print(copyright)
52
+
53
+ from ctypes import (
54
+ LittleEndianStructure,
55
+ c_uint8,
56
+ c_uint32,
57
+ c_char,
58
+ c_uint64,
59
+ c_ubyte,
60
+ c_wchar,
61
+ )
62
+ from typing import Tuple, List, Union
63
+ from dataclasses import dataclass
64
+ from _io import BufferedReader
65
+ from enum import Enum
66
+ from sys import exit
67
+
68
+ SECTOR_SIZE = 512
69
+ DRIVE_PATH = r"\\.\PhysicalDrive0"
70
+
71
+
72
+ class PartitionStatus(Enum):
73
+ """
74
+ Enum for disk partitions status (bootable or not bootable).
75
+ """
76
+
77
+ ACTIVE = 0x80
78
+ INACTIVE = 0x00
79
+
80
+
81
+ class MbrPartitionType(Enum):
82
+ """
83
+ Enum for disk partitions type.
84
+ """
85
+
86
+ EMPTY = 0x00
87
+ FAT12 = 0x01
88
+ FAT16 = 0x04
89
+ FAT32 = 0x0B
90
+ FAT32_LBA = 0x0C
91
+ NTFS = 0x07
92
+ LINUX_SWAP = 0x82
93
+ EXT2 = 0x83
94
+ EXT3 = 0x83
95
+ EXT4 = 0x83
96
+ LINUX_LVM = 0x8E
97
+ WINDOWS_RE = 0x27
98
+ GPT_PROTECTIVE = 0xEE
99
+
100
+
101
+ class GptPartitionType(Enum):
102
+ """
103
+ Enum for disk partitions type.
104
+ """
105
+
106
+ EMPTY = "00000000-0000-0000-0000-000000000000"
107
+ PARTITION_SYSTEM_GUID = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
108
+ LEGACY_MBR_PARTITION_GUID = "024DEE41-33E7-11D3-9D69-0008C781F39F"
109
+ PARTITION_MSFT_RESERVED_GUID = "E3C9E316-0B5C-4DB8-817D-F92DF00215AE"
110
+ PARTITION_BASIC_DATA_GUID = "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"
111
+ PARTITION_LINUX_FILE_SYSTEM_DATA_GUID = (
112
+ "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
113
+ )
114
+ PARTITION_LINUX_RAID_GUID = "A19D880F-05FC-4D3B-A006-743F0F84911E"
115
+ PARTITION_LINUX_SWAP_GUID = "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F"
116
+ PARTITION_LINUX_LVM_GUID = "E6D6D379-F507-44C2-A23C-238F2A3DF928"
117
+ PARTITION_U_BOOT_ENVIRONMENT = "3DE21764-95BD-54BD-A5C3-4ABE786F38A8"
118
+ WINDOWS_RECOVERY_TOOLS = "DE94BBA4-06D1-4D40-A16A-BFD50179D6AC"
119
+
120
+
121
+ status_dict = {status.value: status.name for status in PartitionStatus}
122
+ mbr_type_dict = {
123
+ part_type.value: part_type.name for part_type in MbrPartitionType
124
+ }
125
+ gpt_type_dict = {
126
+ part_type.value: part_type.name for part_type in GptPartitionType
127
+ }
128
+
129
+ gpt_attributes = {
130
+ 0x0000000000000001: "Platform required",
131
+ 0x0000000000000002: "EFI firmware ignore partition",
132
+ 0x0000000000000004: "Legacy BIOS bootable",
133
+ 0x0000000000000008: "Reserved for future use",
134
+ 0x0100000000000000: "Successful boot flag",
135
+ 0x0010000000000000: "Tries remaining",
136
+ 0x0020000000000000: "Tries remaining",
137
+ 0x0040000000000000: "Tries remaining",
138
+ 0x0001000000000000: "Priority low",
139
+ 0x0002000000000000: "Priority medium",
140
+ 0x0004000000000000: "Priority high",
141
+ 0x0008000000000000: "Priority highest",
142
+ 0x0100000000000000: "Successful boot flag",
143
+ 0x1000000000000000: "Read-only",
144
+ 0x2000000000000000: "Shadow copy",
145
+ 0x4000000000000000: "Hidden",
146
+ 0x8000000000000000: "No drive letter",
147
+ }
148
+
149
+
150
+ @dataclass
151
+ class Partition:
152
+ start_sector: int
153
+ end_sector: int
154
+ size: int # In sectors
155
+
156
+
157
+ class MBRPartitionEntry(LittleEndianStructure):
158
+ """
159
+ This class defines the MBR partition structure.
160
+ """
161
+
162
+ _pack_ = 1
163
+ _fields_ = [
164
+ ("status", c_uint8),
165
+ ("chs_first", c_uint8 * 3),
166
+ ("type", c_uint8),
167
+ ("chs_last", c_uint8 * 3),
168
+ ("lba_start", c_uint32),
169
+ ("total_sectors", c_uint32),
170
+ ]
171
+
172
+
173
+ class MBRHeader(LittleEndianStructure):
174
+ """
175
+ This class defines the MBR structure.
176
+ """
177
+
178
+ _pack_ = 1
179
+ _fields_ = [
180
+ ("bootloader", c_ubyte * 446),
181
+ ("partitions", MBRPartitionEntry * 4),
182
+ ("signature", c_ubyte * 2),
183
+ ]
184
+
185
+ def to_partition(self) -> Union[Partition, None]:
186
+ """
187
+ This function makes partition from MBR.
188
+ """
189
+
190
+ for entry in self.partitions:
191
+ if (
192
+ entry.type != 0x00
193
+ and entry.type != 0x05
194
+ and entry.type != 0x0F
195
+ ):
196
+ start = entry.lba_start
197
+ size = entry.total_sectors
198
+ end = start + size - 1
199
+ if size > 2097152:
200
+ return Partition(
201
+ start_sector=start, end_sector=end, size=size
202
+ )
203
+ return None
204
+
205
+
206
+ class GPTHeader(LittleEndianStructure):
207
+ """
208
+ This class defines the GPT structure.
209
+ """
210
+
211
+ _pack_ = 1
212
+ _fields_ = [
213
+ ("signature", c_char * 8),
214
+ ("revision", c_uint32),
215
+ ("header_size", c_uint32),
216
+ ("header_crc32", c_uint32),
217
+ ("reserved", c_uint32),
218
+ ("current_lba", c_uint64),
219
+ ("backup_lba", c_uint64),
220
+ ("first_usable_lba", c_uint64),
221
+ ("last_usable_lba", c_uint64),
222
+ ("disk_guid", c_ubyte * 16),
223
+ ("partition_entry_lba", c_uint64),
224
+ ("num_part_entries", c_uint32),
225
+ ("part_entry_size", c_uint32),
226
+ ("part_array_crc32", c_uint32),
227
+ ]
228
+
229
+ def to_partition(self) -> Union[Partition, None]:
230
+ """
231
+ This function makes partition from GPT.
232
+ """
233
+
234
+ for entry in self.partitions:
235
+ guid_type = format_guid(entry.part_type_guid).upper()
236
+ if (
237
+ guid_type == "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"
238
+ or guid_type == "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
239
+ ):
240
+ start = entry.start_lba
241
+ end = entry.end_lba
242
+ size = end - start + 1
243
+ return Partition(start_sector=start, end_sector=end, size=size)
244
+ return None
245
+
246
+
247
+ class GPTPartitionEntry(LittleEndianStructure):
248
+ """
249
+ This class defines the GPT partition structure.
250
+ """
251
+
252
+ _pack_ = 1
253
+ _fields_ = [
254
+ ("part_type_guid", c_ubyte * 16),
255
+ ("unique_part_guid", c_ubyte * 16),
256
+ ("start_lba", c_uint64),
257
+ ("end_lba", c_uint64),
258
+ ("attributes", c_uint64),
259
+ ("part_name", c_wchar * 36), # 72 bytes, UTF-16 encoded
260
+ ]
261
+
262
+
263
+ data_type = type(c_ubyte * 16)
264
+
265
+
266
+ def format_guid(guid_bytes: data_type) -> str:
267
+ """
268
+ This function returns a GUID string from data.
269
+ """
270
+
271
+ data = bytes(guid_bytes)
272
+ return (
273
+ f"{int.from_bytes(data[0:4], 'little'):08X}-"
274
+ f"{int.from_bytes(data[4:6], 'little'):04X}-"
275
+ f"{int.from_bytes(data[6:8], 'little'):04X}-"
276
+ f"{data[8:10].hex()}-{data[10:].hex()}"
277
+ )
278
+
279
+
280
+ def is_gpt_signature(mbr: MBRHeader, sector_data: bytes) -> bool:
281
+ """
282
+ This function checks the GPT magic bytes
283
+ to detect the start sector structure.
284
+ """
285
+
286
+ return any(
287
+ entry.type == 0xEE for entry in mbr.partitions
288
+ ) and sector_data.startswith(b"EFI PART")
289
+
290
+
291
+ def parse_mbr(mbr_data: bytes) -> MBRHeader:
292
+ """
293
+ This function parses the MBR data.
294
+ """
295
+
296
+ return MBRHeader.from_buffer_copy(mbr_data)
297
+
298
+
299
+ def print_mbr_analysis(mbr: MBRHeader) -> None:
300
+ """
301
+ This function prints informations about MBR.
302
+ """
303
+
304
+ if bytes(mbr.signature) != b"\x55\xaa":
305
+ print("[-] Invalid MBR signature")
306
+ return None
307
+
308
+ print("[+] MBR Detected")
309
+
310
+ line_length = 40
311
+ bootloader = memoryview(bytes(mbr.bootloader))
312
+
313
+ print(" Bootloader")
314
+ for index in range(0, len(bootloader), line_length):
315
+ print(" ", bootloader[index : index + line_length].hex())
316
+
317
+ for index, entry in enumerate(mbr.partitions):
318
+ if entry.type != 0:
319
+ print(f" Partition {index + 1}:")
320
+ print(
321
+ f" Status : 0x{entry.status:02X} ({status_dict[entry.status]})"
322
+ )
323
+ print(
324
+ f" Type : 0x{entry.type:02X} ({mbr_type_dict[entry.type]})"
325
+ )
326
+ print(f" Start LBA : {entry.lba_start}")
327
+ print(
328
+ f" Total Sectors: {entry.total_sectors} B ({(entry.total_sectors * 512) / (1024 ** 2):.2f} MB)"
329
+ )
330
+
331
+ print(" Boot Signature")
332
+ print(" ", bytes(mbr.signature).hex())
333
+
334
+
335
+ def gpt_partitions_size(partition: GPTPartitionEntry) -> float:
336
+ """
337
+ This function returns the size in MB for a GPT partition
338
+ """
339
+
340
+ return (
341
+ (partition.end_lba - partition.start_lba) * SECTOR_SIZE / (1024 * 1024)
342
+ )
343
+
344
+
345
+ def parse_gpt(file: BufferedReader, gpt_data: bytes) -> GPTHeader:
346
+ """
347
+ This function parses the GPT data.
348
+ """
349
+
350
+ gpt_header = GPTHeader.from_buffer_copy(gpt_data)
351
+
352
+ gpt_header.partitions = []
353
+ file.seek(gpt_header.partition_entry_lba * SECTOR_SIZE)
354
+ for _ in range(gpt_header.num_part_entries):
355
+ entry_data = file.read(128)
356
+ if len(entry_data) == 128 and entry_data != b"\0" * 128:
357
+ entry = GPTPartitionEntry.from_buffer_copy(entry_data)
358
+ gpt_header.partitions.append(entry)
359
+ entry.flags = [
360
+ attr
361
+ for value, attr in gpt_attributes.items()
362
+ if entry.attributes & value
363
+ ]
364
+
365
+ return gpt_header
366
+
367
+
368
+ def print_gpt_analysis(gpt_header: GPTHeader) -> None:
369
+ """
370
+ This function prints informations about GPT headers and partitions.
371
+ """
372
+
373
+ if gpt_header.signature != b"EFI PART":
374
+ print("[-] Invalid GPT signature")
375
+ return None
376
+
377
+ print("[+] GPT Detected")
378
+ print(f" GPT Signature : {gpt_header.signature.decode()}")
379
+ print(f" GPT Disk GUID : {format_guid(gpt_header.disk_guid)}")
380
+ print(f" Partition Count : {gpt_header.num_part_entries}")
381
+ print(f" Partition Entry Size: {gpt_header.part_entry_size}")
382
+ print(f" Partition Table LBA : {gpt_header.partition_entry_lba}")
383
+
384
+ print("\n[+] Partition Entries:")
385
+ for index, entry in enumerate(gpt_header.partitions):
386
+ part_name = entry.part_name.strip() if entry.part_name else "No Name"
387
+ part_guid = format_guid(entry.unique_part_guid)
388
+ type_guid = format_guid(entry.part_type_guid).upper()
389
+ print(f" Partition {index+1}:")
390
+ print(f" Type GUID : {type_guid} ({gpt_type_dict[type_guid]})")
391
+ print(f" Unique GUID : {part_guid}")
392
+ print(f" Start LBA : {entry.start_lba}")
393
+ print(f" End LBA : {entry.end_lba}")
394
+ print(f" Size in MB : {gpt_partitions_size(entry):.2f} MB")
395
+ print(
396
+ f" Attributes : {hex(entry.attributes)} ({entry.attributes})"
397
+ )
398
+ if entry.attributes:
399
+ print(f" {', '.join(entry.flags)}")
400
+ print(f" Partition Name: {part_name}")
401
+
402
+
403
+ def disk_parsing(
404
+ keep_open: bool = False,
405
+ ) -> Union[
406
+ Tuple[
407
+ Union[PermissionError, Exception, MBRHeader, GPTHeader], BufferedReader
408
+ ],
409
+ PermissionError,
410
+ Exception,
411
+ MBRHeader,
412
+ GPTHeader,
413
+ ]:
414
+ """
415
+ This function returns the parsed structure or error and opened file if keep_open is True.
416
+ """
417
+
418
+ try:
419
+ file = open(DRIVE_PATH, "rb")
420
+ except PermissionError:
421
+ print("[-] Permission denied. Run as Administrator.")
422
+ return 1
423
+ except Exception as e:
424
+ print(f"[-] Error: {e}")
425
+ return 127
426
+
427
+ first_sector = file.read(SECTOR_SIZE)
428
+ second_sector = file.read(SECTOR_SIZE)
429
+
430
+ mbr = parse_mbr(first_sector)
431
+
432
+ if is_gpt_signature(mbr, second_sector):
433
+ return_value = parse_gpt(file, second_sector)
434
+ else:
435
+ return_value = parse_mbr(mbr)
436
+
437
+ if keep_open:
438
+ return return_value, file
439
+
440
+ file.close()
441
+ return return_value
442
+
443
+
444
+ def get_main_partition(
445
+ *args, **kwargs
446
+ ) -> Tuple[Partition, Union[None, BufferedReader]]:
447
+ """
448
+ This function returns the main partition.
449
+ """
450
+
451
+ file = None
452
+ data = disk_parsing(*args, **kwargs)
453
+
454
+ if isinstance(data, tuple) and isinstance(data[1], BufferedReader):
455
+ file = data[1]
456
+ data = data[0]
457
+
458
+ return data.to_partition(), file
459
+
460
+
461
+ def main() -> int:
462
+ """
463
+ The main function to starts the script from the command line.
464
+ """
465
+
466
+ value = disk_parsing()
467
+
468
+ if isinstance(value, PermissionError):
469
+ print("[-] Permission denied. Run as Administrator.")
470
+ return 1
471
+ elif isinstance(value, Exception):
472
+ print(f"[-] Error: {value}")
473
+ return 127
474
+ elif isinstance(value, GPTHeader):
475
+ print_gpt_analysis(value)
476
+ elif isinstance(value, MBRHeader):
477
+ print_mbr_analysis(value)
478
+ else:
479
+ return 127
480
+
481
+ return 0
482
+
483
+
484
+ if __name__ == "__main__":
485
+ exit(main())