-
Notifications
You must be signed in to change notification settings - Fork 19
010: Sending A Packet
We are going to start by creating our first packet class, KexInit. This class will inherit from Packet. Create this class next to the Packet class. And since every packet needs a packet type, we need to create a PacketType enum with the values from RFC 4250.
public enum PacketType : byte { SSH_MSG_DISCONNECT = 1, SSH_MSG_IGNORE = 2, SSH_MSG_UNIMPLEMENTED = 3, SSH_MSG_DEBUG = 4, SSH_MSG_SERVICE_REQUEST = 5, SSH_MSG_SERVICE_ACCEPT = 6, SSH_MSG_KEXINIT = 20, SSH_MSG_NEWKEYS = 21, SSH_MSG_USERAUTH_REQUEST = 50, SSH_MSG_USERAUTH_FAILURE = 51, SSH_MSG_USERAUTH_SUCCESS = 52, SSH_MSG_USERAUTH_BANNER = 53, SSH_MSG_GLOBAL_REQUEST = 80, SSH_MSG_REQUEST_SUCCESS = 81, SSH_MSG_REQUEST_FAILURE = 82, SSH_MSG_CHANNEL_OPEN = 90, SSH_MSG_CHANNEL_OPEN_CONFIRMATION = 91, SSH_MSG_CHANNEL_OPEN_FAILURE = 92, SSH_MSG_CHANNEL_WINDOW_ADJUST = 93, SSH_MSG_CHANNEL_DATA = 94, SSH_MSG_CHANNEL_EXTENDED_DATA = 95, SSH_MSG_CHANNEL_EOF = 96, SSH_MSG_CHANNEL_CLOSE = 97, SSH_MSG_CHANNEL_REQUEST = 98, SSH_MSG_CHANNEL_SUCCESS = 99, SSH_MSG_CHANNEL_FAILURE = 100, }
Then we need to add the PacketType abstract property to the Packet class to force all type that inherit this to specify their PacketType:
public abstract class Packet { ... public abstract PacketType PacketType { get; } ... }
Finally, add the override to the KexInit class, which now looks like this:
public class KexInit : Packet { public override PacketType PacketType { get { return PacketType.SSH_MSG_KEXINIT; } } }
The next order of business is adding the ByteWriter we'll use to convert a packet into a byte[]: (sorry for the huge block of code)
public class ByteWriter : IDisposable { private MemoryStream m_Stream = new MemoryStream(); public void WritePacketType(PacketType packetType) { WriteByte((byte)packetType); } public void WriteBytes(byte[] value) { WriteUInt32((uint)value.Count()); WriteRawBytes(value); } public void WriteString(string value) { WriteString(value, Encoding.ASCII); } public void WriteString(string value, Encoding encoding) { WriteBytes(encoding.GetBytes(value)); } public void WriteStringList(IEnumerable list) { WriteString(string.Join(",", list)); } public void WriteUInt32(uint value) { byte[] data = BitConverter.GetBytes(value); if (BitConverter.IsLittleEndian) data = data.Reverse().ToArray(); WriteRawBytes(data); } public void WriteMPInt(byte[] value) { if ((value.Length == 1) && (value[0] == 0)) { WriteUInt32(0); return; } uint length = (uint)value.Length; if (((value[0] & 0x80) != 0)) { WriteUInt32((uint)(length + 1)); WriteByte(0x00); } else { WriteUInt32((uint)length); } WriteRawBytes(value); } public void WriteRawBytes(byte[] value) { if (disposedValue) throw new ObjectDisposedException("ByteWriter"); m_Stream.Write(value, 0, value.Count()); } public void WriteByte(byte value) { if (disposedValue) throw new ObjectDisposedException("ByteWriter"); m_Stream.WriteByte(value); } public byte[] ToByteArray() { if (disposedValue) throw new ObjectDisposedException("ByteWriter"); return m_Stream.ToArray(); } #region IDisposable Support private bool disposedValue = false; // To detect redundant calls protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { m_Stream.Dispose(); m_Stream = null; } disposedValue = true; } } public void Dispose() { // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(true); } #endregion }
Okay, we are now ready to add a way to read and write packets. We will add ToByteArray() method on packet that will be used to turn a packet into a byte[], later this will do all of the compression and encoding for us. We will also add GetBytes() method to just build the payload. Finally, we'll add Load() and InternalGetBytes() abstract methods which must be implemented on all classes that inherit from Packet.
public byte[] ToByteArray(SSHClient client, ExchangeContext context) { this.PacketSequence = client.GetSentPacketNumber(); byte[] payload = context.CompressionServerToClient.Compress(GetBytes()); uint blockSize = context.CipherServerToClient.BlockSize; byte paddingLength = (byte)(blockSize - (payload.Length + 5) % blockSize); if (paddingLength < 4) paddingLength += (byte)blockSize; byte[] padding = new byte[paddingLength]; RandomNumberGenerator.Create().GetBytes(padding); uint packetLength = (uint)(payload.Length + paddingLength + 1); using (ByteWriter writer = new ByteWriter()) { writer.WriteUInt32(packetLength); writer.WriteByte(paddingLength); writer.WriteRawBytes(payload); writer.WriteRawBytes(padding); payload = writer.ToByteArray(); } byte[] encryptedPayload = context.CipherServerToClient.Encrypt(payload); if (context.MACAlgorithmServerToClient != null) { byte[] mac = context.MACAlgorithmServerToClient.ComputeHash(this.PacketSequence, payload); return encryptedPayload.Concat(mac).ToArray(); } return encryptedPayload; } public byte[] GetBytes() { using (ByteWriter writer = new ByteWriter()) { writer.WritePacketType(PacketType); InternalGetBytes(writer); return writer.ToByteArray(); } } protected abstract void Load(ByteReader reader); protected abstract void InternalGetBytes(ByteWriter writer);
Now we are getting somewhere! But there is still more code to write! Now we need to implement the abstract methods on KexInit! We also added all of the properties of the packet:
public class KexInit : Packet { public override PacketType PacketType { get { return PacketType.SSH_MSG_KEXINIT; } } public byte[] Cookie { get; set; } = new byte[16]; public List KexAlgorithms { get; private set; } = new List(); public List ServerHostKeyAlgorithms { get; private set; } = new List(); public List EncryptionAlgorithmsClientToServer { get; private set; } = new List(); public List EncryptionAlgorithmsServerToClient { get; private set; } = new List(); public List MacAlgorithmsClientToServer { get; private set; } = new List(); public List MacAlgorithmsServerToClient { get; private set; } = new List(); public List CompressionAlgorithmsClientToServer { get; private set; } = new List(); public List CompressionAlgorithmsServerToClient { get; private set; } = new List(); public List LanguagesClientToServer { get; private set; } = new List(); public List LanguagesServerToClient { get; private set; } = new List(); public bool FirstKexPacketFollows { get; set; } public KexInit() { RandomNumberGenerator.Create().GetBytes(Cookie); } protected override void InternalGetBytes(ByteWriter writer) { writer.WriteRawBytes(Cookie); writer.WriteStringList(KexAlgorithms); writer.WriteStringList(ServerHostKeyAlgorithms); writer.WriteStringList(EncryptionAlgorithmsClientToServer); writer.WriteStringList(EncryptionAlgorithmsServerToClient); writer.WriteStringList(MacAlgorithmsClientToServer); writer.WriteStringList(MacAlgorithmsServerToClient); writer.WriteStringList(CompressionAlgorithmsClientToServer); writer.WriteStringList(CompressionAlgorithmsServerToClient); writer.WriteStringList(LanguagesClientToServer); writer.WriteStringList(LanguagesServerToClient); writer.WriteByte(FirstKexPacketFollows ? (byte)0x01 : (byte)0x00); writer.WriteUInt32(0); } protected override void Load(ByteReader reader) { Cookie = reader.GetBytes(16); KexAlgorithms = reader.GetNameList(); ServerHostKeyAlgorithms = reader.GetNameList(); EncryptionAlgorithmsClientToServer = reader.GetNameList(); EncryptionAlgorithmsServerToClient = reader.GetNameList(); MacAlgorithmsClientToServer = reader.GetNameList(); MacAlgorithmsServerToClient = reader.GetNameList(); CompressionAlgorithmsClientToServer = reader.GetNameList(); CompressionAlgorithmsServerToClient = reader.GetNameList(); LanguagesClientToServer = reader.GetNameList(); LanguagesServerToClient = reader.GetNameList(); FirstKexPacketFollows = reader.GetBoolean(); /* uint32 0 (reserved for future extension) */ uint reserved = reader.GetUInt32(); } }
If you'd like to give me a tip, donate at:
- Bitcoin (BTC): 1NdnffxFC7G7qMrvUYc1x4R5sqXuJhVFR7
- Etherium (ETH): 0xcF0a3f130ba0f8c4CC3A02F782805A448D45388f
- Litecoin (LTC): LV7JL8yA4fAZ3Lib9VoX1tuFPmPVrfFueT