| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| package rdpclient.ntlmssp; |
| |
| import com.cloud.utils.ConstantTimeComparator; |
| |
| import java.util.Arrays; |
| |
| import rdpclient.ntlmssp.asn1.NegoItem; |
| import rdpclient.ntlmssp.asn1.TSRequest; |
| import rdpclient.rdp.RdpConstants; |
| import streamer.ByteBuffer; |
| import streamer.Element; |
| import streamer.Link; |
| import streamer.OneTimeSwitch; |
| import streamer.Pipeline; |
| import streamer.PipelineImpl; |
| import streamer.debug.MockSink; |
| import streamer.debug.MockSource; |
| |
| /** |
| * @see http://msdn.microsoft.com/en-us/library/cc236642.aspx |
| */ |
| public class ServerNtlmsspChallenge extends OneTimeSwitch implements NtlmConstants { |
| |
| protected NtlmState ntlmState; |
| |
| public ServerNtlmsspChallenge(String id, NtlmState state) { |
| super(id); |
| ntlmState = state; |
| } |
| |
| @Override |
| protected void handleOneTimeData(ByteBuffer buf, Link link) { |
| if (buf == null) |
| return; |
| |
| if (verbose) |
| System.out.println("[" + this + "] INFO: Data received: " + buf + "."); |
| |
| // Extract server challenge, extract server flags. |
| |
| // Parse TSRequest in BER format |
| TSRequest request = new TSRequest("TSRequest"); |
| request.readTag(buf); |
| |
| ByteBuffer negoToken = ((NegoItem)request.negoTokens.tags[0]).negoToken.value; |
| ntlmState.challengeMessage = negoToken.toByteArray(); // Store message for MIC calculation in AUTH message |
| |
| parseNtlmChallenge(negoToken); |
| |
| negoToken.unref(); |
| buf.unref(); |
| switchOff(); |
| } |
| |
| public void parseNtlmChallenge(ByteBuffer buf) { |
| |
| // Signature: "NTLMSSP\0" |
| String signature = buf.readVariableString(RdpConstants.CHARSET_8); |
| if (!ConstantTimeComparator.compareStrings(signature, NTLMSSP)) |
| throw new RuntimeException("Unexpected NTLM message singature: \"" + signature + "\". Expected signature: \"" + NTLMSSP + "\". Data: " + buf + "."); |
| |
| // MessageType (CHALLENGE) |
| int messageType = buf.readSignedIntLE(); |
| if (messageType != NtlmConstants.CHALLENGE) |
| throw new RuntimeException("Unexpected NTLM message type: " + messageType + ". Expected type: CHALLENGE (" + NtlmConstants.CHALLENGE + "). Data: " + buf |
| + "."); |
| |
| // TargetName |
| ntlmState.serverTargetName = readStringByDescription(buf); |
| |
| // NegotiateFlags |
| ntlmState.negotiatedFlags = new NegoFlags(buf.readSignedIntLE()); |
| if (verbose) |
| System.out.println("[" + this + "] INFO: Server negotiate flags: " + ntlmState.negotiatedFlags + "."); |
| |
| // ServerChallenge |
| ByteBuffer challenge = buf.readBytes(8); |
| ntlmState.serverChallenge = challenge.toByteArray(); |
| if (verbose) |
| System.out.println("[" + this + "] INFO: Server challenge: " + challenge + "."); |
| challenge.unref(); |
| |
| // Reserved/context |
| buf.skipBytes(8); |
| |
| // TargetInfo |
| ByteBuffer targetInfo = readBlockByDescription(buf); |
| |
| // Store raw target info block for Type3 message |
| ntlmState.serverTargetInfo = targetInfo.toByteArray(); |
| |
| // Parse target info block |
| parseTargetInfo(targetInfo); |
| targetInfo.unref(); |
| |
| // OS Version, NTLM revision, 8 bytes, Optional. Ignore it. |
| |
| // Ignore rest of buffer with allocated blocks |
| |
| buf.unref(); |
| } |
| |
| public void parseTargetInfo(ByteBuffer buf) { |
| // Parse attribute list |
| |
| while (buf.remainderLength() > 0) { |
| int type = buf.readUnsignedShortLE(); |
| int length = buf.readUnsignedShortLE(); |
| |
| if (type == MSV_AV_EOL) |
| // End of list |
| break; |
| |
| ByteBuffer data = buf.readBytes(length); |
| parseAttribute(data, type, length); |
| data.unref(); |
| } |
| } |
| |
| public void parseAttribute(ByteBuffer buf, int type, int length) { |
| switch (type) { |
| case MSV_AV_NETBIOS_DOMAIN_NAME: |
| ntlmState.serverNetbiosDomainName = buf.readString(length, RdpConstants.CHARSET_16); |
| break; |
| case MSV_AV_NETBIOS_COMPUTER_NAME: |
| ntlmState.serverNetbiosComputerName = buf.readString(length, RdpConstants.CHARSET_16); |
| break; |
| case MSV_AV_DNS_DOMAIN_NAME: |
| ntlmState.serverDnsDomainName = buf.readString(length, RdpConstants.CHARSET_16); |
| break; |
| case MSV_AV_DNS_COMPUTER_NAME: |
| ntlmState.serverDnsComputerName = buf.readString(length, RdpConstants.CHARSET_16); |
| break; |
| case MSV_AV_DNS_TREE_NAME: |
| ntlmState.serverDnsTreeName = buf.readString(length, RdpConstants.CHARSET_16); |
| break; |
| |
| case MSV_AV_TIMESTAMP: |
| ByteBuffer tmp = buf.readBytes(length); |
| ntlmState.serverTimestamp = tmp.toByteArray(); |
| //*DEBUG*/System.out.println("Server timestamp: "+tmp.toPlainHexString()); |
| tmp.unref(); |
| break; |
| |
| default: |
| // Ignore |
| //throw new RuntimeException("[" + this + "] ERROR: Unknown NTLM target info attribute: " + type + ". Data: " + buf + "."); |
| |
| } |
| |
| } |
| |
| /** |
| * Read NTLM wide string, by it description. Buffer offset must point to |
| * beginning of NTLM message signature. |
| * |
| * @param buf |
| * buffer with cursor pointing to description |
| * @return |
| */ |
| public static String readStringByDescription(ByteBuffer buf) { |
| ByteBuffer block = readBlockByDescription(buf); |
| String value = block.readString(block.length, RdpConstants.CHARSET_16); |
| block.unref(); |
| |
| return value; |
| } |
| |
| public static ByteBuffer readBlockByDescription(ByteBuffer buf) { |
| int blockLength = buf.readUnsignedShortLE(); // In bytes |
| int allocatedSpace = buf.readUnsignedShortLE(); |
| int offset = buf.readSignedIntLE(); |
| |
| if (allocatedSpace < blockLength) |
| blockLength = allocatedSpace; |
| |
| if (offset > buf.length || offset < 0 || offset + allocatedSpace > buf.length) |
| throw new RuntimeException("ERROR: NTLM block is too long. Allocated space: " + allocatedSpace + ", block offset: " + offset + ", data: " |
| + buf + "."); |
| |
| // Move cursor to position of allocated block, starting from beginning of |
| // this buffer |
| int storedCursor = buf.cursor; |
| buf.cursor = offset; |
| |
| // Read string |
| ByteBuffer value = buf.readBytes(blockLength); |
| |
| // Restore cursor |
| buf.cursor = storedCursor; |
| |
| return value; |
| } |
| |
| /** |
| * Example. |
| */ |
| public static void main(String args[]) { |
| // System.setProperty("streamer.Link.debug", "true"); |
| System.setProperty("streamer.Element.debug", "true"); |
| // System.setProperty("streamer.Pipeline.debug", "true"); |
| |
| /* @formatter:off */ |
| byte[] packet = new byte[] { |
| 0x30, (byte) 0x82, 0x01, 0x02, // TAG: [UNIVERSAL 16] (constructed) "SEQUENCE" LEN: 258 bytes |
| (byte) 0xa0, 0x03, // TAG: [0] (constructed) LEN: 3 bytes |
| 0x02, 0x01, 0x03, // TAG: [UNIVERSAL 2] (primitive) "INTEGER" LEN: 1 bytes, Version: 0x3 |
| (byte) 0xa1, (byte) 0x81, (byte) 0xfa, // TAG: [1] (constructed) LEN: 250 bytes |
| 0x30, (byte) 0x81, (byte) 0xf7, // TAG: [UNIVERSAL 16] (constructed) "SEQUENCE" LEN: 247 bytes |
| 0x30, (byte) 0x81, (byte) 0xf4, // TAG: [UNIVERSAL 16] (constructed) "SEQUENCE" LEN: 244 bytes |
| (byte) 0xa0, (byte) 0x81, (byte) 0xf1, // TAG: [0] (constructed) LEN: 241 bytes |
| 0x04, (byte) 0x81, (byte) 0xee, // TAG: [UNIVERSAL 4] (primitive) "OCTET STRING" LEN: 238 bytes |
| |
| 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, // "NTLMSSP\0" |
| |
| 0x02, 0x00, 0x00, 0x00, // MessageType (CHALLENGE) |
| 0x1e, 0x00, 0x1e, 0x00, 0x38, 0x00, 0x00, 0x00, // TargetName (length: 30, allocated space: 30, offset: 56) |
| 0x35, (byte) 0x82, (byte) 0x8a, (byte) 0xe2, // NegotiateFlags |
| 0x52, (byte) 0xbe, (byte) 0x83, (byte) 0xd1, (byte) 0xf8, (byte) 0x80, 0x16, 0x6a, // ServerChallenge |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Reserved |
| (byte) 0x98, 0x00, (byte) 0x98, 0x00, 0x56, 0x00, 0x00, 0x00, // TargetInfo (length: 152, allocated space: 152, offset: 86) |
| 0x06, 0x03, (byte) 0xd7, 0x24, 0x00, 0x00, 0x00, 0x0f, // Version (6.3, build 9431) , NTLM current revision: 15 |
| |
| |
| 0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x2d, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x34, 0x00, 0x31, 0x00, 0x39, 0x00, 0x42, 0x00, 0x32, 0x00, 0x4c, 0x00, 0x53, 0x00, 0x52, 0x00, 0x30, 0x00, // Target name value: "WIN-LO419B2LSR0" |
| |
| // Target Info value: |
| |
| // Attribute list |
| |
| 0x02, 0x00, // Item Type: NetBIOS domain name (0x0002, LE) |
| 0x1e, 0x00, // Item Length: 30 (LE) |
| 0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x2d, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x34, 0x00, 0x31, 0x00, 0x39, 0x00, 0x42, 0x00, 0x32, 0x00, 0x4c, 0x00, 0x53, 0x00, 0x52, 0x00, 0x30, 0x00, // "WIN-LO419B2LSR0" |
| |
| 0x01, 0x00, // Item Type: NetBIOS computer name (0x0001, LE) |
| 0x1e, 0x00, // Item Length: 30 (LE) |
| 0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x2d, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x34, 0x00, 0x31, 0x00, 0x39, 0x00, 0x42, 0x00, 0x32, 0x00, 0x4c, 0x00, 0x53, 0x00, 0x52, 0x00, 0x30, 0x00, // "WIN-LO419B2LSR0" |
| |
| 0x04, 0x00, // Item Type: DNS domain name (0x0004, LE) |
| 0x1e, 0x00, // Item Length: 30 (LE) |
| 0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x2d, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x34, 0x00, 0x31, 0x00, 0x39, 0x00, 0x42, 0x00, 0x32, 0x00, 0x4c, 0x00, 0x53, 0x00, 0x52, 0x00, 0x30, 0x00, // "WIN-LO419B2LSR0" |
| |
| 0x03, 0x00, // Item Type: DNS computer name (0x0003, LE) |
| 0x1e, 0x00, // Item Length: 30 (LE) |
| 0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x2d, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x34, 0x00, 0x31, 0x00, 0x39, 0x00, 0x42, 0x00, 0x32, 0x00, 0x4c, 0x00, 0x53, 0x00, 0x52, 0x00, 0x30, 0x00, // "WIN-LO419B2LSR0" |
| |
| 0x07, 0x00, // Item Type: Timestamp (0x0007, LE) |
| 0x08, 0x00, // Item Length: 8 (LE) |
| (byte) 0x99, 0x4f, 0x02, (byte) 0xd8, (byte) 0xf4, (byte) 0xaf, (byte) 0xce, 0x01, // TODO |
| |
| // Attribute: End of list |
| 0x00, 0x00, |
| 0x00, 0x00, |
| }; |
| /* @formatter:on */ |
| |
| MockSource source = new MockSource("source", ByteBuffer.convertByteArraysToByteBuffers(packet, new byte[] {1, 2, 3})); |
| NtlmState state = new NtlmState(); |
| Element ntlmssp_challenge = new ServerNtlmsspChallenge("ntlmssp_challenge", state); |
| Element sink = new MockSink("sink", ByteBuffer.convertByteArraysToByteBuffers()); |
| Element mainSink = new MockSink("mainSink", ByteBuffer.convertByteArraysToByteBuffers(new byte[] {1, 2, 3})); |
| |
| Pipeline pipeline = new PipelineImpl("test"); |
| pipeline.add(source, ntlmssp_challenge, sink, mainSink); |
| pipeline.link("source", "ntlmssp_challenge", "mainSink"); |
| pipeline.link("ntlmssp_challenge >" + OTOUT, "sink"); |
| pipeline.runMainLoop("source", STDOUT, false, false); |
| |
| // Check state challenge |
| byte[] challenge = new byte[] {0x52, (byte)0xbe, (byte)0x83, (byte)0xd1, (byte)0xf8, (byte)0x80, 0x16, 0x6a}; |
| if (state.serverChallenge == null) |
| throw new RuntimeException("Challenge was not extracted from server NTLMSSP Challenge packet."); |
| if (!Arrays.equals(challenge, state.serverChallenge)) |
| throw new RuntimeException("Challenge was extracted from server NTLMSSP Challenge packet is not equal to expected. Actual value: " |
| + state.serverChallenge + ", expected value: " + challenge + "."); |
| |
| } |
| |
| } |