{"id":20273041,"url":"https://github.com/janstarke/ntdsextract2","last_synced_at":"2025-04-09T17:26:36.574Z","repository":{"id":41981117,"uuid":"510422901","full_name":"janstarke/ntdsextract2","owner":"janstarke","description":"This aims to be a collection of tools to forensically analyze Active Directory databases","archived":false,"fork":false,"pushed_at":"2025-03-15T15:06:24.000Z","size":5462,"stargazers_count":22,"open_issues_count":2,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-02T15:09:16.652Z","etag":null,"topics":["cli","forensics","forensics-tools","rust"],"latest_commit_sha":null,"homepage":"https://www.bdosecurity.de","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/janstarke.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-07-04T16:03:32.000Z","updated_at":"2025-02-13T06:06:04.000Z","dependencies_parsed_at":"2024-10-19T17:10:30.311Z","dependency_job_id":"a9ba6984-43aa-4241-8867-42f5600f2d42","html_url":"https://github.com/janstarke/ntdsextract2","commit_stats":{"total_commits":212,"total_committers":8,"mean_commits":26.5,"dds":0.4716981132075472,"last_synced_commit":"86e8f5c86111b633392ef2c5df580ab1b34b49ef"},"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janstarke%2Fntdsextract2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janstarke%2Fntdsextract2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janstarke%2Fntdsextract2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janstarke%2Fntdsextract2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/janstarke","download_url":"https://codeload.github.com/janstarke/ntdsextract2/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248076276,"owners_count":21043742,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","forensics","forensics-tools","rust"],"created_at":"2024-11-14T12:47:51.643Z","updated_at":"2025-04-09T17:26:36.553Z","avatar_url":"https://github.com/janstarke.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Crates.io](https://img.shields.io/crates/v/ntdsextract2)](https://crates.io/crates/ntdsextract2)\n![Crates.io](https://img.shields.io/crates/l/ntdsextract2)\n![Crates.io (latest)](https://img.shields.io/crates/dv/ntdsextract2)\n\n- [ntdsextract2](#ntdsextract2)\n  - [Why do you write a tool that's already there and working?](#why-do-you-write-a-tool-thats-already-there-and-working)\n- [Installation](#installation)\n- [Usage](#usage)\n  - [Search for entries](#search-for-entries)\n  - [Displaying a single entry](#displaying-a-single-entry)\n  - [Displaying the tree structure of the AD](#displaying-the-tree-structure-of-the-ad)\n  - [Creating a timeline](#creating-a-timeline)\n  - [Enumerating ...](#enumerating-)\n    - [... users](#-users)\n    - [... groups](#-groups)\n    - [... computers](#-computers)\n    - [... types](#-types)\n  - [Configuring the global timestamp format](#configuring-the-global-timestamp-format)\n- [Forensics details](#forensics-details)\n  - [Interpreting timestamps](#interpreting-timestamps)\n\n# ntdsextract2\n\n\u003cimg align=\"right\" width=\"128px\" src=\"https://raw.githubusercontent.com/janstarke/ntdsextract2/main/doc/images/ntdsextract2.jpeg\"\u003e\n\nThis aims to be a replacement of \u003chttps://github.com/csababarta/ntdsxtract/\u003e by @csababarta.\n\n## Why do you write a tool that's already there and working?\n\n1. ntdsxtract is using Python 2.7, which makes it hard to use on modern systems\n1. There has been no change since a lot of time (the last commit is from February 2016), which suggests that Csaba has other stuff to do at the moment. That's OK. But Windows *is* changing, and therefore the tools to analyze Windows Systems has to adapt. As I don't like some architectural decisions Csaba has made, I started my own development.\n\n# Installation\n\n```bash\ncargo install ntdsextract2\n```\n\n# Usage\n```\nUsage: ntdsextract2 [OPTIONS] \u003cNTDS_FILE\u003e \u003cCOMMAND\u003e\n\nCommands:\n  user      Display user accounts\n  group     Display groups\n  computer  display computer accounts\n  timeline  create a timeline (in bodyfile format)\n  types     list all defined types\n  tree      display the directory information tree\n  entry     display one single entry from the directory information tree\n  search    search for entries whose values match to some regular expression\n  help      Print this message or the help of the given subcommand(s)\n\nArguments:\n  \u003cNTDS_FILE\u003e  name of the file to analyze\n\nOptions:\n  -v, --verbose...  Increase logging verbosity\n  -q, --quiet...    Decrease logging verbosity\n  -h, --help        Print help\n  -V, --version     Print version\n```\n\n## Search for entries\n\n```\nUsage: ntdsextract2 \u003cNTDS_FILE\u003e search [OPTIONS] \u003cREGEX\u003e\n\nArguments:\n  \u003cREGEX\u003e  regular expression to match against\n\nOptions:\n  -i, --ignore-case  case-insensitive search (ignore case)\n  -v, --verbose...   Increase logging verbosity\n  -q, --quiet...     Decrease logging verbosity\n  -h, --help         Print help\n```\n\n## Displaying a single entry\n\n```\nUsage: ntdsextract2 \u003cNTDS_FILE\u003e entry [OPTIONS] \u003cENTRY_ID\u003e\n\nArguments:\n  \u003cENTRY_ID\u003e\n          id of the entry to show\n\nOptions:\n      --sid\n          search for SID instead for NTDS.DIT entry id. \u003cENTRY_ID\u003e will be interpreted as RID, wich is the last part of the SID; e.g. 500 will return the Administrator account\n\n  -F, --format \u003cENTRY_FORMAT\u003e\n          [default: simple]\n\n          Possible values:\n          - json:   use JSON format\n          - table:  display a formatted table\n          - simple: use a simple key-values based format\n\n  -v, --verbose...\n          Increase logging verbosity\n\n  -q, --quiet...\n          Decrease logging verbosity\n\n  -h, --help\n          Print help (see a summary with '-h')\n```\n\n## Displaying the tree structure of the AD\n\n```\nUsage: ntdsextract2 \u003cNTDS_FILE\u003e tree [OPTIONS]\n\nOptions:\n      --max-depth \u003cMAX_DEPTH\u003e  maximum recursion depth [default: 4]\n  -v, --verbose...             Increase logging verbosity\n  -q, --quiet...               Decrease logging verbosity\n  -h, --help                   Print help\n```\n\n## Creating a timeline\n\n```\nUsage: ntdsextract2 \u003cNTDS_FILE\u003e timeline [OPTIONS]\n\nOptions:\n      --all-objects      show objects of any type (this might be a lot)\n      --include-deleted  include also deleted objects (which don't have an AttObjectCategory attribute)\n  -v, --verbose...       Increase logging verbosity\n  -q, --quiet...         Decrease logging verbosity\n  -h, --help             Print help\n```\n\n## Enumerating ...\n\n### ... users\n\n```\nUsage: ntdsextract2 \u003cNTDS_FILE\u003e user [OPTIONS]\n\nOptions:\n  -F, --format \u003cFORMAT\u003e\n          Output format\n          \n          [default: csv]\n          [possible values: csv, json, json-lines]\n\n  -A, --show-all\n          show all non-empty values. This option is ignored when CSV-Output is selected\n\n  -D, --include-dn\n          include the distinguished name (DN) in the output.\n          \n          Note that this property is not an attribute of the AD entry iself; instead it is constructed from the relative DN (RDN) of the entry and all of its parents. That's why this property is normally not shown.\n\n      --member-of \u003cMEMBER_OF_ATTRIBUTE\u003e\n          specify which attribute shall be used to display group memberships\n          \n          [default: rdn]\n\n          Possible values:\n          - sid: show the Security ID (SID)\n          - rdn: show the relative distinguished name (RDN) value\n          - dn:  show the distinguished name (DN)\n          - sam: show the samAccountName attribute\n\n\n  -v, --verbose...\n          Increase logging verbosity\n\n  -q, --quiet...\n          Decrease logging verbosity\n\n  -h, --help\n          Print help (see a summary with '-h')\n```\n\n### ... groups\n\n```\nUsage: ntdsextract2 \u003cNTDS_FILE\u003e group [OPTIONS]\n\nOptions:\n  -F, --format \u003cFORMAT\u003e\n          Output format\n          \n          [default: csv]\n          [possible values: csv, json, json-lines]\n\n  -A, --show-all\n          show all non-empty values. This option is ignored when CSV-Output is selected\n\n  -D, --include-dn\n          include the distinguished name (DN) in the output.\n          \n          Note that this property is not an attribute of the AD entry iself; instead it is constructed from the relative DN (RDN) of the entry and all of its parents. That's why this property is normally not shown.\n\n      --member-of \u003cMEMBER_OF_ATTRIBUTE\u003e\n          specify which attribute shall be used to display group memberships\n          \n          [default: rdn]\n\n          Possible values:\n          - sid: show the Security ID (SID)\n          - rdn: show the relative distinguished name (RDN) value\n          - dn:  show the distinguished name (DN)\n          - sam: show the samAccountName attribute\n\n  -v, --verbose...\n          Increase logging verbosity\n\n  -q, --quiet...\n          Decrease logging verbosity\n\n  -h, --help\n          Print help (see a summary with '-h')\n```\n\n### ... computers\n\n```\nUsage: ntdsextract2 \u003cNTDS_FILE\u003e computer [OPTIONS]\n\nOptions:\n  -F, --format \u003cFORMAT\u003e\n          Output format\n          \n          [default: csv]\n          [possible values: csv, json, json-lines]\n\n  -A, --show-all\n          show all non-empty values. This option is ignored when CSV-Output is selected\n\n  -D, --include-dn\n          include the distinguished name (DN) in the output.\n          \n          Note that this property is not an attribute of the AD entry iself; instead it is constructed from the relative DN (RDN) of the entry and all of its parents. That's why this property is normally not shown.\n\n      --member-of \u003cMEMBER_OF_ATTRIBUTE\u003e\n          specify which attribute shall be used to display group memberships\n          \n          [default: rdn]\n\n          Possible values:\n          - sid: show the Security ID (SID)\n          - rdn: show the relative distinguished name (RDN) value\n          - dn:  show the distinguished name (DN)\n          - sam: show the samAccountName attribute\n\n  -v, --verbose...\n          Increase logging verbosity\n\n  -q, --quiet...\n          Decrease logging verbosity\n\n  -h, --help\n          Print help (see a summary with '-h')\n```\n\n### ... types\n\n```\nUsage: ntdsextract2 \u003cNTDS_FILE\u003e types [OPTIONS]\n\nOptions:\n  -F, --format \u003cFORMAT\u003e  Output format [default: csv] [possible values: csv, json, json-lines]\n  -v, --verbose...       Increase logging verbosity\n  -q, --quiet...         Decrease logging verbosity\n  -h, --help             Print help\n```\n\n\n## Configuring the global timestamp format\n\nPer default, `ntdsextract2` uses an RFC3339-compliant data format. If you want to, you can change the data format\nbeing used by setting the `DFIR_DATE` environment variable. Let's look at an example:\n\n```shell\n$ ntdsextract2 tests/data/ntds_plain.dit user -F json-lines |jq 'select (.rdn == \"Administrator\")'\n```\n\n```json\n{\n  \"sid\": \"S-1-5-21-1467604378-2733498025-3532005688-500\",\n  \"user_principal_name\": null,\n  \"rdn\": \"Administrator\",\n  \"sam_account_name\": \"Administrator\",\n  \"sam_account_type\": \"SAM_USER_OBJECT\",\n  \"user_account_control\": \"ADS_UF_NORMAL_ACCOUNT | ADS_UF_DONT_EXPIRE_PASSWD\",\n  \"logon_count\": 4,\n  \"bad_pwd_count\": 0,\n  \"admin_count\": null,\n  \"is_deleted\": false,\n  \"primary_group_id\": 513,\n  \"primary_group\": \"Domänen-Benutzer\",\n  \"member_of\": [\n    \"Richtlinien-Ersteller-Besitzer\",\n    \"Schema-Admins\",\n    \"Administratoren\",\n    \"Organisations-Admins\",\n    \"Domänen-Admins\"\n  ],\n  \"comment\": null,\n  \"record_time\": \"2023-11-15T06:33:44+0000\",\n  \"when_created\": \"2023-11-15T06:33:44+0000\",\n  \"when_changed\": \"2023-11-15T06:41:50+0000\",\n  \"last_logon\": \"2023-11-15T06:41:50+0000\",\n  \"last_logon_time_stamp\": \"2023-11-15T06:41:50+0000\",\n  \"account_expires\": \"+30828-09-14T02:48:05+0000\",\n  \"password_last_set\": \"2023-11-15T05:40:32+0000\",\n  \"bad_pwd_time\": \"1601-01-01T00:00:00+0000\"\n}\n```\n\n\n```shell\n$ DFIR_DATE=\"%F %T (%Z)\" ntdsextract2 tests/data/ntds_plain.dit user -F json-lines |jq 'select (.rdn == \"Administrator\")'\n```\n\n```json\n{\n  \"sid\": \"S-1-5-21-1467604378-2733498025-3532005688-500\",\n  \"user_principal_name\": null,\n  \"rdn\": \"Administrator\",\n  \"sam_account_name\": \"Administrator\",\n  \"sam_account_type\": \"SAM_USER_OBJECT\",\n  \"user_account_control\": \"ADS_UF_NORMAL_ACCOUNT | ADS_UF_DONT_EXPIRE_PASSWD\",\n  \"logon_count\": 4,\n  \"bad_pwd_count\": 0,\n  \"admin_count\": null,\n  \"is_deleted\": false,\n  \"primary_group_id\": 513,\n  \"primary_group\": \"Domänen-Benutzer\",\n  \"member_of\": [\n    \"Administratoren\",\n    \"Schema-Admins\",\n    \"Domänen-Admins\",\n    \"Organisations-Admins\",\n    \"Richtlinien-Ersteller-Besitzer\"\n  ],\n  \"comment\": null,\n  \"record_time\": \"2023-11-15 06:33:44 (UTC)\",\n  \"when_created\": \"2023-11-15 06:33:44 (UTC)\",\n  \"when_changed\": \"2023-11-15 06:41:50 (UTC)\",\n  \"last_logon\": \"2023-11-15 06:41:50 (UTC)\",\n  \"last_logon_time_stamp\": \"2023-11-15 06:41:50 (UTC)\",\n  \"account_expires\": \"+30828-09-14 02:48:05 (UTC)\",\n  \"password_last_set\": \"2023-11-15 05:40:32 (UTC)\",\n  \"bad_pwd_time\": \"1601-01-01 00:00:00 (UTC)\"\n}\n```\n\nSee the difference?\n\n# Forensics details\n\n## Interpreting timestamps\n\nActive Directory stores its timestamp values mostly as [`JET_coltypCurrency`](https://learn.microsoft.com/en-us/windows/win32/extensible-storage-engine/jet-coltyp), because it is the only supported 64bit fixed integer value. So, in fact, timestamps are stored as unsigned 64bit values. This leaves enough space to use the [`FILETIME`](https://learn.microsoft.com/en-us/office/client-developer/outlook/mapi/filetime) structure.\n\nThis structure specifies the number of 100 nanoseconds since January 1, 1601. The minimum value is - of course - *`1601-01-01T00:00:00`* with a binary value of `0x0000000000000000`. The maximum value in the most cases is `0x7FFFFFFFFFFFFFFF`, which corresponds to the value *`30828-09-14T02:48:05.4775807`*. However, it is up to each software to interpret those special values.\n\nThe following table shows how each of the AD attributes are to be interpreted:\n\n|Attribute| Interpretation of `0x0000000000000000` | Interpretation of `0x7FFFFFFFFFFFFFFF` | \n|-|----|----|\n`record_time`| nothing specific |nothing specific|\n`when_created`| nothing specific |nothing specific|\n`when_changed`| nothing specific | nothing specific|\n[`last_logon`](https://learn.microsoft.com/en-us/windows/win32/adschema/a-lastlogon)| last logon time is unknown | |\n[`last_logon_time_stamp`](https://learn.microsoft.com/en-us/windows/win32/adschema/a-lastlogontimestamp)| nothing specific but likely related to above | |\n[`account_expires`](https://learn.microsoft.com/en-us/windows/win32/adschema/a-accountexpires)| \"If at any point in time an account which was configured with an expiration time is set back to Never Expires, the **accountExpires** attribute is then set to 0.\" |\"When an account is created, the account is initially set to Never Expire. The **accountExpires** attribute is set to the default of *9223372036854775807*, a value which corresponds the maximum value of a 64-bit signed integer.\" | \n`password_last_set`| if UAC attr does not contain `UF_DONT_EXPIRE_PASSWD` then user must change password at next logon| |\n[`bad_pwd_time`](https://learn.microsoft.com/en-us/windows/win32/adschema/a-badpasswordtime)| the last time an incorrect password was used is unknown| |","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjanstarke%2Fntdsextract2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjanstarke%2Fntdsextract2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjanstarke%2Fntdsextract2/lists"}