Microsoft has a specific method for returning basic information that can be used for finding what site (and what is the next closest site) for any given machine that reaches out to a domain controller with a LDAP query. Originally the query had to be performed over CLDAP (connectionless LDAP aka UDP LDAP) but sometime in the Windows Server 2003 timeframe it became available via both UDP and TCP. Personally I like the TCP ping over the UDP ping because no applications are actually using UDP to use AD and this functionality.
The "ping" is a specially crafted anonymous LDAP query that retrieves the NetLogon attribute from the RootDSE of a Domain Controller which then returns the attribute in one of several available BLOB formats. The information returned useful for bootstrapping and validation and in particular for this specific topic, finding out which AD site the client is in. The "LDAP Ping" is documented fully in section 6.3 “Publishing and Locating a Domain Controller” of the Microsoft MS-ADTS Protocol Documentation including the various BLOB structures. Note that when it says something is Big-Endian, pay attention, it is critical to getting a response back.
The key pages are:
6.3 Publishing and Locating a Domain Controller — Base of all of the documentation
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/8ebcf782-87fd-4dc3-8585-1301569dfe4f
6.3.1 Structures and Constants
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/b3006506-4338-45ef-ac52-1e7d5c9c46e9
6.3.3. LDAP Ping
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/895a7744-aff3-4f64-bcfa-f8c05915d2e9
6.3.7 Name Compression and Decompression
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/b0ef9479-78d8-40c1-b6d2-0baad834ed39
I will be releasing a full platform agnostic anonymous DC Locator perl script in the relatively near term that has this implemented as part of the code along with the DNS lookups and some validation tests. I also have generated some notes and a DCR to implement the decoding of this data in AdFind as well. As it is, this is what AdFind will show you right now if you attempt to query for this information:
[Fri 03/29/2019 23:07:04.51]
E:\DEV\Schema>adfind -rootdseanon -f "&(host=k16tst-dc1)(ntver=\04\00\00\00)" -s one netlogon
AdFind V01.51.00cpp Joe Richards (support@joeware.net) October 2017
Using server: K16TST-DC1.k16tst.test.loc:389
Directory: Windows Server 2016
dn:
> netlogon: 1700 0000 FDF1 0100 9011 FD98 67E1 3447 A585 7981 238A 135E 066B 3136 7473 7404 7465 7374 036C 6F63 00C0 180A 4B31 3654 5354 2D44 4331 C018 064B 3136 5453 5400 0A4B 3136 5453 542D 4443 3100 0017 4465 6661 756C 742D 4669 7273 742D 5369 7465 2D4E 616D 6500 136A 6F65 6E65 746C 6F67 6F6E 7465 7374 7369 7465 0005 0000 00FF FFFF FF
1 Objects returned
This is what WireShark will show you:
Now you can also implement the “next closest site” functionality as well with the following query (and this is what jwDCLocator.pl uses)… With this one I needed to specify a specific DC in another site as well as change the ntver attribute value to include NETLOGON_NT_VERSION_WITH_CLOSEST_SITE so that the proper extra info would be sent along…
[Fri 03/29/2019 23:08:14.64]
E:\DEV\Schema>adfind -hh k16tst-scdc1.k16tst.test.loc -rootdseanon -f "&(host=k16tst-scdc1)(ntver=\14\00\00\00)" -s one netlogon
AdFind V01.51.00cpp Joe Richards (support@joeware.net) October 2017
Using server: K16TST-SCDC1.k16tst.test.loc:389
Directory: Windows Server 2016
dn:
> netlogon: 1800 0000 7CF1 0100 9011 FD98 67E1 3447 A585 7981 238A 135E 066B 3136 7473 7404 7465 7374 036C 6F63 00C0 180C 4B31 3654 5354 2D53 4344 4331 C018 064B 3136 5453 5400 0C4B 3136 5453 542D 5343 4443 3100 0005 5369 7465 3200 136A 6F65 6E65 746C 6F67 6F6E 7465 7374 7369 7465 0017 4465 6661 756C 742D 4669 7273 742D 5369 7465 2D4E 616D 6500 1500 0000 FFFF FFFF
1 Objects returned
Unfortunately WireShark gets lost for this extra data and so it doesn’t display really anything…
But if you look down in the packet bytes data you will see it…
which you can see three site names in it…
And of course jwDCLocator.pl knows how to decode it properly…
Dynamically determining site…
Sending LDAP Ping to LDAP://k16tst-scdc1.k16tst.test.loc:389…
AutoDetected Server Site : Site2
AutoDetected Client Site : joenetlogontestsite
AutoDetected Client Next Closest Site: Default-First-Site-Name
Determining site specific DNS records…
The binary (with and without the closest site flag in the query) is described by the structure that can be found at
6.3.1.9 NETLOGON_SAM_LOGON_RESPONSE_EX
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/8401a33f-34a8-40ca-bf03-c3484b66265f
Extracting the information from this structure is not quite as straightforward as what you normally get in that they are compressing the names as described in RFC 1035 – “Domain Names – Implementation and Specification” which is further described in the page mentioned above, 6.3.7 Name Compression and Decompression. Note that when I was writing my perl script I hadn’t seen this page yet so I just looked at the binary and shook my head a few times to clear cobwebs and reversed it out. That means that my original perl script may be implemented slightly differently than what is described and it also means I cannot actually vouchsafe for the pseudocode given on the page so if you use it directly, validate it versus just assume it is 100%. But that goes for all documentation from all vendors at all times.
Note that Perl has some functionality in the Net::DNS module that will decode these for you but I wanted to be as raw as possible in the code to allow people to be able to take the code and change to any language, framework, platform they wanted and be able to do something. Hiding some of the details like this by using other modules defeats that mindset a little.
Now for some samples from jwDCLocator.pl decoding some of compressed string in the structure. As the structure is decoded, a table will be created with strings that are referred to, this is used for the “compression” functionality. If the string isn’t in the table already you will get a length value and the string. If part of the string is in the table (as part of another string response) then you will get whatever part is unique as the length and string and then a pointer to the rest of the string. For clarity of the following examples, this is the table that gets built in memory as the structure is unpacked:
’24’ => ‘k16tst.test.loc’,
’41’ => ‘k16tst.test.loc’,
’43’ => ‘K16TST-SCDC1.k16tst.test.loc’,
’58’ => ‘K16TST’,
’66’ => ‘K16TST-SCDC1’
’80’ => ”,
’81’ => ‘Site2’,
’88’ => ‘joenetlogontestsite’,
‘109’ => ‘Default-First-Site-Name’,
First, the DNS Forest Name.
20190331-144158.89286: DEBUG: NETLOGON REMAINING=066b31367473740474657374036c6f6300c0180a4b313654535
42d444332c018064b3136545354000a4b31365453542d444332
000005536974653200136a6f656e65746c6f676f6e746573747
3697465001744656661756c742d46697273742d536974652d4e
616d650015000000ffffffff
20190331-144158.89290: DEBUG: Enter GetCompressedName…
20190331-144158.89293: DEBUG: +++++++++++++++++++++
20190331-144158.89296: DEBUG: Start Data : 066b31367473740474657374036c6f6300c0180a4b3136545354
2d444332c018064b3136545354000a4b31365453542d44433200
0005536974653200136a6f656e65746c6f676f6e746573747369
7465001744656661756c742d46697273742d536974652d4e616d
650015000000ffffffff
20190331-144158.89299: DEBUG: Enter GetXChars…
20190331-144158.89303: DEBUG: Short Hint : 0
20190331-144158.89306: DEBUG: Enter GetXChars…
20190331-144158.89309: DEBUG: Full Hint : 06
20190331-144158.89313: DEBUG: Enter GetXChars…
20190331-144158.89317: DEBUG: Enter HexToASCII…
20190331-144158.89322: DEBUG: String : k16tst
20190331-144158.89330: DEBUG: Full String : k16tst.
20190331-144158.89338: DEBUG: ———————
20190331-144158.89346: DEBUG: +++++++++++++++++++++
20190331-144158.89352: DEBUG: Start Data : 0474657374036c6f6300c0180a4b31365453542d444332c018064
b3136545354000a4b31365453542d444332000005536974653200
136a6f656e65746c6f676f6e74657374736974650017446566617
56c742d46697273742d536974652d4e616d650015000000ffffff
ff
20190331-144158.89357: DEBUG: Enter GetXChars…
20190331-144158.89364: DEBUG: Short Hint : 0
20190331-144158.89370: DEBUG: Enter GetXChars…
20190331-144158.89376: DEBUG: Full Hint : 04
20190331-144158.89382: DEBUG: Enter GetXChars…
20190331-144158.89388: DEBUG: Enter HexToASCII…
20190331-144158.89395: DEBUG: String : test
20190331-144158.89401: DEBUG: Full String : k16tst.test.
20190331-144158.89407: DEBUG: ———————
20190331-144158.89413: DEBUG: +++++++++++++++++++++
20190331-144158.89418: DEBUG: Start Data : 036c6f6300c0180a4b31365453542d444332c018064b313654535
4000a4b31365453542d444332000005536974653200136a6f656e
65746c6f676f6e7465737473697465001744656661756c742d466
97273742d536974652d4e616d650015000000ffffffff
20190331-144158.89424: DEBUG: Enter GetXChars…
20190331-144158.89431: DEBUG: Short Hint : 0
20190331-144158.89437: DEBUG: Enter GetXChars…
20190331-144158.89451: DEBUG: Full Hint : 03
20190331-144158.89456: DEBUG: Enter GetXChars…
20190331-144158.89459: DEBUG: Enter HexToASCII…
20190331-144158.89463: DEBUG: String : loc
20190331-144158.89466: DEBUG: Full String : k16tst.test.loc.
20190331-144158.89468: DEBUG: ———————
20190331-144158.89471: DEBUG: +++++++++++++++++++++
20190331-144158.89474: DEBUG: Start Data : 00c0180a4b31365453542d444332c018064b3136545354000a4b3
1365453542d444332000005536974653200136a6f656e65746c6f
676f6e7465737473697465001744656661756c742d46697273742
d536974652d4e616d650015000000ffffffff
20190331-144158.89478: DEBUG: Enter GetXChars…
20190331-144158.89481: DEBUG: Short Hint : 0
20190331-144158.89484: DEBUG: Enter GetXChars…
20190331-144158.89486: DEBUG: Full Hint : 00
20190331-144158.89490: DEBUG: FINAL Full String: k16tst.test.loc
And here is the cool part… On the next bit, getting the DNS Domain Name
20190328-220801.19632: DEBUG: Enter GetCompressedName…
20190328-220801.19635: DEBUG: +++++++++++++++++++++
20190328-220801.19638: DEBUG: Start Data : c0180a4b31365453542d444331c018064b3136545354000a4b3136
5453542d44433100001744656661756c742d46697273742d536974
652d4e616d6500136a6f656e65746c6f676f6e7465737473697465
00c04d15000000ffffffff
20190328-220801.19641: DEBUG: Enter GetXChars…
20190328-220801.19644: DEBUG: Short Hint : c
20190328-220801.19646: DEBUG: Enter GetXChars…
20190328-220801.19649: DEBUG: Compression Offset — 0x0018 — 24
20190328-220801.19652: DEBUG: FINAL Full String: k16tst.test.loc
And then the Host’s FQDN…
20190328-220801.19666: DEBUG: Enter GetCompressedName…
20190328-220801.19667: DEBUG: +++++++++++++++++++++
20190328-220801.19668: DEBUG: Start Data : 0a4b31365453542d444331c018064b3136545354000a4b31365453
542d44433100001744656661756c742d46697273742d536974652d
4e616d6500136a6f656e65746c6f676f6e746573747369746500c0
4d15000000ffffffff
20190328-220801.19669: DEBUG: Enter GetXChars…
20190328-220801.19670: DEBUG: Short Hint : 0
20190328-220801.19671: DEBUG: Enter GetXChars…
20190328-220801.19672: DEBUG: Full Hint : 0a
20190328-220801.19673: DEBUG: Enter GetXChars…
20190328-220801.19674: DEBUG: Enter HexToASCII…
20190328-220801.19676: DEBUG: String : K16TST-DC1
20190328-220801.19677: DEBUG: Full String : K16TST-DC1.
20190328-220801.19678: DEBUG: ———————
20190328-220801.19679: DEBUG: +++++++++++++++++++++
20190328-220801.19680: DEBUG: Start Data : c018064b3136545354000a4b31365453542d444331000017446566
61756c742d46697273742d536974652d4e616d6500136a6f656e65
746c6f676f6e746573747369746500c04d15000000ffffffff
20190328-220801.19681: DEBUG: Enter GetXChars…
20190328-220801.19682: DEBUG: Short Hint : c
20190328-220801.19683: DEBUG: Enter GetXChars…
20190328-220801.19684: DEBUG: Compression Offset — 0x0018 — 24
20190328-220801.19685: DEBUG: FINAL Full String: K16TST-DC1.k16tst.test.loc