Fix NTLMv2Response so it can carry a NetNTLMv2 response (Refs #1068) - #1071
Merged
Conversation
NTLMv2Response.NtChallengeResponse was a fixed [24]byte, but an NTLMv2 NT challenge response is NTProofStr(16) || blob(variable) — exactly what NTLMv2Ctx.ComputeNTChallengeResponse in this same package returns — where the blob carries the response type, a timestamp, the client challenge and the server's TargetInfo. A real response runs from roughly 44 bytes to a few hundred, so the field could not represent one at all. HashcatString compounded it by emitting the server challenge followed by the LM and NT responses. That is neither the mode-5600 layout that NetNTLMv2 uses nor the mode-5500 layout that NetNTLMv1 uses, so nothing the type produced for an NTLMv2 capture was crackable. - NtChallengeResponse becomes []byte, and the constructor copies it so a caller reusing a receive buffer cannot rewrite a response already recorded. - NTProofStr and Blob split the response, reporting absence rather than slicing past the end of a short buffer. - HashcatString emits the documented mode-5600 layout, username::domain:serverchallenge:ntproofstr:blob, and returns an error for a response that cannot form one instead of emitting a malformed line. - HashcatMode records the mode number in each package, so a caller writing captured material to a file labels it correctly rather than hardcoding a number at the call site. The two packages render different layouts and the distinction is easy to lose. Nothing outside crypto/ntlmv1 and crypto/ntlmv2 referenced these types, so the signature change breaks no caller. crypto/ntlmv1 needed no correction: it already put the server challenge last, as mode 5500 requires, and ntlmv1_ctx_test.go already asserted that layout against a computed expectation. Added alongside it are a pin to the published mode-5500 example hash and a guard on the new mode constant, since the v1 and v2 layouts differing in exactly this way is what the original defect came from. Tests reproduce the published mode-5600 example hash byte for byte, which pins both field order and field contents; render a response computed from the official [MS-NLMP] 4.2.4 worked example and check the NTProofStr field against the published 68cd0ab851e51c96aabc927bebef6a1c, with NTProofStr and blob reassembling to the whole response; assert the field count and the fixed field widths; assert a realistic variable-length response is preserved whole, which the old fixed array could not do; assert the constructor does not alias the caller's slice; and reject a nil, empty, one-byte-short, and exactly-the-NTProofStr response.
This was referenced Aug 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Commit 3 of 12 for #1068. Independent of the other commits — it touches only
crypto/ntlmv1andcrypto/ntlmv2, so it targetsmaindirectly and can merge in any order relative to #1069 and #1070.NTLMv2Response.NtChallengeResponsewas a fixed[24]byte, but an NTLMv2 NT challenge response isNTProofStr(16) || blob(variable)— exactly whatNTLMv2Ctx.ComputeNTChallengeResponsein this same package returns, where the blob carries the response type, a timestamp, the client challenge and the server's TargetInfo. A real response runs from roughly 44 bytes to a few hundred, so the field could not represent one at all.HashcatStringcompounded it by emitting the server challenge followed by the LM and NT responses. That is neither the mode-5600 layout NetNTLMv2 uses nor the mode-5500 layout NetNTLMv1 uses, so nothing the type produced for an NTLMv2 capture was crackable.The two layouts
Verified against the published example hashes:
user::domain:LM:NT:challenge— challenge lastuser::domain:challenge:NTProofStr:blob— challenge before the proofThe old v2 renderer produced
user::domain:challenge:LM:NT— the challenge in the v2 position, the trailing fields in the v1 sense.Changes
NtChallengeResponsebecomes[]byte, and the constructor copies it, so a caller reusing a receive buffer cannot rewrite a response already recorded.NTProofStr()andBlob()split the response, reporting absence rather than slicing past the end of a short buffer.HashcatStringemits the mode-5600 layout, and returns an error for a response that cannot form one rather than emitting a malformed line.HashcatModerecords the mode number in each package (5500 / 5600), so a caller writing captured material to a file labels it correctly instead of hardcoding a number at the call site.Nothing outside these two packages referenced the types, so the signature change breaks no caller.
crypto/ntlmv1needed no correction: it already put the challenge last, andntlmv1_ctx_test.goalready asserted that layout against a computed expectation. Added alongside it are a pin to the published mode-5500 example and a guard on the new constant — the v1/v2 layouts differing in exactly this way is where the original defect came from, so both sides are now pinned to the published references.Testing
go build ./...,go vet ./...andgo test ./...green across the repository.68cd0ab851e51c96aabc927bebef6a1c, with NTProofStr and blob reassembling to the whole response.String()returning empty rather than a malformed line.Notes
This is a prerequisite for the credential-capture handler in commit 5: capture is the reason the type exists, and until this fix the captured material was unusable.