Skip to content
Open
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,9 @@ private static unsafe void CreateNtlmNegotiateMessage(Span<byte> asBytes, Flags
asBytes.Clear();
NtlmHeader.CopyTo(asBytes);
message.Header.MessageType = MessageType.Negotiate;
message.Flags = requiredFlags;
message.Flags = BitConverter.IsLittleEndian ? requiredFlags : (Flags)BinaryPrimitives.ReverseEndianness((uint)requiredFlags);
Copy link
Member

@rzikm rzikm Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not a big fan of those manual branches. For one, when you look at message.Flags using a debugger, the value will be misrepresented on BE systems, and if you miss one place, you end up with hard to debug bugs. Other questions arise from looking at the code like why do we reverse some properties and not others....

Can we modify the definition of NegotiateMessage to have setter/getter of those properties handle the endianness difference transparently to the caller?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of the fields that are set for NegotiateMessage, message.Header.MessageType is a byte and is represented the same in little-endian and big-endian. Flags is an uint and needs to be endian specific. Version is a struct with ProductBuild as ushort and the rest are bytes, and hence only ProductBuild needs to be reversed. I will implement it using getters and setters.

message.Version = s_version;
if (!BitConverter.IsLittleEndian) message.Version.ProductBuild = (ushort)BinaryPrimitives.ReverseEndianness((ushort)message.Version.ProductBuild);
}

private static unsafe int GetFieldLength(MessageField field)
Expand Down Expand Up @@ -486,7 +487,7 @@ private byte[] ProcessTargetInfo(ReadOnlySpan<byte> targetInfo, out DateTime tim

if (ID == AvId.Timestamp)
{
time = DateTime.FromFileTimeUtc(BitConverter.ToInt64(info.Slice(4, 8)));
time = DateTime.FromFileTimeUtc(BinaryPrimitives.ReadInt64LittleEndian(info.Slice(4, 8)));
}
else if (ID == AvId.TargetName || ID == AvId.ChannelBindings)
{
Expand Down Expand Up @@ -628,8 +629,9 @@ private static byte[] DeriveKey(ReadOnlySpan<byte> exportedSessionKey, ReadOnlyS
NtlmHeader.CopyTo(responseAsSpan);

response.Header.MessageType = MessageType.Authenticate;
response.Flags = s_requiredFlags | (flags & Flags.NegotiateSeal);
response.Flags = BitConverter.IsLittleEndian ? (s_requiredFlags | (flags & Flags.NegotiateSeal)) : (Flags)BinaryPrimitives.ReverseEndianness((uint)(s_requiredFlags | (flags & Flags.NegotiateSeal)));;
response.Version = s_version;
if (!BitConverter.IsLittleEndian) response.Version.ProductBuild = BinaryPrimitives.ReverseEndianness((ushort)response.Version.ProductBuild);

// Calculate hash for hmac - same for lm2 and ntlm2
Span<byte> ntlm2hash = stackalloc byte[DigestLength];
Expand Down Expand Up @@ -662,7 +664,8 @@ private static byte[] DeriveKey(ReadOnlySpan<byte> exportedSessionKey, ReadOnlyS

// Derive session base key
Span<byte> sessionBaseKey = stackalloc byte[HMACMD5.HashSizeInBytes];
int sessionKeyWritten = HMACMD5.HashData(ntlm2hash, responseAsSpan.Slice(response.NtChallengeResponse.PayloadOffset, 16), sessionBaseKey);
var payloadSlice = (BitConverter.IsLittleEndian) ? responseAsSpan.Slice(response.NtChallengeResponse.PayloadOffset, 16) : responseAsSpan.Slice(BinaryPrimitives.ReverseEndianness(response.NtChallengeResponse.PayloadOffset), 16);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably get correct PayloadOffset value first instead of duplicating the whole Slice block. But that is mostly style and readability preference.

int sessionKeyWritten = HMACMD5.HashData(ntlm2hash, payloadSlice, sessionBaseKey);
Debug.Assert(sessionKeyWritten == HMACMD5.HashSizeInBytes);

// Encrypt exportedSessionKey with sessionBaseKey
Expand Down
Loading