blob: d8cfb4c959413c94d33e3008757779324ce8c536 [file] [log] [blame]
// 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 + ".");
}
}