Skip to content

Commit 770ac93

Browse files
authored
Merge pull request #135 from rowingdude/3.0.6.5
Critical fixes
2 parents 73162ea + 2db6aa2 commit 770ac93

File tree

3 files changed

+89
-30
lines changed

3 files changed

+89
-30
lines changed

src/analyzeMFT/cli.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,28 +55,38 @@ async def main():
5555
if not options.export_format:
5656
options.export_format = "csv"
5757

58-
59-
analyzer = MftAnalyzer(options.filename, options.output_file, options.debug, options.verbosity, options.compute_hashes, options.export_format)
60-
await analyzer.analyze()
61-
print(f"Analysis complete. Results written to {options.output_file}")
62-
6358
try:
64-
analyzer = MftAnalyzer(options.filename, options.output_file, options.debug, options.compute_hashes, options.export_format)
59+
analyzer = MftAnalyzer(options.filename, options.output_file, options.debug, options.verbosity, options.compute_hashes, options.export_format)
60+
6561
await analyzer.analyze()
62+
6663
print(f"Analysis complete. Results written to {options.output_file}")
64+
6765
except FileNotFoundError:
66+
6867
print(f"Error: The file '{options.filename}' was not found.")
6968
sys.exit(1)
69+
7070
except PermissionError:
71+
7172
print(f"Error: Permission denied when trying to read '{options.filename}' or write to '{options.output_file}'.")
7273
sys.exit(1)
74+
7375
except Exception as e:
76+
7477
print(f"An unexpected error occurred: {str(e)}")
78+
7579
if options.debug:
7680
import traceback
7781
traceback.print_exc()
82+
7883
sys.exit(1)
7984

8085

8186
if __name__ == "__main__":
82-
asyncio.run(main())
87+
try:
88+
asyncio.run(main())
89+
except KeyboardInterrupt:
90+
print("\nScript terminated by user.")
91+
except Exception as e:
92+
print(f"An unexpected error occurred: {e}")

src/analyzeMFT/mft_analyzer.py

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
import csv
33
import io
4+
import signal
45
import sys
56
import traceback
67
from typing import Dict, Set, List, Optional, Any
@@ -9,24 +10,20 @@
910
from .file_writers import FileWriters
1011

1112
class MftAnalyzer:
12-
13-
def __init__(self, mft_file: str, output_file: str, debug: bool = False, very_debug: bool = False,
14-
verbosity: int = 0, compute_hashes: bool = False, export_format: str = "csv") -> None:
13+
def __init__(self, mft_file: str, output_file: str, debug: int = 0, verbosity: int = 0,
14+
compute_hashes: bool = False, export_format: str = "csv") -> None:
1515
self.mft_file = mft_file
1616
self.output_file = output_file
1717
self.debug = debug
18-
self.very_debug = very_debug
19-
self.verbosity = verbosity
20-
self.compute_hashes = compute_hashes
21-
self.export_format = export_format
18+
self.verbosity = int(verbosity)
2219
self.compute_hashes = compute_hashes
2320
self.export_format = export_format
24-
self.mft_records = {}
25-
self.interrupt_flag = asyncio.Event()
26-
2721
self.csvfile = None
2822
self.csv_writer = None
29-
23+
self.interrupt_flag = asyncio.Event()
24+
self.setup_interrupt_handler()
25+
26+
self.mft_records = {}
3027
self.stats = {
3128
'total_records': 0,
3229
'active_records': 0,
@@ -41,12 +38,26 @@ def __init__(self, mft_file: str, output_file: str, debug: bool = False, very_de
4138
'unique_crc32': set(),
4239
})
4340

41+
def setup_interrupt_handler(self):
42+
def interrupt_handler(signum, frame):
43+
self.log("Interrupt received. Cleaning up...", 1)
44+
self.interrupt_flag.set()
45+
46+
if sys.platform == "win32": # Windows is evil ...
47+
import win32api
48+
win32api.SetConsoleCtrlHandler(lambda x: interrupt_handler(None, None), True)
49+
50+
else: # On a proper operating system ...
51+
signal.signal(signal.SIGINT, interrupt_handler)
52+
signal.signal(signal.SIGTERM, interrupt_handler)
53+
4454
def log(self, message: str, level: int = 0):
4555
if level <= self.debug or level <= self.verbosity:
4656
print(message)
4757

4858
async def analyze(self) -> None:
4959
try:
60+
self.log("Starting MFT analysis...", 1)
5061
self.initialize_csv_writer()
5162
await self.process_mft()
5263
await self.write_output()
@@ -57,6 +68,10 @@ async def analyze(self) -> None:
5768
finally:
5869
if self.csvfile:
5970
self.csvfile.close()
71+
if self.interrupt_flag.is_set():
72+
self.log("Analysis interrupted by user.", 1)
73+
else:
74+
self.log("Analysis complete.", 1)
6075
self.print_statistics()
6176

6277

@@ -65,12 +80,14 @@ async def process_mft(self) -> None:
6580
try:
6681
with open(self.mft_file, 'rb') as f:
6782
while not self.interrupt_flag.is_set():
68-
raw_record = f.read(MFT_RECORD_SIZE)
83+
raw_record = await self.read_record(f)
6984
if not raw_record:
7085
break
7186

7287
try:
88+
self.log(f"Processing record {self.stats['total_records']}", 2)
7389
record = MftRecord(raw_record, self.compute_hashes)
90+
self.log(f"Record parsed, recordnum: {record.recordnum}", 2)
7491
self.stats['total_records'] += 1
7592

