4 * Routines for Minecraft Pocket Edition protocol packet disassembly.
6 * Nick Carter <ncarter100@gmail.com>
7 * Copyright 2014 Nick Carter
10 * http://wiki.vg/Pocket_Minecraft_Protocol#Packet_Encapsulation
12 * Wireshark - Network traffic analyzer
13 * By Gerald Combs <gerald@wireshark.org>
14 * Copyright 1998 Gerald Combs
16 * SPDX-License-Identifier: GPL-2.0-or-later
20 #include <epan/conversation.h>
21 #include <epan/expert.h>
22 #include <epan/packet.h>
23 #include <epan/prefs.h>
25 #include <wsutil/array.h>
27 #include "packet-raknet.h"
29 /* Minecraft Pocket Edition Protocol
32 * http://wiki.vg/Pocket_Edition_Protocol_Documentation
35 #define MCPE_UDP_PORT_DEFAULT 19132 /* Not IANA registered */
36 static unsigned mcpe_udp_port_requested
= MCPE_UDP_PORT_DEFAULT
;
38 static int proto_mcpe
;
39 static int ett_mcpe
; /* Should this node be expanded */
40 static int ett_mcpe_batch
;
41 static int ett_mcpe_batch_record
;
42 static int ett_mcpe_login
;
43 static int ett_mcpe_string
;
48 static dissector_handle_t mcpe_handle
;
49 static dissector_table_t mcpe_packet_dissectors
;
54 static expert_field ei_mcpe_unknown_packet_id
;
55 static expert_field ei_mcpe_decompression_failed
;
56 static expert_field ei_mcpe_encrypted_packet
;
59 * Common Header fields
61 static int hf_mcpe_message_id
;
62 static int hf_mcpe_packet_id
;
63 static int hf_mcpe_string_length
;
64 static int hf_mcpe_UTF8_string
;
65 static int hf_mcpe_byte_string
;
68 * Fields specific to a packet ID
70 static int hf_mcpe_protocol_version
;
71 static int hf_mcpe_login_data_length
;
72 static int hf_mcpe_login_data
;
73 static int hf_mcpe_login
;
74 static int hf_mcpe_chain_JSON
;
75 static int hf_mcpe_client_data_JWT
;
76 static int hf_mcpe_public_key
;
77 static int hf_mcpe_server_token
;
79 static int hf_mcpe_batch_length
;
80 static int hf_mcpe_batch_body
;
81 static int hf_mcpe_batch_records
;
82 static int hf_mcpe_batch_record_length
;
83 static int hf_mcpe_batch_record
;
88 static const value_string mcpe_message_names
[] = {
94 * Forward declarations
96 void proto_register_mcpe(void);
97 void proto_reg_handoff_mcpe(void);
98 static int mcpe_dissect_login(tvbuff_t
*, packet_info
*, proto_tree
*, void*);
99 static int mcpe_dissect_server_to_client_handshake(tvbuff_t
*, packet_info
*, proto_tree
*, void*);
100 static int mcpe_dissect_batch(tvbuff_t
*, packet_info
*, proto_tree
*, void*);
103 * Protocol definition and handlers.
105 struct mcpe_handler_entry
{
107 dissector_t dissector_fp
;
110 static const struct mcpe_handler_entry mcpe_packet_handlers
[] = {
112 mcpe_dissect_login
},
113 { { 0x03, "Server to Client Handshake" },
114 mcpe_dissect_server_to_client_handshake
},
116 mcpe_dissect_batch
},
120 * Look up table from packet ID to name
122 static value_string mcpe_packet_names
[array_length(mcpe_packet_handlers
)+1];
127 typedef struct mcpe_session_state
{
129 uint32_t encryption_starts_after
; /* Frame number */
130 } mcpe_session_state_t
;
132 static mcpe_session_state_t
*
133 mcpe_get_session_state(packet_info
*pinfo
) {
134 conversation_t
* conversation
;
135 mcpe_session_state_t
* state
;
137 conversation
= find_or_create_conversation(pinfo
);
138 state
= (mcpe_session_state_t
*)conversation_get_proto_data(conversation
, proto_mcpe
);
141 state
= wmem_new(wmem_file_scope(), mcpe_session_state_t
);
142 state
->encrypted
= false;
143 state
->encryption_starts_after
= 0;
145 conversation_add_proto_data(conversation
, proto_mcpe
, state
);
155 mcpe_dissect_string(packet_info
*pinfo
, proto_tree
*tree
, int hf
, tvbuff_t
*tvb
, int *offset
, unsigned encoding
) {
157 proto_tree
*string_tree
;
159 uint32_t length_width
;
161 if (encoding
& ENC_LITTLE_ENDIAN
) {
163 * Yes it's crazy. Lengths of string come with two flavors:
164 * big-endian uint16 and little-endian uint32.
166 length
= tvb_get_letohl(tvb
, *offset
);
170 length
= tvb_get_ntohs(tvb
, *offset
);
174 if (encoding
& ENC_UTF_8
) {
177 string
= tvb_get_string_enc(pinfo
->pool
, tvb
, *offset
+ length_width
, length
, ENC_UTF_8
);
179 ti
= proto_tree_add_string(tree
, hf
, tvb
, *offset
, length
+ length_width
, string
);
180 string_tree
= proto_item_add_subtree(ti
, ett_mcpe_string
);
182 proto_tree_add_item(string_tree
, hf_mcpe_string_length
, tvb
,
183 *offset
, length_width
, encoding
);
184 *offset
+= length_width
;
186 proto_tree_add_item(string_tree
, hf_mcpe_UTF8_string
, tvb
,
187 *offset
, length
, ENC_UTF_8
);
193 bytes
= (uint8_t*)tvb_memdup(pinfo
->pool
, tvb
, *offset
+ length_width
, length
);
195 ti
= proto_tree_add_bytes_with_length(tree
, hf
, tvb
, *offset
, length
+ length_width
, bytes
, length
);
196 string_tree
= proto_item_add_subtree(ti
, ett_mcpe_string
);
198 proto_tree_add_item(string_tree
, hf_mcpe_string_length
, tvb
,
199 *offset
, length_width
, encoding
);
200 *offset
+= length_width
;
202 proto_tree_add_item(string_tree
, hf_mcpe_byte_string
, tvb
,
203 *offset
, length
, ENC_NA
);
209 mcpe_dissect_login(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void* data _U_
)
214 uint32_t comp_length
;
219 proto_tree_add_item(tree
, hf_mcpe_protocol_version
, tvb
,
220 offset
, item_size
, ENC_BIG_ENDIAN
);
224 proto_tree_add_item_ret_uint(tree
, hf_mcpe_login_data_length
, tvb
,
225 offset
, item_size
, ENC_BIG_ENDIAN
, &comp_length
);
228 item_size
= comp_length
;
229 ti
= proto_tree_add_item(tree
, hf_mcpe_login_data
, tvb
,
230 offset
, item_size
, ENC_NA
);
232 login_tvb
= tvb_child_uncompress_zlib(tvb
, tvb
, offset
, comp_length
);
234 uint32_t decomp_length
;
235 proto_tree
*login_tree
;
237 add_new_data_source(pinfo
, login_tvb
, "MCPE Decompressed login data");
238 decomp_length
= tvb_captured_length(login_tvb
);
241 item_size
= decomp_length
;
242 ti
= proto_tree_add_item(tree
, hf_mcpe_login
, login_tvb
,
243 offset
, item_size
, ENC_NA
);
244 login_tree
= proto_item_add_subtree(ti
, ett_mcpe_login
);
245 proto_item_append_text(ti
, " (%u octets)", decomp_length
);
246 proto_item_set_generated(ti
);
248 mcpe_dissect_string(pinfo
, login_tree
, hf_mcpe_chain_JSON
, login_tvb
, &offset
, ENC_LITTLE_ENDIAN
| ENC_UTF_8
);
249 mcpe_dissect_string(pinfo
, login_tree
, hf_mcpe_client_data_JWT
, login_tvb
, &offset
, ENC_LITTLE_ENDIAN
| ENC_UTF_8
);
252 expert_add_info(pinfo
, ti
, &ei_mcpe_decompression_failed
);
255 return tvb_reported_length(tvb
);
259 mcpe_dissect_server_to_client_handshake(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void* data _U_
)
263 mcpe_session_state_t
*state
;
265 mcpe_dissect_string(pinfo
, tree
, hf_mcpe_public_key
, tvb
, &offset
, ENC_BIG_ENDIAN
| ENC_UTF_8
);
266 mcpe_dissect_string(pinfo
, tree
, hf_mcpe_server_token
, tvb
, &offset
, ENC_BIG_ENDIAN
);
269 * Everything will be encrypted once the server sends this.
271 state
= mcpe_get_session_state(pinfo
);
272 state
->encrypted
= true;
273 state
->encryption_starts_after
= pinfo
->num
;
275 return tvb_reported_length(tvb
);
279 mcpe_dissect_batch(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void* data
)
285 uint32_t comp_length
;
289 proto_tree_add_item_ret_uint(tree
, hf_mcpe_batch_length
, tvb
,
290 offset
, item_size
, ENC_BIG_ENDIAN
, &comp_length
);
293 item_size
= comp_length
;
294 ti
= proto_tree_add_item(tree
, hf_mcpe_batch_body
, tvb
,
295 offset
, item_size
, ENC_NA
);
297 batch_tvb
= tvb_child_uncompress_zlib(tvb
, tvb
, offset
, comp_length
);
299 uint32_t decomp_length
;
300 proto_tree
*batch_tree
;
302 add_new_data_source(pinfo
, batch_tvb
, "MCPE Decompressed batch");
303 decomp_length
= tvb_captured_length(batch_tvb
);
306 item_size
= decomp_length
;
307 ti
= proto_tree_add_item(tree
, hf_mcpe_batch_records
, batch_tvb
,
308 offset
, item_size
, ENC_NA
);
309 batch_tree
= proto_item_add_subtree(ti
, ett_mcpe_batch
);
310 proto_item_append_text(ti
, " (%u octets)", decomp_length
);
311 proto_item_set_generated(ti
);
313 col_append_str(pinfo
->cinfo
, COL_INFO
, " [");
316 uint32_t record_length
;
317 tvbuff_t
*record_tvb
;
318 proto_tree
*record_tree
;
323 proto_tree_add_item_ret_uint(batch_tree
, hf_mcpe_batch_record_length
, batch_tvb
,
324 offset
, item_size
, ENC_BIG_ENDIAN
, &record_length
);
327 record_tvb
= tvb_new_subset_length(batch_tvb
, offset
, record_length
);
328 offset
+= record_length
;
331 * Take the whole buffer as a single MCPE packet.
333 ti
= proto_tree_add_item(batch_tree
, hf_mcpe_batch_record
, record_tvb
,
335 record_tree
= proto_item_add_subtree(ti
, ett_mcpe_batch_record
);
338 * The first octet is the packet ID.
340 proto_tree_add_item_ret_uint(record_tree
, hf_mcpe_packet_id
,
341 record_tvb
, 0, 1, ENC_NA
, &packet_id
);
343 proto_item_append_text(ti
, " (%s)",
344 val_to_str(packet_id
, mcpe_packet_names
, "Unknown ID: %#x"));
345 col_append_str(pinfo
->cinfo
, COL_INFO
,
346 val_to_str(packet_id
, mcpe_packet_names
, "Unknown packet ID: %#x"));
349 dissector_try_uint_with_data(mcpe_packet_dissectors
, packet_id
,
350 record_tvb
, pinfo
, record_tree
, true, data
);
352 expert_add_info(pinfo
, ti
, &ei_mcpe_unknown_packet_id
);
355 if (offset
< decomp_length
) {
356 col_append_str(pinfo
->cinfo
, COL_INFO
, ", ");
363 col_append_str(pinfo
->cinfo
, COL_INFO
, "]");
366 expert_add_info(pinfo
, ti
, &ei_mcpe_decompression_failed
);
369 return tvb_reported_length(tvb
);
373 mcpe_init_message_names(void)
377 for (i
= 0; i
< array_length(mcpe_packet_handlers
); i
++) {
378 mcpe_packet_names
[i
].value
= mcpe_packet_handlers
[i
].vs
.value
;
379 mcpe_packet_names
[i
].strptr
= mcpe_packet_handlers
[i
].vs
.strptr
;
381 mcpe_packet_names
[array_length(mcpe_packet_handlers
)].value
= 0;
382 mcpe_packet_names
[array_length(mcpe_packet_handlers
)].strptr
= NULL
;
386 test_mcpe_heur(tvbuff_t
*tvb
, packet_info
*pinfo _U_
, proto_tree
*tree _U_
, void* data
)
389 * 0xFE "Wrapper" is the only message ID that MCPE uses. The sole
390 * purpose of Wrapper message is to make a RakNet message out of a
393 if (tvb_strneql(tvb
, 0, "\xFE", 1) == 0) {
395 * Does the message have a packet ID?
397 if (tvb_captured_length(tvb
) >= 2) {
399 * Inspect the packet ID. If it's known to us the message
400 * can be considered to be an MCPE packet.
402 int8_t packet_id
= tvb_get_uint8(tvb
, 1);
404 *(dissector_handle_t
*)data
=
405 dissector_get_uint_handle(mcpe_packet_dissectors
, packet_id
);
407 if (*(dissector_handle_t
*)data
) {
416 dissect_mcpe_heur(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data
)
418 dissector_handle_t handle
;
420 if (test_mcpe_heur(tvb
, pinfo
, tree
, &handle
)) {
422 proto_tree
*mcpe_tree
;
425 tvbuff_t
*packet_tvb
;
427 raknet_conversation_set_dissector(pinfo
, mcpe_handle
);
428 col_set_str(pinfo
->cinfo
, COL_PROTOCOL
, "MCPE");
429 col_clear(pinfo
->cinfo
, COL_INFO
);
432 * Take the whole buffer as a single MCPE packet.
434 ti
= proto_tree_add_item(tree
, proto_mcpe
, tvb
, 0, -1, ENC_NA
);
435 mcpe_tree
= proto_item_add_subtree(ti
, ett_mcpe
);
438 * The first octet is always 0xFE (Wrapper). We intentionally
439 * use DISSECTOR_ASSERT() here because test_mcpe_heur() has
442 proto_tree_add_item_ret_uint(mcpe_tree
, hf_mcpe_message_id
,
443 tvb
, 0, 1, ENC_NA
, &message_id
);
444 DISSECTOR_ASSERT(message_id
== 0xFE);
447 * The next octet is the packet ID.
449 proto_tree_add_item_ret_uint(mcpe_tree
, hf_mcpe_packet_id
,
450 tvb
, 1, 1, ENC_NA
, &packet_id
);
452 proto_item_append_text(ti
, " (%s)",
453 val_to_str(packet_id
, mcpe_packet_names
, "Unknown ID: %#x"));
454 col_add_str(pinfo
->cinfo
, COL_INFO
,
455 val_to_str(packet_id
, mcpe_packet_names
, "Unknown packet ID: %#x"));
457 packet_tvb
= tvb_new_subset_remaining(tvb
, 1);
458 return call_dissector_only(handle
, packet_tvb
, pinfo
, mcpe_tree
, data
) > 0;
466 dissect_mcpe(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data
)
468 mcpe_session_state_t
*state
;
470 state
= mcpe_get_session_state(pinfo
);
471 if (state
->encrypted
&& pinfo
->num
> state
->encryption_starts_after
) {
473 * Encrypted packets don't even have any headers indicating
474 * they are encrypted. And we don't support the cipher they
480 col_set_str(pinfo
->cinfo
, COL_PROTOCOL
, "MCPE");
481 col_set_str(pinfo
->cinfo
, COL_INFO
, "Encrypted packet");
483 packet_size
= tvb_reported_length(tvb
);
484 ti
= proto_tree_add_item(tree
, proto_mcpe
, tvb
, 0, packet_size
, ENC_NA
);
485 proto_item_append_text(ti
, ", Encrypted packet (%d octets)", packet_size
);
486 expert_add_info(pinfo
, ti
, &ei_mcpe_encrypted_packet
);
488 return tvb_captured_length(tvb
);
492 * We reuse our heuristic dissector here because we have no reason
493 * to implement almost the same dissector twice.
495 if (dissect_mcpe_heur(tvb
, pinfo
, tree
, data
)) {
496 return tvb_captured_length(tvb
);
505 proto_register_mcpe(void)
507 static hf_register_info hf
[] = {
509 * Common Header fields
511 { &hf_mcpe_message_id
,
512 { "MCPE Message ID", "mcpe.message.id",
514 VALS(mcpe_message_names
), 0x0,
517 { &hf_mcpe_packet_id
,
518 { "MCPE Packet ID", "mcpe.packet.id",
520 VALS(mcpe_packet_names
), 0x0,
523 { &hf_mcpe_string_length
,
524 { "MCPE String length", "mcpe.string.length",
529 { &hf_mcpe_UTF8_string
,
530 { "MCPE UTF-8 String", "mcpe.string.UTF8",
531 FT_STRING
, BASE_NONE
,
535 { &hf_mcpe_byte_string
,
536 { "MCPE Byte string", "mcpe.string.bytes",
542 * Fields specific to a packet ID
544 { &hf_mcpe_protocol_version
,
545 { "MCPE Protocol version", "mcpe.protocol.version",
550 { &hf_mcpe_login_data_length
,
551 { "MCPE Compressed login data length", "mcpe.login.data.length",
556 { &hf_mcpe_login_data
,
557 { "MCPE Compressed login data", "mcpe.login.data",
563 { "MCPE Decompressed login data", "mcpe.login",
568 { &hf_mcpe_chain_JSON
,
569 { "MCPE Chain JSON", "mcpe.chain.JSON",
570 FT_STRING
, BASE_NONE
,
574 { &hf_mcpe_client_data_JWT
,
575 { "MCPE Client data JWT", "mcpe.client.data.JWT",
576 FT_STRING
, BASE_NONE
,
580 { &hf_mcpe_public_key
,
581 { "MCPE Public key", "mcpe.public.key",
582 FT_STRING
, BASE_NONE
,
586 { &hf_mcpe_server_token
,
587 { "MCPE Server token", "mcpe.server.token",
592 { &hf_mcpe_batch_length
,
593 { "MCPE Compressed batch length", "mcpe.batch.length",
598 { &hf_mcpe_batch_body
,
599 { "MCPE Compressed batch body", "mcpe.batch.body",
604 { &hf_mcpe_batch_records
,
605 { "MCPE Decompressed batch records", "mcpe.batch.records",
610 { &hf_mcpe_batch_record_length
,
611 { "MCPE Batch record length", "mcpe.batch.record.length",
616 { &hf_mcpe_batch_record
,
617 { "MCPE Batch record", "mcpe.batch.record",
625 * Setup protocol subtree array
627 static int *ett
[] = {
630 &ett_mcpe_batch_record
,
634 module_t
*mcpe_module
;
637 * Set up expert info.
639 static ei_register_info ei
[] = {
640 { &ei_mcpe_unknown_packet_id
,
641 { "mcpe.unknown.id", PI_UNDECODED
, PI_WARN
,
642 "MCPE unknown packet ID",
645 { &ei_mcpe_decompression_failed
,
646 { "mcpe.decompression.failed", PI_MALFORMED
, PI_ERROR
,
647 "MCPE packet decompression failed",
650 { &ei_mcpe_encrypted_packet
,
651 { "mcpe.encrypted", PI_DECRYPTION
, PI_NOTE
,
652 "MCPE encrypted packet",
656 expert_module_t
*expert_mcpe
;
661 mcpe_init_message_names();
664 * Register the protocol with wireshark.
666 proto_mcpe
= proto_register_protocol ("Minecraft Pocket Edition", "MCPE", "mcpe");
669 * Register expert support.
671 expert_mcpe
= expert_register_protocol(proto_mcpe
);
672 expert_register_field_array(expert_mcpe
, ei
, array_length(ei
));
675 * Register detailed dissection arrays.
677 proto_register_field_array(proto_mcpe
, hf
, array_length(hf
));
678 proto_register_subtree_array(ett
, array_length(ett
));
681 * Register dissectors.
684 register_dissector("mcpe", dissect_mcpe
, proto_mcpe
);
686 mcpe_packet_dissectors
=
687 register_dissector_table("mcpe.packet.id", "MCPE packets",
688 proto_mcpe
, FT_UINT8
, BASE_HEX
);
690 /* Register a configuration option for UDP port */
692 prefs_register_protocol(proto_mcpe
, proto_reg_handoff_mcpe
);
694 prefs_register_uint_preference(mcpe_module
, "udp.port",
695 "MCPE Server UDP Port",
696 "Set the UDP port for the MCPE Server",
697 10, &mcpe_udp_port_requested
);
701 proto_reg_handoff_mcpe(void)
703 static unsigned last_server_port
;
704 static bool init_done
= false;
707 raknet_delete_udp_dissector(last_server_port
, mcpe_handle
);
712 for (i
= 0; i
< array_length(mcpe_packet_handlers
); i
++) {
715 mcpe_packet_handlers
[i
].vs
.value
,
716 create_dissector_handle(
717 mcpe_packet_handlers
[i
].dissector_fp
, proto_mcpe
));
720 heur_dissector_add("raknet", dissect_mcpe_heur
,
721 "MCPE over RakNet", "mcpe_raknet", proto_mcpe
, HEURISTIC_ENABLE
);
724 last_server_port
= mcpe_udp_port_requested
;
727 /* MCPE is a protocol that carries RakNet packets over UDP */
728 raknet_add_udp_dissector(mcpe_udp_port_requested
, mcpe_handle
);
732 * Editor modelines - https://www.wireshark.org/tools/modelines.html
737 * indent-tabs-mode: nil
740 * vi: set shiftwidth=4 tabstop=8 expandtab:
741 * :indentSize=4:tabSize=8:noTabs=true: