Coverage Report

Created: 2024-02-25 06:29

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