7693
if record.flags & FILE_RECORD_IN_USE:
@@ -90,9 +107,14 @@ async def process_mft(self) -> None:
90107
if self.stats['total_records'] % 1000 == 0:
91108
await self.write_csv_block()
92109
self.mft_records.clear()
110+
111+
if self.interrupt_flag.is_set():
112+
self.log("Interrupt detected. Stopping processing.", 1)
113+
break
93114

94115
except Exception as e:
95116
self.log(f"Error processing record {self.stats['total_records']}: {str(e)}", 1)
117+
self.log(f"Raw record (first 100 bytes): {raw_record[:100].hex()}", 2)
96118
if self.debug >= 2:
97119
traceback.print_exc()
98120
continue
@@ -104,6 +126,8 @@ async def process_mft(self) -> None:
104126

105127
self.log(f"MFT processing complete. Total records processed: {self.stats['total_records']}", 0)
106128

129+
async def read_record(self, file):
130+
return file.read(MFT_RECORD_SIZE)
107131

108132
def handle_interrupt(self) -> None:
109133
if sys.platform == "win32":
@@ -146,19 +170,19 @@ async def write_csv_block(self) -> None:
146170
csv_row = [str(item) for item in csv_row]
147171

148172
self.csv_writer.writerow(csv_row)
149-
if self.very_debug:
173+
if self.debug:
150174
self.log(f"Wrote record {record.recordnum} to CSV", 2)
151175
except Exception as e:
152176
self.log(f"Error writing record {record.recordnum}: {str(e)}", 1)
153-
if self.very_debug:
177+
if self.debug:
154178
traceback.print_exc()
155179

156180
if self.csvfile:
157181
self.csvfile.flush()
158182
self.log(f"CSV block written. Current file size: {self.csvfile.tell() if self.csvfile else 0} bytes", 2)
159183
except Exception as e:
160184
self.log(f"Error in write_csv_block: {str(e)}", 0)
161-
if self.debug or self.very_debug:
185+
if self.debug:
162186
traceback.print_exc()
163187

164188

@@ -222,4 +246,10 @@ async def write_output(self) -> None:
222246
elif self.export_format == "excel":
223247
await FileWriters.write_excel(list(self.mft_records.values()), self.output_file)
224248
else:
225-
print(f"Unsupported export format: {self.export_format}")
249+
print(f"Unsupported export format: {self.export_format}")
250+
251+
async def cleanup(self):
252+
self.log("Performing cleanup...", 1)
253+
# to-do add more cleanup after database stuff is integrated.
254+
await self.write_remaining_records()
255+
self.log("Cleanup complete.", 1)

src/analyzeMFT/mft_record.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010

1111

1212
class MftRecord:
13-
def __init__(self, raw_record: bytes, compute_hashes: bool = False) -> None:
13+
def __init__(self, raw_record: bytes, compute_hashes: bool = False, debug_level: int = 0, logger=None):
1414
self.raw_record = raw_record
15+
self.debug_level = debug_level
16+
self.logger = logger or self._default_logger
1517
self.magic = 0
1618
self.upd_off = 0
1719
self.upd_cnt = 0
@@ -65,6 +67,12 @@ def __init__(self, raw_record: bytes, compute_hashes: bool = False) -> None:
6567
self.ea = None
6668
self.logged_utility_stream = None
6769

70+
def _default_logger(self, message: str, level: int = 0):
71+
if level <= self.debug_level:
72+
print(message)
73+
74+
def log(self, message: str, level: int = 0):
75+
self.logger(message, level)
6876

6977
def parse_record(self) -> None:
7078
try:
@@ -87,16 +95,20 @@ def parse_record(self) -> None:
8795
if hasattr(self, 'debug') and self.debug:
8896
print(f"Error parsing MFT record header for record {self.recordnum}")
8997

90-
def parse_attributes(self) -> None:
91-
offset = self.attr_off
98+
def parse_attributes(self):
99+
offset = int(self.attr_off)
92100
while offset < len(self.raw_record) - 8:
93101
try:
94-
attr_type = struct.unpack("<L", self.raw_record[offset:offset+4])[0]
95-
attr_len = struct.unpack("<L", self.raw_record[offset+4:offset+8])[0]
102+
self.log(f"Parsing attribute at offset {offset}", 3)
103+
attr_type = int(struct.unpack("<L", self.raw_record[offset:offset+4])[0])
104+
attr_len = int(struct.unpack("<L", self.raw_record[offset+4:offset+8])[0])
105+
106+
self.log(f"Attribute type: {attr_type}, length: {attr_len}", 3)
96107

97108
if attr_type == 0xffffffff or attr_len == 0:
109+
self.log("End of attributes reached", 3)
98110
break
99-
111+
100112
self.attribute_types.add(attr_type)
101113

102114
if attr_type == STANDARD_INFORMATION_ATTRIBUTE:
@@ -131,7 +143,14 @@ def parse_attributes(self) -> None:
131143
self.parse_logged_utility_stream(offset)
132144

133145
offset += attr_len
134-
except struct.error:
146+
147+
except Exception as e:
148+
print(f"Error processing record {self.recordnum}: {str(e)}")
149+
print(f"attr_type: {attr_type} (type: {type(attr_type)})")
150+
print(f"attr_len: {attr_len} (type: {type(attr_len)})")
151+
print(f"offset: {offset}")
152+
if self.debug >= 2:
153+
traceback.print_exc()
135154
offset += 1
136155

137156
def parse_si_attribute(self, offset: int) -> None:

0 commit comments

Comments
 (0)