Coverage Report

Created: 2025-07-11 07:47

/src/PcapPlusPlus/Packet++/header/SSHLayer.h
Line
Count
Source (jump to first uncovered line)
1
#pragma once
2
3
#include "Layer.h"
4
5
/// @file
6
/// This file introduces classes and structures that represent the SSH (Secure Shell) protocol.
7
///
8
/// An overview of this protocol can be found here: https://en.wikipedia.org/wiki/Ssh_(Secure_Shell)
9
///
10
/// For more details please refer to RFC 4253: https://tools.ietf.org/html/rfc4253
11
///
12
/// These current implementation supports parsing of SSH packets when possible (meaning when they are not encrypted).
13
/// Creation and editing of SSH packets is currently __not supported__.
14
///
15
/// SSH typically uses TCP port 22 so PcapPlusPlus assumes all traffic on this port is SSH traffic.
16
/// PcapPlusPlus uses some heuristics to determine the type of the SSH message (which will be covered later).
17
/// If it doesn't find a match to one of the other SSH messages, it assumes it is an encrypted SSH message.
18
///
19
/// Following is an overview of the SSH protocol classes currently supported in PcapPlusPlus. They cover the different
20
/// messages of the SSH protocol:
21
///
22
/// @code{.unparsed}
23
///                          +----------------------------+      SSH version identification
24
///                      +---|  SSHIdentificationMessage  | ===> as described here:
25
///                      |   +----------------------------+      https://tools.ietf.org/html/rfc4253#section-4.2
26
///                      |
27
///  +------------+      |   +----------------------------+      SSH handshake message
28
///  |  SSHLayer  |------+---|  SSHHandshakeMessage       | ===> which is typically one of the messages described here:
29
///  | (abstract) |      |   +----------------------------+      https://tools.ietf.org/html/rfc4253#section-12
30
///  +------------+      |                 |
31
///                      |                 |     +----------------------------+
32
///                      |                 +-----|  SSHKeyExchangeInitMessage | ===> SSH Key Exchange message
33
///                      |                       +----------------------------+      as described here:
34
///                      |                                                 https://tools.ietf.org/html/rfc4253#section-7
35
///                      |
36
///                      |   +----------------------------+
37
///                      +---|  SSHEncryptedMessage       | ===> An encrypted SSH message
38
///                          +----------------------------+
39
///
40
/// @endcode
41
/// The following points describe the heuristics for deciding the
42
/// message type for each packet:
43
/// 1. If the data starts with the characters "SSH-" and ends with
44
/// "\n" (or "\r\n") it's assumed the message is of type
45
///    pcpp#SSHIdentificationMessage
46
/// 2. Try to determine if this is a non-encrypted SSH handshake
47
/// message:
48
///    - Look at the first 4 bytes of the data which may contain
49
///    the packet length and see if the value is smaller of
50
///      equal than the entire layer length.
51
///    - The next byte contains the padding length, check if it's
52
///    smaller or equal than the packet length
53
///    - The next byte contains the message type, check if the
54
///    value is a valid message type as described in:
55
///      <https://tools.ietf.org/html/rfc4253#section-12>
56
///
57
///    If all of these condition are met, this message is either
58
///    pcpp#SSHKeyExchangeInitMessage (if message type is
59
///    pcpp#SSHHandshakeMessage#SSH_MSG_KEX_INIT) or
60
///    pcpp#SSHHandshakeMessage (for all other message types)
61
/// 3. If non of these conditions are met, it is assumed this is an
62
/// encrypted message (pcpp#SSHEncryptedMessage)
63
64
/// @namespace pcpp
65
/// @brief The main namespace for the PcapPlusPlus lib
66
namespace pcpp
67
{
68
  /// @class SSHLayer
69
  /// This is the base class for the SSH layer. It is an abstract class that cannot be instantiated.
70
  /// It holds some common functionality, but its most important method is createSSHMessage()
71
  /// which takes raw data and creates an SSH message according to the heuristics described
72
  /// in the SSHLayer.h file description
73
  class SSHLayer : public Layer
74
  {
75
  public:
76
    /// A static method that takes raw packet data and uses the heuristics described in the
77
    /// SSHLayer.h file description to create an SSH layer instance. This method assumes the data is
78
    /// indeed SSH data and not some other arbitrary data
79
    /// @param[in] data A pointer to the raw data
80
    /// @param[in] dataLen Size of the data in bytes
81
    /// @param[in] prevLayer A pointer to the previous layer
82
    /// @param[in] packet A pointer to the Packet instance where layer will be stored in
83
    /// @return An instance of one of the classes that inherit SSHLayer as described in the
84
    /// SSHLayer.h file description
85
    static SSHLayer* createSSHMessage(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet);
86
87
    /// A static method that takes src and dst ports and determines whether it's SSH traffic or not.
88
    /// @param[in] portSrc The source TCP port to examine
89
    /// @param[in] portDst The dest TCP port to examine
90
    /// @return Currently the implementation is very simple and returns "true" if either src or dst ports
91
    /// are equal to 22, "false" otherwise
92
    static bool isSSHPort(uint16_t portSrc, uint16_t portDst)
93
78.8k
    {
94
78.8k
      return portSrc == 22 || portDst == 22;
95
78.8k
    }
96
97
    // implement abstract methods
98
99
    /// Several SSH records can reside in a single packets. This method examins the remaining data and creates
100
    /// additional SSH records if applicable
101
    void parseNextLayer() override;
102
103
    /// Does nothing for this layer
104
    void computeCalculateFields() override
105
747
    {}
106
107
    OsiModelLayer getOsiModelLayer() const override
108
747
    {
109
747
      return OsiModelApplicationLayer;
110
747
    }
111
112
  protected:
113
    // protected c'tor, this class cannot be instantiated
114
    SSHLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet)
115
3.99k
        : Layer(data, dataLen, prevLayer, packet, SSH)
116
3.99k
    {}
117
118
  private:
119
    // this layer supports only parsing
120
    SSHLayer();
121
  };
122
123
  /// @class SSHIdentificationMessage
124
  /// A class that represents SSH identification message as described in RFC 4253:
125
  /// <https://tools.ietf.org/html/rfc4253#section-4.2>
126
  ///
127
  /// The message content is typically a string that contains the protocol version, software version and a few more
128
  /// details. This string can be retrieved using the getIdentificationMessage() method
129
  class SSHIdentificationMessage : public SSHLayer
130
  {
131
  public:
132
    /// @return The SSH identification message which is typically the content of this message
133
    std::string getIdentificationMessage();
134
135
    /// A static method that takes raw data and tries to parse it as an SSH identification message using the
136
    /// heuristics described in the SSHLayer.h file description. It returns a SSHIdentificationMessage instance if
137
    /// such a message can be identified or nullptr otherwise.
138
    /// @param[in] data A pointer to the raw data
139
    /// @param[in] dataLen Size of the data in bytes
140
    /// @param[in] prevLayer A pointer to the previous layer
141
    /// @param[in] packet A pointer to the Packet instance where layer will be stored in
142
    /// @return An instance of SSHIdentificationMessage or nullptr if this is not an identification message
143
    static SSHIdentificationMessage* tryParse(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet);
144
145
    // implement abstract methods
146
147
    /// @return The size of the identification message
148
    size_t getHeaderLen() const override
149
162
    {
150
162
      return m_DataLen;
151
162
    }
152
153
    std::string toString() const override;
154
155
  private:
156
    // this layer supports only parsing
157
    SSHIdentificationMessage();
158
159
    // private c'tor, this class cannot be instantiated
160
    SSHIdentificationMessage(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet)
161
135
        : SSHLayer(data, dataLen, prevLayer, packet)
162
135
    {}
163
  };
164
165
  /// @class SSHHandshakeMessage
166
  /// A class representing all of the non-encrypted SSH handshake messages.
167
  /// An handshake message typically has the following structure:
168
  ///
169
  /// @code{.unparsed}
170
  /// 0         1         2         3         4         5         6
171
  /// +---------+---------+---------+---------+---------+---------+-----------     ---------+
172
  /// |            Packet Length              | Padding | Message |  Message  ....  Padding |
173
  /// |                                       | Length  |  Type   |  Content  ....          |
174
  /// +---------------------------------------+---------+---------+-----------     ---------+
175
  /// @endcode
176
  ///
177
  /// The first 4 bytes hold the packet length, followed by 1 byte that holds the padding length (which comes at the
178
  /// end of the message), then 1 byte that holds the message type (which can be of type
179
  /// SSHHandshakeMessage#SSHHandshakeMessageType) and then the message content. At the end of the content there is
180
  /// typically padding.
181
  ///
182
  /// This class provides access to all of these values. The message content itself is not parse with the exception of
183
  /// SSHKeyExchangeInitMessage
184
  /// which inherits from this class and provides parsing of the Key Exchange Init message.
185
  class SSHHandshakeMessage : public SSHLayer
