Skip to content

Commit

Permalink
Introduce ruff (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
vojtechjelinek authored Sep 30, 2024
1 parent 53075f7 commit 0220221
Show file tree
Hide file tree
Showing 29 changed files with 512 additions and 488 deletions.
13 changes: 4 additions & 9 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,14 @@ jobs:
- name: Install dependencies
run: |
pdm sync
- name: Lint with flake8
- name: Run checks with ruff
run: |
# stop the build if there are Python syntax errors or undefined names
pdm run flake8 . --exclude .venv,maldump/parsers --select=E9,F63,F7,F82 --show-source --statistics
# run flake8
pdm run flake8 . --exclude .venv,maldump/parsers --show-source --statistics
- name: Check sorting of dependencies
run: |
pdm run isort . --check --diff
pdm run ruff check
pdm run ruff format --check
- name: Check typing
run: |
pdm run mypy maldump
- name: Run simple tests
run: |
pdm run python -m unittest
pdm run python -m maldump ./test/root
pdm run python -m maldump ./test/root
92 changes: 46 additions & 46 deletions maldump/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,26 @@
import sys
import tarfile
from pathlib import Path
from typing import List
from typing import TYPE_CHECKING

from colorama import Fore, Style, init

from maldump.av_manager import AVManager
from maldump.structures import Quarantine

__version__ = '0.4.0'
if TYPE_CHECKING:
from maldump.structures import Quarantine

__version__ = "0.4.0"

def main() -> None:

def main() -> None:
init()
args = parse_cli()

# Admin privileges are required for optimal function (windows only)
if sys.platform == 'win32' and not ctypes.windll.shell32.IsUserAnAdmin():
print('Please try again with admin privileges')
exit(1)
if sys.platform == "win32" and not ctypes.windll.shell32.IsUserAnAdmin():
print("Please try again with admin privileges")
sys.exit(1)

# Save the destination directory
dest: Path = args.dest.resolve()
Expand All @@ -51,18 +52,17 @@ def main() -> None:


def export_files(
avs: List[Quarantine],
dest: Path,
out_file: str = 'quarantine.tar') -> None:
avs: list[Quarantine], 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')
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 = tarfile.TarInfo(av.name + "/" + entry.md5)
tarinfo.size = len(entry.malfile)
tar.addfile(tarinfo, io.BytesIO(entry.malfile))
tar.close()
Expand All @@ -71,9 +71,8 @@ def export_files(


def export_meta(
avs: List[Quarantine],
dest: Path,
meta_file: str = 'quarantine.csv') -> None:
avs: list[Quarantine], dest: Path, meta_file: str = "quarantine.csv"
) -> None:
entries = []
for av in avs:
for e in av.export():
Expand All @@ -82,70 +81,71 @@ def export_meta(
entries.append(d)
if len(entries) > 0:
csv_path = dest.joinpath(meta_file)
with open(csv_path, 'w', encoding='utf-8', newline='') as f:
fields = [
'timestamp',
'antivirus',
'threat',
'path',
'size',
'md5'
]
writer = csv.DictWriter(f, fields, extrasaction='ignore')
with open(csv_path, "w", encoding="utf-8", newline="") as f:
fields = ["timestamp", "antivirus", "threat", "path", "size", "md5"]
writer = csv.DictWriter(f, fields, extrasaction="ignore")
writer.writeheader()
writer.writerows(entries)
print(f"Written {len(entries)} row(s) into file '{meta_file}'")


def list_files(avs: List[Quarantine]) -> None:
def list_files(avs: list[Quarantine]) -> None:
for i, av in enumerate(avs):
entries = av.export()
if len(entries) > 0:
if i != 0:
print()
print(Fore.YELLOW + '---', av.name, '---' + Style.RESET_ALL)
print(Fore.YELLOW + "---", av.name, "---" + Style.RESET_ALL)
for e in entries:
print(e.path)


def parse_cli() -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog='maldump',
prog="maldump",
formatter_class=argparse.RawTextHelpFormatter,
description='Multi-quarantine extractor',
description="Multi-quarantine extractor",
epilog=(
'Supported quarantines:\n' +
'\n'.join(sorted([' * ' + av.name for av in AVManager.avs]))
)
"Supported quarantines:\n"
+ "\n".join(sorted([" * " + av.name for av in AVManager.avs]))
),
)

