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