Coverage Report

Created: 2023-01-17 06:15

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