parser.add_argument(
'root_dir', type=Path,
help=r'root directory where OS is installed (example C:\)'
"root_dir",
type=Path,
help=r"root directory where OS is installed (example C:\)",
)
parser.add_argument(
'-l', '--list', action='store_true',
help='list quarantined file(s) to stdout (default action)'
"-l",
"--list",
action="store_true",
help="list quarantined file(s) to stdout (default action)",
)
parser.add_argument(
'-q', '--quar', action='store_true',
help='dump quarantined file(s) to archive \'quarantine.tar\''
"-q",
"--quar",
action="store_true",
help="dump quarantined file(s) to archive 'quarantine.tar'",
)
parser.add_argument(
'-m', '--meta', action='store_true',
help='dump metadata to CSV file \'quarantine.csv\''
"-m",
"--meta",
action="store_true",
help="dump metadata to CSV file 'quarantine.csv'",
)
parser.add_argument(
'-a', '--all', action='store_true',
help='equivalent of running both -q and -m'
"-a", "--all", action="store_true", help="equivalent of running both -q and -m"
)
parser.add_argument(
'-v', '--version', action='version', version='%(prog)s ' + __version__
"-v", "--version", action="version", version="%(prog)s " + __version__
)
parser.add_argument(
'-d', '--dest', type=Path,
help='destination of (quarantine.tar/quaratine.csv)',
default=os.getcwd()
"-d",
"--dest",
type=Path,
help="destination of (quarantine.tar/quaratine.csv)",
default=os.getcwd(),
)

return parser.parse_args()
Expand Down
29 changes: 21 additions & 8 deletions maldump/av_manager.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
from __future__ import annotations

from typing import List
from typing import TYPE_CHECKING, ClassVar

from maldump.avs import (avast, avg, avira, eset, forticlient, gdata,
kaspersky, malwarebytes, mcafee, windef)
from maldump.structures import Quarantine
from maldump.avs import (
avast,
avg,
avira,
eset,
forticlient,
gdata,
kaspersky,
malwarebytes,
mcafee,
windef,
)

if TYPE_CHECKING:
from maldump.structures import Quarantine

class AVManager():

