Tuesday, 18 May 2021

Network Discovery and Name Resolution under Windows 10 in a Home Network (zero-configuration networking)

Network discovery (for example, the “discovery” performed by the “Network” item in the left (navigation) pane of “File Explorer” under Windows 10) in a home network should “just work” in the sense of discovering and displaying the network devices that are known to be in the home network. However, one often reads in technical support forums that “network discovery” is not working to some extent; sometimes this results from outdated expectations (for example, that the “net view” command is the full extent of “network discovery”) but sometimes also from old network equipment that does not support newer discovery mechanisms or from network equipment that has been configured not to respond to network discovery requests (perhaps for security reasons).

Let’s first consider how “network discovery” works and what can be done to influence its behaviour.

The Microsoft interface IFunctionDiscovery is the entry point into performing network discovery in the same style as File Explorer. The method CreateInstanceCollectionQuery of this interface is called first with either a “layered category” (e.g. "Layered\Microsoft.Networking.Devices") which will use a collection of providers appropriate to the layer or a “provider category” (e.g. “Provider\Microsoft.Networking.WSD”) which will use a specific provider/technology/protocol.

Some of the providers that are relevant to discovering networking devices are:

Provider\Microsoft.Networking.WSD
Provider\Microsoft.Networking.SSDP
Provider\Microsoft.Networking.Netbios

Network discovery can take some time, so the method that executes the discovery normally returns a “pending” status (E_PENDING) and delivers discovery results to its caller asynchronously (as they happen). The main work of discovery is performed in the “Function Discovery Provider Host” (fdPHost) service.

One piece of advice that one often sees on the Internet is to ensure that Windows services used in the discovery process are running and/or configured to run. This is not something that I would recommend. The relevant services (e.g. fdPHost, FDResPub, SSDPSRV) are normally configured as “demand” start; some may also include “trigger” configuration (e.g. FDResPub triggers on specific event values of the Microsoft-Windows-NetworkProfileTriggerProvider ETW provider); some are defined as “dependencies” for other services; some services explicitly start other services. The ability of a service to operate is also often dependent on Windows Firewall rules (that are also actively maintained and changed as system events occur). Manual interference should be a last step, guided by evidence that there is actually a misconfiguration, rather than a first/early troubleshooting step.

The progress of network discovery can be followed using ETW. A combination of the providers Microsoft-Windows-FunctionDiscovery, Microsoft-Windows-WFP (to check for firewall packet drops) and Microsoft-Windows-PktMon (or equivalent, to observe the actual network protocol interactions) is often a good combination.

Web Services Dynamic Discovery (WS-Discovery or WSD)

The Microsoft.Networking.WSD provider is the provider most likely to detect computers and file servers on the home network. During the discovery operation, the fdPHost service sends WSD Probe messages to the WSD IPv4 and IPv6 multicast addresses defined by the WSD protocol. If and when the fdPHost receives a ProbeMatch message, it sends a Get request to the responder (via TCP) to obtain a Get response. In the case of Windows computers, the responder is the FDResPub (Function Discovery Resource Publication) service

The key information in the Get response is contained within the wsdp:Relationship/wsdp:Host/pub:Computer element. As the [MS-PBSD] document says, if the computer is domain joined then the value will be of the form “<NetBIOS_Computer_Name>/Domain:<NetBIOS_Domain_Name>”, if the computer is in a workgroup then the value will have the form “<NetBIOS_Computer_Name>\Workgroup:<Workgroup_Name>”, otherwise it will have the form “<NetBIOS_Computer_Name>\NotJoined”.

Network Discovery via IFunctionDiscovery finds all of these variants and File Explorer displays all of the results that represent domain joined or workgroup computers, but it does not display computers that report “not joined”. FDResPub uses the NetGetJoinInformation API to obtain workgroup/domain information; it normally obtains the information when the service starts, so if the LanmanWorkstation service (which serves the NetGetJoinInformation request) has not (completely) started when FDResPub calls NetGetJoinInformation, then the published information will state that the computer is “not joined”. 

A workaround for the above problem is to add a service dependency to the FDResPub service on the LanmanWorkstation service. The problem could be called a “bug” and it has a simple source code fix. FDResPub calls NetGetJoinInformation specifying the name of the local computer as the system for which the information should be retrieved; if NetGetJoinInformation fails with RPC_S_SERVER_UNAVAILABLE and a system name was specified then a failure code is returned to the caller (NERR_WkstaNotStarted), but if no system name was specified (a null was passed as parameter, implying the local system) then NetGetJoinInformation uses other local mechanisms to obtain join information and returns a success code to the caller.

This discovery mechanism should discover all devices (Windows, Apple, Linux, Network Attached Storage (NAS), etc.) that support WS-Discovery, have a WS-Discovery publisher service running and are not blocking WS-Discovery messages via firewall mechanisms.

For Windows systems, the “Network and Sharing Centre, Advanced sharing settings” dialog (on each Windows system in the home network) should be the only thing that needs to be checked to ensure that network discovery is correctly configured.

Simple Service Discovery Protocol (SSDP)

The Microsoft.Networking.SSDP provider “discovers” most of the printers, scanners, displays, etc. in the home network. The SSDPSRV service periodically multicasts SSDP M_SEARCH requests and observes SSDP NOTIFY announcements. When network discovery is started, fdPHost retrieves a list of responses from SSDPSRV via RPC. The fdPHost then retrieves detailed information about the service by querying the Location URL in the SSDP response. For services hosted on Windows systems (perhaps directly attached printers, music and video libraries, etc.), the upnphost (UPnP Device Host) service is normally the process that is listening at the Location URL.

NetBIOS

The Microsoft.Networking.Netbios provider essentially performs a classic “net view” command, using the WNetOpenEnum/WNetEnumResource/WNetCloseEnum API.

A prerequisite for this resolution mechanism is that NetBIOS over TCP/IP is enabled. By default, the relevant setting is set to “Use NetBIOS from the DHCP server. If static IP address is used or the DHCP server does not provide NetBIOS setting, enable NetBIOS over TCP/IP”.

If SMBv1 is installed, then this method should produce the classically expected results. If SMBv1 is not installed/enabled then this discovery method will only work in the computer has been elected as the “Master Browser” of a workgroup.

If the local computer is not the Master Browser, then the local computer will try to negotiate a connection with the Master Browser. Normally, the newest SMB protocol version available to both parties will be negotiated – typically SMBv3. From a network trace perspective, it seems as though the negotiation has been concluded successfully, but post processing by the client causes the connection to be disconnected.

The stack on the client (local computer) when a disconnection is initiated looks like this:

mrxsmb!SmbCeDisconnectServerConnections+0x2d6:
mrxsmb20!MRxSmb2HandOverSrvCall+0x2054:
mrxsmb!SubRdrClaimSrvCall+0x90:
mrxsmb!SmbCeCompleteSrvCallConstructionPhase2+0x146:
mrxsmb!SmbCeCompleteServerEntryInitialization+0x176:
mrxsmb!SmbCeCompleteNegotiatedConnectionEstablishment+0x155:
mrxsmb!SmbNegotiate_Finalize+0x5b:

Some code in mrxsmb20!MRxSmb2HandOverSrvCall decides that a disconnect is necessary and a quick look at that routine shows that the condition is ConnectionType == Tdi. Possible values for ConnectionType are Tdi (TDI - Transport Driver Interface), Wsk (Windows Kernel Sockets), Rdma (Remote Direct Access Memory) and VMBUS.

TDI is a deprecated technology and is used by "NetBIOS over TCP/IP" (netbt.sys). It seems as though the client will refuse to use SMBv2/3 in conjunction with "NetBIOS over TCP/IP".

