ntdsextract2 1.4.33

Display contents of Active Directory database files (ntds.dit)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
[![Crates.io](https://img.shields.io/crates/v/ntdsextract2)](https://crates.io/crates/ntdsextract2)
![Crates.io](https://img.shields.io/crates/l/ntdsextract2)
![Crates.io (latest)](https://img.shields.io/crates/dv/ntdsextract2)

- [ntdsextract2]#ntdsextract2
  - [Why do you write a tool that's already there and working?]#why-do-you-write-a-tool-thats-already-there-and-working
- [Installation]#installation
- [Usage]#usage
  - [Inspecting the directory information tree visually]#inspecting-the-directory-information-tree-visually
  - [Search for entries]#search-for-entries
  - [Displaying a single entry]#displaying-a-single-entry
  - [Displaying the tree structure of the AD]#displaying-the-tree-structure-of-the-ad
  - [Creating a timeline]#creating-a-timeline
  - [Enumerating ...]#enumerating-
    - [... users]#-users
    - [... groups]#-groups
    - [... computers]#-computers
    - [... types]#-types
  - [Configuring the global timestamp format]#configuring-the-global-timestamp-format
- [Forensics details]#forensics-details
  - [Interpreting timestamps]#interpreting-timestamps

# ntdsextract2

<img align="right" width="128px" src="https://raw.githubusercontent.com/janstarke/ntdsextract2/main/doc/images/ntdsextract2.jpeg">

This aims to be a replacement of <https://github.com/csababarta/ntdsxtract/> by @csababarta.

## Why do you write a tool that's already there and working?

1. ntdsxtract is using Python 2.7, which makes it hard to use on modern systems
1. 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.


<table>
<tr>
<td>
<a href="https://codeberg.org/janstarke/ntdsextract2/raw/commit/6ffa4adb3c0165a688cac3a6263ed4e2a7312ee3/doc/images/attributes.png"><img src="https://codeberg.org/janstarke/ntdsextract2/raw/commit/6ffa4adb3c0165a688cac3a6263ed4e2a7312ee3/doc/images/attributes.png" /></a>
</td>
<td>
<a href="https://codeberg.org/janstarke/ntdsextract2/raw/commit/6ffa4adb3c0165a688cac3a6263ed4e2a7312ee3/doc/images/acl.png"><img src="https://codeberg.org/janstarke/ntdsextract2/raw/commit/6ffa4adb3c0165a688cac3a6263ed4e2a7312ee3/doc/images/acl.png" /></a>
</td>
<td>
<a href="https://codeberg.org/janstarke/ntdsextract2/raw/commit/6ffa4adb3c0165a688cac3a6263ed4e2a7312ee3/doc/images/members.png"><img src="https://codeberg.org/janstarke/ntdsextract2/raw/commit/6ffa4adb3c0165a688cac3a6263ed4e2a7312ee3/doc/images/members.png" /></a>
</td>
</tr>
</table>

# Installation

```bash
cargo install ntdsextract2
```

# Usage
```
Display contents of Active Directory database files (ntds.dit)

Usage: ntdsextract2 [OPTIONS] <NTDS_FILE> <COMMAND>

Commands:
  user      Display user accounts
  group     Display groups
  computer  display computer accounts
  timeline  create a timeline (in flow-record format)
  types     list all defined types
  tree      display the directory information tree
  entry     display one single entry from the directory information tree
  search    search for entries whose values match to some regular expression
  acl       search for signs of ACL manipulation
  walk      open the NTDS database in an interactive mode
  info      show metainfo about the NTDS database
  help      Print this message or the help of the given subcommand(s)

Arguments:
  <NTDS_FILE>
          name of the file to analyze

Options:
  -v, --verbose...
          Increase logging verbosity

  -q, --quiet...
          Decrease logging verbosity

      --use-cache
          set this flag if you want to use a cache file, to speed up you work with ntdsextract2

      --cache-file <CACHE_FILE>
          cache file to use. If you use the cache, but don't specify a filename, ntdsextract2 will generate a filename and store a cache in /tmp
          
          Be aware that ntdsextract2 will overwrite this file at any time without any warning!!!

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version

```

## Inspecting the directory information tree visually

```
Usage: ntdsextract2 <NTDS_FILE> walk
```

opens a screen which looks similar to the following:


<a href="https://codeberg.org/janstarke/ntdsextract2/raw/commit/6ffa4adb3c0165a688cac3a6263ed4e2a7312ee3/doc/images/attributes.png"><img src="https://codeberg.org/janstarke/ntdsextract2/raw/commit/6ffa4adb3c0165a688cac3a6263ed4e2a7312ee3/doc/images/attributes.png" /></a>
</td>

where you can walk around and inspect the entries that you like. I suggest that you also use the `--use-cache` switch, which can speed up the loading time.


## Search for entries

```
Usage: ntdsextract2 <NTDS_FILE> search [OPTIONS] <REGEX>

Arguments:
  <REGEX>  regular expression to match against

Options:
  -i, --ignore-case  case-insensitive search (ignore case)
  -v, --verbose...   Increase logging verbosity
  -q, --quiet...     Decrease logging verbosity
  -h, --help         Print help
```

## Displaying a single entry

```
Usage: ntdsextract2 <NTDS_FILE> entry [OPTIONS] <ENTRY_ID>

Arguments:
  <ENTRY_ID>
          id of the entry to show

Options:
      --sid
          search for SID instead for NTDS.DIT entry id. <ENTRY_ID> will be interpreted as RID, wich is the last part of the SID; e.g. 500 will return the Administrator account

  -F, --format <ENTRY_FORMAT>
          [default: simple]

          Possible values:
          - json:   use JSON format
          - table:  display a formatted table
          - simple: use a simple key-values based format

  -v, --verbose...
          Increase logging verbosity

  -q, --quiet...
          Decrease logging verbosity

  -h, --help
          Print help (see a summary with '-h')
```

## Displaying the tree structure of the AD

```
Usage: ntdsextract2 <NTDS_FILE> tree [OPTIONS]

Options:
      --max-depth <MAX_DEPTH>  maximum recursion depth [default: 4]
  -v, --verbose...             Increase logging verbosity
  -q, --quiet...               Decrease logging verbosity
  -h, --help                   Print help
```

## Creating a timeline

```
Usage: ntdsextract2 <NTDS_FILE> timeline [OPTIONS]

Options:
      --all-objects      show objects of any type (this might be a lot)
      --include-deleted  include also deleted objects (which don't have an AttObjectCategory attribute)
  -v, --verbose...       Increase logging verbosity
  -q, --quiet...         Decrease logging verbosity
  -h, --help             Print help
```

## Enumerating ...

### ... users

```
Usage: ntdsextract2 <NTDS_FILE> user [OPTIONS]

Options:
  -F, --format <FORMAT>
          Output format
          
          [default: csv]
          [possible values: csv, json, json-lines]

  -A, --show-all
          show all non-empty values. This option is ignored when CSV-Output is selected

  -D, --include-dn
          include the distinguished name (DN) in the output.
          
          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.

      --member-of <MEMBER_OF_ATTRIBUTE>
          specify which attribute shall be used to display group memberships
          
          [default: rdn]

          Possible values:
          - sid: show the Security ID (SID)
          - rdn: show the relative distinguished name (RDN) value
          - dn:  show the distinguished name (DN)
          - sam: show the samAccountName attribute


  -v, --verbose...
          Increase logging verbosity

  -q, --quiet...
          Decrease logging verbosity

  -h, --help
          Print help (see a summary with '-h')
```

### ... groups

```
Usage: ntdsextract2 <NTDS_FILE> group [OPTIONS]

Options:
  -F, --format <FORMAT>
          Output format
          
          [default: csv]
          [possible values: csv, json, json-lines]

  -A, --show-all
          show all non-empty values. This option is ignored when CSV-Output is selected

  -D, --include-dn
          include the distinguished name (DN) in the output.
          
          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.

      --member-of <MEMBER_OF_ATTRIBUTE>
          specify which attribute shall be used to display group memberships
          
          [default: rdn]

          Possible values:
          - sid: show the Security ID (SID)
          - rdn: show the relative distinguished name (RDN) value
          - dn:  show the distinguished name (DN)
          - sam: show the samAccountName attribute

  -v, --verbose...
          Increase logging verbosity

  -q, --quiet...
          Decrease logging verbosity

  -h, --help
          Print help (see a summary with '-h')
```

### ... computers

```
Usage: ntdsextract2 <NTDS_FILE> computer [OPTIONS]

Options:
  -F, --format <FORMAT>
          Output format
          
          [default: csv]
          [possible values: csv, json, json-lines]

  -A, --show-all
          show all non-empty values. This option is ignored when CSV-Output is selected

  -D, --include-dn
          include the distinguished name (DN) in the output.
          
          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.

      --member-of <MEMBER_OF_ATTRIBUTE>
          specify which attribute shall be used to display group memberships
          
          [default: rdn]

          Possible values:
          - sid: show the Security ID (SID)
          - rdn: show the relative distinguished name (RDN) value
          - dn:  show the distinguished name (DN)
          - sam: show the samAccountName attribute

  -v, --verbose...
          Increase logging verbosity

  -q, --quiet...
          Decrease logging verbosity

  -h, --help
          Print help (see a summary with '-h')
```

### ... types

```
Usage: ntdsextract2 <NTDS_FILE> types [OPTIONS]

Options:
  -F, --format <FORMAT>  Output format [default: csv] [possible values: csv, json, json-lines]
  -v, --verbose...       Increase logging verbosity
  -q, --quiet...         Decrease logging verbosity
  -h, --help             Print help
```


## Configuring the global timestamp format

Per default, `ntdsextract2` uses an RFC3339-compliant data format. If you want to, you can change the data format
being used by setting the `DFIR_DATE` environment variable. Let's look at an example:

```shell
$ ntdsextract2 tests/data/ntds_plain.dit user -F json-lines |jq 'select (.rdn == "Administrator")'
```

```json
{
  "sid": "S-1-5-21-1467604378-2733498025-3532005688-500",
  "user_principal_name": null,
  "rdn": "Administrator",
  "sam_account_name": "Administrator",
  "sam_account_type": "SAM_USER_OBJECT",
  "user_account_control": "ADS_UF_NORMAL_ACCOUNT | ADS_UF_DONT_EXPIRE_PASSWD",
  "logon_count": 4,
  "bad_pwd_count": 0,
  "admin_count": null,
  "is_deleted": false,
  "primary_group_id": 513,
  "primary_group": "Domänen-Benutzer",
  "member_of": [
    "Richtlinien-Ersteller-Besitzer",
    "Schema-Admins",
    "Administratoren",
    "Organisations-Admins",
    "Domänen-Admins"
  ],
  "comment": null,
  "record_time": "2023-11-15T06:33:44+0000",
  "when_created": "2023-11-15T06:33:44+0000",
  "when_changed": "2023-11-15T06:41:50+0000",
  "last_logon": "2023-11-15T06:41:50+0000",
  "last_logon_time_stamp": "2023-11-15T06:41:50+0000",
  "account_expires": "+30828-09-14T02:48:05+0000",
  "password_last_set": "2023-11-15T05:40:32+0000",
  "bad_pwd_time": "1601-01-01T00:00:00+0000"
}
```


```shell
$ DFIR_DATE="%F %T (%Z)" ntdsextract2 tests/data/ntds_plain.dit user -F json-lines |jq 'select (.rdn == "Administrator")'
```

```json
{
  "sid": "S-1-5-21-1467604378-2733498025-3532005688-500",
  "user_principal_name": null,
  "rdn": "Administrator",
  "sam_account_name": "Administrator",
  "sam_account_type": "SAM_USER_OBJECT",
  "user_account_control": "ADS_UF_NORMAL_ACCOUNT | ADS_UF_DONT_EXPIRE_PASSWD",
  "logon_count": 4,
  "bad_pwd_count": 0,
  "admin_count": null,
  "is_deleted": false,
  "primary_group_id": 513,
  "primary_group": "Domänen-Benutzer",
  "member_of": [
    "Administratoren",
    "Schema-Admins",
    "Domänen-Admins",
    "Organisations-Admins",
    "Richtlinien-Ersteller-Besitzer"
  ],
  "comment": null,
  "record_time": "2023-11-15 06:33:44 (UTC)",
  "when_created": "2023-11-15 06:33:44 (UTC)",
  "when_changed": "2023-11-15 06:41:50 (UTC)",
  "last_logon": "2023-11-15 06:41:50 (UTC)",
  "last_logon_time_stamp": "2023-11-15 06:41:50 (UTC)",
  "account_expires": "+30828-09-14 02:48:05 (UTC)",
  "password_last_set": "2023-11-15 05:40:32 (UTC)",
  "bad_pwd_time": "1601-01-01 00:00:00 (UTC)"
}
```

See the difference?

# Forensics details

## Interpreting timestamps

Active 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.

This 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.

The following table shows how each of the AD attributes are to be interpreted:

|Attribute| Interpretation of `0x0000000000000000` | Interpretation of `0x7FFFFFFFFFFFFFFF` | 
|-|----|----|
`record_time`| nothing specific |nothing specific|
`when_created`| nothing specific |nothing specific|
`when_changed`| nothing specific | nothing specific|
[`last_logon`]https://learn.microsoft.com/en-us/windows/win32/adschema/a-lastlogon| last logon time is unknown | |
[`last_logon_time_stamp`]https://learn.microsoft.com/en-us/windows/win32/adschema/a-lastlogontimestamp| nothing specific but likely related to above | |
[`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." | 
`password_last_set`| if UAC attr does not contain `UF_DONT_EXPIRE_PASSWD` then user must change password at next logon| |
[`bad_pwd_time`]https://learn.microsoft.com/en-us/windows/win32/adschema/a-badpasswordtime| the last time an incorrect password was used is unknown| |