From 50edc6f392ff9dd766e8c585a4393dc989a3bb0e Mon Sep 17 00:00:00 2001 From: sumeshi Date: Mon, 7 Mar 2022 21:41:15 +0900 Subject: [PATCH 01/20] docs: fix command --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 07554aa..c356cf5 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ $ ntfsdump /Windows/System32/winevt/Logs -o ./dump ./path/to/your/imagefile.raw extracting from E01 image (included splited-E01). ```.bash -$ls +$ ls imagefile.E01 imagefile.E02 imagefile.E03 From 7276953ba714ea34099abf35117cbdbb813ada55 Mon Sep 17 00:00:00 2001 From: sumeshi Date: Mon, 7 Mar 2022 21:54:38 +0900 Subject: [PATCH 02/20] refactor: clean init file --- src/ntfsdump/__init__.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/ntfsdump/__init__.py b/src/ntfsdump/__init__.py index e5557d1..2c6167d 100644 --- a/src/ntfsdump/__init__.py +++ b/src/ntfsdump/__init__.py @@ -1,14 +1,20 @@ # coding: utf-8 -from typing import List, Literal, Optional +from typing import Literal, Optional from ntfsdump.presenters.NtfsDumpPresenter import NtfsDumpPresenter -def ntfsdump(imagefile_path: str, output_path: str, target_queries: List[str], volume_num: Optional[int] = None, file_type: Literal['raw', 'e01'] = 'raw'): +def ntfsdump( + imagefile_path: str, + output_path: str, + target_queries: list[str], + volume_num: Optional[int] = None, + file_type: Literal['raw', 'e01'] = 'raw' +): NtfsDumpPresenter().ntfsdump( - imagefile_path=imagefile_path, - output_path=output_path, - target_queries=target_queries, - volume_num=volume_num, - file_type=file_type, + imagefile_path, + output_path, + target_queries, + volume_num, + file_type, ) \ No newline at end of file From 92fcbbab3760c83a814744a0eedcef434b9e7e5a Mon Sep 17 00:00:00 2001 From: sumeshi Date: Mon, 7 Mar 2022 21:55:22 +0900 Subject: [PATCH 03/20] docs: add docstring to ntfsdump func --- src/ntfsdump/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ntfsdump/__init__.py b/src/ntfsdump/__init__.py index 2c6167d..67decde 100644 --- a/src/ntfsdump/__init__.py +++ b/src/ntfsdump/__init__.py @@ -10,7 +10,15 @@ def ntfsdump( volume_num: Optional[int] = None, file_type: Literal['raw', 'e01'] = 'raw' ): + """A tool for extract any files from an NTFS volume on an image file. + Args: + imagefile_path (str): target image file path. + output_path (str): output target file path, or output target directory path. + target_queries (list[str]): query for extracted file paths. + volume_num (Optional[int], optional): system volume number. Defaults to None. + file_type (Literal['raw', 'e01'], optional): target image file format. Defaults to 'raw'. + """ NtfsDumpPresenter().ntfsdump( imagefile_path, output_path, From 713deb6c22049d07377488525689e3cce60c8f18 Mon Sep 17 00:00:00 2001 From: sumeshi Date: Mon, 7 Mar 2022 22:41:37 +0900 Subject: [PATCH 04/20] refactor: line breaks --- src/ntfsdump/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ntfsdump/__init__.py b/src/ntfsdump/__init__.py index 67decde..a92a847 100644 --- a/src/ntfsdump/__init__.py +++ b/src/ntfsdump/__init__.py @@ -1,8 +1,8 @@ # coding: utf-8 from typing import Literal, Optional - from ntfsdump.presenters.NtfsDumpPresenter import NtfsDumpPresenter + def ntfsdump( imagefile_path: str, output_path: str, From ea7b04a2ad57b8f0e652c1493adc707221c546ff Mon Sep 17 00:00:00 2001 From: sumeshi Date: Tue, 8 Mar 2022 01:52:49 +0900 Subject: [PATCH 05/20] feat: add software info func --- src/ntfsdump/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ntfsdump/__init__.py b/src/ntfsdump/__init__.py index a92a847..51682e6 100644 --- a/src/ntfsdump/__init__.py +++ b/src/ntfsdump/__init__.py @@ -1,8 +1,17 @@ # coding: utf-8 from typing import Literal, Optional +from importlib.metadata import version + from ntfsdump.presenters.NtfsDumpPresenter import NtfsDumpPresenter +def get_program_name() -> str: + return 'ntfsdump' + +def get_version() -> str: + return version(get_program_name()) + + def ntfsdump( imagefile_path: str, output_path: str, From 34c94f85889e026887d0cde7e7defb0c7e9c894f Mon Sep 17 00:00:00 2001 From: sumeshi Date: Tue, 8 Mar 2022 01:53:16 +0900 Subject: [PATCH 06/20] feat: split log func --- src/ntfsdump/views/BaseView.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/ntfsdump/views/BaseView.py b/src/ntfsdump/views/BaseView.py index e47ea02..06e9bc0 100644 --- a/src/ntfsdump/views/BaseView.py +++ b/src/ntfsdump/views/BaseView.py @@ -1,8 +1,7 @@ # coding: utf-8 import argparse from abc import ABCMeta, abstractmethod -from importlib.metadata import version - +from ntfsdump import get_version class BaseView(metaclass=ABCMeta): @@ -11,13 +10,9 @@ def __init__(self): self.__define_common_options() def __define_common_options(self): - self.parser.add_argument("--version", "-v", action="version", version=version('ntfsdump')) + self.parser.add_argument("--version", "-v", action="version", version=get_version()) self.parser.add_argument("--quiet", "-q", action='store_true', help="flag to suppress standard output.") @abstractmethod def define_options(self): pass - - def log(self, message: str, is_quiet: bool): - if not is_quiet: - print(message) \ No newline at end of file From 805e5bfc3699532baea0f9fdd214a4b545481a0a Mon Sep 17 00:00:00 2001 From: sumeshi Date: Tue, 8 Mar 2022 01:53:25 +0900 Subject: [PATCH 07/20] feat: add log class --- src/ntfsdump/models/Log.py | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/ntfsdump/models/Log.py diff --git a/src/ntfsdump/models/Log.py b/src/ntfsdump/models/Log.py new file mode 100644 index 0000000..49f9b6b --- /dev/null +++ b/src/ntfsdump/models/Log.py @@ -0,0 +1,40 @@ +# coding: utf-8 +from pathlib import Path +from traceback import format_exc +from datetime import datetime + +from ntfsdump import get_program_name, get_version + +def get_datetime() -> datetime: + return datetime.utcnow() + +def get_strdatetime() -> str: + return get_datetime().strftime('%Y%m%d_%H%M%S_%f') + + +class Log(object): + def __init__(self, path: Path = Path('.', f"{get_program_name()}_{get_strdatetime()}.log"), is_quiet: bool = False): + self.path = path + self.is_quiet = is_quiet + self.__create_logfile() + + def __create_logfile(self): + self.path.write_text(f"- {get_program_name()} v{get_version()} - \n") + + def __write_to_log(self, message: str): + try: + with self.path.open('a') as f: + f.write(f"{get_datetime().isoformat()}: {message}\n") + except Exception as e: + self.print_danger(format_exc()) + + def print_info(self, message): + print(f"\033[36m{message}\033[0m") + + def print_danger(self, message): + print(f"\033[31m{message}\033[0m") + + def log(self, message: str): + self.__write_to_log(message) + if not self.is_quiet: + self.print_info(message) From c79a0c9fecf21f33b1d0f6a622341567b7b51d43 Mon Sep 17 00:00:00 2001 From: sumeshi Date: Tue, 8 Mar 2022 02:01:10 +0900 Subject: [PATCH 08/20] docs: add docstrings to log class --- src/ntfsdump/models/Log.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/ntfsdump/models/Log.py b/src/ntfsdump/models/Log.py index 49f9b6b..299fb4d 100644 --- a/src/ntfsdump/models/Log.py +++ b/src/ntfsdump/models/Log.py @@ -13,7 +13,17 @@ def get_strdatetime() -> str: class Log(object): - def __init__(self, path: Path = Path('.', f"{get_program_name()}_{get_strdatetime()}.log"), is_quiet: bool = False): + def __init__( + self, + path: Path = Path('.', f"{get_program_name()}_{get_strdatetime()}.log"), + is_quiet: bool = False + ): + """Logging class + + Args: + path (Path, optional): path of log file. Defaults to Path('.', f"{get_program_name()}_{get_strdatetime()}.log"). + is_quiet (bool, optional): flag to supress standard output. Defaults to False. + """ self.path = path self.is_quiet = is_quiet self.__create_logfile() @@ -28,13 +38,28 @@ def __write_to_log(self, message: str): except Exception as e: self.print_danger(format_exc()) - def print_info(self, message): + def print_info(self, message: str): + """print with cyan color + + Args: + message (str): a message to be printed. + """ print(f"\033[36m{message}\033[0m") - def print_danger(self, message): + def print_danger(self, message: str): + """print with red color + + Args: + message (str): a message to be printed. + """ print(f"\033[31m{message}\033[0m") def log(self, message: str): + """print and write message to logfile + + Args: + message (str): a message to be logged. + """ self.__write_to_log(message) if not self.is_quiet: self.print_info(message) From d0baa2818cf64a2c26c0bfc7ced957c32aef4124 Mon Sep 17 00:00:00 2001 From: sumeshi Date: Tue, 8 Mar 2022 02:04:31 +0900 Subject: [PATCH 09/20] refactor: replace print to log method --- src/ntfsdump/models/NtfsVolume.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ntfsdump/models/NtfsVolume.py b/src/ntfsdump/models/NtfsVolume.py index 0b5f4f1..a3a4c95 100644 --- a/src/ntfsdump/models/NtfsVolume.py +++ b/src/ntfsdump/models/NtfsVolume.py @@ -2,6 +2,8 @@ from pathlib import Path from typing import List, Optional +from ntfsdump.models.Log import Log + import pytsk3 @@ -78,15 +80,18 @@ def __read_alternate_data_stream(self, query: str, ads: str) -> Optional[bytes]: return None def __write_file(self, destination_path: Path, content: Optional[bytes], filename: str) -> None: + + logger = Log() + # destination path is a file try: destination_path.write_bytes(content) - print(f"dumped: {destination_path}") + logger.log(f"[dumped] {destination_path}") # destination path is a directory except IsADirectoryError: Path(destination_path / filename).write_bytes(content) - print(f"dumped: {Path(destination_path / filename)}") + logger.log(f"[dumped] {Path(destination_path / filename)}") def dump_files(self, query: str, destination_path: Path) -> None: query = query.replace('\\', '/') @@ -136,4 +141,4 @@ def dump_files(self, query: str, destination_path: Path) -> None: self.__write_file(destination_path, content, filename) except Exception as e: print(e) - print(f"dump error: {query}") \ No newline at end of file + print(f"[error]: {query}") \ No newline at end of file From e49c4741d06bb681cc3e70cf0f4efadd2593e835 Mon Sep 17 00:00:00 2001 From: sumeshi Date: Tue, 8 Mar 2022 21:48:52 +0900 Subject: [PATCH 10/20] docs: rewrite help description --- src/ntfsdump/views/NtfsDumpView.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/ntfsdump/views/NtfsDumpView.py b/src/ntfsdump/views/NtfsDumpView.py index d11fe08..2470242 100644 --- a/src/ntfsdump/views/NtfsDumpView.py +++ b/src/ntfsdump/views/NtfsDumpView.py @@ -1,6 +1,5 @@ # coding: utf-8 import sys -from typing import Literal from ntfsdump.views.BaseView import BaseView from ntfsdump.presenters.NtfsDumpPresenter import NtfsDumpPresenter @@ -20,30 +19,30 @@ def define_options(self): "target_queries", nargs="+", type=str, - help="Target File Windows Path (ex. /Users/user/Desktop/target.txt).", + help="file paths to be extracted (e.g. '/$MFT').", ) - self.parser.add_argument("imagefile_path", type=str, help="raw image file") + self.parser.add_argument("imagefile_path", type=str, help="file path of the source image file.") self.parser.add_argument( "--volume-num", "-n", type=int, default=None, - help="NTFS volume number(default: autodetect).", + help="number of the source volume. usually, it is the system partition that is specified here (default: autodetect).", ) self.parser.add_argument( "--output-path", "-o", type=str, default=".", - help="Output directory or file path(default: current directory \'.\' ).", + help="output target directory, or file path (default: current directory \'.\' ).", ) self.parser.add_argument( "--type", "-t", type=str, default='raw', - help="Image file format (default: raw(dd-format))" + help="format of the source image file (default: raw(dd-format))." ) def run(self): From fc932c2724c4465e6fccf9abf2eca7ae57c62d78 Mon Sep 17 00:00:00 2001 From: sumeshi Date: Tue, 8 Mar 2022 22:08:01 +0900 Subject: [PATCH 11/20] fix: resolve circular import --- src/ntfsdump/__init__.py | 8 -------- src/ntfsdump/models/Log.py | 6 +++--- src/ntfsdump/views/BaseView.py | 4 ++-- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/ntfsdump/__init__.py b/src/ntfsdump/__init__.py index 51682e6..ddeb9d5 100644 --- a/src/ntfsdump/__init__.py +++ b/src/ntfsdump/__init__.py @@ -1,17 +1,9 @@ # coding: utf-8 from typing import Literal, Optional -from importlib.metadata import version from ntfsdump.presenters.NtfsDumpPresenter import NtfsDumpPresenter -def get_program_name() -> str: - return 'ntfsdump' - -def get_version() -> str: - return version(get_program_name()) - - def ntfsdump( imagefile_path: str, output_path: str, diff --git a/src/ntfsdump/models/Log.py b/src/ntfsdump/models/Log.py index 299fb4d..66ed450 100644 --- a/src/ntfsdump/models/Log.py +++ b/src/ntfsdump/models/Log.py @@ -2,8 +2,8 @@ from pathlib import Path from traceback import format_exc from datetime import datetime +from importlib.metadata import entry_points, version -from ntfsdump import get_program_name, get_version def get_datetime() -> datetime: return datetime.utcnow() @@ -15,7 +15,7 @@ def get_strdatetime() -> str: class Log(object): def __init__( self, - path: Path = Path('.', f"{get_program_name()}_{get_strdatetime()}.log"), + path: Path = Path('.', f"{entry_points().get('name')}_{get_strdatetime()}.log"), is_quiet: bool = False ): """Logging class @@ -29,7 +29,7 @@ def __init__( self.__create_logfile() def __create_logfile(self): - self.path.write_text(f"- {get_program_name()} v{get_version()} - \n") + self.path.write_text(f"- {__package__} v{version(entry_points().get('name'))} - \n") def __write_to_log(self, message: str): try: diff --git a/src/ntfsdump/views/BaseView.py b/src/ntfsdump/views/BaseView.py index 06e9bc0..d488093 100644 --- a/src/ntfsdump/views/BaseView.py +++ b/src/ntfsdump/views/BaseView.py @@ -1,7 +1,7 @@ # coding: utf-8 import argparse from abc import ABCMeta, abstractmethod -from ntfsdump import get_version +from importlib.metadata import entry_points, version class BaseView(metaclass=ABCMeta): @@ -10,7 +10,7 @@ def __init__(self): self.__define_common_options() def __define_common_options(self): - self.parser.add_argument("--version", "-v", action="version", version=get_version()) + self.parser.add_argument("--version", "-v", action="version", version=version(entry_points().get('name'))) self.parser.add_argument("--quiet", "-q", action='store_true', help="flag to suppress standard output.") @abstractmethod From b69f39da4018871e12d45d3fb660b383ba24f6bc Mon Sep 17 00:00:00 2001 From: sumeshi Date: Wed, 9 Mar 2022 03:54:11 +0900 Subject: [PATCH 12/20] feat: global quiet option --- src/ntfsdump/views/NtfsDumpView.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ntfsdump/views/NtfsDumpView.py b/src/ntfsdump/views/NtfsDumpView.py index 2470242..c2ad6d7 100644 --- a/src/ntfsdump/views/NtfsDumpView.py +++ b/src/ntfsdump/views/NtfsDumpView.py @@ -1,6 +1,7 @@ # coding: utf-8 import sys +from ntfsdump.models.MetaData import MetaData from ntfsdump.views.BaseView import BaseView from ntfsdump.presenters.NtfsDumpPresenter import NtfsDumpPresenter @@ -49,6 +50,8 @@ def run(self): # pipeline stdin or args target_queries = [i.strip() for i in sys.stdin] if not sys.stdin.isatty() else self.args.target_queries + MetaData.quiet = self.args.quiet + NtfsDumpPresenter().ntfsdump( imagefile_path=self.args.imagefile_path, output_path=self.args.output_path, From e91549db6af4382667bcde924ce6388357dd2186 Mon Sep 17 00:00:00 2001 From: sumeshi Date: Wed, 9 Mar 2022 03:54:42 +0900 Subject: [PATCH 13/20] feat: add metadata class --- src/ntfsdump/models/Log.py | 21 ++++++++++++--------- src/ntfsdump/models/MetaData.py | 11 +++++++++++ src/ntfsdump/views/BaseView.py | 6 ++++-- 3 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 src/ntfsdump/models/MetaData.py diff --git a/src/ntfsdump/models/Log.py b/src/ntfsdump/models/Log.py index 66ed450..5c2305a 100644 --- a/src/ntfsdump/models/Log.py +++ b/src/ntfsdump/models/Log.py @@ -2,34 +2,37 @@ from pathlib import Path from traceback import format_exc from datetime import datetime -from importlib.metadata import entry_points, version + +from ntfsdump.models.MetaData import MetaData def get_datetime() -> datetime: return datetime.utcnow() -def get_strdatetime() -> str: - return get_datetime().strftime('%Y%m%d_%H%M%S_%f') +def get_logfile_time(): + if not MetaData.run_time: + MetaData.run_time = get_datetime() + return MetaData.run_time.strftime('%Y%m%d_%H%M%S_%f') class Log(object): def __init__( self, - path: Path = Path('.', f"{entry_points().get('name')}_{get_strdatetime()}.log"), - is_quiet: bool = False + path: Path = Path('.', f"{MetaData.name}_{get_logfile_time()}.log"), ): """Logging class Args: - path (Path, optional): path of log file. Defaults to Path('.', f"{get_program_name()}_{get_strdatetime()}.log"). + path (Path, optional): path of log file. Defaults to Path('.', f"{MetaData.name}_{get_logfile_time()}.log"). is_quiet (bool, optional): flag to supress standard output. Defaults to False. """ self.path = path - self.is_quiet = is_quiet + self.is_quiet = MetaData.quiet self.__create_logfile() - + def __create_logfile(self): - self.path.write_text(f"- {__package__} v{version(entry_points().get('name'))} - \n") + if not self.path.exists(): + self.path.write_text(f"- {MetaData.name} v{MetaData.version} - \n") def __write_to_log(self, message: str): try: diff --git a/src/ntfsdump/models/MetaData.py b/src/ntfsdump/models/MetaData.py new file mode 100644 index 0000000..ba60ea4 --- /dev/null +++ b/src/ntfsdump/models/MetaData.py @@ -0,0 +1,11 @@ +# coding: utf-8 +from typing import Optional, Final +from datetime import datetime +from importlib.metadata import version + + +class MetaData(object): + name: Final[str] = 'ntfsdump' + version: Final[str] = version(name) + run_time: Optional[datetime] = None + quiet: bool = False diff --git a/src/ntfsdump/views/BaseView.py b/src/ntfsdump/views/BaseView.py index d488093..dedae57 100644 --- a/src/ntfsdump/views/BaseView.py +++ b/src/ntfsdump/views/BaseView.py @@ -1,7 +1,9 @@ # coding: utf-8 import argparse from abc import ABCMeta, abstractmethod -from importlib.metadata import entry_points, version + +from ntfsdump.models.MetaData import MetaData + class BaseView(metaclass=ABCMeta): @@ -10,7 +12,7 @@ def __init__(self): self.__define_common_options() def __define_common_options(self): - self.parser.add_argument("--version", "-v", action="version", version=version(entry_points().get('name'))) + self.parser.add_argument("--version", "-v", action="version", version=MetaData.version) self.parser.add_argument("--quiet", "-q", action='store_true', help="flag to suppress standard output.") @abstractmethod From 90dd33ee10028e38fb7f96b1166c9ebfd7f6bfb6 Mon Sep 17 00:00:00 2001 From: sumeshi Date: Wed, 9 Mar 2022 23:25:28 +0900 Subject: [PATCH 14/20] feat: nolog option --- src/ntfsdump/models/Log.py | 8 ++++++-- src/ntfsdump/models/MetaData.py | 1 + src/ntfsdump/views/BaseView.py | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ntfsdump/models/Log.py b/src/ntfsdump/models/Log.py index 5c2305a..ac558bc 100644 --- a/src/ntfsdump/models/Log.py +++ b/src/ntfsdump/models/Log.py @@ -28,7 +28,9 @@ def __init__( """ self.path = path self.is_quiet = MetaData.quiet - self.__create_logfile() + + if not MetaData.nolog: + self.__create_logfile() def __create_logfile(self): if not self.path.exists(): @@ -63,6 +65,8 @@ def log(self, message: str): Args: message (str): a message to be logged. """ - self.__write_to_log(message) + if not MetaData.nolog: + self.__write_to_log(message) + if not self.is_quiet: self.print_info(message) diff --git a/src/ntfsdump/models/MetaData.py b/src/ntfsdump/models/MetaData.py index ba60ea4..2bf12fb 100644 --- a/src/ntfsdump/models/MetaData.py +++ b/src/ntfsdump/models/MetaData.py @@ -9,3 +9,4 @@ class MetaData(object): version: Final[str] = version(name) run_time: Optional[datetime] = None quiet: bool = False + nolog: bool = False diff --git a/src/ntfsdump/views/BaseView.py b/src/ntfsdump/views/BaseView.py index dedae57..211089a 100644 --- a/src/ntfsdump/views/BaseView.py +++ b/src/ntfsdump/views/BaseView.py @@ -14,6 +14,7 @@ def __init__(self): def __define_common_options(self): self.parser.add_argument("--version", "-v", action="version", version=MetaData.version) self.parser.add_argument("--quiet", "-q", action='store_true', help="flag to suppress standard output.") + self.parser.add_argument("--nolog", "-n", action='store_true', help="flag to no logs are output.") @abstractmethod def define_options(self): From 65a57ed4407a4e2a235688ed86e64349bbd03e4e Mon Sep 17 00:00:00 2001 From: sumeshi Date: Wed, 9 Mar 2022 23:28:58 +0900 Subject: [PATCH 15/20] fix: nolog options --- src/ntfsdump/models/MetaData.py | 4 ++-- src/ntfsdump/views/BaseView.py | 2 +- src/ntfsdump/views/NtfsDumpView.py | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ntfsdump/models/MetaData.py b/src/ntfsdump/models/MetaData.py index 2bf12fb..75d838c 100644 --- a/src/ntfsdump/models/MetaData.py +++ b/src/ntfsdump/models/MetaData.py @@ -8,5 +8,5 @@ class MetaData(object): name: Final[str] = 'ntfsdump' version: Final[str] = version(name) run_time: Optional[datetime] = None - quiet: bool = False - nolog: bool = False + quiet: bool = True + nolog: bool = True diff --git a/src/ntfsdump/views/BaseView.py b/src/ntfsdump/views/BaseView.py index 211089a..fd9f5f5 100644 --- a/src/ntfsdump/views/BaseView.py +++ b/src/ntfsdump/views/BaseView.py @@ -14,7 +14,7 @@ def __init__(self): def __define_common_options(self): self.parser.add_argument("--version", "-v", action="version", version=MetaData.version) self.parser.add_argument("--quiet", "-q", action='store_true', help="flag to suppress standard output.") - self.parser.add_argument("--nolog", "-n", action='store_true', help="flag to no logs are output.") + self.parser.add_argument("--nolog", action='store_true', help="flag to no logs are output.") @abstractmethod def define_options(self): diff --git a/src/ntfsdump/views/NtfsDumpView.py b/src/ntfsdump/views/NtfsDumpView.py index c2ad6d7..ebb81a7 100644 --- a/src/ntfsdump/views/NtfsDumpView.py +++ b/src/ntfsdump/views/NtfsDumpView.py @@ -51,6 +51,7 @@ def run(self): target_queries = [i.strip() for i in sys.stdin] if not sys.stdin.isatty() else self.args.target_queries MetaData.quiet = self.args.quiet + MetaData.nolog = self.args.nolog NtfsDumpPresenter().ntfsdump( imagefile_path=self.args.imagefile_path, From d8bffe73c8ce55be52399cfe243fffb0f8c577f3 Mon Sep 17 00:00:00 2001 From: sumeshi Date: Wed, 9 Mar 2022 23:44:42 +0900 Subject: [PATCH 16/20] docs: update readme about args --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index c356cf5..f87f01e 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,12 @@ $ ntfsfind '.*\.evtx' ./path/to/your/imagefile.raw | ntfsdump ./path/to/your/ima --version, -v: show program's version number and exit. +--quiet, -q: + flat to suppress standard output. + +--nolog: + flag to no logs are output. + --volume-num, -n: NTFS volume number (default: autodetect). @@ -96,6 +102,20 @@ The image file to be processed must meet the following conditions. Additional file formats will be added in the future. If you have any questions, please submit an issue. + +## LogFormat +ntfsdump outputs logs in the following format. +By default, it outputs the files to the current directory, but if you do not need them, please use the `--nolog` option. + +``` +- ntfsdump v{{version}} - +2022-01-01T00:00:00.000000: [{{EventName}}] {{Description}} +2022-01-01T00:00:00.000000: [{{EventName}}] {{Description}} +2022-01-01T00:00:00.000000: [{{EventName}}] {{Description}} +... +``` + + ## Installation ### via PyPI From 1dcebad3827d6bb1b0fa278ad9807c1b76e1c419 Mon Sep 17 00:00:00 2001 From: sumeshi Date: Thu, 10 Mar 2022 00:18:23 +0900 Subject: [PATCH 17/20] refactor: linebreaks --- src/ntfsdump/models/ImageFile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ntfsdump/models/ImageFile.py b/src/ntfsdump/models/ImageFile.py index 63abe41..3f4c24a 100644 --- a/src/ntfsdump/models/ImageFile.py +++ b/src/ntfsdump/models/ImageFile.py @@ -7,11 +7,11 @@ import pytsk3 import pyewf + class ewf_Img_Info(pytsk3.Img_Info): def __init__(self, ewf_handle): self._ewf_handle = ewf_handle - super(ewf_Img_Info, self).__init__( - url="", type=pytsk3.TSK_IMG_TYPE_EXTERNAL) + super(ewf_Img_Info, self).__init__(url="", type=pytsk3.TSK_IMG_TYPE_EXTERNAL) def close(self): self._ewf_handle.close() From b9c047d40bf68d36e08fd12340066f86e1e37ac1 Mon Sep 17 00:00:00 2001 From: sumeshi Date: Thu, 10 Mar 2022 01:04:26 +0900 Subject: [PATCH 18/20] feat: extend logger --- src/ntfsdump/models/ImageFile.py | 9 +++++++++ src/ntfsdump/models/Log.py | 9 +++++++-- src/ntfsdump/models/NtfsVolume.py | 30 +++++++++++++++--------------- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/ntfsdump/models/ImageFile.py b/src/ntfsdump/models/ImageFile.py index 3f4c24a..e449690 100644 --- a/src/ntfsdump/models/ImageFile.py +++ b/src/ntfsdump/models/ImageFile.py @@ -3,6 +3,7 @@ from typing import List, Literal, Optional from ntfsdump.models.NtfsVolume import NtfsVolume +from ntfsdump.models.Log import Log import pytsk3 import pyewf @@ -27,18 +28,26 @@ def get_size(self): class ImageFile(object): def __init__(self, path: Path, volume_num: Optional[int], file_type: Literal['raw', 'e01'] = 'raw'): self.path: Path = path + self.logger: Log = Log() self.block_size: int = 512 self.file_type: Literal['raw', 'e01'] = file_type self.volumes: List[NtfsVolume] = self.__analyze_partitions() self.main_volume: NtfsVolume = self.__auto_detect_main_partition(volume_num) + self.logger.log(f"[analyze] {len(self.volumes)} volumes were detected as NTFS volumes.", 'system') + for index, volume in enumerate(self.volumes): + self.logger.log(f"[analyze] NTFS Volume {index}: {volume.description}", 'system') + self.logger.log(f"[analyze] Volume {self.volumes.index(self.main_volume)} was automatically detected as the main partition.", 'system') + def __analyze_partitions(self) -> List[NtfsVolume]: if self.file_type == 'e01': + self.logger.log(f"[analyze] E01 Format Image", 'system') filenames = pyewf.glob(str(self.path)) ewf_handle = pyewf.handle() ewf_handle.open(filenames) img_info = ewf_Img_Info(ewf_handle) else: + self.logger.log(f"[analyze] Raw Format Image", 'system') img_info = pytsk3.Img_Info(str(self.path)) volumes = pytsk3.Volume_Info(img_info) diff --git a/src/ntfsdump/models/Log.py b/src/ntfsdump/models/Log.py index ac558bc..b3f6057 100644 --- a/src/ntfsdump/models/Log.py +++ b/src/ntfsdump/models/Log.py @@ -1,5 +1,6 @@ # coding: utf-8 from pathlib import Path +from typing import Literal from traceback import format_exc from datetime import datetime @@ -59,14 +60,18 @@ def print_danger(self, message: str): """ print(f"\033[31m{message}\033[0m") - def log(self, message: str): + def log(self, message: str, type: Literal['system', 'info', 'danger'] = 'system'): """print and write message to logfile Args: message (str): a message to be logged. + type (Literal['system', 'info', 'danger']): 'system ' is used only for logging. """ if not MetaData.nolog: self.__write_to_log(message) if not self.is_quiet: - self.print_info(message) + if type == 'info': + self.print_info(message) + elif type == 'danger': + self.print_danger(message) diff --git a/src/ntfsdump/models/NtfsVolume.py b/src/ntfsdump/models/NtfsVolume.py index a3a4c95..5b9d554 100644 --- a/src/ntfsdump/models/NtfsVolume.py +++ b/src/ntfsdump/models/NtfsVolume.py @@ -16,6 +16,10 @@ def __init__(self, path: Path, addr: int, description: str, start_byte: int, end self.start_byte = start_byte self.end_byte = end_byte self.fs_info = fs_info + self.logger = Log() + + def __clean_query(self, query: str) -> str: + return query.replace('\\', '/') def __is_dir(self, query: str) -> bool: f = self.fs_info.open(query) @@ -33,7 +37,6 @@ def __list_artifacts(self, query: str) -> List[str]: ] def __read_file(self, query: str) -> bytes: - f = self.fs_info.open(query) offset = 0 @@ -53,10 +56,9 @@ def __read_file(self, query: str) -> bytes: return result def __read_alternate_data_stream(self, query: str, ads: str) -> Optional[bytes]: - query = query.replace('\\', '/') f = self.fs_info.open(query) - OFFSET = 0 + offset = 0 BUFF_SIZE = 1024 * 1024 ads_attribute = None @@ -69,32 +71,30 @@ def __read_alternate_data_stream(self, query: str, ads: str) -> Optional[bytes]: result = bytes() ADS_SIZE = ads_attribute.info.size - while OFFSET < ADS_SIZE: - available_to_read = min(BUFF_SIZE, ADS_SIZE - OFFSET) - data = f.read_random(OFFSET, available_to_read, ads_attribute.info.type, ads_attribute.info.id) + while offset < ADS_SIZE: + available_to_read = min(BUFF_SIZE, ADS_SIZE - offset) + data = f.read_random(offset , available_to_read, ads_attribute.info.type, ads_attribute.info.id) if not data: break - OFFSET += len(data) + offset += len(data) result += data return result return None def __write_file(self, destination_path: Path, content: Optional[bytes], filename: str) -> None: - - logger = Log() - # destination path is a file try: destination_path.write_bytes(content) - logger.log(f"[dumped] {destination_path}") + self.logger.log(f"[dumped] {destination_path}", 'info') # destination path is a directory except IsADirectoryError: Path(destination_path / filename).write_bytes(content) - logger.log(f"[dumped] {Path(destination_path / filename)}") + self.logger.log(f"[dumped] {Path(destination_path / filename)}", 'info') def dump_files(self, query: str, destination_path: Path) -> None: - query = query.replace('\\', '/') + query = self.__clean_query(query) + self.logger.log(f"[query] {query}", 'system') if self.__is_dir(query): for artifact in self.__list_artifacts(query): @@ -140,5 +140,5 @@ def dump_files(self, query: str, destination_path: Path) -> None: content = self.__read_file(query) self.__write_file(destination_path, content, filename) except Exception as e: - print(e) - print(f"[error]: {query}") \ No newline at end of file + self.logger.log(f"[error] {query}", 'danger') + self.logger.log(e, 'danger') \ No newline at end of file From 10fb4b4d4ac82282573675c5f62770e5fdfbba33 Mon Sep 17 00:00:00 2001 From: sumeshi Date: Thu, 10 Mar 2022 01:04:41 +0900 Subject: [PATCH 19/20] refactor: presenter args --- src/ntfsdump/presenters/NtfsDumpPresenter.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ntfsdump/presenters/NtfsDumpPresenter.py b/src/ntfsdump/presenters/NtfsDumpPresenter.py index 6e0291e..d4ea1fd 100644 --- a/src/ntfsdump/presenters/NtfsDumpPresenter.py +++ b/src/ntfsdump/presenters/NtfsDumpPresenter.py @@ -5,7 +5,14 @@ from ntfsdump.models.ImageFile import ImageFile class NtfsDumpPresenter(object): - def ntfsdump(self, imagefile_path: str, output_path: str, target_queries: List[str], volume_num: Optional[int] = None, file_type: Literal['raw', 'e01'] = 'raw'): + def ntfsdump( + self, + imagefile_path: str, + output_path: str, + target_queries: List[str], + volume_num: Optional[int] = None, + file_type: Literal['raw', 'e01'] = 'raw' + ): # dump files image = ImageFile(Path(imagefile_path), volume_num, file_type) for target_query in target_queries: From 84dc2b002c4e42fa39b84aeea4f37b76475496fa Mon Sep 17 00:00:00 2001 From: sumeshi Date: Thu, 10 Mar 2022 01:08:51 +0900 Subject: [PATCH 20/20] release: v2.3.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a5681c2..244bc75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ntfsdump" -version = "2.2.1" +version = "2.3.0" description = "A tool for extract any files from an NTFS volume on an image file." authors = ["sumeshi "] license = "MIT"