/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 |