mirror of
https://github.com/TalAloni/SMBLibrary.git
synced 2025-05-17 01:47:09 +02:00
492 lines
20 KiB
C#
492 lines
20 KiB
C#
/* Copyright (C) 2017 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
|
|
*
|
|
* You can redistribute this program and/or modify it under the terms of
|
|
* the GNU Lesser Public License as published by the Free Software Foundation,
|
|
* either version 3 of the License, or (at your option) any later version.
|
|
*/
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Security.Cryptography;
|
|
using Utilities;
|
|
|
|
namespace SMBLibrary.SMB2
|
|
{
|
|
public abstract class SMB2Command
|
|
{
|
|
public SMB2Header Header;
|
|
|
|
public SMB2Command(SMB2CommandName commandName)
|
|
{
|
|
Header = new SMB2Header(commandName);
|
|
}
|
|
|
|
public SMB2Command(byte[] buffer, int offset)
|
|
{
|
|
Header = new SMB2Header(buffer, offset);
|
|
}
|
|
|
|
public void WriteBytes(byte[] buffer, int offset)
|
|
{
|
|
Header.WriteBytes(buffer, offset);
|
|
WriteCommandBytes(buffer, offset + SMB2Header.Length);
|
|
}
|
|
|
|
public abstract void WriteCommandBytes(byte[] buffer, int offset);
|
|
|
|
public byte[] GetBytes()
|
|
{
|
|
byte[] buffer = new byte[this.Length];
|
|
WriteBytes(buffer, 0);
|
|
return buffer;
|
|
}
|
|
|
|
public SMB2CommandName CommandName
|
|
{
|
|
get
|
|
{
|
|
return Header.Command;
|
|
}
|
|
}
|
|
|
|
public int Length
|
|
{
|
|
get
|
|
{
|
|
return SMB2Header.Length + CommandLength;
|
|
}
|
|
}
|
|
|
|
public abstract int CommandLength
|
|
{
|
|
get;
|
|
}
|
|
|
|
public static SMB2Command ReadRequest(byte[] buffer, int offset)
|
|
{
|
|
SMB2CommandName commandName = (SMB2CommandName)LittleEndianConverter.ToUInt16(buffer, offset + 12);
|
|
switch (commandName)
|
|
{
|
|
case SMB2CommandName.Negotiate:
|
|
return new NegotiateRequest(buffer, offset);
|
|
case SMB2CommandName.SessionSetup:
|
|
return new SessionSetupRequest(buffer, offset);
|
|
case SMB2CommandName.Logoff:
|
|
return new LogoffRequest(buffer, offset);
|
|
case SMB2CommandName.TreeConnect:
|
|
return new TreeConnectRequest(buffer, offset);
|
|
case SMB2CommandName.TreeDisconnect:
|
|
return new TreeDisconnectRequest(buffer, offset);
|
|
case SMB2CommandName.Create:
|
|
return new CreateRequest(buffer, offset);
|
|
case SMB2CommandName.Close:
|
|
return new CloseRequest(buffer, offset);
|
|
case SMB2CommandName.Flush:
|
|
return new FlushRequest(buffer, offset);
|
|
case SMB2CommandName.Read:
|
|
return new ReadRequest(buffer, offset);
|
|
case SMB2CommandName.Write:
|
|
return new WriteRequest(buffer, offset);
|
|
case SMB2CommandName.Lock:
|
|
return new LockRequest(buffer, offset);
|
|
case SMB2CommandName.IOCtl:
|
|
return new IOCtlRequest(buffer, offset);
|
|
case SMB2CommandName.Cancel:
|
|
return new CancelRequest(buffer, offset);
|
|
case SMB2CommandName.Echo:
|
|
return new EchoRequest(buffer, offset);
|
|
case SMB2CommandName.QueryDirectory:
|
|
return new QueryDirectoryRequest(buffer, offset);
|
|
case SMB2CommandName.ChangeNotify:
|
|
return new ChangeNotifyRequest(buffer, offset);
|
|
case SMB2CommandName.QueryInfo:
|
|
return new QueryInfoRequest(buffer, offset);
|
|
case SMB2CommandName.SetInfo:
|
|
return new SetInfoRequest(buffer, offset);
|
|
default:
|
|
throw new InvalidDataException("Invalid SMB2 command 0x" + ((ushort)commandName).ToString("X4"));
|
|
}
|
|
}
|
|
|
|
public static List<SMB2Command> ReadRequestChain(byte[] buffer, int offset)
|
|
{
|
|
List<SMB2Command> result = new List<SMB2Command>();
|
|
SMB2Command command;
|
|
do
|
|
{
|
|
command = ReadRequest(buffer, offset);
|
|
result.Add(command);
|
|
offset += (int)command.Header.NextCommand;
|
|
}
|
|
while (command.Header.NextCommand != 0);
|
|
return result;
|
|
}
|
|
|
|
public static byte[] GetCommandChainBytes(List<SMB2Command> commands)
|
|
{
|
|
return GetCommandChainBytes(commands, null);
|
|
}
|
|
|
|
/// <param name="sessionKey">
|
|
/// command will be signed using this key if (not null and) SMB2_FLAGS_SIGNED is set.
|
|
/// </param>
|
|
public static byte[] GetCommandChainBytes(List<SMB2Command> commands, byte[] sessionKey)
|
|
{
|
|
int totalLength = 0;
|
|
for (int index = 0; index < commands.Count; index++)
|
|
{
|
|
// Any subsequent SMB2 header MUST be 8-byte aligned
|
|
int length = commands[index].Length;
|
|
if (index < commands.Count - 1)
|
|
{
|
|
int paddedLength = (int)Math.Ceiling((double)length / 8) * 8;
|
|
totalLength += paddedLength;
|
|
}
|
|
else
|
|
{
|
|
totalLength += length;
|
|
}
|
|
}
|
|
byte[] buffer = new byte[totalLength];
|
|
int offset = 0;
|
|
for (int index = 0; index < commands.Count; index++)
|
|
{
|
|
SMB2Command command = commands[index];
|
|
int commandLength = command.Length;
|
|
int paddedLength;
|
|
if (index < commands.Count - 1)
|
|
{
|
|
paddedLength = (int)Math.Ceiling((double)commandLength / 8) * 8;
|
|
command.Header.NextCommand = (uint)paddedLength;
|
|
}
|
|
else
|
|
{
|
|
paddedLength = commandLength;
|
|
}
|
|
command.WriteBytes(buffer, offset);
|
|
if (command.Header.IsSigned && sessionKey != null)
|
|
{
|
|
// [MS-SMB2] Any padding at the end of the message MUST be used in the hash computation.
|
|
byte[] signature = new HMACSHA256(sessionKey).ComputeHash(buffer, offset, paddedLength);
|
|
// [MS-SMB2] The first 16 bytes of the hash MUST be copied into the 16-byte signature field of the SMB2 Header.
|
|
ByteWriter.WriteBytes(buffer, offset + SMB2Header.SignatureOffset, signature, 16);
|
|
}
|
|
offset += paddedLength;
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
public static SMB2Command ReadResponse(byte[] buffer, int offset)
|
|
{
|
|
SMB2CommandName commandName = (SMB2CommandName)LittleEndianConverter.ToUInt16(buffer, offset + 12);
|
|
ushort structureSize = LittleEndianConverter.ToUInt16(buffer, offset + SMB2Header.Length + 0);
|
|
switch (commandName)
|
|
{
|
|
case SMB2CommandName.Negotiate:
|
|
{
|
|
if (structureSize == NegotiateResponse.DeclaredSize)
|
|
{
|
|
return new NegotiateResponse(buffer, offset);
|
|
}
|
|
else if (structureSize == ErrorResponse.DeclaredSize)
|
|
{
|
|
return new ErrorResponse(buffer, offset);
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
}
|
|
case SMB2CommandName.SessionSetup:
|
|
{
|
|
// SESSION_SETUP Response and ERROR Response have the same declared StructureSize of 9.
|
|
if (structureSize == SessionSetupResponse.DeclaredSize)
|
|
{
|
|
NTStatus status = (NTStatus)LittleEndianConverter.ToUInt32(buffer, offset + 8);
|
|
if (status == NTStatus.STATUS_SUCCESS || status == NTStatus.STATUS_MORE_PROCESSING_REQUIRED)
|
|
{
|
|
return new SessionSetupResponse(buffer, offset);
|
|
}
|
|
else
|
|
{
|
|
return new ErrorResponse(buffer, offset);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
}
|
|
case SMB2CommandName.Logoff:
|
|
{
|
|
if (structureSize == LogoffResponse.DeclaredSize)
|
|
{
|
|
return new LogoffResponse(buffer, offset);
|
|
}
|
|
else if (structureSize == ErrorResponse.DeclaredSize)
|
|
{
|
|
return new ErrorResponse(buffer, offset);
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
}
|
|
case SMB2CommandName.TreeConnect:
|
|
{
|
|
if (structureSize == TreeConnectResponse.DeclaredSize)
|
|
{
|
|
return new TreeConnectResponse(buffer, offset);
|
|
}
|
|
else if (structureSize == ErrorResponse.DeclaredSize)
|
|
{
|
|
return new ErrorResponse(buffer, offset);
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
}
|
|
case SMB2CommandName.TreeDisconnect:
|
|
{
|
|
if (structureSize == TreeDisconnectResponse.DeclaredSize)
|
|
{
|
|
return new TreeDisconnectResponse(buffer, offset);
|
|
}
|
|
else if (structureSize == ErrorResponse.DeclaredSize)
|
|
{
|
|
return new ErrorResponse(buffer, offset);
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
}
|
|
case SMB2CommandName.Create:
|
|
{
|
|
if (structureSize == CreateResponse.DeclaredSize)
|
|
{
|
|
return new CreateResponse(buffer, offset);
|
|
}
|
|
else if (structureSize == ErrorResponse.DeclaredSize)
|
|
{
|
|
return new ErrorResponse(buffer, offset);
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
}
|
|
case SMB2CommandName.Close:
|
|
{
|
|
if (structureSize == CloseResponse.DeclaredSize)
|
|
{
|
|
return new CloseResponse(buffer, offset);
|
|
}
|
|
else if (structureSize == ErrorResponse.DeclaredSize)
|
|
{
|
|
return new ErrorResponse(buffer, offset);
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
}
|
|
case SMB2CommandName.Flush:
|
|
{
|
|
if (structureSize == FlushResponse.DeclaredSize)
|
|
{
|
|
return new FlushResponse(buffer, offset);
|
|
}
|
|
else if (structureSize == ErrorResponse.DeclaredSize)
|
|
{
|
|
return new ErrorResponse(buffer, offset);
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
}
|
|
case SMB2CommandName.Read:
|
|
{
|
|
if (structureSize == SMB2.ReadResponse.DeclaredSize)
|
|
{
|
|
return new ReadResponse(buffer, offset);
|
|
}
|
|
else if (structureSize == ErrorResponse.DeclaredSize)
|
|
{
|
|
return new ErrorResponse(buffer, offset);
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
}
|
|
case SMB2CommandName.Write:
|
|
{
|
|
if (structureSize == WriteResponse.DeclaredSize)
|
|
{
|
|
return new WriteResponse(buffer, offset);
|
|
}
|
|
else if (structureSize == ErrorResponse.DeclaredSize)
|
|
{
|
|
return new ErrorResponse(buffer, offset);
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
}
|
|
case SMB2CommandName.Lock:
|
|
{
|
|
if (structureSize == LockResponse.DeclaredSize)
|
|
{
|
|
return new LockResponse(buffer, offset);
|
|
}
|
|
else if (structureSize == ErrorResponse.DeclaredSize)
|
|
{
|
|
return new ErrorResponse(buffer, offset);
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
}
|
|
case SMB2CommandName.IOCtl:
|
|
{
|
|
if (structureSize == IOCtlResponse.DeclaredSize)
|
|
{
|
|
return new IOCtlResponse(buffer, offset);
|
|
}
|
|
else if (structureSize == ErrorResponse.DeclaredSize)
|
|
{
|
|
return new ErrorResponse(buffer, offset);
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
}
|
|
case SMB2CommandName.Cancel:
|
|
{
|
|
if (structureSize == ErrorResponse.DeclaredSize)
|
|
{
|
|
return new ErrorResponse(buffer, offset);
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
}
|
|
case SMB2CommandName.Echo:
|
|
{
|
|
if (structureSize == EchoResponse.DeclaredSize)
|
|
{
|
|
return new EchoResponse(buffer, offset);
|
|
}
|
|
else if (structureSize == ErrorResponse.DeclaredSize)
|
|
{
|
|
return new ErrorResponse(buffer, offset);
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
}
|
|
case SMB2CommandName.QueryDirectory:
|
|
{
|
|
// QUERY_DIRECTORY Response and ERROR Response have the same declared StructureSize of 9.
|
|
if (structureSize == QueryDirectoryResponse.DeclaredSize)
|
|
{
|
|
NTStatus status = (NTStatus)LittleEndianConverter.ToUInt32(buffer, offset + 8);
|
|
if (status == NTStatus.STATUS_SUCCESS)
|
|
{
|
|
return new QueryDirectoryResponse(buffer, offset);
|
|
}
|
|
else
|
|
{
|
|
return new ErrorResponse(buffer, offset);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
}
|
|
case SMB2CommandName.ChangeNotify:
|
|
{
|
|
// CHANGE_NOTIFY Response and ERROR Response have the same declared StructureSize of 9.
|
|
if (structureSize == ChangeNotifyResponse.DeclaredSize)
|
|
{
|
|
NTStatus status = (NTStatus)LittleEndianConverter.ToUInt32(buffer, offset + 8);
|
|
if (status == NTStatus.STATUS_SUCCESS ||
|
|
status == NTStatus.STATUS_NOTIFY_CLEANUP ||
|
|
status == NTStatus.STATUS_NOTIFY_ENUM_DIR)
|
|
{
|
|
return new ChangeNotifyResponse(buffer, offset);
|
|
}
|
|
else
|
|
{
|
|
return new ErrorResponse(buffer, offset);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
}
|
|
case SMB2CommandName.QueryInfo:
|
|
{
|
|
// QUERY_INFO Response and ERROR Response have the same declared StructureSize of 9.
|
|
if (structureSize == QueryInfoResponse.DeclaredSize)
|
|
{
|
|
NTStatus status = (NTStatus)LittleEndianConverter.ToUInt32(buffer, offset + 8);
|
|
if (status == NTStatus.STATUS_SUCCESS)
|
|
{
|
|
return new QueryInfoResponse(buffer, offset);
|
|
}
|
|
else
|
|
{
|
|
return new ErrorResponse(buffer, offset);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
}
|
|
case SMB2CommandName.SetInfo:
|
|
{
|
|
if (structureSize == SetInfoResponse.DeclaredSize)
|
|
{
|
|
return new SetInfoResponse(buffer, offset);
|
|
}
|
|
else if (structureSize == ErrorResponse.DeclaredSize)
|
|
{
|
|
return new ErrorResponse(buffer, offset);
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException();
|
|
}
|
|
}
|
|
default:
|
|
throw new InvalidDataException("Invalid SMB2 command 0x" + ((ushort)commandName).ToString("X4"));
|
|
}
|
|
}
|
|
|
|
public static List<SMB2Command> ReadResponseChain(byte[] buffer, int offset)
|
|
{
|
|
List<SMB2Command> result = new List<SMB2Command>();
|
|
SMB2Command command;
|
|
do
|
|
{
|
|
command = ReadResponse(buffer, offset);
|
|
result.Add(command);
|
|
offset += (int)command.Header.NextCommand;
|
|
}
|
|
while (command.Header.NextCommand != 0);
|
|
return result;
|
|
}
|
|
}
|
|
}
|