If the local computer is the Master Browser, it has access to the list of servers via local mechanisms and the results are made available to the user of IFunctionDiscovery. Users of IFunctionDiscovery, such as Windows File Explorer, typically recognize that some systems have been discovered by more than one mechanism (perhaps WSD and NetBIOS) and display just a single entry for such systems in their user interface.

Name Resolution

If network discovery fails to discover some resource (for example, a file server), it may still be possible to reference the resource by name (rather than by IP address; IP addresses are typically not permanently assigned but rather leased, so it is difficult to be certain of the IP address in the long term in a home network). Name resolution uses different protocols to network discovery and these may well work, even if discovery has failed.

Windows uses 3 mechanisms to resolve names: multicast DNS (mDNS), Link-Local Multicast Name Resolution (LLMNR) and NetBIOS Name Service (NBNS). Name resolution via all applicable mechanisms is normally started in parallel (i.e. the mechanisms are not tried sequentially, waiting for one method to fail before the next is tried). If NetBIOS over TCP/IP is disabled or the name being queried is not NetBIOS compatible (e.g. it is longer than 15 characters) then the NetBIOS Name Service resolution method is not used.

Wednesday, 12 May 2021

PktMon

Judging by Web search results, Windows 10 has included a new network traffic capturing mechanism since October 2018; however, two and a half years later, it still seems to be largely unknown (I only discovered it a few days ago).

Microsoft provides and supports a “classic” NDIS Filter traffic capturing mechanism (NdisCap, its associated Microsoft-Windows-NDIS-PacketCapture ETW provider and a PowerShell cmdlet Add-NetEventPacketCaptureProvider) and previously also supported a Windows Filtering Platform capture mechanism (WFPCapture, its associated Microsoft-Pef-WFP-MessageProvider ETW provider and a PowerShell cmdlet Add-NetEventWFPCaptureProvider). The new mechanism (PktMon and its associated Microsoft-Windows-PktMon ETW provider) does not yet have a PowerShell cmdlet to add it to ETW tracing sessions.

According to the PktMon home page, “[PktMon] is especially helpful in virtualization scenarios, like container networking and SDN, because it provides visibility within the networking stack”. The mechanism that allows PktMon to intercept a packet at various points in its transition through the network stack are additional “hooks” introduced into NDIS.sys. Some typical stack traces of the points at which PktMon is invoked are:

PktMon!PktMonPacketLogCallback+0x19
ndis!PktMonClientNblLog+0xbd
ndis!PktMonClientNblLogNdis+0x2b
ndis!ndisCallSendHandler+0x3ca4b
ndis!ndisInvokeNextSendHandler+0x10e
ndis!NdisSendNetBufferLists+0x17d

PktMon!PktMonPacketLogCallback+0x19
ndis!PktMonClientNblLog+0xbd
ndis!PktMonClientNblLogNdis+0x2b
ndis!ndisMIndicateNetBufferListsToOpen+0x3e95c
ndis!ndisMTopReceiveNetBufferLists+0x1bd
ndis!ndisCallReceiveHandler+0x61
ndis!ndisInvokeNextReceiveHandler+0x1df
ndis!ndisFilterIndicateReceiveNetBufferLists+0x3be91
ndis!NdisFIndicateReceiveNetBufferLists+0x6e

PktMon can be seen as an improvement on NdisCap. The main advantage (in my opinion) is that PktMon can be loaded and started without requiring rebinding of the network stack. As I have mentioned in other articles, rebinding the network stack can, under unfortunate circumstances, be a risky undertaking. The new ability to intercept packets at various points in the network stack is something that I have never personally had a need to use but is probably welcomed by those who have had difficulty in diagnosing network problems in “virtualization scenarios”.

One thing that PktMon cannot do is to trace loopback traffic, since the Windows loopback implementation does not use NDIS (WFP mechanisms and raw sockets can capture such traffic).

There are 3 main components of PktMon: the driver (PktMon.sys), a DLL (PktMonApi.dll) and an executable (PktMon.exe).

PktMon.sys

PktMon.sys is the core component. It is controlled via a small set of IOCTLs (to start, stop and query a capture; add, remove and list packet filters; list traceable components; reset trace counters) and the keywords used in the ETWENABLECALLBACK (Config, Rundown, NblParsed, NblInfo and Payload).

The information in the list of traceable components will seem familiar to anyone who has used the kernel debugger extensions “!ndiskd.miniports”, “!ndiskd.protocols” and “!ndiskd.filters”. The list of components is not only available via the IOCTL but is also included (in a different form) at the end of an ETW trace if the “Rundown” keyword is enabled.

The packet filtering possibilities are of the address/protocol-type/port type rather than what Microsoft sometimes calls OLP (Offset value, bit Length, and value Pattern). The output of the command “pktmon filter add help” accurately reflects the filtering possibilities. The filtering mechanism does not allow “negative” conditions to be expressed – for example, one can’t specify “ignore RDP” (as one might wish to do if one is logged onto a system via RDP).

Similar to both NdisCap and WFPCapture, PktMon must be explicitly loaded and/or started before it can generate any events; just starting an ETW trace session containing Microsoft-Windows-PktMon is not enough to capture trace data.

PktMonApi.dll

PktMonApi.dll currently has 9 exports, which are mostly just simple wrappings around IOCTLs to PktMon.sys:

PktmonAddFilter
PktmonGetComponentList
PktmonGetFilterList
PktmonGetStatus
PktmonRemoveAllFilters
PktmonResetCounters
PktmonStart
PktmonStop
PktmonUnload

PktMonApi.dll is not used by PktMon.exe (which contains its own simple wrappings around the IOCTLs).

PktMon.exe

PktMon.exe has several facets: it can configure and control PktMon.sys via its IOCTLs, it can manage ETW trace sessions, it can extract information from the Microsoft-Windows-PktMon ETL and save it in various formats (including “pcapng”), and it can perform “tcpdump” style simple formatting of packets captured and display in real-time.

As mentioned, PktMon.sys must be explicitly managed in order for Microsoft-Windows-PktMon to capture data. It would be ideal (for me) if Microsoft-Windows-PktMon could just be included in a Windows Performance Recorder (WPR) Profile along with other providers and use of advanced ETW features (such as stack traces, SID information, etc.). Since that is not possible, one must be content with the limited ETW configuration options of PktMon.exe (provider, keywords and level) – a similar situation to that with NdisCap and “netsh trace”.

The PktMon home page currently says “Packet drops from Windows Firewall are not visible through Packet Monitor yet” (my emphasis of “yet”). I would not expect Windows Firewall drop detection to be included in PktMon.sys, since they deal with different technologies. By including both Microsoft-Windows-PktMon and Microsoft-Windows-WFP in ETW trace sessions, one can see both the drops types detected by PktMon and the drops caused by Windows Firewall. Perhaps the “yet” is just a nod to future improvements in the PktMon.exe user interface to present a unified view of the output of the two providers.

The configuration defaults of PktMon.exe cause packets to be logged at all interception points (--comp all); if the intent of a capture is just to analyse the traffic in a tool like Wireshark, this can cause each packet to be repeated twenty or more times in the network trace. One can tackle this by selecting specific components when exporting captured data to the pcapng format, but I prefer to use “PktMon start” with the “--comp nics” qualifier.

Microsoft Message Analyzer

Microsoft Message Analyzer (MMA) was discontinued more-or-less contemporaneously with the introduction of PktMon and the PktMon team provides no support for MMA. The OPN (Open Protocol Notation) below allows the ETL output of PktMon to be viewed comfortably in MMA (if one still has a copy installed).

The OPN is short and it works (for me), but it includes some “design” decisions and is incomplete (no support for the MBB (Mobile BroadBand) NDIS medium type, for example, and probably in many unknown/unanticipated ways).

module PktMon;

using Microsoft_Windows_PktMon;
using Standard;
using Ethernet;
using WiFi;

