Skip to content

Commit 46976a8

Browse files
authored
Merge pull request #139 from rowingdude/sql_support
Sql support - TESTING!!!!!
2 parents 40a7bc8 + e48b84d commit 46976a8

File tree

7 files changed

+154
-3
lines changed

7 files changed

+154
-3
lines changed

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
openpyxl==3.0.10
1+
openpyxl==3.0.10
2+
sqlite3

setup.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
author_email='[email protected]',
1212
package_dir={'': 'src'},
1313
packages=find_packages(where='src'),
14+
package_data={
15+
'analyzeMFT': ['sql/*.sql'],
16+
},
1417
url='http://github.com/rowingdude/analyzeMFT',
1518
license='LICENSE.txt',
1619
description='Analyze the $MFT from a NTFS filesystem.',
@@ -31,6 +34,7 @@
3134
python_requires=">=3.7",
3235
install_requires=[
3336
"pywin32;platform_system=='Windows'",
37+
"openpyxl==3.0.10",
3438
],
3539
entry_points={
3640
'console_scripts': [

src/analyzeMFT/cli.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ async def main():
2727
help="Export as TSK timeline")
2828
export_group.add_option("--l2t", action="store_const", const="l2t", dest="export_format",
2929
help="Export as log2timeline CSV")
30+
export_group.add_option("--sqlite", action="store_const", const="sqlite", dest="export_format",
31+
help="Export as SQLite database")
32+
export_group.add_option("--tsk", action="store_const", const="tsk", dest="export_format",
33+
help="Export as TSK bodyfile format")
34+
3035
parser.add_option_group(export_group)
3136

3237
verbosity_group = OptionGroup(parser, "Verbosity Options")

src/analyzeMFT/file_writers.py

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import asyncio
12
import csv
3+
import os
24
import json
5+
import sqlite3
36
import xml.etree.ElementTree as ET
4-
import asyncio
57
from typing import List, Dict, Any
68
from .mft_record import MftRecord
79
from .constants import *
@@ -86,4 +88,69 @@ async def write_l2t(records: List[MftRecord], output_file: str) -> None:
8688
date_str, time_str, 'UTC', macb, 'MFT', 'FILESYSTEM', time_type, '', '', '',
8789
f"{record.filename} {time_type}", '', record.filename, record.recordnum, '', '', ''
8890
])
91+
await asyncio.sleep(0)
92+
93+
94+
@staticmethod
95+
async def write_sqlite(records: List[MftRecord], output_file: str) -> None:
96+
conn = sqlite3.connect(output_file)
97+
cursor = conn.cursor()
98+
99+
# Create and populate static tables
100+
sql_dir = os.path.join(os.path.dirname(__file__), 'sql')
101+
for sql_file in os.listdir(sql_dir):
102+
with open(os.path.join(sql_dir, sql_file), 'r') as f:
103+
cursor.executescript(f.read())
104+
105+
# Create MFT records table
106+
cursor.execute('''
107+
CREATE TABLE mft_records (
108+
record_number INTEGER PRIMARY KEY,
109+
filename TEXT,
110+
parent_record_number INTEGER,
111+
file_size INTEGER,
112+
is_directory INTEGER,
113+
creation_time TEXT,
114+
modification_time TEXT,
115+
access_time TEXT,
116+
entry_time TEXT,
117+
attribute_types TEXT
118+
)
119+
''')
120+
121+
# Insert MFT records
122+
for record in records:
123+
cursor.execute('''
124+
INSERT INTO mft_records (
125+
record_number, filename, parent_record_number, file_size,
126+
is_directory, creation_time, modification_time, access_time,
127+
entry_time, attribute_types
128+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
129+
''', (
130+
record.recordnum,
131+
record.filename,
132+
record.get_parent_record_num(),
133+
record.filesize,
134+
1 if record.flags & FILE_RECORD_IS_DIRECTORY else 0,
135+
record.fn_times['crtime'].dtstr,
136+
record.fn_times['mtime'].dtstr,
137+
record.fn_times['atime'].dtstr,
138+
record.fn_times['ctime'].dtstr,
139+
','.join(map(str, record.attribute_types))
140+
))
141+
142+
conn.commit()
143+
conn.close()
144+
await asyncio.sleep(0)
145+
146+
@staticmethod
147+
async def write_tsk(records: List[MftRecord], output_file: str) -> None:
148+
with open(output_file, 'w', newline='', encoding='utf-8') as tskfile:
149+
for record in records:
150+
# TSK body file format:
151+
# MD5|name|inode|mode_as_string|UID|GID|size|atime|mtime|ctime|crtime
152+
tskfile.write(f"0|{record.filename}|{record.recordnum}|{record.flags:04o}|0|0|"
153+
f"{record.filesize}|{record.fn_times['atime'].unixtime}|"
154+
f"{record.fn_times['mtime'].unixtime}|{record.fn_times['ctime'].unixtime}|"
155+
f"{record.fn_times['crtime'].unixtime}\n")
89156
await asyncio.sleep(0)

