Coverage Report

Created: 2025-08-04 07:15

/src/wireshark/epan/dissectors/packet-ssyncp.c
Line
Count
Source (jump to first uncovered line)
1
/* packet-ssyncp.c
2
 * Routines for dissecting mosh's State Synchronization Protocol
3
 * Copyright 2020 Google LLC
4
 *
5
 * Wireshark - Network traffic analyzer
6
 * By Gerald Combs <gerald@wireshark.org>
7
 * Copyright 1998 Gerald Combs
8
 *
9
 * SPDX-License-Identifier: GPL-2.0-or-later
10
 */
11
12
/*
13
 * State Synchronization Protocol is the protocol used by mosh:
14
 * <https://mosh.org/mosh-paper-draft.pdf>
15
 *
16
 * The protocol name is abbreviated as SSyncP to avoid conflict with the
17
 * "Scripting Service Protocol".
18
 *
19
 * The protocol is based on UDP, with a plaintext header followed by an
20
 * encrypted payload. For now we just support decrypting a single connection at
21
 * a time, using the MOSH_KEY dumped from the environment variables
22
 * (`cat /proc/$pid/environ | tr '\0' '\n' | grep MOSH_KEY` on Linux).
23
 * Note that to display the embedded protobuf properly, you'll have to add
24
 * src/protobufs/ from mosh's source code to the ProtoBuf search path.
25
 * For now we stop decoding after reaching the first level of protobufs; in
26
 * them, a second layer of protobufs is sometimes embedded (e.g. for
27
 * transmitting screen contents and such). Implementing that is left as an
28
 * exercise for the reader.
29
 */
30
31
#include <config.h>
32
33
#include <epan/packet.h>   /* Should be first Wireshark include (other than config.h) */
34
#include <epan/conversation.h>
35
#include <epan/wmem_scopes.h>
36
#include <epan/proto_data.h>
37
#include <epan/prefs.h>
38
#include <epan/expert.h>
39
#include <epan/tfs.h>
40
#include <wsutil/array.h>
41
#include <wsutil/report_message.h>
42
#include <wsutil/wsgcrypt.h>
43
44
void proto_reg_handoff_ssyncp(void);
45
void proto_register_ssyncp(void);
46
47
static dissector_handle_t ssyncp_handle;
48
49
static int proto_ssyncp;
50
static int hf_ssyncp_direction;
51
static int hf_ssyncp_seq;
52
static int hf_ssyncp_encrypted;
53
static int hf_ssyncp_seq_delta;
54
static int hf_ssyncp_timestamp;
55
static int hf_ssyncp_timestamp_reply;
56
static int hf_ssyncp_frag_seq;
57
static int hf_ssyncp_frag_final;
58
static int hf_ssyncp_frag_idx;
59
static int hf_ssyncp_rtt_to_server;
60
static int hf_ssyncp_rtt_to_client;
61
62
/* Initialize the subtree pointers */
63
static int ett_ssyncp;
64
static int ett_ssyncp_decrypted;
65
66
static expert_field ei_ssyncp_fragmented;
67
static expert_field ei_ssyncp_bad_key;
68
69
static const char *pref_ssyncp_key;
70
static char ssyncp_raw_aes_key[16];
71
static bool have_ssyncp_key;
72
73
static dissector_handle_t dissector_protobuf;
74
75
typedef struct _ssyncp_conv_info_t {
76
    /* last sequence numbers per direction */
77
    uint64_t last_seq[2];
78
    /* for each direction, have we seen any traffic yet? */
79
    bool seen_packet[2];
80
81
    uint16_t clock_offset[2];
82
    bool clock_seen[2];
83
} ssyncp_conv_info_t;
84
85
typedef struct _ssyncp_packet_info_t {
86
    bool first_packet;
87
    int64_t seq_delta;
88
    bool have_rtt_estimate;
89
    int16_t rtt_estimate;
90
} ssyncp_packet_info_t;
91
92
0
#define SSYNCP_IV_PAD 4
93
6
#define SSYNCP_SEQ_LEN 8
94
6
#define SSYNCP_DATAGRAM_HEADER_LEN (SSYNCP_SEQ_LEN + 2 + 2) /* 64-bit IV and two 16-bit timestamps */
95
6
#define SSYNCP_TRANSPORT_HEADER_LEN (8 + 2)
96
6
#define SSYNCP_AUTHTAG_LEN 16 /* 128-bit auth tag */
97
98
/*
99
 * We only match on 60001, which mosh uses for its first connection.
100
 * If there are more connections in the range 60002-61000, the user will have to
101
 * mark those as ssyncp traffic manually - we'd have too many false positives
102
 * otherwise.
103
 */
