Skip to content

Fix NTLMv2Response so it can carry a NetNTLMv2 response (Refs #1068) - #1071

Merged
p0dalirius merged 1 commit into
mainfrom
enhancement-netntlmv2-response
Aug 25, 2026
Merged

Fix NTLMv2Response so it can carry a NetNTLMv2 response (Refs #1068)#1071
p0dalirius merged 1 commit into
mainfrom
enhancement-netntlmv2-response

Conversation

@p0dalirius

Copy link
Copy Markdown
Contributor

Summary

Commit 3 of 12 for #1068. Independent of the other commits — it touches only crypto/ntlmv1 and crypto/ntlmv2, so it targets main directly and can merge in any order relative to #1069 and #1070.

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 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:

Mode Layout
5500 (NetNTLMv1) user::domain:LM:NT:challenge — challenge last
5600 (NetNTLMv2) user::domain:challenge:NTProofStr:blob — challenge before the proof

The old v2 renderer produced user::domain:challenge:LM:NT — the challenge in the v2 position, the trailing fields in the v1 sense.

Changes

  • 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 mode-5600 layout, and returns an error for a response that cannot form one rather than emitting a malformed line.
  • HashcatMode records 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/ntlmv1 needed no correction: it already put the challenge last, 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 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 ./... and go test ./... green across the repository.

  • Reproduces the published mode-5600 example hash byte for byte, which pins both field order and field contents.
  • Renders a response computed from the official [MS-NLMP] 4.2.4 worked example and checks the NTProofStr field against the published 68cd0ab851e51c96aabc927bebef6a1c, with NTProofStr and blob reassembling to the whole response.
  • Field count and the fixed field widths (16 hex chars of challenge, 32 of NTProofStr).
  • A realistic variable-length response is preserved whole — the case the old fixed array could not represent.
  • The constructor does not alias the caller's slice.
  • Rejects a nil, empty, one-byte-short, and exactly-the-NTProofStr response, with String() returning empty rather than a malformed line.
  • The v1 side: the published mode-5500 example, asserting the challenge is the last field.

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.

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.
@p0dalirius
p0dalirius merged commit 53fb9f2 into main Aug 25, 2026
5 checks passed
@p0dalirius
p0dalirius deleted the enhancement-netntlmv2-response branch August 25, 2026 10:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant