diff --git a/maldump/__main__.py b/maldump/__main__.py index 83ddba1..ed13c68 100755 --- a/maldump/__main__.py +++ b/maldump/__main__.py @@ -40,45 +40,69 @@ def main() -> None: # Save the destination directory dest: Path = args.dest.resolve() + root_dir = args.root_dir + quar_entires: list[QuarEntry] = [] + + if not args.velociraptor: + quar_entires.extend(run_in_one_root( + root_dir, + args.detect_avs, + )) + else: + for directory in os.listdir(root_dir): + new_root_dir = os.path.join(root_dir, directory, 'uploads', 'auto', 'C%3A') + if not os.path.isdir(new_root_dir): + continue + + quar_entires.extend(run_in_one_root( + new_root_dir, + args.detect_avs + )) + + if args.quar or args.all: + export_files(quar_entires, dest) + + if args.meta or args.all: + export_meta(quar_entires, dest) + + list_files(quar_entires) + + + +def run_in_one_root(root_dir, detect_avs) -> list[QuarEntry]: # Switch to root partition - os.chdir(args.root_dir) + os.chdir(root_dir) logger.debug( - 'Working in directory "%s", files would be stored into "%s"', os.getcwd(), dest + 'Working in directory "%s"', os.getcwd() ) # Get a list of all supported or all installed avs - avs = AVManager.detect() if args.detect_avs else AVManager.retrieve() + avs = AVManager.detect() if detect_avs else AVManager.retrieve() logger.debug("Detected AVs: %s", [av.name for av in avs]) - if args.quar: - export_files(avs, dest) - elif args.meta: - export_meta(avs, dest) - elif args.all: - export_files(avs, dest) - export_meta(avs, dest) - else: - list_files(avs) + quar_entires: list[QuarEntry] = [] + for av in avs: + quar_entires.extend(av.export()) + + return quar_entires def export_files( - avs: list[Quarantine], dest: Path, out_file: str = "quarantine.tar" + quar_entries: list[QuarEntry], dest: Path, out_file: str = "quarantine.tar" ) -> None: total = 0 - for av in avs: - entries = av.export() - if (len(entries)) > 0: - tar_path = dest.joinpath(out_file) - tar = tarfile.open(tar_path, total and "a" or "w") - total += len(entries) - for entry in entries: - tarinfo = tarfile.TarInfo(av.name + "/" + entry.md5) - tarinfo.size = len(entry.malfile) - tar.addfile(tarinfo, io.BytesIO(entry.malfile)) - tar.close() + if (len(quar_entries)) > 0: + tar_path = dest.joinpath(out_file) + tar = tarfile.open(tar_path, total and "a" or "w") + total += len(quar_entries) + for entry in quar_entries: + tarinfo = tarfile.TarInfo(av.name + "/" + entry.md5) + tarinfo.size = len(entry.malfile) + tar.addfile(tarinfo, io.BytesIO(entry.malfile)) + tar.close() if total > 0: print(f"Exported {total} object(s) into '{out_file}'") else: @@ -86,15 +110,9 @@ def export_files( def export_meta( - avs: list[Quarantine], dest: Path, meta_file: str = "quarantine.csv" + quar_entries: list[QuarEntry], dest: Path, meta_file: str = "quarantine.csv" ) -> None: - entries = [] - for av in avs: - for e in av.export(): - d = vars(e) - d.update(antivirus=av.name) - entries.append(d) - if len(entries) > 0: + if len(quar_entries) > 0: csv_path = dest.joinpath(meta_file) with open(csv_path, "w", encoding="utf-8", newline="") as f: fields = [ @@ -102,6 +120,7 @@ def export_meta( "antivirus", "threat", "path", + "orig_path", "size", "md5", "sha1", @@ -109,27 +128,21 @@ def export_meta( ] writer = csv.DictWriter(f, fields, extrasaction="ignore") writer.writeheader() - writer.writerows(entries) - print(f"Written {len(entries)} row(s) into file '{meta_file}'") + writer.writerows(vars(e) for e in quar_entries) + print(f"Written {len(quar_entries)} row(s) into file '{meta_file}'") else: print( f"The file '{meta_file}' wasn't created as there is nothing in quarantine" ) -def list_files(avs: list[Quarantine]) -> None: - quarantined_file_exists = False - for i, av in enumerate(avs): - entries = av.export() - if len(entries) > 0: - quarantined_file_exists = True - if i != 0: - print() - print(Fore.YELLOW + "---", av.name, "---" + Style.RESET_ALL) - for e in entries: - print(e.path) - if not quarantined_file_exists: +def list_files(quar_entries: list[QuarEntry]) -> None: + if not quar_entries: print("No quarantined files found!") + return + + for e in quar_entries: + print(e.path) def parse_cli() -> argparse.Namespace: @@ -179,9 +192,14 @@ def parse_cli() -> argparse.Namespace: "-t", "--log-level", choices=["critical", "fatal", "error", "warn", "warning", "info", "debug"], - default="warning", + default="error", help="log level", ) + parser.add_argument( + "--velociraptor", + action="store_true", + help="load quarantine from velociraptor dump", + ) parser.add_argument( "-v", "--version", action="version", version="%(prog)s " + __version__ ) diff --git a/maldump/parsers/avast_parser.py b/maldump/parsers/avast_parser.py index 00c2a79..349b53e 100644 --- a/maldump/parsers/avast_parser.py +++ b/maldump/parsers/avast_parser.py @@ -54,7 +54,7 @@ def _initDB(self) -> bool: ) self.root = ET.parse(self.location / "index.xml").getroot() except (ParseError, OSError) as e: - logger.exception("Cannot open and parse index.xml", exc_info=e) + logger.info("Cannot open and parse index.xml", exc_info=e) return False # Decrypt vault.db and prepare db connection @@ -65,7 +65,7 @@ def _initDB(self) -> bool: with open(self.tmpfile, "wb") as f: f.write(self._decryptVault("$AV_ASW/$VAULT/vault.db")) except OSError as e: - logger.exception("Cannot open nor write temporary file", exc_info=e) + logger.warning("Cannot open nor write temporary file", exc_info=e) return False try: @@ -74,7 +74,7 @@ def _initDB(self) -> bool: ) self.db = sqlite3.connect(self.tmpfile) except sqlite3.Error as e: - logger.exception("Cannot connect to SQLite3 chest database", exc_info=e) + logger.warning("Cannot connect to SQLite3 chest database", exc_info=e) print("Avast DB Error: " + str(e)) return False diff --git a/maldump/parsers/avg_parser.py b/maldump/parsers/avg_parser.py index beee3e9..5bb904d 100644 --- a/maldump/parsers/avg_parser.py +++ b/maldump/parsers/avg_parser.py @@ -54,7 +54,7 @@ def _initDB(self) -> bool: ) self.root = ET.parse(self.location / "index.xml").getroot() except (ParseError, OSError) as e: - logger.exception("Cannot open and parse index.xml", exc_info=e) + logger.info("Cannot open and parse index.xml", exc_info=e) return False # Decrypt vault.db and prepare db connection @@ -65,7 +65,7 @@ def _initDB(self) -> bool: with open(self.tmpfile, "wb") as f: f.write(self._decryptVault("$AV_AVG/$VAULT/vault.db")) except OSError as e: - logger.exception("Cannot open nor write temporary file", exc_info=e) + logger.warning("Cannot open nor write temporary file", exc_info=e) return False try: @@ -74,7 +74,7 @@ def _initDB(self) -> bool: ) self.db = sqlite3.connect(self.tmpfile) except sqlite3.Error as e: - logger.exception("Cannot connect to SQLite3 chest database", exc_info=e) + logger.warning("Cannot connect to SQLite3 chest database", exc_info=e) print("Avast DB Error: " + str(e)) return False diff --git a/maldump/parsers/eset_parser.py b/maldump/parsers/eset_parser.py index 9384bb6..4529e89 100644 --- a/maldump/parsers/eset_parser.py +++ b/maldump/parsers/eset_parser.py @@ -18,6 +18,7 @@ import logging import re import typing +import os from pathlib import Path from maldump.constants import ThreatMetadata @@ -90,7 +91,7 @@ def convertToDict(parser: EsetVirlogParser): { **{ y.name.name: y.arg if hasattr(y, "arg") else None - for y in x.record.data_fields + for y in x.record.data_fields if hasattr(y.name, "name") }, "timestamp": x.record.win_timestamp.date_time, } @@ -100,13 +101,20 @@ def convertToDict(parser: EsetVirlogParser): @log.log(lgr=logger) def mainParsing(virlog_path): + if not virlog_path.is_file(): + logger.debug("virlog.dat file not found") + return [] kt = parse(EsetParser).kaitai(EsetVirlogParser, virlog_path) if kt is None: - logger.warning("Skipping virlog.dat parsing") + logger.warning("Skipping virlog.dat parsing at %s", os.path.abspath(virlog_path)) return [] kt.close() - threats = convertToDict(kt) + try: + threats = convertToDict(kt) + except Exception as e: + logger.warning("Cannot parse virlog.dat at %s", os.path.abspath(virlog_path), exc_info=e) + return [] parsedRecords = [] for idx, record in enumerate(threats): @@ -161,9 +169,6 @@ def parse_from_log(self, _=None) -> dict[tuple[str, datetime], QuarEntry]: for idx, metadata in enumerate(mainParsing(self.location)): logger.debug("Parsing entry, idx %s", idx) - if metadata["user"] == "SYSTEM": - logger.debug("Entry's (idx %s) user is SYSTEM, skipping", idx) - continue q = QuarEntry(self) q.timestamp = metadata["timestamp"] q.threat = metadata["infiltration"] @@ -207,7 +212,7 @@ def parse_from_fs( logger.debug('Skipping entry idx %s, path "%s"', idx, entry) continue timestamp = DTC.get_dt_from_stat(entry_stat) - path = str(entry) + path = orig_path = str(os.path.abspath(entry)) sha1 = None size = entry_stat.st_size threat = ThreatMetadata.UNKNOWN_THREAT @@ -223,6 +228,7 @@ def parse_from_fs( q = QuarEntry(self) q.timestamp = timestamp q.path = path + q.orig_path = orig_path q.sha1 = sha1 q.size = size q.threat = threat diff --git a/maldump/parsers/kaitai/eset_ndf_parser.ksy b/maldump/parsers/kaitai/eset_ndf_parser.ksy index 8684053..8ccc39a 100644 --- a/maldump/parsers/kaitai/eset_ndf_parser.ksy +++ b/maldump/parsers/kaitai/eset_ndf_parser.ksy @@ -3,6 +3,7 @@ meta: title: ESET Antivirus quarantine metadata file parser (NDF) file-extension: NDF endian: le + seq: - id: magic size: 0x08 @@ -31,19 +32,8 @@ types: seq: - id: mal_path type: widestr - - id: date_block_size - type: u4 - - id: date_block_header - size: 0x04 - contents: [ 0x4e, 0x49, 0x57, 0x49 ] # NIWI - - id: datetime_quar_enc_start - type: windate - - id: datetime_first_utc - type: windate - - id: datetime_quar_enc_stop - type: windate - - id: unknown_size - type: u4 + - id: date_block + type: dateblock - id: datetime_latest_occurence type: unixdate - id: filler1 @@ -65,6 +55,28 @@ types: - id: mal_path2 type: widestr + dateblock: + seq: + - id: date_block_size + type: u4 + - id: date_block_contents + type: dateblock_contents + if: date_block_size != 0 + + dateblock_contents: + seq: + - id: date_block_header + size: 0x04 + contents: [ 0x4e, 0x49, 0x57, 0x49 ] # NIWI + - id: datetime_quar_enc_start + type: windate + - id: datetime_first_utc + type: windate + - id: datetime_quar_enc_stop + type: windate + - id: unknown_size + type: u4 + windate: seq: - id: date_time diff --git a/maldump/parsers/kaitai/eset_ndf_parser.py b/maldump/parsers/kaitai/eset_ndf_parser.py index 9a96d81..b7b5001 100644 --- a/maldump/parsers/kaitai/eset_ndf_parser.py +++ b/maldump/parsers/kaitai/eset_ndf_parser.py @@ -1,37 +1,30 @@ # This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild +# type: ignore import kaitaistruct from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO import maldump.utils -if getattr(kaitaistruct, "API_VERSION", (0, 9)) < (0, 9): - raise Exception( - "Incompatible Kaitai Struct Python API: 0.9 or later is required, but you have %s" - % (kaitaistruct.__version__) - ) - +if getattr(kaitaistruct, 'API_VERSION', (0, 9)) < (0, 11): + raise Exception("Incompatible Kaitai Struct Python API: 0.11 or later is required, but you have %s" % (kaitaistruct.__version__)) class EsetNdfParser(KaitaiStruct): def __init__(self, _io, _parent=None, _root=None): - self._io = _io + super(EsetNdfParser, self).__init__(_io) self._parent = _parent - self._root = _root if _root else self + self._root = _root or self self._read() def _read(self): self.magic = self._io.read_bytes(8) - if not self.magic == b"\x46\x51\x44\x46\xa4\x0f\x00\x00": - raise kaitaistruct.ValidationNotEqualError( - b"\x46\x51\x44\x46\xa4\x0f\x00\x00", self.magic, self._io, "/seq/0" - ) + if not self.magic == b"\x46\x51\x44\x46\xA4\x0F\x00\x00": + raise kaitaistruct.ValidationNotEqualError(b"\x46\x51\x44\x46\xA4\x0F\x00\x00", self.magic, self._io, u"/seq/0") self.num_findings = self._io.read_u4le() self.datetime_unix = EsetNdfParser.Unixdate(self._io, self, self._root) self.filler = self._io.read_bytes(4) if not self.filler == b"\x00\x00\x00\x00": - raise kaitaistruct.ValidationNotEqualError( - b"\x00\x00\x00\x00", self.filler, self._io, "/seq/3" - ) + raise kaitaistruct.ValidationNotEqualError(b"\x00\x00\x00\x00", self.filler, self._io, u"/seq/3") self.mal_size = self._io.read_u8le() self.len_mal_hash_sha1 = self._io.read_u4le() self.mal_hash_sha1 = self._io.read_bytes(self.len_mal_hash_sha1) @@ -39,83 +32,147 @@ def _read(self): for i in range(self.num_findings): self.findings.append(EsetNdfParser.Threat(self._io, self, self._root)) - class Threat(KaitaiStruct): + + + def _fetch_instances(self): + pass + self.datetime_unix._fetch_instances() + for i in range(len(self.findings)): + pass + self.findings[i]._fetch_instances() + + + class Dateblock(KaitaiStruct): def __init__(self, _io, _parent=None, _root=None): - self._io = _io + super(EsetNdfParser.Dateblock, self).__init__(_io) self._parent = _parent - self._root = _root if _root else self + self._root = _root self._read() def _read(self): - self.mal_path = EsetNdfParser.Widestr(self._io, self, self._root) self.date_block_size = self._io.read_u4le() + if self.date_block_size != 0: + pass + self.date_block_contents = EsetNdfParser.DateblockContents(self._io, self, self._root) + + + + def _fetch_instances(self): + pass + if self.date_block_size != 0: + pass + self.date_block_contents._fetch_instances() + + + + class DateblockContents(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + super(EsetNdfParser.DateblockContents, self).__init__(_io) + self._parent = _parent + self._root = _root + self._read() + + def _read(self): self.date_block_header = self._io.read_bytes(4) - if not self.date_block_header == b"\x4e\x49\x57\x49": - raise kaitaistruct.ValidationNotEqualError( - b"\x4e\x49\x57\x49", - self.date_block_header, - self._io, - "/types/threat/seq/2", - ) - self.datetime_quar_enc_start = EsetNdfParser.Windate( - self._io, self, self._root - ) + if not self.date_block_header == b"\x4E\x49\x57\x49": + raise kaitaistruct.ValidationNotEqualError(b"\x4E\x49\x57\x49", self.date_block_header, self._io, u"/types/dateblock_contents/seq/0") + self.datetime_quar_enc_start = EsetNdfParser.Windate(self._io, self, self._root) self.datetime_first_utc = EsetNdfParser.Windate(self._io, self, self._root) - self.datetime_quar_enc_stop = EsetNdfParser.Windate( - self._io, self, self._root - ) + self.datetime_quar_enc_stop = EsetNdfParser.Windate(self._io, self, self._root) self.unknown_size = self._io.read_u4le() - self.datetime_latest_occurence = EsetNdfParser.Unixdate( - self._io, self, self._root - ) + + + def _fetch_instances(self): + pass + self.datetime_quar_enc_start._fetch_instances() + self.datetime_first_utc._fetch_instances() + self.datetime_quar_enc_stop._fetch_instances() + + + class Threat(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + super(EsetNdfParser.Threat, self).__init__(_io) + self._parent = _parent + self._root = _root + self._read() + + def _read(self): + self.mal_path = EsetNdfParser.Widestr(self._io, self, self._root) + self.date_block = EsetNdfParser.Dateblock(self._io, self, self._root) + self.datetime_latest_occurence = EsetNdfParser.Unixdate(self._io, self, self._root) self.filler1 = self._io.read_bytes(4) if not self.filler1 == b"\x00\x00\x00\x00": - raise kaitaistruct.ValidationNotEqualError( - b"\x00\x00\x00\x00", self.filler1, self._io, "/types/threat/seq/8" - ) + raise kaitaistruct.ValidationNotEqualError(b"\x00\x00\x00\x00", self.filler1, self._io, u"/types/threat/seq/3") self.threat_local = EsetNdfParser.Widestr(self._io, self, self._root) self.threat_canonized = EsetNdfParser.Widestr(self._io, self, self._root) self.filler2 = self._io.read_bytes(4) if not self.filler2 == b"\x00\x00\x00\x00": - raise kaitaistruct.ValidationNotEqualError( - b"\x00\x00\x00\x00", self.filler2, self._io, "/types/threat/seq/11" - ) + raise kaitaistruct.ValidationNotEqualError(b"\x00\x00\x00\x00", self.filler2, self._io, u"/types/threat/seq/6") self.threat_occurence = self._io.read_u4le() self.unknown = self._io.read_bytes(4) self.datetime_unix = EsetNdfParser.Unixdate(self._io, self, self._root) self.mal_path2 = EsetNdfParser.Widestr(self._io, self, self._root) - class Windate(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - def _read(self): - self._raw_date_time = self._io.read_bytes(8) - _process = maldump.utils.RawTimeConverter("windows") - self.date_time = _process.decode(self._raw_date_time) + def _fetch_instances(self): + pass + self.mal_path._fetch_instances() + self.date_block._fetch_instances() + self.datetime_latest_occurence._fetch_instances() + self.threat_local._fetch_instances() + self.threat_canonized._fetch_instances() + self.datetime_unix._fetch_instances() + self.mal_path2._fetch_instances() + class Unixdate(KaitaiStruct): def __init__(self, _io, _parent=None, _root=None): - self._io = _io + super(EsetNdfParser.Unixdate, self).__init__(_io) self._parent = _parent - self._root = _root if _root else self + self._root = _root self._read() def _read(self): self._raw_date_time = self._io.read_bytes(4) - _process = maldump.utils.RawTimeConverter("unix") + _process = maldump.utils.RawTimeConverter(u"unix") self.date_time = _process.decode(self._raw_date_time) + + def _fetch_instances(self): + pass + + class Widestr(KaitaiStruct): def __init__(self, _io, _parent=None, _root=None): - self._io = _io + super(EsetNdfParser.Widestr, self).__init__(_io) self._parent = _parent - self._root = _root if _root else self + self._root = _root self._read() def _read(self): self.len_str = self._io.read_u4le() - self.str = (self._io.read_bytes((2 * self.len_str))).decode("UTF-16LE") + self.str = (self._io.read_bytes(2 * self.len_str)).decode(u"UTF-16LE") + + + def _fetch_instances(self): + pass + + + class Windate(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + super(EsetNdfParser.Windate, self).__init__(_io) + self._parent = _parent + self._root = _root + self._read() + + def _read(self): + self._raw_date_time = self._io.read_bytes(8) + _process = maldump.utils.RawTimeConverter(u"windows") + self.date_time = _process.decode(self._raw_date_time) + + + def _fetch_instances(self): + pass + + + diff --git a/maldump/parsers/kaitai/eset_virlog_parser.ksy b/maldump/parsers/kaitai/eset_virlog_parser.ksy index 1dd2154..7365271 100644 --- a/maldump/parsers/kaitai/eset_virlog_parser.ksy +++ b/maldump/parsers/kaitai/eset_virlog_parser.ksy @@ -33,8 +33,6 @@ types: type: windate - id: num_threats2 type: u4 - valid: - expr: _ == num_threats - id: unknown size-eos: true @@ -59,10 +57,7 @@ types: type: windate - id: unknown_u4int0 type: u4 - - id: record_id2 # this should be equal to record_id - valid: - expr: _ == record_id - + - id: record_id2 type: u4 - id: unknown_u4int1 type: u4 diff --git a/maldump/parsers/kaitai/eset_virlog_parser.py b/maldump/parsers/kaitai/eset_virlog_parser.py index 2031597..a9465ac 100644 --- a/maldump/parsers/kaitai/eset_virlog_parser.py +++ b/maldump/parsers/kaitai/eset_virlog_parser.py @@ -1,21 +1,18 @@ # This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild +# type: ignore import kaitaistruct from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO -from enum import Enum +from enum import IntEnum import maldump.utils -if getattr(kaitaistruct, "API_VERSION", (0, 9)) < (0, 9): - raise Exception( - "Incompatible Kaitai Struct Python API: 0.9 or later is required, but you have %s" - % (kaitaistruct.__version__) - ) - +if getattr(kaitaistruct, 'API_VERSION', (0, 9)) < (0, 11): + raise Exception("Incompatible Kaitai Struct Python API: 0.11 or later is required, but you have %s" % (kaitaistruct.__version__)) class EsetVirlogParser(KaitaiStruct): - class Opcode(Enum): + class Opcode(IntEnum): unknown_u4int14 = 4259844 unknown_u4int15 = 4259845 unknown_u4int16 = 4259850 @@ -51,145 +48,310 @@ class Opcode(Enum): path_name = 5116841 infiltration_name = 5119309 virus_db = 5121815 - def __init__(self, _io, _parent=None, _root=None): - self._io = _io + super(EsetVirlogParser, self).__init__(_io) self._parent = _parent - self._root = _root if _root else self + self._root = _root or self self._read() def _read(self): self.magic = self._io.read_bytes(4) - if not self.magic == b"\x78\xf3\x9b\xcf": - raise kaitaistruct.ValidationNotEqualError( - b"\x78\xf3\x9b\xcf", self.magic, self._io, "/seq/0" - ) + if not self.magic == b"\x78\xF3\x9B\xCF": + raise kaitaistruct.ValidationNotEqualError(b"\x78\xF3\x9B\xCF", self.magic, self._io, u"/seq/0") self.len_header = self._io.read_u4le() - self._raw_header = self._io.read_bytes((self.len_header - 8)) + self._raw_header = self._io.read_bytes(self.len_header - 8) _io__raw_header = KaitaiStream(BytesIO(self._raw_header)) self.header = EsetVirlogParser.Header(_io__raw_header, self, self._root) self.threats = [] for i in range(self.header.num_threats): self.threats.append(EsetVirlogParser.Threat(self._io, self, self._root)) - class Widestr(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - def _read(self): - self.len_str = self._io.read_u4le() - self.str = (self._io.read_bytes((self.len_str - 2))).decode("UTF-16LE") - if self.len_str != 0: - self.nullbytes = self._io.read_bytes(2) - if not self.nullbytes == b"\x00\x00": - raise kaitaistruct.ValidationNotEqualError( - b"\x00\x00", self.nullbytes, self._io, "/types/widestr/seq/2" - ) - class Unixdate(KaitaiStruct): + def _fetch_instances(self): + pass + self.header._fetch_instances() + for i in range(len(self.threats)): + pass + self.threats[i]._fetch_instances() + + + class Epilogue(KaitaiStruct): def __init__(self, _io, _parent=None, _root=None): - self._io = _io + super(EsetVirlogParser.Epilogue, self).__init__(_io) self._parent = _parent - self._root = _root if _root else self + self._root = _root self._read() def _read(self): - self._raw_date_time = self._io.read_bytes(8) - _process = maldump.utils.RawTimeConverter("unix") - self.date_time = _process.decode(self._raw_date_time) + self.data = self._io.read_bytes_full() + + + def _fetch_instances(self): + pass + class Hash(KaitaiStruct): def __init__(self, _io, _parent=None, _root=None): - self._io = _io + super(EsetVirlogParser.Hash, self).__init__(_io) self._parent = _parent - self._root = _root if _root else self + self._root = _root self._read() def _read(self): self.len_hash = self._io.read_u4le() self.hash = self._io.read_bytes(self.len_hash) + + def _fetch_instances(self): + pass + + class Header(KaitaiStruct): def __init__(self, _io, _parent=None, _root=None): - self._io = _io + super(EsetVirlogParser.Header, self).__init__(_io) self._parent = _parent - self._root = _root if _root else self + self._root = _root self._read() def _read(self): self.num_threats = self._io.read_u4le() self.filesize = self._io.read_u8le() self.timsestamp = EsetVirlogParser.Windate(self._io, self, self._root) - self.windowsdatetime_unknown2 = EsetVirlogParser.Windate( - self._io, self, self._root - ) - self.windowsdatetime_unknown3 = EsetVirlogParser.Windate( - self._io, self, self._root - ) + self.windowsdatetime_unknown2 = EsetVirlogParser.Windate(self._io, self, self._root) + self.windowsdatetime_unknown3 = EsetVirlogParser.Windate(self._io, self, self._root) self.num_threats2 = self._io.read_u4le() - _ = self.num_threats2 - if not _ == self.num_threats: - raise kaitaistruct.ValidationExprError( - self.num_threats2, self._io, "/types/header/seq/5" - ) self.unknown = self._io.read_bytes_full() - class Threat(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - def _read(self): - self.magic = self._io.read_bytes(4) - if not self.magic == b"\xdc\xcf\x8b\x63": - raise kaitaistruct.ValidationNotEqualError( - b"\xdc\xcf\x8b\x63", self.magic, self._io, "/types/threat/seq/0" - ) - self.len_record = self._io.read_u4le() - self._raw_record = self._io.read_bytes((self.len_record - 8)) - _io__raw_record = KaitaiStream(BytesIO(self._raw_record)) - self.record = EsetVirlogParser.Record(_io__raw_record, self, self._root) + def _fetch_instances(self): + pass + self.timsestamp._fetch_instances() + self.windowsdatetime_unknown2._fetch_instances() + self.windowsdatetime_unknown3._fetch_instances() - class Epilogue(KaitaiStruct): + + class Op(KaitaiStruct): def __init__(self, _io, _parent=None, _root=None): - self._io = _io + super(EsetVirlogParser.Op, self).__init__(_io) self._parent = _parent - self._root = _root if _root else self + self._root = _root self._read() def _read(self): - self.data = self._io.read_bytes_full() + self.name = KaitaiStream.resolve_enum(EsetVirlogParser.Opcode, self._io.read_u4le()) + _on = self.name + if _on == EsetVirlogParser.Opcode.firstseen: + pass + self.arg = EsetVirlogParser.Unixdate(self._io, self, self._root) + elif _on == EsetVirlogParser.Opcode.infiltration_name: + pass + self.arg = EsetVirlogParser.Widestr(self._io, self, self._root) + elif _on == EsetVirlogParser.Opcode.object_hash: + pass + self.arg = EsetVirlogParser.Hash(self._io, self, self._root) + elif _on == EsetVirlogParser.Opcode.object_name: + pass + self.arg = EsetVirlogParser.Widestr(self._io, self, self._root) + elif _on == EsetVirlogParser.Opcode.path_name: + pass + self.arg = EsetVirlogParser.Widestr(self._io, self, self._root) + elif _on == EsetVirlogParser.Opcode.progpath_name: + pass + self.arg = EsetVirlogParser.Widestr(self._io, self, self._root) + elif _on == EsetVirlogParser.Opcode.program_hash: + pass + self.arg = EsetVirlogParser.Hash(self._io, self, self._root) + elif _on == EsetVirlogParser.Opcode.program_name: + pass + self.arg = EsetVirlogParser.Widestr(self._io, self, self._root) + elif _on == EsetVirlogParser.Opcode.unknown_epilogue: + pass + self.arg = EsetVirlogParser.Epilogue(self._io, self, self._root) + elif _on == EsetVirlogParser.Opcode.unknown_hash: + pass + self.arg = EsetVirlogParser.Hash(self._io, self, self._root) + elif _on == EsetVirlogParser.Opcode.unknown_hash2: + pass + self.arg = EsetVirlogParser.Hash(self._io, self, self._root) + elif _on == EsetVirlogParser.Opcode.unknown_hash3: + pass + self.arg = EsetVirlogParser.Hash(self._io, self, self._root) + elif _on == EsetVirlogParser.Opcode.unknown_u1int1: + pass + self.arg = self._io.read_u1() + elif _on == EsetVirlogParser.Opcode.unknown_u1int2: + pass + self.arg = self._io.read_u1() + elif _on == EsetVirlogParser.Opcode.unknown_u4int1: + pass + self.arg = self._io.read_u4le() + elif _on == EsetVirlogParser.Opcode.unknown_u4int10: + pass + self.arg = self._io.read_u4le() + elif _on == EsetVirlogParser.Opcode.unknown_u4int11: + pass + self.arg = self._io.read_u4le() + elif _on == EsetVirlogParser.Opcode.unknown_u4int12: + pass + self.arg = self._io.read_u4le() + elif _on == EsetVirlogParser.Opcode.unknown_u4int13: + pass + self.arg = self._io.read_u4le() + elif _on == EsetVirlogParser.Opcode.unknown_u4int14: + pass + self.arg = self._io.read_u4le() + elif _on == EsetVirlogParser.Opcode.unknown_u4int15: + pass + self.arg = self._io.read_u4le() + elif _on == EsetVirlogParser.Opcode.unknown_u4int16: + pass + self.arg = self._io.read_u4le() + elif _on == EsetVirlogParser.Opcode.unknown_u4int2: + pass + self.arg = self._io.read_u4le() + elif _on == EsetVirlogParser.Opcode.unknown_u4int3: + pass + self.arg = self._io.read_u4le() + elif _on == EsetVirlogParser.Opcode.unknown_u4int4: + pass + self.arg = self._io.read_u4le() + elif _on == EsetVirlogParser.Opcode.unknown_u4int5: + pass + self.arg = self._io.read_u4le() + elif _on == EsetVirlogParser.Opcode.unknown_u4int6: + pass + self.arg = self._io.read_u4le() + elif _on == EsetVirlogParser.Opcode.unknown_u4int7: + pass + self.arg = self._io.read_u4le() + elif _on == EsetVirlogParser.Opcode.unknown_u4int8: + pass + self.arg = self._io.read_u4le() + elif _on == EsetVirlogParser.Opcode.unknown_u4int9: + pass + self.arg = self._io.read_u4le() + elif _on == EsetVirlogParser.Opcode.unknown_u8int1: + pass + self.arg = self._io.read_u8le() + elif _on == EsetVirlogParser.Opcode.unknown_u8int2: + pass + self.arg = self._io.read_u8le() + elif _on == EsetVirlogParser.Opcode.unknown_u8int3: + pass + self.arg = self._io.read_u8le() + elif _on == EsetVirlogParser.Opcode.user_name: + pass + self.arg = EsetVirlogParser.Widestr(self._io, self, self._root) + elif _on == EsetVirlogParser.Opcode.virus_db: + pass + self.arg = EsetVirlogParser.Widestr(self._io, self, self._root) + + + def _fetch_instances(self): + pass + _on = self.name + if _on == EsetVirlogParser.Opcode.firstseen: + pass + self.arg._fetch_instances() + elif _on == EsetVirlogParser.Opcode.infiltration_name: + pass + self.arg._fetch_instances() + elif _on == EsetVirlogParser.Opcode.object_hash: + pass + self.arg._fetch_instances() + elif _on == EsetVirlogParser.Opcode.object_name: + pass + self.arg._fetch_instances() + elif _on == EsetVirlogParser.Opcode.path_name: + pass + self.arg._fetch_instances() + elif _on == EsetVirlogParser.Opcode.progpath_name: + pass + self.arg._fetch_instances() + elif _on == EsetVirlogParser.Opcode.program_hash: + pass + self.arg._fetch_instances() + elif _on == EsetVirlogParser.Opcode.program_name: + pass + self.arg._fetch_instances() + elif _on == EsetVirlogParser.Opcode.unknown_epilogue: + pass + self.arg._fetch_instances() + elif _on == EsetVirlogParser.Opcode.unknown_hash: + pass + self.arg._fetch_instances() + elif _on == EsetVirlogParser.Opcode.unknown_hash2: + pass + self.arg._fetch_instances() + elif _on == EsetVirlogParser.Opcode.unknown_hash3: + pass + self.arg._fetch_instances() + elif _on == EsetVirlogParser.Opcode.unknown_u1int1: + pass + elif _on == EsetVirlogParser.Opcode.unknown_u1int2: + pass + elif _on == EsetVirlogParser.Opcode.unknown_u4int1: + pass + elif _on == EsetVirlogParser.Opcode.unknown_u4int10: + pass + elif _on == EsetVirlogParser.Opcode.unknown_u4int11: + pass + elif _on == EsetVirlogParser.Opcode.unknown_u4int12: + pass + elif _on == EsetVirlogParser.Opcode.unknown_u4int13: + pass + elif _on == EsetVirlogParser.Opcode.unknown_u4int14: + pass + elif _on == EsetVirlogParser.Opcode.unknown_u4int15: + pass + elif _on == EsetVirlogParser.Opcode.unknown_u4int16: + pass + elif _on == EsetVirlogParser.Opcode.unknown_u4int2: + pass + elif _on == EsetVirlogParser.Opcode.unknown_u4int3: + pass + elif _on == EsetVirlogParser.Opcode.unknown_u4int4: + pass + elif _on == EsetVirlogParser.Opcode.unknown_u4int5: + pass + elif _on == EsetVirlogParser.Opcode.unknown_u4int6: + pass + elif _on == EsetVirlogParser.Opcode.unknown_u4int7: + pass + elif _on == EsetVirlogParser.Opcode.unknown_u4int8: + pass + elif _on == EsetVirlogParser.Opcode.unknown_u4int9: + pass + elif _on == EsetVirlogParser.Opcode.unknown_u8int1: + pass + elif _on == EsetVirlogParser.Opcode.unknown_u8int2: + pass + elif _on == EsetVirlogParser.Opcode.unknown_u8int3: + pass + elif _on == EsetVirlogParser.Opcode.user_name: + pass + self.arg._fetch_instances() + elif _on == EsetVirlogParser.Opcode.virus_db: + pass + self.arg._fetch_instances() + class Record(KaitaiStruct): def __init__(self, _io, _parent=None, _root=None): - self._io = _io + super(EsetVirlogParser.Record, self).__init__(_io) self._parent = _parent - self._root = _root if _root else self + self._root = _root self._read() def _read(self): self.record_header_magic = self._io.read_bytes(8) if not self.record_header_magic == b"\x24\x00\x00\x00\x01\x00\x01\x00": - raise kaitaistruct.ValidationNotEqualError( - b"\x24\x00\x00\x00\x01\x00\x01\x00", - self.record_header_magic, - self._io, - "/types/record/seq/0", - ) + raise kaitaistruct.ValidationNotEqualError(b"\x24\x00\x00\x00\x01\x00\x01\x00", self.record_header_magic, self._io, u"/types/record/seq/0") self.record_id = self._io.read_u4le() self.win_timestamp = EsetVirlogParser.Windate(self._io, self, self._root) self.unknown_u4int0 = self._io.read_u4le() self.record_id2 = self._io.read_u4le() - _ = self.record_id2 - if not _ == self.record_id: - raise kaitaistruct.ValidationExprError( - self.record_id2, self._io, "/types/record/seq/4" - ) self.unknown_u4int1 = self._io.read_u4le() self.unknown_u4int2 = self._io.read_u4le() self.unknown_u4int3 = self._io.read_u4le() @@ -199,97 +361,96 @@ def _read(self): self.data_fields.append(EsetVirlogParser.Op(self._io, self, self._root)) i += 1 - class Windate(KaitaiStruct): + + + def _fetch_instances(self): + pass + self.win_timestamp._fetch_instances() + for i in range(len(self.data_fields)): + pass + self.data_fields[i]._fetch_instances() + + + + class Threat(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + super(EsetVirlogParser.Threat, self).__init__(_io) + self._parent = _parent + self._root = _root + self._read() + + def _read(self): + self.magic = self._io.read_bytes(4) + if not self.magic == b"\xDC\xCF\x8B\x63": + raise kaitaistruct.ValidationNotEqualError(b"\xDC\xCF\x8B\x63", self.magic, self._io, u"/types/threat/seq/0") + self.len_record = self._io.read_u4le() + self._raw_record = self._io.read_bytes(self.len_record - 8) + _io__raw_record = KaitaiStream(BytesIO(self._raw_record)) + self.record = EsetVirlogParser.Record(_io__raw_record, self, self._root) + + + def _fetch_instances(self): + pass + self.record._fetch_instances() + + + class Unixdate(KaitaiStruct): def __init__(self, _io, _parent=None, _root=None): - self._io = _io + super(EsetVirlogParser.Unixdate, self).__init__(_io) self._parent = _parent - self._root = _root if _root else self + self._root = _root self._read() def _read(self): self._raw_date_time = self._io.read_bytes(8) - _process = maldump.utils.RawTimeConverter("windows") + _process = maldump.utils.RawTimeConverter(u"unix") self.date_time = _process.decode(self._raw_date_time) - class Op(KaitaiStruct): + + def _fetch_instances(self): + pass + + + class Widestr(KaitaiStruct): def __init__(self, _io, _parent=None, _root=None): - self._io = _io + super(EsetVirlogParser.Widestr, self).__init__(_io) self._parent = _parent - self._root = _root if _root else self + self._root = _root self._read() def _read(self): - self.name = KaitaiStream.resolve_enum( - EsetVirlogParser.Opcode, self._io.read_u4le() - ) - _on = self.name - if _on == EsetVirlogParser.Opcode.unknown_u1int1: - self.arg = self._io.read_u1() - elif _on == EsetVirlogParser.Opcode.unknown_u4int7: - self.arg = self._io.read_u4le() - elif _on == EsetVirlogParser.Opcode.unknown_u4int8: - self.arg = self._io.read_u4le() - elif _on == EsetVirlogParser.Opcode.unknown_u4int16: - self.arg = self._io.read_u4le() - elif _on == EsetVirlogParser.Opcode.unknown_hash: - self.arg = EsetVirlogParser.Hash(self._io, self, self._root) - elif _on == EsetVirlogParser.Opcode.unknown_u4int15: - self.arg = self._io.read_u4le() - elif _on == EsetVirlogParser.Opcode.unknown_u4int3: - self.arg = self._io.read_u4le() - elif _on == EsetVirlogParser.Opcode.unknown_u4int1: - self.arg = self._io.read_u4le() - elif _on == EsetVirlogParser.Opcode.unknown_u1int2: - self.arg = self._io.read_u1() - elif _on == EsetVirlogParser.Opcode.path_name: - self.arg = EsetVirlogParser.Widestr(self._io, self, self._root) - elif _on == EsetVirlogParser.Opcode.program_hash: - self.arg = EsetVirlogParser.Hash(self._io, self, self._root) - elif _on == EsetVirlogParser.Opcode.firstseen: - self.arg = EsetVirlogParser.Unixdate(self._io, self, self._root) - elif _on == EsetVirlogParser.Opcode.unknown_u4int2: - self.arg = self._io.read_u4le() - elif _on == EsetVirlogParser.Opcode.progpath_name: - self.arg = EsetVirlogParser.Widestr(self._io, self, self._root) - elif _on == EsetVirlogParser.Opcode.infiltration_name: - self.arg = EsetVirlogParser.Widestr(self._io, self, self._root) - elif _on == EsetVirlogParser.Opcode.unknown_hash2: - self.arg = EsetVirlogParser.Hash(self._io, self, self._root) - elif _on == EsetVirlogParser.Opcode.unknown_u4int14: - self.arg = self._io.read_u4le() - elif _on == EsetVirlogParser.Opcode.unknown_epilogue: - self.arg = EsetVirlogParser.Epilogue(self._io, self, self._root) - elif _on == EsetVirlogParser.Opcode.program_name: - self.arg = EsetVirlogParser.Widestr(self._io, self, self._root) - elif _on == EsetVirlogParser.Opcode.object_name: - self.arg = EsetVirlogParser.Widestr(self._io, self, self._root) - elif _on == EsetVirlogParser.Opcode.unknown_hash3: - self.arg = EsetVirlogParser.Hash(self._io, self, self._root) - elif _on == EsetVirlogParser.Opcode.unknown_u8int1: - self.arg = self._io.read_u8le() - elif _on == EsetVirlogParser.Opcode.unknown_u4int13: - self.arg = self._io.read_u4le() - elif _on == EsetVirlogParser.Opcode.unknown_u4int4: - self.arg = self._io.read_u4le() - elif _on == EsetVirlogParser.Opcode.unknown_u8int3: - self.arg = self._io.read_u8le() - elif _on == EsetVirlogParser.Opcode.unknown_u4int5: - self.arg = self._io.read_u4le() - elif _on == EsetVirlogParser.Opcode.virus_db: - self.arg = EsetVirlogParser.Widestr(self._io, self, self._root) - elif _on == EsetVirlogParser.Opcode.object_hash: - self.arg = EsetVirlogParser.Hash(self._io, self, self._root) - elif _on == EsetVirlogParser.Opcode.unknown_u4int12: - self.arg = self._io.read_u4le() - elif _on == EsetVirlogParser.Opcode.unknown_u8int2: - self.arg = self._io.read_u8le() - elif _on == EsetVirlogParser.Opcode.unknown_u4int9: - self.arg = self._io.read_u4le() - elif _on == EsetVirlogParser.Opcode.unknown_u4int10: - self.arg = self._io.read_u4le() - elif _on == EsetVirlogParser.Opcode.unknown_u4int6: - self.arg = self._io.read_u4le() - elif _on == EsetVirlogParser.Opcode.user_name: - self.arg = EsetVirlogParser.Widestr(self._io, self, self._root) - elif _on == EsetVirlogParser.Opcode.unknown_u4int11: - self.arg = self._io.read_u4le() + self.len_str = self._io.read_u4le() + self.str = (self._io.read_bytes(self.len_str - 2)).decode(u"UTF-16LE") + if self.len_str != 0: + pass + self.nullbytes = self._io.read_bytes(2) + if not self.nullbytes == b"\x00\x00": + raise kaitaistruct.ValidationNotEqualError(b"\x00\x00", self.nullbytes, self._io, u"/types/widestr/seq/2") + + + + def _fetch_instances(self): + pass + if self.len_str != 0: + pass + + + + class Windate(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + super(EsetVirlogParser.Windate, self).__init__(_io) + self._parent = _parent + self._root = _root + self._read() + + def _read(self): + self._raw_date_time = self._io.read_bytes(8) + _process = maldump.utils.RawTimeConverter(u"windows") + self.date_time = _process.decode(self._raw_date_time) + + + def _fetch_instances(self): + pass + + + diff --git a/maldump/parsers/kaspersky_parser.py b/maldump/parsers/kaspersky_parser.py index 0aa4969..333fb63 100644 --- a/maldump/parsers/kaspersky_parser.py +++ b/maldump/parsers/kaspersky_parser.py @@ -56,7 +56,7 @@ def parse_from_log(self, _=None) -> dict[str, QuarEntry]: cursor.execute("SELECT * FROM 'objects'") rows = cursor.fetchall() except sqlite3.Error as e: - logger.exception( + logger.info( 'Cannot open nor read from a database file, path "%s"', db_file, exc_info=e, diff --git a/maldump/structures.py b/maldump/structures.py index 3b4a516..025361b 100644 --- a/maldump/structures.py +++ b/maldump/structures.py @@ -15,6 +15,7 @@ class QuarEntry: timestamp: dt threat: str path: str + orig_path: str | None size: int | None = None _md5: str | None = None sha1: str | None = None diff --git a/maldump/utils.py b/maldump/utils.py index 80e589f..033b7de 100755 --- a/maldump/utils.py +++ b/maldump/utils.py @@ -7,6 +7,7 @@ import contextlib import logging from datetime import datetime, timezone +import os from typing import TYPE_CHECKING, Any, Callable, Generic, TypeVar import kaitaistruct @@ -130,7 +131,7 @@ def kaitai(self, kaitai: type[T], path: Path) -> T | None: try: logger.debug( 'Trying to parse file, path "%s" to kaitai, type "%s" on <%s>', - path, + os.path.abspath(path), kaitai.__name__, self.objname, ) @@ -138,13 +139,13 @@ def kaitai(self, kaitai: type[T], path: Path) -> T | None: except OSError as e: logger.exception( 'Cannot open nor read kaitai for path "%s"', - path, + os.path.abspath(path), exc_info=e, ) except kaitaistruct.KaitaiStructError as e: logger.warning( 'Cannot read kaitai, probably incorrect format for path "%s"', - path, + os.path.abspath(path), exc_info=e, ) return kt @@ -187,7 +188,7 @@ def contents(path: Path, filetype: str = "") -> bytes | None: logger.debug('Trying to read %s file, path "%s"', filetype, path) data = f.read() except OSError as e: - logger.exception( + logger.warning( 'Cannot open %s file in ESET on path "%s"', filetype, path, exc_info=e ) data = None diff --git a/pdm.lock b/pdm.lock index 643048e..934c42b 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "lint"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:bc00e59127ba01521bbf7b5726d076f1f127f03c3d7d82f7a34309e5051e90d6" +content_hash = "sha256:e95562862d400112e83e493cc0e490c7c454d79bee4de917fcb284a7b0bf0abb" [[metadata.targets]] requires_python = ">=3.8" @@ -61,7 +61,7 @@ files = [ [[package]] name = "kaitaistruct" -version = "0.10" +version = "0.11" requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" summary = "Kaitai Struct declarative parser generator for binary data: runtime library for Python" groups = ["default"] @@ -69,8 +69,8 @@ dependencies = [ "enum34; python_version < \"3.4\"", ] files = [ - {file = "kaitaistruct-0.10-py2.py3-none-any.whl", hash = "sha256:a97350919adbf37fda881f75e9365e2fb88d04832b7a4e57106ec70119efb235"}, - {file = "kaitaistruct-0.10.tar.gz", hash = "sha256:a044dee29173d6afbacf27bcac39daf89b654dd418cfa009ab82d9178a9ae52a"}, + {file = "kaitaistruct-0.11-py2.py3-none-any.whl", hash = "sha256:5c6ce79177b4e193a577ecd359e26516d1d6d000a0bffd6e1010f2a46a62a561"}, + {file = "kaitaistruct-0.11.tar.gz", hash = "sha256:053ee764288e78b8e53acf748e9733268acbd579b8d82a427b1805453625d74b"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index 3e6273f..eb33e3e 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,12 +7,12 @@ dynamic = ["version"] name = "maldump" description = "Multi-quarantine extractor" authors = [ - {name = "Erik Kuna, Jež Dominik, Jonáš Novotný, Nikola Knežević"}, + {name = "Erik Kuna, Jež Dominik, Jonáš Novotný, Nikola Knežević, Vojtěch Jelínek"}, ] dependencies = [ "colorama==0.4.6", "defusedxml==0.7.1", - "kaitaistruct==0.10", + "kaitaistruct==0.11", "arc4==0.4.0", "types-colorama==0.4.15.12", ]