104
14
#define SSYNCP_UDP_PORT 60001
105
106
static int
107
dissect_ssyncp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
108
        void *data _U_)
109
6
{
110
    /* Check that we have at least a datagram plus an OCB auth tag. */
111
6
    if (tvb_reported_length(tvb) < SSYNCP_DATAGRAM_HEADER_LEN + SSYNCP_TRANSPORT_HEADER_LEN + SSYNCP_AUTHTAG_LEN)
112
1
        return 0;
113
114
5
    uint64_t direction_and_seq = tvb_get_uint64(tvb, 0, ENC_BIG_ENDIAN);
115
5
    unsigned direction = direction_and_seq >> 63;
116
5
    uint64_t seq = direction_and_seq & ~(1ULL << 63);
117
118
    /* Heuristic: The 63-bit sequence number starts from zero and increments
119
     * from there. Even if you send 1000 packets per second over 10 years, you
120
     * won't reach 2^35. So check that the sequence number is not outrageously
121
     * high.
122
     */
123
5
    if (seq > (1ULL << 35))
124
2
        return 0;
125
126
    /* On the first pass, track the previous sequence numbers per direction,
127
     * compute deltas between sequence numbers, and save those deltas.
128
     * On subsequent passes, use the computed deltas.
129
     */
130
3
    ssyncp_packet_info_t *ssyncp_pinfo;
131
3
    ssyncp_conv_info_t *ssyncp_info = NULL;
132
3
    if (pinfo->fd->visited) {
133
0
        ssyncp_pinfo = (ssyncp_packet_info_t *)p_get_proto_data(wmem_file_scope(), pinfo, proto_ssyncp, 0);
134
3
    } else {
135
3
        conversation_t *conversation = find_or_create_conversation(pinfo);
136
3
        ssyncp_info = (ssyncp_conv_info_t *)conversation_get_proto_data(conversation, proto_ssyncp);
137
3
        if (!ssyncp_info) {
138
2
            ssyncp_info = wmem_new0(wmem_file_scope(), ssyncp_conv_info_t);
139
2
            conversation_add_proto_data(conversation, proto_ssyncp, ssyncp_info);
140
2
        }
141
142
3
        ssyncp_pinfo = wmem_new(wmem_file_scope(), ssyncp_packet_info_t);
143
3
        ssyncp_pinfo->first_packet = !ssyncp_info->seen_packet[direction];
144
3
        if (ssyncp_pinfo->first_packet) {
145
2
            ssyncp_info->seen_packet[direction] = true;
146
2
        } else {
147
1
            ssyncp_pinfo->seq_delta = seq - ssyncp_info->last_seq[direction];
148
1
        }
149
3
        ssyncp_pinfo->have_rtt_estimate = false;
150
3
        p_add_proto_data(wmem_file_scope(), pinfo, proto_ssyncp, 0, ssyncp_pinfo);
151
152
3
        ssyncp_info->last_seq[direction] = seq;
153
3
    }
154
155
    /*** COLUMN DATA ***/
156
157
3
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "ssyncp");
158
159
3
    col_clear(pinfo->cinfo, COL_INFO);
160
161
3
    char *direction_str = direction ? "Server->Client" : "Client->Server";
162
3
    col_set_str(pinfo->cinfo, COL_INFO, direction_str);
163
164
    /*** PROTOCOL TREE ***/
165
166
    /* create display subtree for the protocol */
167
3
    proto_item *ti = proto_tree_add_item(tree, proto_ssyncp, tvb, 0, -1, ENC_NA);
168
169
3
    proto_tree *ssyncp_tree = proto_item_add_subtree(ti, ett_ssyncp);
170
171
    /* Add an item to the subtree, see section 1.5 of README.dissector for more
172
     * information. */
173
3
    proto_tree_add_item(ssyncp_tree, hf_ssyncp_direction, tvb,
174
3
            0, 1, ENC_BIG_ENDIAN);
175
3
    proto_tree_add_item(ssyncp_tree, hf_ssyncp_seq, tvb,
176
3
            0, 8, ENC_BIG_ENDIAN);
177
3
#ifdef GCRY_OCB_BLOCK_LEN
178
3
    proto_item *encrypted_item =
179
3
#endif
180
3
       proto_tree_add_item(ssyncp_tree, hf_ssyncp_encrypted,
181
3
            tvb, 8, -1, ENC_NA);
182
183
3
    if (!ssyncp_pinfo->first_packet) {
184
1
        proto_item *delta_item =
185
1
                proto_tree_add_int64(ssyncp_tree, hf_ssyncp_seq_delta, tvb, 0, 0,
186
1
                        ssyncp_pinfo->seq_delta);
187
1
        proto_item_set_generated(delta_item);
188
1
    }
189
190
3
    unsigned char *decrypted = NULL;
191
3
    unsigned decrypted_len = 0;
192
193
    /* avoid build failure on ancient libgcrypt without OCB support */
194
3
#ifdef GCRY_OCB_BLOCK_LEN
195
3
    if (have_ssyncp_key) {
196
0
        gcry_error_t gcry_err;
197
198
        /* try to decrypt the rest of the packet */
199
0
        gcry_cipher_hd_t gcry_hd;
200
0
        gcry_err = gcry_cipher_open(&gcry_hd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_OCB, 0);
201
0
        if (gcry_err_code(gcry_err)) {
202
            /* this shouldn't happen (even if the packet is garbage) */
203
0
            report_failure("ssyncp: unable to initialize cipher???");
204
0
            return tvb_captured_length(tvb);
205
0
        }
206
0
        gcry_err = gcry_cipher_setkey(gcry_hd, ssyncp_raw_aes_key, sizeof(ssyncp_raw_aes_key));
207
0
        if (gcry_err_code(gcry_err)) {
208
            /* this shouldn't happen (even if the packet is garbage) */
209
0
            report_failure("ssyncp: unable to set key???");
210
0
            gcry_cipher_close(gcry_hd);
211
0
            return tvb_captured_length(tvb);
212
0
        }
213
0
        char nonce[SSYNCP_IV_PAD + SSYNCP_SEQ_LEN];
214
0
        memset(nonce, 0, SSYNCP_IV_PAD);
215
0
        tvb_memcpy(tvb, nonce + SSYNCP_IV_PAD, 0, SSYNCP_SEQ_LEN);
216
0
        gcry_err = gcry_cipher_setiv(gcry_hd, nonce, sizeof(nonce));
217
0
        if (gcry_err_code(gcry_err)) {
218
            /* this shouldn't happen (even if the packet is garbage) */
219
0
            report_failure("ssyncp: unable to set iv???");
220
0
            gcry_cipher_close(gcry_hd);
221
0
            return tvb_captured_length(tvb);
222
0
        }
223
0
        decrypted_len = tvb_captured_length(tvb) - SSYNCP_SEQ_LEN - SSYNCP_AUTHTAG_LEN;
224
0
        decrypted = (unsigned char *)tvb_memdup(pinfo->pool, tvb,
225
0
                    SSYNCP_SEQ_LEN, decrypted_len);
226
0
        gcry_cipher_final(gcry_hd);
227
0
        gcry_err = gcry_cipher_decrypt(gcry_hd, decrypted, decrypted_len, NULL, 0);
228
0
        if (gcry_err_code(gcry_err)) {
229
            /* this shouldn't happen (even if the packet is garbage) */
230
0
            report_failure("ssyncp: unable to decrypt???");
231
0
            gcry_cipher_close(gcry_hd);
232
0
            return tvb_captured_length(tvb);
233
0
        }
234
0
        gcry_err = gcry_cipher_checktag(gcry_hd,
235
0
            tvb_get_ptr(tvb, SSYNCP_SEQ_LEN+decrypted_len, SSYNCP_AUTHTAG_LEN),
236
0
            SSYNCP_AUTHTAG_LEN);
237
0
        if (gcry_err_code(gcry_err) && gcry_err_code(gcry_err) != GPG_ERR_CHECKSUM) {
238
            /* this shouldn't happen (even if the packet is garbage) */
239
0
            report_failure("ssyncp: unable to check auth tag???");
240
0
            gcry_cipher_close(gcry_hd);
241
0
            return tvb_captured_length(tvb);
242
0
        }
243
0
        if (gcry_err_code(gcry_err)) {
244
            /* if the tag is wrong, the key was wrong and the decrypted data is useless */
245
0
            decrypted = NULL;
246
0
            expert_add_info(pinfo, encrypted_item, &ei_ssyncp_bad_key);
247
0
        }
248
0
        gcry_cipher_close(gcry_hd);
249
0
    }
250
3
#endif
251
252
3
    if (decrypted) {
253
0
        tvbuff_t *decrypted_tvb = tvb_new_child_real_data(tvb, decrypted, decrypted_len, decrypted_len);
254
0
        add_new_data_source(pinfo, decrypted_tvb, "Decrypted data");
255
256
0
        if (!pinfo->fd->visited && ssyncp_info) {
257
0
            uint16_t our_clock16 = ((uint64_t)pinfo->abs_ts.secs * 1000 + pinfo->abs_ts.nsecs / 1000000) & 0xffff;
258
0
            uint16_t sender_ts = tvb_get_uint16(decrypted_tvb, 0, ENC_BIG_ENDIAN);
259
0
            uint16_t reply_ts = tvb_get_uint16(decrypted_tvb, 2, ENC_BIG_ENDIAN);
260
0
            ssyncp_info->clock_offset[direction] = sender_ts - our_clock16;
261
0
            ssyncp_info->clock_seen[direction] = true;
262
0
            if (reply_ts != 0xffff && ssyncp_info->clock_seen[1-direction]) {
263
0
                uint16_t projected_send_time_our_clock = reply_ts - ssyncp_info->clock_offset[1-direction];
264
0
                ssyncp_pinfo->rtt_estimate = our_clock16 - projected_send_time_our_clock;
265
0
                ssyncp_pinfo->have_rtt_estimate = true;
266
0
            }
267
0
        }
268
269
0
        proto_tree *dec_tree = proto_tree_add_subtree(ssyncp_tree, decrypted_tvb,
270
0
                0, -1, ett_ssyncp_decrypted, NULL, "Decrypted data");
271
272
0
        proto_tree_add_item(dec_tree, hf_ssyncp_timestamp, decrypted_tvb,
273
0
                0, 2, ENC_BIG_ENDIAN);
274
0
        proto_tree_add_item(dec_tree, hf_ssyncp_timestamp_reply, decrypted_tvb,
275
0
                2, 2, ENC_BIG_ENDIAN);
276
277
0
        if (ssyncp_pinfo->have_rtt_estimate) {
278
0
            int rtt_id = direction ? hf_ssyncp_rtt_to_server : hf_ssyncp_rtt_to_client;
279
0
            proto_item *rtt_item = proto_tree_add_int(dec_tree, rtt_id, decrypted_tvb, 2, 2, ssyncp_pinfo->rtt_estimate);
280
0
            proto_item_set_generated(rtt_item);
281
0
        }
282
283
0
        proto_tree_add_item(dec_tree, hf_ssyncp_frag_seq, decrypted_tvb,
284
0
                4, 8, ENC_BIG_ENDIAN);
285
0
        proto_tree_add_item(dec_tree, hf_ssyncp_frag_final, decrypted_tvb,
286
0
                12, 2, ENC_BIG_ENDIAN);
287
0
        proto_item *frag_idx_item = proto_tree_add_item(dec_tree,
288
0
                hf_ssyncp_frag_idx, decrypted_tvb, 12, 2, ENC_BIG_ENDIAN);
289
290
        /* TODO actually handle fragmentation; for now just bail out on fragmentation */
291
0
        if (tvb_get_uint16(decrypted_tvb, 12, ENC_BIG_ENDIAN) != 0x8000) {
292
0
            expert_add_info(pinfo, frag_idx_item, &ei_ssyncp_fragmented);
293
0
            return tvb_captured_length(tvb);
294
0
        }
295
296
0
        tvbuff_t *inflated_tvb = tvb_child_uncompress_zlib(decrypted_tvb, decrypted_tvb, 14, decrypted_len - 14);
297
0
        if (inflated_tvb == NULL)
298
0
            return tvb_captured_length(tvb);
299
0
        add_new_data_source(pinfo, inflated_tvb, "Inflated data");
300
301
0
        if (dissector_protobuf) {
302
0
            call_dissector_with_data(dissector_protobuf, inflated_tvb, pinfo,
303
0
                    dec_tree, "message,TransportBuffers.Instruction");
304
0
        }
305
0
    }
306
307
3
    return tvb_captured_length(tvb);
308
3
}
309
310
/* Register the protocol with Wireshark.
311
 *
312
 * This format is required because a script is used to build the C function that
313
 * calls all the protocol registration.
314
 */
315
void
316
proto_register_ssyncp(void)
317
14
{
318
14
    static const true_false_string direction_name = {
319
14
        "Server->Client",
320
14
        "Client->Server"
321
14
    };
322
323
    /* Setup list of header fields  See Section 1.5 of README.dissector for
324
     * details. */
325
14
    static hf_register_info hf[] = {
326
14
        { &hf_ssyncp_direction,
327
14
          { "Direction", "ssyncp.direction",
328
14
            FT_BOOLEAN, 8, TFS(&direction_name), 0x80,
329
14
            "Direction of packet", HFILL }
330
14
        },
331
14
        { &hf_ssyncp_seq,
332
14
          { "Sequence number", "ssyncp.seq",
333
14
            FT_UINT64, BASE_HEX, NULL, 0x7fffffffffffffff,
334
14
            "Monotonically incrementing packet sequence number", HFILL }
335
14
        },
336
14
        { &hf_ssyncp_encrypted,
337
14
          { "Encrypted data", "ssyncp.enc_data",
338
14
            FT_BYTES, BASE_NONE, NULL, 0,
339
14
            "Encrypted RTT estimation fields and Transport Layer payload, encrypted with AES-128-OCB",
340
14
            HFILL }
341
14
        },
342
14
        { &hf_ssyncp_seq_delta,
343
14
          { "Sequence number delta", "ssyncp.seq_delta",
344
14
            FT_INT64, BASE_DEC, NULL, 0,
345
14
            "Delta from last sequence number; 1 is normal, 0 is duplicated packet, <0 is reordering, >1 is reordering or packet loss", HFILL }
346
14
        },
347
14
        { &hf_ssyncp_timestamp,
348
14
          { "Truncated timestamp", "ssyncp.timestamp",
349
14
            FT_UINT16, BASE_HEX, NULL, 0,
350
14
            "Low 16 bits of sender's time in milliseconds", HFILL }
351
14
        },
352
14
        { &hf_ssyncp_timestamp_reply,
353
14
          { "Last timestamp received", "ssyncp.timestamp_reply",
354
14
            FT_UINT16, BASE_HEX, NULL, 0,
355
14
            "Low 16 bits of timestamp of last received packet plus time since it was received (for RTT estimation)", HFILL }
356
14
        },
357
14
        { &hf_ssyncp_frag_seq,
358
14
          { "Fragment ID", "ssyncp.frag_seq",
359
14
            FT_UINT64, BASE_HEX, NULL, 0,
360
14
            "Transport-level sequence number, used for fragment reassembly", HFILL }
361
14
        },
362
14
        { &hf_ssyncp_frag_final,
363
14
          { "Final fragment", "ssyncp.frag_final",
364
14
            FT_BOOLEAN, 16, NULL, 0x8000,
365
14
            "Is this the last fragment?", HFILL }
366
14
        },
367
14
        { &hf_ssyncp_frag_idx,
368
14
          { "Fragment Index", "ssyncp.frag_idx",
369
14
            FT_UINT16, BASE_HEX, NULL, 0x7fff,
370
14
            "Index of this fragment in the list of fragments of the transport-level message", HFILL }
371
14
        },
372
14
        { &hf_ssyncp_rtt_to_server,
373
14
          { "RTT estimate to server (in ms)", "ssyncp.rtt_est_to_server",
374
14
            FT_INT16, BASE_DEC, NULL, 0,
375
14
            "Estimated round trip time from point of capture to server", HFILL }
376
14
        },
377
14
        { &hf_ssyncp_rtt_to_client,
378
14
          { "RTT estimate to client (in ms)", "ssyncp.rtt_est_to_client",
379
14
            FT_INT16, BASE_DEC, NULL, 0,
380
14
            "Estimated round trip time from point of capture to client", HFILL }
381
14
        }
382
14
    };
383
384
    /* Setup protocol subtree array */
385
14
    static int *ett[] = {
386
14
        &ett_ssyncp,
387
14
        &ett_ssyncp_decrypted
388
14
    };
389
390
    /* Setup protocol expert items */
391
14
    static ei_register_info ei[] = {
392
14
        { &ei_ssyncp_fragmented,
393
14
          { "ssyncp.fragmented", PI_REASSEMBLE, PI_WARN,
394
14
            "SSYNCP-level fragmentation, dissector can't handle that", EXPFILL }
395
14
        },
396
14
        { &ei_ssyncp_bad_key,
397
14
          { "ssyncp.badkey", PI_DECRYPTION, PI_WARN,
398
14
            "Encrypted data could not be decrypted with the provided key", EXPFILL }
399
14
        }
400
14
    };
401
402
    /* Register the protocol name and description */
403
14
    proto_ssyncp = proto_register_protocol("State Synchronization Protocol", "SSyncP", "ssyncp");
404
405
    /* Register the dissector handle */
406
14
    ssyncp_handle = register_dissector("ssyncp", dissect_ssyncp, proto_ssyncp);
407
408
    /* Required function calls to register the header fields and subtrees */
409
14
    proto_register_field_array(proto_ssyncp, hf, array_length(hf));
410
14
    proto_register_subtree_array(ett, array_length(ett));
411
412
14
    expert_module_t *expert_ssyncp = expert_register_protocol(proto_ssyncp);
413
14
    expert_register_field_array(expert_ssyncp, ei, array_length(ei));
414
415
14
    module_t *ssyncp_module = prefs_register_protocol(proto_ssyncp, proto_reg_handoff_ssyncp);
416
417
14
    prefs_register_string_preference(ssyncp_module, "key",
418
14
        "ssyncp MOSH_KEY",
419
14
        "MOSH_KEY AES key (from mosh-{client,server} environment variable)",
420
14
        &pref_ssyncp_key);
421
14
}
422
423
void
424
proto_reg_handoff_ssyncp(void)
425
14
{
426
14
    static bool initialized = false;
427
428
14
    if (!initialized) {
429
14
        dissector_add_uint("udp.port", SSYNCP_UDP_PORT, ssyncp_handle);
430
431
14
        dissector_protobuf = find_dissector("protobuf");
432
14
        if (dissector_protobuf == NULL) {
433
0
            report_failure("unable to find protobuf dissector");
434
0
        }
435
436
14
        initialized = true;
437
14
    }
438
439
14
    have_ssyncp_key = false;
440
14
    if (strlen(pref_ssyncp_key) != 0) {
441
0
        if (strlen(pref_ssyncp_key) != 22) {
442
0
            report_failure("ssyncp: invalid key, must be 22 characters long");
443
0
            return;
444
0
        }
445
0
        char base64_key[25];
446
0
        memcpy(base64_key, pref_ssyncp_key, 22);
447
0
        memcpy(base64_key+22, "==\0", 3);
448
0
        size_t out_len;
449
0
        if (g_base64_decode_inplace(base64_key, &out_len) == NULL || out_len != sizeof(ssyncp_raw_aes_key)) {
450
0
            report_failure("ssyncp: invalid key, base64 decoding (with \"==\" appended) failed");
451
0
            return;
452
0
        }
453
0
        memcpy(ssyncp_raw_aes_key, base64_key, sizeof(ssyncp_raw_aes_key));
454
0
        have_ssyncp_key = true;
455
0
    }
456
14
}
457
458
/*
459
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
460
 *
461
 * Local variables:
462
 * c-basic-offset: 4
463
 * tab-width: 8
464
 * indent-tabs-mode: nil
465
 * End:
466
 *
467
 * vi: set shiftwidth=4 tabstop=8 expandtab:
468
 * :indentSize=4:tabSize=8:noTabs=true:
469
 */