autostart actor PktMonPayload(ep_Microsoft_Windows_PktMon e)
{
    process e accepts m:Event_160 where m.PacketType == 1
    {
                dispatch endpoint Ethernet.Node accepts BinaryDecoder<Ethernet.Frame[m.LoggedPayloadSize < m.OriginalPayloadSize]>(m.Payload) as Ethernet.Frame;
    }

    process e accepts m:Event_160 where m.PacketType == 2
    {
                DecodeWiFiMessageAndDispatch(m.Payload);
    }
}

Saving this OPN in a file named %LOCALAPPDATA%\Microsoft\MessageAnalyzer\OPNAndConfiguration\OPNForEtw\CoreNetworking\PktMon.opn (for example) and restarting MMA should enable the functionality (MMA may take some time to completely start while it recompiles various OPN files).

Microsoft_Windows_PktMon

The Microsoft_Windows_PktMon ETW provider defines 5 keywords:

1.       Config: PktMon.exe help says “Internal Packet Monitor errors”; I have never observed any.

2.       Rundown: this causes the list of components to be logged to the ETL when PktMon is stopped.

3.       NblParsed: this causes address, protocol type, port, etc. information for each packet to be logged. The same information could be extracted from the binary payload (if present), but it is not trivial to do this because of various options at each layer (data link, network, transport).

4.       NblInfo: this seems to be a superset of the information logged as TcpipNlbOob by the Microsoft-Windows-TCPIP provider at level 17 (uninteresting for most people).

5.       Payload: this causes the raw data of the packet to be logged. The data can be truncated, if desired (truncation length information is included in the start IOCTL to PktMon.sys).

pktmon etl2pcap