class AVManager:
"""Container class holding all instances"""
avs: List[Quarantine] = [

avs: ClassVar[list[Quarantine]] = [
windef.WindowsDefender(),
forticlient.FortiClient(),
malwarebytes.Malwarebytes(),
Expand All @@ -19,10 +32,10 @@ class AVManager():
kaspersky.Kaspersky(),
eset.EsetNOD32(),
mcafee.McAfee(),
avg.AVG()
avg.AVG(),
]

@classmethod
def detect(cls) -> List[Quarantine]:
def detect(cls) -> list[Quarantine]:
"""Returns a list of avs installed on the system"""
return [av for av in cls.avs if av.location.exists()]
9 changes: 6 additions & 3 deletions maldump/avs/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from __future__ import annotations

from pkgutil import walk_packages
from typing import Any

__all__ = []
for loader, module_name, is_pkg in walk_packages(__path__):
__all__.append(module_name)
__all__: list[Any] = []
for _, module_name, __ in walk_packages(__path__):
__all__ += module_name
14 changes: 4 additions & 10 deletions maldump/avs/avast.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

from pathlib import Path
from typing import List

from maldump.parsers.avast_parser import AvastParser
from maldump.structures import Quarantine, QuarEntry
Expand All @@ -12,13 +11,8 @@ class Avast(Quarantine):

def __init__(self) -> None:
super().__init__()
self.name = 'Avast'
self.location = Path('ProgramData/Avast Software/Avast/chest')
self.name = "Avast"
self.location = Path("ProgramData/Avast Software/Avast/chest")

def export(self) -> List[QuarEntry]:
quarfiles = AvastParser().from_file(
name=self.name,
location=self.location
)

return quarfiles
def export(self) -> list[QuarEntry]:
return AvastParser().from_file(name=self.name, location=self.location)
14 changes: 4 additions & 10 deletions maldump/avs/avg.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

from pathlib import Path
from typing import List

from maldump.parsers.avg_parser import AVGParser
from maldump.structures import Quarantine, QuarEntry
Expand All @@ -12,13 +11,8 @@ class AVG(Quarantine):

def __init__(self) -> None:
super().__init__()
self.name = 'AVG'
self.location = Path('ProgramData/AVG/Antivirus/chest')
self.name = "AVG"
self.location = Path("ProgramData/AVG/Antivirus/chest")

def export(self) -> List[QuarEntry]:
quarfiles = AVGParser().from_file(
name=self.name,
location=self.location
)

return quarfiles
def export(self) -> list[QuarEntry]:
return AVGParser().from_file(name=self.name, location=self.location)
14 changes: 4 additions & 10 deletions maldump/avs/avira.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

from pathlib import Path
from typing import List

from maldump.parsers.avira_parser import AviraParser
from maldump.structures import Quarantine, QuarEntry
Expand All @@ -12,13 +11,8 @@ class Avira(Quarantine):

def __init__(self) -> None:
super().__init__()
self.name = 'Avira'
self.location = Path('ProgramData/Avira/Antivirus/INFECTED')
self.name = "Avira"
self.location = Path("ProgramData/Avira/Antivirus/INFECTED")

def export(self) -> List[QuarEntry]:
quarfiles = AviraParser().from_file(
name=self.name,
location=self.location
)

return quarfiles
def export(self) -> list[QuarEntry]:
return AviraParser().from_file(name=self.name, location=self.location)
14 changes: 4 additions & 10 deletions maldump/avs/eset.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

from pathlib import Path
from typing import List

from maldump.parsers.eset_parser import EsetParser
from maldump.structures import Quarantine, QuarEntry
Expand All @@ -12,14 +11,9 @@ class EsetNOD32(Quarantine):

def __init__(self) -> None:
super().__init__()
self.name = 'Eset NOD32'
self.name = "Eset NOD32"
# File containing metadata
self.location = Path('ProgramData/ESET/ESET Security/Logs/virlog.dat')
self.location = Path("ProgramData/ESET/ESET Security/Logs/virlog.dat")

def export(self) -> List[QuarEntry]:
quarfiles = EsetParser().from_file(
name=self.name,
location=self.location
)

return quarfiles
def export(self) -> list[QuarEntry]:
return EsetParser().from_file(name=self.name, location=self.location)
14 changes: 4 additions & 10 deletions maldump/avs/forticlient.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

from pathlib import Path
from typing import List

from maldump.parsers.forticlient_parser import ForticlientParser
from maldump.structures import Quarantine, QuarEntry
Expand All @@ -12,13 +11,8 @@ class FortiClient(Quarantine):

def __init__(self) -> None:
super().__init__()
self.name = 'FortiClient'
self.location = Path('Program Files/Fortinet/FortiClient/quarantine')
self.name = "FortiClient"
self.location = Path("Program Files/Fortinet/FortiClient/quarantine")

def export(self) -> List[QuarEntry]:
quarfiles = ForticlientParser().from_file(
name=self.name,
location=self.location
)

return quarfiles
def export(self) -> list[QuarEntry]:
return ForticlientParser().from_file(name=self.name, location=self.location)
Loading

0 comments on commit 0220221

Please sign in to comment.