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