Converting pktmon packet events to PCAPNG format is, in principle, relatively straightforward. There is a GitHub Microsoft repository with the code of a utility that performs the slightly more difficult task of converting “netsh trace” packet data to PCAPNG format (https://github.com/microsoft/etl2pcapng).

Perhaps surprisingly, the “pktmon etl2pcap” command (“Convert pktmon log file to pcapng format”) only produces a useful PCAPNG file if the packets were captured on an Ethernet/802.3 link. If packets were captured on a WiFi/802.11 link, the resulting PCAPNG packet is recorded as having been captured on an Ethernet link; since the datalink headers are different in content and length, a tool like Wireshark “decodes” the packet data incorrectly. If most of the packets in the capture were obtained from a WiFi/802.11 link, the command “editcap -T ieee-802-11 <infile> <outfile>” should make the PCAPNG file usable.

Apart from correctly identifying 802.11 frames as such (LINKTYPE_IEEE802_11), there is one other additional step that might be needed when saving 802.11 packets to PCAPNG format: the “protected” flag in the 802.11 frame control field might need to be cleared. The “protected” flag indicates whether the frame was encrypted; packets carrying network layer data are protected/encrypted but, by the time received packets have reached the packet capture hooks, the protected/encrypted content has been decrypted – however the captured 802.11 packet header (frame control, etc.) is not necessarily updated (probably depends on the network interface driver). If the protected bit is not cleared when saving the capture then, when the capture is loaded into Wireshark, the packet is assumed to still be protected and is displayed as such (no attempt is made to decode the “encrypted” portion of the packet).

pktmon start --trace

PktMon can control (start/stop) other ETW trace providers, as can “netsh trace”, logman and wpr (Windows Performance Recorder) amongst others. However, PktMon differs from the other controllers in the default values for “keywords” and “level” passed to the providers. By default, most providers use “maximal” values for keywords and level but PktMon uses a value of 0xFFFFFFFF for the keywords value (which is actually a 64-bit value, so the high 32 bits are set to zero) and 4 for the level (the maximum value is 255 and Microsoft-Windows-TCPIP, for example, logs some events at level 17).

Although the help/documentation (“pktmon start help”) mentions this, I have been caught out more than once puzzling over why certain expected events were missing from a trace.

New Packet Monitor API (Windows 11 24H2, Windows Server 2025)

PktMonApi.dll now exports additional, documented routines:

PacketMonitorAddCaptureConstraint
PacketMonitorAddSingleDataSourceToSession
PacketMonitorAttachOutputToSession
PacketMonitorCloseRealtimeStream
PacketMonitorCloseSessionHandle
PacketMonitorCreateLiveSession
PacketMonitorCreateRealtimeStream
PacketMonitorEnumDataSources
PacketMonitorInitialize
PacketMonitorSetSessionActive
PacketMonitorUninitialize

The documentation highlights “Multisession” and “Packet-streaming” as new capabilities. The new capabilities are enabled via “Controlled Feature Rollout” (CFR) for the feature named "UxConfTest"; Microsoft notes:

Using CFR, features may be gradually rolled out, starting with devices that install the monthly optional non-security preview release. When we've validated that each feature is ready, we'll gradually roll it out to new devices, and eventually include it enabled-by-default in a subsequent monthly security update.

Two characteristics of the new routines immediately irritated me. The first irritation is the granularity of the captured packet timestamps; the documentation says:

TimeStamp – Timestamp when the packet was reported. This is retrieved using ‘KeQuerySystemTime’.

KeQuerySystemTime documentation includes the following remark:

System time is typically updated approximately every ten milliseconds.

My first test of the new routines was to capture packets and write them to a file in PCAPNG format. I was surprised, when looking at the packets in Wireshark, how many packets had identical timestamps (a lot of packets can be exchanged in ten to twenty milliseconds).

The second irritation is that the length of the packet is not included in the metadata describing a captured packet. The length of the captured data is, of course, available but if a TruncationSize (snapshot length) is specified, the original length of the packet is not available from the metadata (it might be possible to infer the original length of the packet from the packet contents). Since the original length and snapshot length are included in the PCAPNG packet block, I chose to capture full packets (TruncationSize = 9000).

Tuesday, 28 April 2020

RPC NDR Engine, DCE & NDR64


RPC NDR Engine, DCE & NDR64




The Microsoft documentation for the RPC NDR Engine (https://docs.microsoft.com/en-us/windows/win32/rpc/rpc-ndr-engine) does a good job of explaining the “stubs” generated by MIDL that are used to marshal and unmarshal data for the DCE Transfer Syntax; it is however a “snapshot” of the stub functionality at a certain point in time (May 2018, according to the web pages) and does not reflect the newest MIDL generated stub output.



The “transfer syntax” or “wire protocol” is defined by chapter 14 of the technical standard “DCE 1.1: Remote Procedure Call” [C706] (https://publications.opengroup.org/c706) and this has remained stable; the new developments in the MIDL stubs has been in the area of data consistency checking/validation. [MS-RPCE] contains information about the Microsoft IDL syntax extensions and validation checks.



The RPC NDR Engine documentation states: “The format strings described in this document—and indeed all information generated by the compiler for NDR engine consumption—have always been considered an internal interface between the compiler and the engine.”. The documentation also references ndrtypes.h (“All format string characters used by MIDL and the NDR engine are defined in the Ndrtypes.h file”), but this file is no longer included in the Windows SDK, probably reflecting its “internal use only” status (ndr64types.h is present, but this file is essential for use of NDR64).



One of the newer type format descriptions (FC_RANGE) is present in the documentation but is “hidden” (i.e. is not where one would expect to find it) in the section called “User-Marshal”; the section “Correlation Descriptors” seems like a more natural home for it.



The format characters used by the NDR engine (such as FC_RANGE) are one byte in size and were originally assigned with related meanings grouped together; newer functionality is typically assigned a higher number (I say “typically” because there were/are a few lower values that were initially assigned with names like FC_UNUSED3) and so can be easily identified. MIDL emits “comments” to annotate its type format strings and these comments can be used to understand new usages.



One example is FC_SUPPLEMENT; section 3.1.1.5.3.3.1 of [MS-RPCE] discusses “Target Level 6.0”, “Additional Limitations”. If one constructs a “test” IDL/ACF to exercise “type_strict_context_handle” or “range Attribute to Limit the Range of Maximum Count of Conformant Array and String Length” functionality, one can see (commented) use of FC_SUPPLEMENT in the MIDL output and infer its format and purpose.



The major omission in the NDR engine documentation is the format of the ExprFormatString although this can be deduced from the comments in MIDL output (assuming that the IDL content adequately exercises expression evaluation) and the ndr64types.h file (the EXPR_TOKEN enumeration and the NDR64_EXPR_* structure definitions).



NDR64




The RPC NDR Engine documentation does not say much about NDR64. The two main statements are: “The NDR64 protocol is an extension to the 32-bit based NDR transfer syntax, created specifically to enable developers targeting 64-bit systems to achieve optimized performance.” and “The differences between the NDR wire format and the NDR64 wire format addresses the different size of pointers in a 64-bit environment, as well as other issues.”.



The wire format differences seem modest: full support for 64-bit pointer types and 64-bit representation of count and offset values – allowing much more data to be transferred (although it is difficult to imagine 32-bit (4GB) counts not being adequate in almost all RPC circumstances!).



The architecture of the MIDL stubs for NDR64 differs substantially from those for DCE. DCE uses strings of bytes and no effort is made to align multi-byte values; NDR64 uses a network of structure definitions (the structures are linked by pointers) and members of the structures are aligned on their natural boundaries – this may improve performance. NDR64 also abandons support for “big-endian” integer and floating point representations.



Only supporting “little-endian” data means that it is easier to decide which sequences of bytes can be bit-blitted. This also may improve performance and means that it is not necessary to include information about the individual members of a bit-blittable sequences of bytes in the NDR64 stubs. Depending on one’s perspective (reverse engineering interfaces from stubs or concealing details of an interface), this has negative or positive side-effects.



NDR64 only uses one mechanism for encoding the expressions in correlation descriptors whereas DCE uses three: explicit format codes for simple/common expressions (plus/minus one, multiply/divide by two) and the expression string for most other expressions except under limited circumstances where a callback to an expression evaluation routine is needed (for example, the documentation says: “If the first_is() attribute is applied to a conformant varying array, a callback to an expression evaluation routine is forced.”).



NDR64 uses a byte to encode the format character, just like DCE, but fewer values are assigned (because the “simple expression” formats are not used, structure padding uses one format and a count rather than 7 format characters). This allows related format characters to be grouped, with unused values between the groups – this makes it easy to spot “new” functionality and guess its application.



Range
Used
Description
0x00 – 0x1F
0x14
Simple Types
0x20 – 0x2F
0x05
Pointers
0x30 – 0x3F
0x08
Structures
0x40 – 0x4F
0x08
Arrays
0x50 – 0x5F
0x02
Unions
0x60 – 0x6F
0x06
Strings
0x70 – 0x7F
0x06
Handles
0x80 – 0x8F
0x05
Pointer Layout
0x90 – 0x9F
0x04
Structure Members
0xA0 –
0x06
Miscellaneous (Transmit-As, Represent-As, User-Marshal, Pipe, etc.)



A “fly-in-the-ointment” of this classification is the format code FC64_SYSTEM_HANDLE (0x3C), which is not structure related.



Using NDR64 in a pure C# application




The C language code produced by MIDL for both DCE and NDR64 is simple and predictable enough to be converted, with a few regular expressions, into compilable C# code. For DCE stubs, it is relatively easy to handle marshalling between managed code and calls to the native NdrClientCall2 or NdrClientCall3 routines. For NDR64 stubs, the network of structures linked together with pointers makes marshalling the data for a call to NdrClientCall3 more challenging.



Probably the most common way of using RPC from a .NET application is to use a mixed language approach (managed C++ (cl/clr) and C#, for example) and this approach works equally well with both types of stub.

Saturday, 21 March 2020

NDR-RPC


I used Microsoft Interface Definition Language (MIDL) defined Remote Procedure Call interfaces many times in the past (for both defining custom interfaces and using standard interfaces), but had not used it again since 2005 – that is until the last few weeks, when exploring Kerberos Armouring. MIDL was needed to use the Directory Replication Service (DRS) Remote Protocol to obtain the keys needed to decipher armoured Kerberos tickets and to decode the claims in the PAC_CLIENT_CLAIMS_INFO within Kerberos tickets. Both of these usages required use of some information that is not easy to find; I also wanted to use C# to develop the code.


MIDL and C#


The default options for two important features of MIDL are “/Oicf” and “/protocol dce”. “/Oicf” is almost a “boilerplate” option (can/should be used under (almost) all circumstances); “/protocol” can be used to specify DCE, NDR64 or both as the “transfer syntax”. NDR64 is optimized for 64 bit applications but DCE can be used under all circumstances. The outputs of MIDL are C language source and include files and the output produced by “/protocol dce” is the easiest to incorporate in a pure C# application.

One can use the full features of MIDL (including NDR64 transfer syntax) if one creates a “mixed” application, compiling the MIDL output with a C compiler (perhaps C++/CLI) and using C# to native interoperability mechanisms (if necessary) to use the RPC functions.

Another approach is to “massage” the MIDL output into a form that can be “compiled” by C#. For a DCE client stub, one just needs to create and initialize two structs/classes (RPC_CLIENT_INTERFACE and MIDL_STUB_DESC) and two byte arrays (TypeFormatString and ProcFormatString); this is straightforward for anyone who is familiar with .NET interoperability. The only minor problem is that the client stub code uses macros to expand 2 and 4 byte integers into byte sequences in the format strings, but some simple pre-processing of the format strings with regular expressions can resolve this issue.

One way of calling NdrClientCall2 to initiate RPC is to define the RPC function as follows, using the DllImport EntryPoint attribute:

[DllImport("rpcrt4.dll", EntryPoint = "NdrClientCall2")]
static extern int GetNCChanges(MIDL_STUB_DESC stub, byte[] format, IntPtr drs,
                               uint inver, DRS_MSG_GETCHGREQ msgin,
                               out uint outver, [Out] DRS_MSG_GETCHGREPLY_NATIVE msgout);

This finesses the issue of variable length argument lists (with marshalling directives) for NdrClientCall2 and provides some type safety.
The ProcFormatString can be divided into individual chunks for each remote procedure, so the “public” interface to the RPC routine is constructed thus:

public static int GetNCChanges(IntPtr drs, uint inver, DRS_MSG_GETCHGREQ msgin,
                               out uint outver, DRS_MSG_GETCHGREPLY_NATIVE msgout)
{
    byte[] format = new byte[]
     { 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x30, 0x00,
       0x30, 0x40, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x24, 0x00,
       0x47, 0x06, 0x0A, 0x47, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
       0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x4E, 0x00, 0x48, 0x00,
       0x08, 0x00, 0x08, 0x00, 0x0B, 0x01, 0x10, 0x00, 0x56, 0x00,
       0x50, 0x21, 0x18, 0x00, 0x08, 0x00, 0x13, 0x01, 0x20, 0x00,
       0xF8, 0x01, 0x70, 0x00, 0x28, 0x00, 0x08, 0x00, 0x00 };
    return GetNCChanges(Stub, format, drs, inver, msgin, out outver, msgout);
}


It was possible to create a single source file that could be copied between systems (e.g. by copying the source text to the clipboard, in a remote desktop scenario) and compiled with the local C# compiler. My generously formatted source code, including formatting/display of the acquired keys, was about 900 lines (30 kilobytes) long.

Calling IDL_DRSGetNCChanges and deciphering the result


Calling IDL_DRSGetNCChanges is mostly just a straightforward use of the RPC interface defined in [MS-DRSR], using the information in section 4.1.10.6.17 to decrypt some of the returned attributes (e.g. “supplementalCredentials”) and the information in [MS-SAMR] section 2.2.11.1 to decrypt some others (e.g. “unicodePwd”).

There is no documented method to get access to the key material needed to perform [MS-DRSR] 4.1.10.6.17, but the functions/definitions needed are present in the “include” files in the SDK (albeit in the file rpcdcep.h, the final “p” probably indicating “private”). The routine I_RpcBindingInqSecurityContext returns a security context handle that can be used as the first argument to QueryContextAttributes in a call to retrieve the attribute SECPKG_ATTR_SESSION_KEY. The bigger problem is to obtain access to a suitable RPC_BINDING_HANDLE value for use as the first argument to I_RpcBindingInqSecurityContext: an RPC server can call I_RpcGetCurrentCallHandle (during the handling of a call) to get a suitable handle, but a client has to first call RpcBindingSetOption to set a RPC_C_OPT_SECURITY_CALLBACK routine (the callback routine is invoked with a parameter that can be used in a call to I_RpcBindingInqSecurityContext). These are the methods that the NTDS service uses.

Setting the krbtgt password


I used a “lab” set-up to explore Kerberos Armouring and was quite happy to set a simple password for the krbtgt account. I found a script from Microsoft to reset the krbtgt password (New-CtmADKrbtgtKeys.ps1) that verified that one just needs to reset the password in AD (i.e. the password is not also stored somewhere else (and needs to be kept in sync)). The script generates a complex password and “sets” that as the krbtgt password. I put “sets” in quotes because, under the hood, the password value is ignored and NTDS sets a password of 512 random bytes (notionally 256 Unicode characters, but not all of the byte pairs in the 512 random bytes gives a valid Unicode character). Since I could not set a simple password or meaningfully use the generated password, I needed to obtain the keys derived from the password – hence the use of IDL_DRSGetNCChanges.

Decoding claims in Kerberos tickets


[MS-PAC] describes the PAC_CLIENT_CLAIMS_INFO and PAC_DEVICE_CLAIMS_INFO items that can be found in the Privilege Attribute Certificate (PAC) data included in some Kerberos tickets. Details of the claims data structure (CLAIMS_SET_METADATA) are defined in [MS-ADTS]. The complex claims data structures are serialized using NDR and [MS-ADTS] just describes the deserialization process in terms of an abstract function (NdrDecode) and references [MS-RPCE] for a brief description of “type serialization”.

In the hope that there might be some library function provided by Microsoft that could be used to decode/deserialize the claims, one might search the include files in the Windows SDK and indeed this does reveal some undocumented functions (defined in midles.h) that might be useful: NdrMesTypeDecode, NdrMesTypeDecode2 and NdrMesTypeDecode3.

Looking at how Windows deserializes the data (in samsrv.dll), one can see that it uses NdrMesTypeDecode3 and extract the “constant” data that it uses in conjunction with this routine: a MIDL_STUB_DESC and type format string plus a MIDL_TYPE_PICKLING_INFO structure – this is the essential additional element that is needed.

Samsrv.dll uses a MIDL_TYPE_PICKLING_INFO flag value of 7 and this flag is related to the functionality in the type format string. The value of 7 works with the type format string embedded in samsrv.dll (this type format string uses NDR correlation descriptors that contain both robust_flags and extended range conformance checks) but does not work with the type format string produced by applying midl.exe to the IDL text given in [MS-ADTS] (because this type format string has robust_flags but no extended range conformance checks); to use the midl.exe generated type format string, a flags value of 3 is appropriate. NdrMesTypeDecode presumably defaults a flags value of 0 or 1 (no robust_flags), which does not match the type format string produced by midl.exe applied to the given IDL text. NdrMesTypeDecode2 also takes a MIDL_TYPE_PICKLING_INFO argument and this routine can be used in place of NdrMesTypeDecode3.

Midl.exe does have a “/no_robust” option, but it is ineffective on 64-bit platforms, producing the warning:

midl : warning MIDL2469 : invalid command line option, ignored:  -no_robust in 64bit platforms

MIDL_TYPE_PICKLING_INFO includes a Version member and samsrv.dll uses a value of 0x33205054 (the four ASCII characters “TP 3”), but this value does not appear to be checked/used by the decode routines.

Midl.exe can produce a type format string identical to that contained in samsrv.dll, but that involves changing the IDL text. The enumerations CLAIMS_SOURCE_TYPE and CLAIMS_COMPRESSION_FORMAT are removed and the USHORT type is used in their place; the “range” attribute is removed from the ValueCount and applied to the associated array instead. This is an example of the change:

[MS-ADTS]
struct {
  [range(1, 10*1024*1024)] ULONG ValueCount;
  [size_is(ValueCount)] ULONG64* Uint64Values;
};


samsrv.dll
struct {
  ULONG ValueCount;
  [size_is(ValueCount), range(1, 10*1024*1024)] ULONG64* Uint64Values;
};


Tip


It can be difficult to determine the offset of specific types in the MIDL type format string. The IDL definition for claims (given in [MS-ADTS]) just defines types, but it can be useful to add a dummy routine to the IDL that references the types that need to be deserialized – one can then easily see the type offsets because these are easily identifiable in the ProcFormatString. I extended the IDL definition thus:

    // opnum 0
    void
    DecodeClaimsSet(
        [out] PCLAIMS_SET_METADATA_WRAPPER* meta,
        [out] PCLAIMS_SET_WRAPPER* claims);

Thursday, 5 March 2020

Kerberos Armouring under Windows – an example

The information carried in the encrypted parts of Kerberos messages is not very interesting and is difficult to view in an unencrypted form; the plaintext content of the messages is normally adequate to debug Kerberos related authentication problems. Some tools, such as Wireshark, can decrypt and display the information (given the necessary key information, normally in the form of a keytab file), but I suspect that this seldom happens. Kerberos Armouring adds an additional level of complexity to the encryption process and makes it unlikely that fully decrypted Kerberos armoured messages are often seen.

The following sections display the decoded messages and describes the steps and difficulties in decrypting them. Since Kerberos armouring requires a Ticket Granting Ticket (TGT) for the device (Windows client), we start there. The data structures shown are defined in RFC 4120 (Kerberos V5) and RFC 6113 (Kerberos Preauth Framework).

AS-REQ for the device, no pre-authentication


There is no encrypted information in this message.


ERROR for the device, pre-authentication required

There is no encrypted information in this message.


AS-REQ for the device, with pre-authentication


Decrypting the PA-ENC-TIMESTAMP EncryptedData gives, using the password of the device (or the appropriate key derived from the password (the aes256-cts-hmac-sha1-96 key, in this case)):

{
  [0] { 03/03/2020 17:09:58 } -- patimestamp
  [1] { 313892 } -- pausec
}


AS-REP for the device


Decrypting the final EncPart EncryptedData shown above using the password/key of the device gives:

[APPLICATION 25] { -- EncKDCRepPart
  {
    [0] { -- key
          { [0] { 18 }
            [1] { 'mMcDNF8bhHlXF+ntZ7CosH1qNaUujc6WoCyGustqEG0=' } -- base64
          }
        }
    [1] { -- last-req
          { { [0] { 0 }
              [1] { 03/03/2020 17:09:58 }
          } }
        }
    [2] { 716117998 } -- nonce
    [3] { 14/09/2037 04:48:05 } -- key-expiration
    [4] { 40-E1-00-00 } -- flags
    [5] { 03/03/2020 17:09:58 } -- authtime
    [6] { 03/03/2020 17:09:58 } -- starttime
    [7] { 04/03/2020 03:09:58 } -- endtime
    [8] { 10/03/2020 17:09:58 } -- renew-till
    [9] { 'LEIMGRUBE.CH' } -- srealm
    [10] { -- sname
           { [0] { 2 }
             [1] { { 'krbtgt' 'LEIMGRUBE.CH' } }
           }
         }
    [11] { -- caddr
           { { [0] { 20 }
               [1] { 'EC2AMAZ-EHM3HUN ' }
           } }
         }
    [12] { -- padata, RFC 6806
           { { [1] { 167 } -- PA-PAC-OPTIONS
               [2] { byte[11] => { [0] { 80-00-00-00 } } } }
             { [1] { 165 } -- PA-SUPPORTED-ENCTYPES
               [2] { 1F-00-05-00 }
           } }
         }
  }
}

 
Decrypting the ticket EncPart EncryptedData shown above using the password/key of the krbtgt account gives:

[APPLICATION 3] { -- EncTicketPart
  {
    [0] { 40-E1-00-00 } -- flags
    [1] { -- key
          { [0] { 18 }
            [1] { 'mMcDNF8bhHlXF+ntZ7CosH1qNaUujc6WoCyGustqEG0=' } -- base64
          }
        }
    [2] { 'LEIMGRUBE.CH' } -- crealm
    [3] { -- cname
          { [0] { 1 }
            [1] { { 'EC2AMAZ-EHM3HUN$' } }
          }
        }
    [4] { -- transited
          { [0] { 0 }
            [1] {  }
          }
        }
    [5] { 03/03/2020 17:09:58 } -- authtime
    [6] { 03/03/2020 17:09:58 } -- starttime
    [7] { 04/03/2020 03:09:58 } -- endtime
    [8] { 10/03/2020 17:09:58 } -- renew-till
    [10] { -- authorization-data
           { { [0] { 1 } -- AD-IF-RELEVANT
               [1] { byte[862] => { { [0] { 128 } -- AD-WIN2K-PAC
                                      [1] { byte[840] } } } } }
           }
         }
  }
}


The same “key” value is present in both encrypted parts. This enables the “session” key to be shared between the krbtgt account and the device. This key will be needed to decrypt later Kerberos armouring.

The AD-WIN2K-PAC data is just serialized data (and not encoded ASN.1). Here is a formatted dump of its contents:

PAC Type LoginInformation:
  LogonTime: 2020-03-03T16:06:52.9729666Z
  LogoffTime: 0001-01-01T00:00:00.0000000
  KickOffTime: 0001-01-01T00:00:00.0000000
  PasswordLastSet: 2020-02-17T06:58:17.9280757Z
  PasswordCanChange: 2020-02-18T06:58:17.9280757Z
  PasswordMustChange: 0001-01-01T00:00:00.0000000
  EffectiveName: EC2AMAZ-EHM3HUN$
  FullName:
  LogonScript:
  ProfilePath:
  HomeDirectory:
  HomeDirectoryDrive:
  LogonCount: 429
  BadPasswordCount: 0
  UserId: 1008
  PrimaryGroupId: 516
  GroupIds:        7 516
  UserFlags: 0x20
  UserSessionKey: 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
  LogonServer: EC2AMAZ-EHM3HUN
  LogonDomainName: LEIMGRUBE
  LogonDomainId: S-1-5-21-1700842961-3107118889-547158845
  UserAccountControl: 0x2100
  SubAuthStatus: 0x0
  LastSuccessfulILogon: 1601-01-01T00:00:00.0000000Z
  LastFailedILogon: 1601-01-01T00:00:00.0000000Z
  FailedILogonCount: 0x0
  ExtraSids: 0x7 S-1-5-9
  ExtraSids: 0x7 S-1-5-21-0-0-0-497
  ExtraSids: 0x7 S-1-18-1
PAC Type ClientInformation: 00-8F-57-2F-76-F1-D5-01 EC2AMAZ-EHM3HUN$
PAC Type UpnDns: EC2AMAZ-EHM3HUN$@Leimgrube.ch LEIMGRUBE.CH
PAC Type ClientClaimsInformation: ()
PAC Type ServerChecksum: 10-00-00-00-B0-E5-EC-FF-0A-9A-B5-8F-CD-F9-FC-E1
PAC Type KdcChecksum: 10-00-00-00-52-C2-12-E0-7E-47-C3-61-22-2D-FD-0B



AS-REQ for the user, no pre-authentication


There are 3 EncryptedData elements in the AS-REQ, but we have decrypted and shown one already, namely the EncPart of the Ticket used for the armouring (the device TGT).

Decrypting the Authenticator of the armour value AP-REQ with the session key from the device TGT gives:

[APPLICATION 2] { -- Authenticator
  {
    [0] { 5 } -- authenticator-vno
    [1] { 'LEIMGRUBE.CH' } -- crealm
    [2] { -- cname
          { [0] { 1 }
            [1] { { 'EC2AMAZ-EHM3HUN$' } }
          }
        }
    [4] { 24660 } -- cusec
    [5] { 03/03/2020 17:09:58 } -- ctime
    [6] { -- subkey
          { [0] { 18 }
            [1] { '2ft/oGqS50PtgmO0OK+3UH4KBwp4LtIUOuPS7JJ6ia8=' } -- base64
          }
        }
    [7] { 0 } -- seq-number
  }
}


Combining the subkey from above with the ticket session key, using the function KRB-FX-CF2 allows us to derive the armour key.

KRB-FX-CF2 is defined in RFC 6113 and it utilises the pseudo-random method of the encryption algorithm profile. Appendix A of RFC 6113 says “In performing interoperability testing, there was significant ambiguity surrounding [RFC3961] pseudo-random operations” and that was certainly my experience too.

aes256-cts-hmac-sha1-96 uses a simplified algorithm profile (defined in RFC 3962) and the pseudo-random method is defaulted from section 5.3 of RFC 3961. The default pseudo-random method uses the encryption function (E) of the simplified profile. Perhaps the first uncertainty is whether “encryption function” and “E” are synonymous; initially, I thought that they were, as when RFC 3961 section 5.2 refers to “encryption/decryption functions, E and D”. However section 5.3 seems to define “encryption function” in terms of “E” and other functions:

encryption function       conf = Random string of length c
                          pad  = Shortest string to bring confounder
                                 and plaintext to a length that's a
                                 multiple of m.
                          (C1, newIV) = E(Ke, conf | plaintext | pad,
                                          oldstate.ivec)
                          H1 = HMAC(Ki, conf | plaintext | pad)
                          ciphertext =  C1 | H1[1..h]
                          newstate.ivec = newIV


The biggest confusion for me was the “mode” of “E” (e.g. ECB/CBC/CTS). Even when I deduced/noticed that “No integrity checking or confounder should be included here”, I persisted with the idea that E was aes-cts and “m” (the message block size) was 1. The definition of “m” includes the statement “If a block cipher is used in a mode that can handle messages that are not multiples of the cipher block size, such as CBC mode with cipher text stealing (CTS, see [RC5]), this value would be one octet”.

Only the remark in Appendix A that “These test vectors assume that the AES pseudo-random operation is aes-ecb(trunc128(sha-1(input)))” finally made the requirements of an interoperable implementation of “pseudo-random” for aes256-cts-hmac-sha1-96 clear.
 
Decrypting the EncFastReq element with the armour key gives:


{ --  KrbFastReq
  [0] { 00-00-00-00 } -- fast-options
  [1] { -- padata
        { { [1] { 128 } -- PA-PAC-REQUEST
            [2] { byte[7] => { [0] { True } } } }
          { [1] { 167 } -- PA-PAC-OPTIONS
            [2] { byte[11] => { [0] { 80-00-00-00 } } } } }
      }
  [2] { -- req-body
        { [0] { 40-81-00-10 } -- kdc-options
          [1] { -- cname
                { [0] { 1 }
                  [1] { { 'Administrator' } } }
              }
          [2] { 'LEIMGRUBE.CH' } -- realm
          [3] { -- sname
                { [0] { 2 }
                  [1] { { 'krbtgt' 'LEIMGRUBE.CH' } } }
              }
          [5] { 13/09/2037 04:48:05 } -- till
          [6] { 13/09/2037 04:48:05 } -- rtime
          [7] { 716117995 } -- nonce
          [8] { -- etype
                { 18 23 -133 -128 24 -135 }
              }
          [9] { -- addresses
                { { [0] { 20 }
                    [1] { 'EC2AMAZ-EHM3HUN ' } } } }
              }
      }
}


ERROR for the user, pre-authentication required


Decrypting the EncFastRep element with the armour key gives:

{ -- KrbFastResponse
  [0] { -- padata
        { { [1] { 137 } -- PA-FX-ERROR
            [2] { byte[96] => [APPLICATION 30] { -- KRB-ERROR
                  { [0] { 5 } -- pvno
                    [1] { 30 } -- msg-type
                    [4] { 03/03/2020 17:09:58 } -- stime
                    [5] { 313892 } -- susec
                    [6] { 25 } -- error-code
                    [9] { 'LEIMGRUBE.CH' } -- realm
                    [10] { { [0] { 2 } [1] { { 'krbtgt' 'LEIMGRUBE.CH' } } } } -- sname
                  } } } }
          { [1] { 133 } -- PA-FX-COOKIE
            [2] { 4D-69-63-72-6F-73-6F-66-74 } }
          { [1] { 19 } -- PA-ETYPE-INFO2
     [2] { byte[60] => { { [0] { 18 } [1] { 'LEIMGRUBE.CHAdministrator' } }
                                { [0] { 23 } }
                                { [0] { -133 } }
                                { [0] { -128 } } } } }
          { [1] { 138 } -- PA-ENCRYPTED-CHALLENGE
            [2] {  } }
          { [1] { 16 } -- PA-PK-AS-REP
            [2] {  } }
          { [1] { 15 } -- PA-PK-AS-REP_OLD
            [2] {  } }
        }
      }
   [3] { 716117995 } -- nonce
}


AS-REQ for the user, with pre-authentication




Decrypting the Authenticator of the armour value AP-REQ with the session key from the device TGT gives:

[APPLICATION 2] { -- Authenticator
  {
    [0] { 5 } -- authenticator-vno
    [1] { 'LEIMGRUBE.CH' } -- crealm
    [2] { -- cname
          { [0] { 1 }
            [1] { { 'EC2AMAZ-EHM3HUN$' } }
          }
        }
    [4] { 24661 } -- cusec
    [5] { 03/03/2020 17:09:58 } -- ctime
    [6] { -- subkey
          { [0] { 18 }
            [1] { 'FI0VJO9MmOPrTFeo3Qn8g19rzjNgiVTnFegDvcrP55E=' } -- base64
          }
         }
    [7] { 0 } -- seq-number
  }
}


Decrypting the EncFastReq element with the new armour key derived from the subkey above gives:

{ --  KrbFastReq
  [0] { 00-00-00-00 } -- fast-options
  [1] { -- padata
        { { [1] { 138 } -- PA-ENCRYPTED-CHALLENGE
            [2] { byte[67] => { [0] { 18 } [2] { byte[56] } } } }
          { [1] { 128 } -- PA-PAC-REQUEST
            [2] { byte[7] => { [0] { True } } } }
          { [1] { 167 } -- PA-PAC-OPTIONS
            [2] { byte[11] => { [0] { 80-00-00-00 } } } } } }
 [2] { -- req-body
       { [0] { 40-81-00-10 } -- kdc-options
         [1] { -- cname
               { [0] { 1 }
                 [1] { { 'Administrator' } } }
             }
         [2] { 'LEIMGRUBE.CH' } -- realm
         [3] { -- sname
               { [0] { 2 }
                 [1] { { 'krbtgt' 'LEIMGRUBE.CH' } } }
             }
         [5] { 13/09/2037 04:48:05 } -- till
         [6] { 13/09/2037 04:48:05 } -- rtime
         [7] { 716117988 } -- nonce
         [8] { -- etype
               { 18 23 -133 -128 24 -135 }
             }
         [9] { -- addresses
               { { [0] { 20 }
                   [1] { 'EC2AMAZ-EHM3HUN ' } } } }
             }
     }
}


Decrypting the embedded PA-ENCRYPTED-CHALLENGE, using the client challenge armour key (derived from the client key and the armour key, using KRB-FX-CF2) gives:

{ [0] { 03/03/2020 17:09:58 } [1] { 313892 } }


AS-REP for the user


Decrypting the KrbFastArmoredRep EncryptedData using the armour key gives:

{ -- KrbFastResponse
   [0] { -- padata
         { { [1] { 19 } -- PA-ETYPE-INFO2
             [2] { byte[38] => { { [0] { 18 }
                                   [1] { 'LEIMGRUBE.CHAdministrator' } } } } }
           { [1] { 138 } -- PA-ENCRYPTED-CHALLENGE
             [2] { byte[67] => { [0] { 18 } [2] { byte[56] } } } } }
       }
   [1] { -- strengthen-key
         { [0] { 18 }
           [1] { '4aNHiHJpxx293D5zLMVKqaFkkwsQt0gHXESecGd86YM=' } } -- base64
       }
   [2] { -- finished
         { -- KrbFastFinished
           [0] { 03/03/2020 17:09:58 } -- timestamp
           [1] { 313892 } -- usec
           [2] { 'LEIMGRUBE.CH' } -- crealm
           [3] { { [0] { 1 } [1] { { 'Administrator' } } } } -- cname
           [4] { -- ticket-checksum
                 { [0] { 16 } [1] { 98-66-FC-21-FD-21-21-80-81-76-07-C4 } }
               }
         }
       }
   [3] { 716117988 } -- nonce
}


Decrypting the embedded PA-ENCRYPTED-CHALLENGE, using the KDC challenge armour key (derived from the client key and the armour key, using KRB-FX-CF2) gives:

{ [0] { 03/03/2020 17:09:58 } [1] { 313892 } }
 
Decrypting the final EncPart EncryptedData shown above using the password/key of the user, strengthened with the strengthen-key in the KrbFastResponse gives:


[APPLICATION 25] { -- EncKDCRepPart
  {
    [0] { -- key
          { [0] { 18 }
             [1] { 'lsJxd2LXykXBPBZ/jXRjCeJlfIsWanhSWIcs+eoGovk=' } -- base64
          }
        }
    [1] { -- last-req
          { { [0] { 0 }
              [1] { 03/03/2020 17:09:58 }
          } }
        }
    [2] { 716117988 } -- nonce
    [3] { 06/04/2020 23:04:57 } -- key-expiration
    [4] { 40-E1-00-00 } -- flags
    [5] { 03/03/2020 17:09:58 } -- authtime
    [6] { 03/03/2020 17:09:58 } -- starttime
    [7] { 04/03/2020 03:09:58 } -- endtime
    [8] { 10/03/2020 17:09:58 } -- renew-till
    [9] { 'LEIMGRUBE.CH' } -- srealm
    [10] { -- sname
           { [0] { 2 } [1] { { 'krbtgt' 'LEIMGRUBE.CH' } } }
         }
    [11] { -- caddr
           { { [0] { 20 } [1] { 'EC2AMAZ-EHM3HUN ' } } } }
    [12] { -- padata
           { { [1] { 167 } -- PA-PAC-OPTIONS
               [2] { byte[11] => { [0] { 80-00-00-00 } } } }
             { [1] { 165 } -- PA-SUPPORTED-ENCTYPES
               [2] { 1F-00-05-00 }
           } }
         }
  }
}


Decrypting the Ticket EncPart EncryptedData shown above using the password/key of the host/admin account gives:

[APPLICATION 3] { -- EncTicketPart
  {
    [0] { 40-E1-00-00 } -- flags
    [1] { -- key
          { [0] { 18 }
            [1] { 'lsJxd2LXykXBPBZ/jXRjCeJlfIsWanhSWIcs+eoGovk=' } -- base64
          }
        }
    [2] { 'LEIMGRUBE.CH' } -- crealm
    [3] { { [0] { 1 } [1] { { 'Administrator' } } } } -- cname
    [4] { { [0] { 0 } [1] {  } } } -- transited
    [5] { 03/03/2020 17:09:58 } -- authtime
    [6] { 03/03/2020 17:09:58 } -- starttime
    [7] { 04/03/2020 03:09:58 } -- endtime
    [8] { 10/03/2020 17:09:58 } -- renew-till
    [10] { -- authorization-data
           { { [0] { 1 } -- AD-IF-RELEVANT
               [1] { byte[846] => { { [0] { 128 } -- AD-WIN2K-PAC
                                      [1] { byte[824] } } } } }
           } }
  }
}



TGS-REQ

The EncPart of the PA-TGS-REQ ticket is the same as that in the previous section. Decrypting the Authenticator of the PA-TGS-REQ ticket with the session key of the ticket gives:

[APPLICATION 2] { -- Authenticator
  {
    [0] { 5 } -- authenticator-vno
    [1] { 'LEIMGRUBE.CH' } -- crealm
    [2] { { [0] { 1 } [1] { { 'Administrator' } } } } -- cname
    [3] { -- cksum
          { [0] { 7 }
            [1] { BB-FE-FC-99-91-8D-23-CD-F8-01-F6-56-18-E5-AF-2B }
          }
        }
    [4] { 24662 } -- cusec
    [5] { 03/03/2020 17:09:58 } -- ctime
    [6] { -- subkey
          { [0] { 18 }
            [1] { '7YGfZdDpOIgvAS6gx8CkUpwLdRlvp8M+gqz/4c0VKrY=' } -- base64
          }
        }
    [7] { 716117985 } -- seq-number
  }
}

 
Decrypting the EncFastReq element with the new armour key derived from the subkey above and the key from the PA-TGS-REQ ticket gives:

{ --  KrbFastReq
  [0] { 00-00-00-00 } -- fast-options
  [1] { -- padata
        { { [1] { 167 } -- PA-PAC-OPTIONS
            [2] { byte[11] => { [0] { C0-00-00-00 } } } } } }
  [2] { -- req-body
        { [0] { 40-81-00-00 } -- kdc-options
          [2] { 'LEIMGRUBE.CH' } -- realm
          [3] { { [0] { 2 } [1] { { 'host' 'admin' } } } } -- sname
          [5] { 13/09/2037 04:48:05 } -- till
          [7] { 716117985 } -- nonce
          [8] { { 18 17 } } -- etype
          [10] { { [0] { 18 } [2] { byte[136] } } } -- enc-authorization-data
        }
      }
}


The two EncAuthorizationData elements (in the outer ReqBody and the KrbFastReq req-body) are identical. Decrypting with the subkey gives:

{
  { [0] { 1 } -- AD-IF-RELEVANT
    [1] { byte[95] =>
           { { [0] { 141 } -- KERB_AUTH_DATA_TOKEN_RESTRICTIONS
               [1] { byte[53] => { { [0] { 0 } [1] { byte[40] } } } } }
             { [0] { 142 } -- KERB-LOCAL
               [1] { D0-23-FE-DF-21-02-00-00-30-93-AD-23-00-00-00-00 } } } } }
}


TGS-REP


Decrypting the KrbFastArmoredRep EncryptedData using the armour key gives:

{ -- KrbFastResponse
  [0] { -- padata
        {  }
      }
  [1] { -- strengthen-key
        { [0] { 18 }
          [1] { 'cNisWraV1QLW4/WuGrbZTArNS/Yx0VY9FhDI5ohbcRU=' } } -- base64
      }
  [2] { -- finished
        { -- KrbFastFinished
          [0] { 03/03/2020 17:09:58 } -- timestamp
          [1] { 313892 } -- usec
          [2] { 'LEIMGRUBE.CH' } -- crealm
          [3] { { [0] { 1 } [1] { { 'Administrator' } } } } -- cname
          [4] { -- ticket-checksum
                { [0] { 16 } [1] { 21-1F-C3-BC-95-02-B7-6D-D5-3E-8D-01 } }
              }
        }
      }
  [3] { 716117985 } -- nonce
}


Decrypting the Ticket EncPart EncryptedData shown above using the password/key of the host/admin account gives:

[APPLICATION 3] { -- EncTicketPart
  {
    [0] { 40-A1-00-00 } -- flags
    [1] { -- key
          { [0] { 18 }
            [1] { 'mvIE6JfoLACcnJcq+ZDazBstli4/ywWyeJfsKGiuQV0=' } -- base64
          }
        }
    [2] { 'LEIMGRUBE.CH' } -- crealm
    [3] { { [0] { 1 } [1] { { 'Administrator' } } } } -- cname
    [4] { { [0] { 0 } [1] {  } } } -- transited
    [5] { 03/03/2020 17:09:58 } -- authtime
    [6] { 03/03/2020 17:09:58 } -- starttime
    [7] { 04/03/2020 03:09:58 } -- endtime
    [8] { 10/03/2020 17:09:58 } -- renew-till
    [10] { -- authorization-data
           {
             { [0] { 1 } -- AD-IF-RELEVANT
               [1] { byte[846] =>
                    { { [0] { 128 } -- AD-WIN2K-PAC
                        [1] { byte[824] } } } } }
             }
             { [0] { 1 } -- AD-IF-RELEVANT
               [1] { byte[95] =>
                    { { [0] { 141 } -- KERB_AUTH_DATA_TOKEN_RESTRICTIONS
                        [1] { byte[53] => { { [0] { 0 } [1] { byte[40] } } } } }
                      { [0] { 142 } -- KERB-LOCAL
                        [1] { D0-23-FE-DF-21-02-00-00-30-93-AD-23-00-00-00-00 } } } } }
             }
           }
  }
}

 
Decrypting the final EncPart EncryptedData shown above using the subkey from the Authenticator in the TGS-REQ, strengthened with the strengthen-key in the KrbFastResponse gives:

[APPLICATION 26] { -- EncTGSRepPart
  {
    [0] { -- key
          { [0] { 18 }
            [1] { 'mvIE6JfoLACcnJcq+ZDazBstli4/ywWyeJfsKGiuQV0=' } -- base64
          }
        }
    [1] { { { [0] { 0 } [1] { 03/03/2020 17:09:58 } } } } -- last-req
    [2] { 716117985 } -- nonce
    [4] { 40-A1-00-00 } -- flags
    [5] { 03/03/2020 17:09:58 } -- authtime
    [6] { 03/03/2020 17:09:58 } -- starttime
    [7] { 04/03/2020 03:09:58 } -- endtime
    [8] { 10/03/2020 17:09:58 } -- renew-till
    [9] { 'LEIMGRUBE.CH' } -- srealm
    [10] { { [0] { 2 } [1] { { 'host' 'admin' } } } } -- sname
    [12] { -- padata
           { { [1] { 165 } -- PA-SUPPORTED-ENCTYPES
               [2] { 1F-00-00-00 } }
             { [1] { 167 } -- PA-PAC-OPTIONS
               [2] { byte[11] => { [0] { C0-00-00-00 } } }
           } }
         }
  }
}


Summary


I have nothing “profound” to say by way of summary, but I thought a distinct “end-marker” was needed after the last decrypted Kerberos message.

The most interesting part of the encrypted data is possibly the authorization data; if one can obtain a handle to a Windows token created as a result of Kerberos authentication then much of the information can be obtained via the GetTokenInformation and LsaGetLogonSessionData routines.