Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
7eb2cdc
issue exchange attributes on 2012 R2
william-billaud Jan 23, 2026
794994c
dns node
william-billaud Jan 23, 2026
1f83a05
revert change
william-billaud Jan 28, 2026
7be4506
revert change
william-billaud Jan 28, 2026
04548ec
Add stubs + ruff linting
william-billaud Mar 26, 2026
0ac165a
Docs + tombstoned records
william-billaud Mar 27, 2026
09d7aa5
Add tests
william-billaud Mar 27, 2026
96a087e
Add tests
william-billaud Mar 27, 2026
7db93dc
Fix tests
william-billaud Mar 27, 2026
f8a71f1
Add tests, and move unpacking of dns records to a constructor
william-billaud Apr 1, 2026
0a1f0f4
Fix as dict method
william-billaud Apr 1, 2026
f63f033
Add tests of repr and as dict
william-billaud Apr 1, 2026
3a7b609
Add doc strings.
william-billaud Apr 1, 2026
59f6d86
Merge branch 'main' into dns_node
william-billaud May 6, 2026
9c23e47
Apply suggestions from code review
william-billaud May 6, 2026
75314bf
Satisfy obsession with aligned text
william-billaud May 6, 2026
8ee8c46
Replace swap_endianess with dissect.cstruct.utils.swap*
william-billaud May 6, 2026
dd17b9c
Fix tests + docstring changes.
william-billaud May 6, 2026
680020f
Move test_dns_nodename.py to ntds/objects
william-billaud May 6, 2026
05a8ecc
Rename c_record_header to header. Add typing hint.
william-billaud May 6, 2026
8c46e81
Remove useless usage of bytearray.
william-billaud May 6, 2026
0ed5042
change error catching/logging logic.
william-billaud May 6, 2026
730e6d5
Linting.
william-billaud May 6, 2026
0857474
Apply suggestions from code review
william-billaud May 6, 2026
4791468
Delete duplicate entry.
william-billaud May 6, 2026
813b183
Use windows type in structure.
william-billaud May 7, 2026
d8f5e60
Apply suggestions from code review
william-billaud May 7, 2026
3acdf99
Apply suggestion from review + modification of docstring with list
william-billaud May 7, 2026
035306d
Fix tests
william-billaud May 7, 2026
8e2fff4
Fix tests
william-billaud May 7, 2026
78ddf68
Use DecoderMap
william-billaud May 8, 2026
10892af
Small textual changes
Schamper May 8, 2026
18494ac
Rename cstruct file
Schamper May 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions dissect/database/ese/ntds/ntds.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from dissect.database.ese.ntds.objects import (
Computer,
DnsNode,
DomainDNS,
Group,
GroupPolicyContainer,
Expand Down Expand Up @@ -118,6 +119,10 @@ def secrets(self) -> Iterator[Secret]:
"""Get all secret objects from the database."""
yield from self.search(objectClass="secret")

def dns_nodes(self) -> Iterator[DnsNode]:
"""Get all DnsNode objects from the database."""
yield from self.search(objectClass="dnsNode")

def backup_keys(self) -> Iterator[BackupKey]:
"""Get all DPAPI backup keys from the database."""
if not self.pek.unlocked:
Expand Down
109 changes: 109 additions & 0 deletions dissect/database/ese/ntds/objects/c_dnsnode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from __future__ import annotations

from dissect.cstruct import cstruct

dns_record_def = """

// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dnsp/39b03b89-2264-4063-8198-d62f62a6441a
enum DNS_RECORD_TYPE : WORD {
ZERO = 0x0000, // An empty record type ([RFC1034] section 3.6 and [RFC1035] section 3.2.2).
A = 0x0001, // An A record type, used for storing an IP address ([RFC1035] section 3.2.2).
NS = 0x0002, // An authoritative name-server
// record type ([RFC1034] section 3.6 and [RFC1035] section 3.2.2).
MD = 0x0003, // A mail-destination record type ([RFC1035] section 3.2.2).
MF = 0x0004, // A mail forwarder record type ([RFC1035] section 3.2.2).
CNAME = 0x0005, // A record type that contains the canonical name of a DNS alias ([RFC1035] section 3.2.2).
SOA = 0x0006, // A Start of Authority (SOA) record type ([RFC1035] section 3.2.2).
MB = 0x0007, // A mailbox record type ([RFC1035] section 3.2.2).
MG = 0x0008, // A mail group member record type ([RFC1035] section 3.2.2).
MR = 0x0009, // A mail-rename record type ([RFC1035] section 3.2.2).
NULL = 0x000A, // A record type for completion queries ([RFC1035] section 3.2.2).
WKS = 0x000B, // A record type for a well-known service ([RFC1035] section 3.2.2).
PTR = 0x000C, // A record type containing FQDN pointer ([RFC1035] section 3.2.2).
HINFO = 0x000D, // A host information record type ([RFC1035] section 3.2.2).
MINFO = 0x000E, // A mailbox or mailing list information record type ([RFC1035] section 3.2.2).
MX = 0x000F, // A mail-exchanger record type ([RFC1035] section 3.2.2).
TXT = 0x0010, // A record type containing a text string ([RFC1035] section 3.2.2).
RP = 0x0011, // A responsible-person record type [RFC1183].
AFSDB = 0x0012, // A record type containing AFS database location [RFC1183].
X25 = 0x0013, // An X25 PSDN address record type [RFC1183].
ISDN = 0x0014, // An ISDN address record type [RFC1183].
RT = 0x0015, // A route through record type [RFC1183].
SIG = 0x0018, // A cryptographic public key signature record type [RFC2931].
KEY = 0x0019, // A record type containing public key used in DNSSEC [RFC2535].
AAAA = 0x001C, // An IPv6 address record type [RFC3596].
LOC = 0x001D, // A location information record type [RFC1876].
NXT = 0x001E, // A next-domain record type [RFC2065].
SRV = 0x0021, // A server selection record type [RFC2782].
ATMA = 0x0022, // An Asynchronous Transfer Mode (ATM) address record type [ATMA].
NAPTR = 0x0023, // An NAPTR record type [RFC2915].
DNAME = 0x0027, // A DNAME record type [RFC2672].
DS = 0x002B, // A DS record type [RFC4034].
RRSIG = 0x002E, // An RRSIG record type [RFC4034].
NSEC = 0x002F, // An NSEC record type [RFC4034].
DNSKEY = 0x0030, // A DNSKEY record type [RFC4034].
DHCID = 0x0031, // A DHCID record type [RFC4701].
NSEC3 = 0x0032, // An NSEC3 record type [RFC5155].
NSEC3PARAM = 0x0033, // An NSEC3PARAM record type [RFC5155].
TLSA = 0x0034, // A TLSA record type [RFC6698].
ALL = 0x00FF, // A query-only type requesting all records [RFC1035].
WINS = 0xFF01, // A record type containing Windows Internet Name Service (WINS)
// forward lookup data [MS-WINSRADNS_TYPE_WINSR].
WINSR = 0xFF02 // A record type containing WINS reverse lookup data [MS-WINSRA].
};

// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dnsp/6912b338-5472-4f59-b912-0edb536b6ed8
typedef struct DNS_RECORD_HEADER {
WORD DataLength;
DNS_RECORD_TYPE Type;
BYTE Version; // Must be 0x05
BYTE Rank;
WORD Flags; // Must be 0x00
DWORD Serial;
DWORD TtlSeconds; // Big Endian
DWORD Reserved; // MUST be 0x00000000.
DWORD TimeStamp;
CHAR Data[DataLength];
};
Comment thread
william-billaud marked this conversation as resolved.

// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dnsp/3fd41adc-c69e-407b-979e-721251403132
// MS docs indicate that structure is 4 byte aligned, and that the string MUST NOT be null-terminated.
// But observed reality is a null terminated string (null char not counted in NameLength)
typedef struct DNS_RPC_NAME{
BYTE NameLength;
CHAR dnsName[NameLength];
};

// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dnsp/db37cab7-f121-43ba-81c5-ca0e198d4b9a
typedef struct DNS_RPC_RECORD_SRV {
WORD Priority;
WORD Weight;
WORD Port;
DNS_RPC_NAME nameTarget;
};

Comment thread
william-billaud marked this conversation as resolved.
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dnsp/f647d391-6614-4c3e-b38b-4df971590eb6
typedef struct DNS_RPC_RECORD_NAME_PREFERENCE {
WORD Preference;
DNS_RPC_NAME nameExchange;
};

Comment thread
william-billaud marked this conversation as resolved.
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dnsp/dcd3ec16-d6bf-4bb4-9128-6172f9e5f066
typedef struct DNS_RPC_RECORD_SOA {
DWORD Serial;
DWORD Refresh;
DWORD Retry;
DWORD Expire;
DWORD MinimumTtl;
DNS_RPC_NAME namePrimaryServer;
BYTE _pad;
DNS_RPC_NAME ZoneAdministratorEmail;
};

Comment thread
william-billaud marked this conversation as resolved.
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dnsp/def7736a-dd09-4b4a-b8d6-6a702a7ecde0
typedef struct DNS_RPC_RECORD_TS {
QWORD EntombedTime;
};
"""
c_dns_record = cstruct(dns_record_def)
DNS_RECORD_TYPE = c_dns_record.DNS_RECORD_TYPE
Comment thread
william-billaud marked this conversation as resolved.
146 changes: 146 additions & 0 deletions dissect/database/ese/ntds/objects/c_dnsnode.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Generated by cstruct-stubgen
from typing import BinaryIO, Literal, TypeAlias, overload

import dissect.cstruct as __cs__

class _c_dns_record(__cs__.cstruct):
class DNS_RECORD_TYPE(__cs__.Enum):
ZERO = ...
A = ...
NS = ...
MD = ...
MF = ...
CNAME = ...
SOA = ...
MB = ...
MG = ...
MR = ...
NULL = ...
WKS = ...
PTR = ...
HINFO = ...
MINFO = ...
MX = ...
TXT = ...
RP = ...
AFSDB = ...
X25 = ...
ISDN = ...
RT = ...
SIG = ...
KEY = ...
AAAA = ...
LOC = ...
NXT = ...
SRV = ...
ATMA = ...
NAPTR = ...
DNAME = ...
DS = ...
RRSIG = ...
NSEC = ...
DNSKEY = ...
DHCID = ...
NSEC3 = ...
NSEC3PARAM = ...
TLSA = ...
ALL = ...
WINS = ...
WINSR = ...

class DNS_RECORD_HEADER(__cs__.Structure):
DataLength: _c_dns_record.uint16
Type: _c_dns_record.DNS_RECORD_TYPE
Version: _c_dns_record.uint8
Rank: _c_dns_record.uint8
Flags: _c_dns_record.uint16
Serial: _c_dns_record.uint32
TtlSeconds: _c_dns_record.uint32
Reserved: _c_dns_record.uint32
TimeStamp: _c_dns_record.uint32
Data: __cs__.CharArray
@overload
def __init__(
self,
DataLength: _c_dns_record.uint16 | None = ...,
Type: _c_dns_record.DNS_RECORD_TYPE | None = ...,
Version: _c_dns_record.uint8 | None = ...,
Rank: _c_dns_record.uint8 | None = ...,
Flags: _c_dns_record.uint16 | None = ...,
Serial: _c_dns_record.uint32 | None = ...,
TtlSeconds: _c_dns_record.uint32 | None = ...,
Reserved: _c_dns_record.uint32 | None = ...,
TimeStamp: _c_dns_record.uint32 | None = ...,
Data: __cs__.CharArray | None = ...,
): ...
@overload
def __init__(self, fh: bytes | memoryview | bytearray | BinaryIO, /): ...

class DNS_RPC_NAME(__cs__.Structure):
NameLength: _c_dns_record.uint8
dnsName: __cs__.CharArray
@overload
def __init__(self, NameLength: _c_dns_record.uint8 | None = ..., dnsName: __cs__.CharArray | None = ...): ...
@overload
def __init__(self, fh: bytes | memoryview | bytearray | BinaryIO, /): ...

class DNS_RPC_RECORD_SRV(__cs__.Structure):
Priority: _c_dns_record.uint16
Weight: _c_dns_record.uint16
Port: _c_dns_record.uint16
nameTarget: _c_dns_record.DNS_RPC_NAME
@overload
def __init__(
self,
Priority: _c_dns_record.uint16 | None = ...,
Weight: _c_dns_record.uint16 | None = ...,
Port: _c_dns_record.uint16 | None = ...,
nameTarget: _c_dns_record.DNS_RPC_NAME | None = ...,
): ...
@overload
def __init__(self, fh: bytes | memoryview | bytearray | BinaryIO, /): ...

class DNS_RPC_RECORD_NAME_PREFERENCE(__cs__.Structure):
Preference: _c_dns_record.uint16
nameExchange: _c_dns_record.DNS_RPC_NAME
@overload
def __init__(
self, Preference: _c_dns_record.uint16 | None = ..., nameExchange: _c_dns_record.DNS_RPC_NAME | None = ...
): ...
@overload
def __init__(self, fh: bytes | memoryview | bytearray | BinaryIO, /): ...

class DNS_RPC_RECORD_SOA(__cs__.Structure):
Serial: _c_dns_record.uint32
Refresh: _c_dns_record.uint32
Retry: _c_dns_record.uint32
Expire: _c_dns_record.uint32
MinimumTtl: _c_dns_record.uint32
namePrimaryServer: _c_dns_record.DNS_RPC_NAME
_pad: _c_dns_record.uint8
ZoneAdministratorEmail: _c_dns_record.DNS_RPC_NAME
@overload
def __init__(
self,
Serial: _c_dns_record.uint32 | None = ...,
Refresh: _c_dns_record.uint32 | None = ...,
Retry: _c_dns_record.uint32 | None = ...,
Expire: _c_dns_record.uint32 | None = ...,
MinimumTtl: _c_dns_record.uint32 | None = ...,
namePrimaryServer: _c_dns_record.DNS_RPC_NAME | None = ...,
_pad: _c_dns_record.uint8 | None = ...,
ZoneAdministratorEmail: _c_dns_record.DNS_RPC_NAME | None = ...,
): ...
@overload
def __init__(self, fh: bytes | memoryview | bytearray | BinaryIO, /): ...

class DNS_RPC_RECORD_TS(__cs__.Structure):
EntombedTime: _c_dns_record.uint64
@overload
def __init__(self, EntombedTime: _c_dns_record.uint64 | None = ...): ...
@overload
def __init__(self, fh: bytes | memoryview | bytearray | BinaryIO, /): ...

# Technically `c_dns_record` is an instance of `_c_dns_record`, but then we can't use it in type hints
c_dns_record: TypeAlias = _c_dns_record
DNS_RECORD_TYPE: TypeAlias = _c_dns_record.DNS_RECORD_TYPE
Loading
Loading