186
  {
187
  public:
188
    /// An enum that represents SSH non-encrypted message types
189
    enum SSHHandshakeMessageType
190
    {
191
      /// Key Exchange Init message
192
      SSH_MSG_KEX_INIT = 20,
193
      /// New Keys message
194
      SSH_MSG_NEW_KEYS = 21,
195
      /// Diffie-Hellman Key Exchange Init message
196
      SSH_MSG_KEX_DH_INIT = 30,
197
      ///  message
198
      SSH_MSG_KEX_DH_REPLY = 31,
199
      /// Diffie-Hellman Group Exchange Init message
200
      SSH_MSG_KEX_DH_GEX_INIT = 32,
201
      /// "Diffie-Hellman Group Exchange Reply message
202
      SSH_MSG_KEX_DH_GEX_REPLY = 33,
203
      /// Diffie-Hellman Group Exchange Request message
204
      SSH_MSG_KEX_DH_GEX_REQUEST = 34,
205
      /// Unknown message
206
      SSH_MSG_UNKNOWN = 999
207
    };
208
209
    /// @return The message type
210
    SSHHandshakeMessageType getMessageType() const;
211
212
    /// @return A string representation of the message type
213
    std::string getMessageTypeStr() const;
214
215
    /// @return A raw byte stream of the message content
216
    uint8_t* getSSHHandshakeMessage() const;
217
218
    /// @return The message content length in [bytes] which is calculated by the overall packet length
219
    /// minus the message header (which includes packet length, padding length and message type) and
220
    /// minus the padding bytes
221
    size_t getSSHHandshakeMessageLength() const;
222
223
    /// @return The padding length in [bytes]
224
    size_t getPaddingLength() const;
225
226
    /// A static method that takes raw packet data and uses some heuristics described in the
227
    /// SSHLayer.h file description to parse it as SSH handshake message instance
228
    /// @param[in] data A pointer to the raw data
229
    /// @param[in] dataLen Size of the data in bytes
230
    /// @param[in] prevLayer A pointer to the previous layer
231
    /// @param[in] packet A pointer to the Packet instance where layer will be stored in
232
    /// @return Upon successful parsing the return value would be an instance of SSHKeyExchangeInitMessage
233
    /// for Key Exchange Init message or SSHHandshakeMessage for any other message type. If parsing fails nullptr
234
    /// will be returned
235
    static SSHHandshakeMessage* tryParse(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet);
236
237
    // implement abstract methods
238
239
    /// @return The size of the SSH handshake message including the padding and message header
240
    size_t getHeaderLen() const override;
241
242
    std::string toString() const override;
243
244
  protected:
245
#pragma pack(push, 1)
246
    /// An internal struct representing the SSH handshake message header
247
    struct ssh_message_base
248
    {
249
      uint32_t packetLength;
250
      uint8_t paddingLength;
251
      uint8_t messageCode;
252
    };
253
#pragma pack(pop)
254
    static_assert(sizeof(ssh_message_base) == 6, "ssh_message_base size is not 6 bytes");
255
256
    // this layer supports only parsing
257
    SSHHandshakeMessage();
258
259
    // private c'tor, this class cannot be instantiated
260
    SSHHandshakeMessage(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet)
261
688
        : SSHLayer(data, dataLen, prevLayer, packet)
262
688
    {}
263
264
    ssh_message_base* getMsgBaseHeader() const
265
1.07k
    {
266
1.07k
      return reinterpret_cast<ssh_message_base*>(m_Data);
267
1.07k
    }
268
  };
269
270
  /// @class SSHKeyExchangeInitMessage
271
  /// A class representing the SSH Key Exchange Init message. This is a non-encrypted message that contains
272
  /// information about the algorithms used for key exchange, encryption, MAC and compression. This class provides
273
  /// methods to access these details
274
  class SSHKeyExchangeInitMessage : public SSHHandshakeMessage
