diff --git a/README.md b/README.md index 4850822..5af021b 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,17 @@ This module is also automatically installed if you install the `dissect` package ## Tools +Some CLI tools related to specific databases exists. These tools allow you to dump or inspect database content. + +| Commands | Description | +|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `dissect-ntds` | Windows NTDS (Active Directory database). | +| `dissect-ual` | Windows [User Access Logging](https://learn.microsoft.com/en-us/windows-server/administration/user-access-logging/get-started-with-user-access-logging) database. | +| `dissect-sru` | Windows System Resources And Usage Monitor database. | +| `dissect-certlog` | Windows [AD CS database](https://learn.microsoft.com/en-us/windows-server/identity/ad-cs/active-directory-certificate-services-overview) database. | +| `dissect-rpm` | [Red Hat Package Manager](https://rpm.org/) database. | +| `dissect-impacket` | Impacket compatibility shim for `secretsdump.py`. | + ### Impacket compatibility shim for secretsdump.py Impacket does not ([yet](https://github.com/fortra/impacket/pull/1452)) have native support for `dissect.database`, @@ -27,7 +38,7 @@ so in the meantime a compatibility shim is provided. To use this shim, simply in instructions above, and execute `secretsdump.py` like so: ```bash -python -m dissect.database.ese.tools.impacket /path/to/impacket/examples/secretsdump.py -h +dissect-impacket /path/to/impacket/examples/secretsdump.py -h ``` Impacket `secretsdump.py` will now use `dissect.database` for parsing the `NTDS.dit` file, resulting in a significant performance improvement! diff --git a/dissect/database/ese/ntds/tools/__init__.py b/dissect/database/ese/ntds/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dissect/database/ese/ntds/tools/ntds.py b/dissect/database/ese/ntds/tools/ntds.py new file mode 100644 index 0000000..60d4741 --- /dev/null +++ b/dissect/database/ese/ntds/tools/ntds.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from dissect.database.ese.ntds import NTDS + + +def main() -> None: + parser = argparse.ArgumentParser(description="dissect.database.ese.ntds NTDS parser") + parser.add_argument("input", help="NTDS database to read") + parser.add_argument("-c", "--objectClass", help="show only objects of this class", required=True) + parser.add_argument("-j", "--json", action="store_true", default=False, help="output in JSON format") + args = parser.parse_args() + + with Path(args.input).open("rb") as fh: + ntds = NTDS(fh) + + for record in ntds.search(objectClass=args.objectClass): + if args.json: + print(json.dumps(record.as_dict(), default=str)) + else: + print(record) + + +if __name__ == "__main__": + main() diff --git a/dissect/database/ese/tools/sru.py b/dissect/database/ese/tools/sru.py index 049612e..54a3cbd 100644 --- a/dissect/database/ese/tools/sru.py +++ b/dissect/database/ese/tools/sru.py @@ -1,6 +1,7 @@ from __future__ import annotations import argparse +import json from pathlib import Path from typing import TYPE_CHECKING, BinaryIO @@ -148,24 +149,32 @@ def __repr__(self) -> str: column_values = serialise_record_column_values(self.record) return f"" + def as_dict(self) -> dict: + ret = self.record.as_dict() + ret["provider"] = self.table.name + return ret + def main() -> None: parser = argparse.ArgumentParser(description="dissect.database.ese SRU parser") parser.add_argument("input", help="SRU database to read") parser.add_argument("-p", "--provider", help="filter records from this provider") + parser.add_argument("-j", "--json", action="store_true", default=False, help="output in JSON format") args = parser.parse_args() with Path(args.input).open("rb") as fh: parser = SRU(fh) if args.provider in NAME_TO_GUID_MAP: - for e in parser.get_table_entries(table_name=args.provider): - print(e) + generator = parser.get_table_entries(table_name=args.provider) elif args.provider: - for e in parser.get_table_entries(table_guid=args.provider): - print(e) + generator = parser.get_table_entries(table_guid=args.provider) else: - for e in parser.entries(): + generator = parser.entries() + for e in generator: + if args.json: + print(json.dumps(e.as_dict(), default=str)) + else: print(e) diff --git a/dissect/database/ese/tools/ual.py b/dissect/database/ese/tools/ual.py index 9cec70f..9f95fc1 100644 --- a/dissect/database/ese/tools/ual.py +++ b/dissect/database/ese/tools/ual.py @@ -1,6 +1,7 @@ import argparse import datetime import ipaddress +import json from collections.abc import Iterator from pathlib import Path from typing import BinaryIO @@ -89,6 +90,7 @@ def convert_day_num_to_date(year: int, day_num: int) -> datetime.datetime: def main() -> None: parser = argparse.ArgumentParser(description="dissect.database.ese UAL parser") + parser.add_argument("-j", "--json", action="store_true", default=False, help="output in JSON format") parser.add_argument("input", help="UAL database to read") args = parser.parse_args() @@ -100,7 +102,10 @@ def main() -> None: continue for record in parser.get_table_records(table.name): - print(record) + if args.json: + print(json.dumps(record, default=str)) + else: + print(record) if __name__ == "__main__": diff --git a/pyproject.toml b/pyproject.toml index 2fb6ff0..e52db00 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,6 +66,14 @@ dev = [ {include-group = "debug"}, ] +[project.scripts] +dissect-ntds = "dissect.database.ese.ntds.tools.ntds:main" +dissect-ual = "dissect.database.ese.tools.ual:main" +dissect-sru = "dissect.database.ese.tools.sru:main" +dissect-certlog = "dissect.database.ese.tools.certlog:main" +dissect-rpm = "dissect.database.bsd.tools.rpm:main" +dissect-impacket = "dissect.database.ese.tools.impacket:main" + [tool.ruff] line-length = 120 required-version = ">=0.13.1"