src/analyzeMFT/mft_analyzer.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import asyncio
22
import csv
33
import io
4+
import os
45
import signal
6+
import sqlite3
57
import sys
68
import traceback
79
from typing import Dict, Set, List, Optional, Any
@@ -245,11 +247,52 @@ async def write_output(self) -> None:
245247
await FileWriters.write_xml(list(self.mft_records.values()), self.output_file)
246248
elif self.export_format == "excel":
247249
await FileWriters.write_excel(list(self.mft_records.values()), self.output_file)
250+
elif self.export_format == "sqlite":
251+
await FileWriters.write_sqlite(list(self.mft_records.values()), self.output_file)
252+
elif self.export_format == "tsk":
253+
await FileWriters.write_tsk(list(self.mft_records.values()), self.output_file)
248254
else:
249255
print(f"Unsupported export format: {self.export_format}")
250256

251257
async def cleanup(self):
252258
self.log("Performing cleanup...", 1)
253259
# to-do add more cleanup after database stuff is integrated.
254260
await self.write_remaining_records()
255-
self.log("Cleanup complete.", 1)
261+
self.log("Cleanup complete.", 1)
262+
263+
async def create_sqlite_database(self):
264+
conn = sqlite3.connect(self.output_file)
265+
cursor = conn.cursor()
266+
267+
# Create and populate static tables
268+
sql_dir = os.path.join(os.path.dirname(__file__), 'sql')
269+
for sql_file in os.listdir(sql_dir):
270+
with open(os.path.join(sql_dir, sql_file), 'r') as f:
271+
cursor.executescript(f.read())
272+
273+
# Create MFT records table
274+
cursor.execute('''
275+
CREATE TABLE mft_records (
276+
record_number INTEGER PRIMARY KEY,
277+
filename TEXT,
278+
parent_record_number INTEGER,
279+
-- Add other fields as needed
280+
FOREIGN KEY (attribute_type) REFERENCES attribute_types(id)
281+
)
282+
''')
283+
284+
conn.commit()
285+
return conn
286+
287+
async def write_sqlite(self):
288+
conn = await self.create_sqlite_database()
289+
cursor = conn.cursor()
290+
291+
for record in self.mft_records.values():
292+
cursor.execute('''
293+
INSERT INTO mft_records (record_number, filename, parent_record_number)
294+
VALUES (?, ?, ?)
295+
''', (record.recordnum, record.filename, record.get_parent_record_num()))
296+
297+
conn.commit()
298+
conn.close()
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
CREATE TABLE attribute_types (
2+
id INTEGER PRIMARY KEY,
3+
name TEXT NOT NULL
4+
);
5+
6+
INSERT INTO attribute_types (id, name) VALUES
7+
(16, '$STANDARD_INFORMATION'),
8+
(32, '$ATTRIBUTE_LIST'),
9+
(48, '$FILE_NAME'),
10+
(64, '$OBJECT_ID'),
11+
(80, '$SECURITY_DESCRIPTOR'),
12+
(96, '$VOLUME_NAME'),
13+
(112, '$VOLUME_INFORMATION'),
14+
(128, '$DATA'),
15+
(144, '$INDEX_ROOT'),
16+
(160, '$INDEX_ALLOCATION'),
17+
(176, '$BITMAP'),
18+
(192, '$REPARSE_POINT'),
19+
(208, '$EA_INFORMATION'),
20+
(224, '$EA'),
21+
(256, '$LOGGED_UTILITY_STREAM');
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CREATE TABLE file_record_flags (
2+
id INTEGER PRIMARY KEY,
3+
name TEXT NOT NULL
4+
);
5+
6+
INSERT INTO file_record_flags (id, name) VALUES
7+
(1, 'FILE_RECORD_IN_USE'),
8+
(2, 'FILE_RECORD_IS_DIRECTORY'),
9+
(4, 'FILE_RECORD_IS_EXTENSION'),
10+
(8, 'FILE_RECORD_HAS_SPECIAL_INDEX');

0 commit comments

Comments
 (0)