275
  {
276
  public:
277
    /// A c'tor for this class that accepts raw message data. Please avoid using it as it's used internally
278
    /// when parsing SSH handshake messages in SSHHandshakeMessage#tryParse()
279
    /// @param[in] data A pointer to the raw data
280
    /// @param[in] dataLen Size of the data in bytes
281
    /// @param[in] prevLayer A pointer to the previous layer
282
    /// @param[in] packet A pointer to the Packet instance where layer will be stored in
283
    SSHKeyExchangeInitMessage(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet);
284
285
    /// Each SSH Key Exchange Init message contains a random 16-byte value generated by the sender.
286
    /// This method returns a pointer to this 16-byte cookie. To get the value as a hex string
287
    /// please refer to getCookieAsHexStream()
288
    /// @return A pointer to the 16-byte cookie value or nullptr if the message is malformed
289
    uint8_t* getCookie();
290
291
    /// Each SSH Key Exchange Init message contains a random 16-byte value generated by the sender.
292
    /// This method returns the 16-byte cookie as a hex stream. To get the raw data please refer to
293
    /// getCookie()
294
    /// @return A hex stream of the 16-byte cookie value or an empty string if the message is malformed
295
    std::string getCookieAsHexStream();
296
297
    /// @return A comma-separated list of the key exchange algorithms used in this session.
298
    /// Can be empty if the value is missing or the message is malformed
299
    std::string getKeyExchangeAlgorithms()
300
0
    {
301
0
      return getFieldValue(0);
302
0
    }
303
304
    /// @return A comma-separated list of the algorithms supported for the server host key.
305
    /// Can be empty if the value is missing or the message is malformed
306
    std::string getServerHostKeyAlgorithms()
307
0
    {
308
0
      return getFieldValue(1);
309
0
    }
310
311
    /// @return A comma-separated list of acceptable symmetric encryption algorithms (also known as ciphers)
312
    /// from the client to the server. Can be empty if the value is missing or the message is malformed
313
    std::string getEncryptionAlgorithmsClientToServer()
314
0
    {
315
0
      return getFieldValue(2);
316
0
    }
317
318
    /// @return A comma-separated list of acceptable symmetric encryption algorithms (also known as ciphers)
319
    /// from the server to the client. Can be empty if the value is missing or the message is malformed
320
    std::string getEncryptionAlgorithmsServerToClient()
321
0
    {
322
0
      return getFieldValue(3);
323
0
    }
324
325
    /// @return A comma-separated list of acceptable MAC algorithms from the client to the server.
326
    /// Can be empty if the value is missing or the message is malformed
327
    std::string getMacAlgorithmsClientToServer()
328
0
    {
329
0
      return getFieldValue(4);
330
0
    }
331
332
    /// @return A comma-separated list of acceptable MAC algorithms from the server to the client.
333
    /// Can be empty if the value is missing or the message is malformed
334
    std::string getMacAlgorithmsServerToClient()
335
0
    {
336
0
      return getFieldValue(5);
337
0
    }
338
339
    /// @return A comma-separated list of acceptable compression algorithms from the client to the server.
340
    /// Can be empty if the value is missing or the message is malformed
341
    std::string getCompressionAlgorithmsClientToServer()
342
0
    {
343
0
      return getFieldValue(6);
344
0
    }
345
346
    /// @return A comma-separated list of acceptable compression algorithms from the server to the client.
347
    /// Can be empty if the value is missing or the message is malformed
348
    std::string getCompressionAlgorithmsServerToClient()
349
0
    {
350
0
      return getFieldValue(7);
351
0
    }
352
353
    /// @return A comma-separated list of language tags from the client to the server.
354
    /// Can be empty if the value is missing or the message is malformed
355
    std::string getLanguagesClientToServer()
356
0
    {
357
0
      return getFieldValue(8);
358
0
    }
359
360
    /// @return A comma-separated list of language tags from the server to the client.
361
    /// Can be empty if the value is missing or the message is malformed
362
363
    std::string getLanguagesServerToClient()
364
0
    {
365
0
      return getFieldValue(9);
366
0
    }
367
368
    /// @return Indicates whether a guessed key exchange packet follows. If a
369
    /// guessed packet will be sent, the return value is true. If no guessed
370
    /// packet will be sent or if this value is missing, the return value is false.
371
    bool isFirstKexPacketFollows();
372
373
  private:
374
    size_t m_FieldOffsets[11];
375
    bool m_OffsetsInitialized;
376
377
    void parseMessageAndInitOffsets();
378
379
    std::string getFieldValue(int fieldOffsetIndex);
380
  };
381
382
  /// @class SSHEncryptedMessage
383
  /// A class representing an SSH encrypted message. In such messages there is very little information to extract from
384
  /// the packet, hence this class doesn't expose any methods or getters, other than the ones inherited from parent
385
  /// classes.
386
  ///
387
  /// It is assumed that any SSH message which does not fit to any of the other SSH message types, according to the
388
  /// heuristics described in the SSHLayer.h file description, is considered as an encrypted message.
389
  class SSHEncryptedMessage : public SSHLayer
390
  {
391
  public:
392
    /// A c'tor for this class that accepts raw message data. Please avoid using it as it's used internally
393
    /// when parsing SSH messages in SSHLayer#createSSHMessage()
394
    SSHEncryptedMessage(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet)
395
3.16k
        : SSHLayer(data, dataLen, prevLayer, packet)
396
3.16k
    {}
397
398
    // implement abstract methods
399
400
    /// @return The size of the message which is equal to the size of the layer
401
    size_t getHeaderLen() const override
402
3.75k
    {
403
3.75k
      return m_DataLen;
404
3.75k
    }
405
406
    std::string toString() const override;
407
  };
408
409
}  // namespace pcpp