1 /* packet-tplink-smarthome.c
3 * Routines for TP-Link Smart Home Protocol dissection
5 * Copyright 2020-2021, Fulko Hew <fulko.hew@gmail.com>
7 * Wireshark - Network traffic analyzer
8 * By Gerald Combs <gerald@wireshark.org>
9 * Copyright 1998 Gerald Combs
11 * SPDX-License-Identifier: GPL-2.0-or-later
15 * TP-Link Smart Home Protocol (Port 9999) Wireshark Dissector
16 * For decrypting local network traffic between TP-Link Smart Home Devices (such as a KP400)
17 * and the Kasa Smart Home App (or equivalent)
21 * +--+--+--+--+--+--+--+--+--+--+
22 * UDP | Autokey XOR'ed message ... |
23 * +--+--+--+--+--+--+--+--+--+--+
25 * +-------+-------+-------+-------+--+--+--+--+--+--+--+--+--+--+
26 * TCP | Big-endian 32-bit byte count + Autokey XOR'ed message ... |
27 * +-------+-------+-------+-------+--+--+--+--+--+--+--+--+--+--+
29 * I.e. They are both the same except TCP is prefixed with a byte count.
33 #include <epan/packet.h>
34 #include <epan/address.h>
35 #include <epan/conversation.h>
36 #include <epan/wmem_scopes.h>
37 #include "packet-tcp.h"
39 #define TPLINK_SMARTHOME_PORT 9999 /* Not IANA registered */
40 /* TP-Link Smart Home devices use this port on both TCP and UDP */
41 #define FRAME_HEADER_LEN 4 /* 4 bytes of TCP frame length header info */
44 /* (Required to prevent [-Wmissing-prototypes] warnings */
46 void proto_reg_handoff_tplink_smarthome(void);
47 void proto_register_tplink_smarthome(void);
49 static dissector_handle_t tplink_smarthome_handle
;
50 static dissector_handle_t tplink_smarthome_message_handle
;
52 /* Initialize the protocol and registered fields */
54 static int proto_tplink_smarthome
;
55 static int ett_tplink_smarthome
; /* Initialize the subtree pointers */
57 static int hf_tplink_smarthome_Len
;
58 static int hf_tplink_smarthome_Msg
;
61 test_tplink_smarthome(packet_info
*pinfo _U_
, tvbuff_t
*tvb
, int offset
, void *data _U_
)
65 if (tvb_captured_length_remaining(tvb
, offset
) < 2) {
69 /* The message is always JSON, so test the first two characters.
70 * They must be {" or {}, as the protocol doesn't appear to
71 * have whitespace.). */
72 c
= tvb_get_uint8(tvb
, offset
);
77 d
= c
^ tvb_get_uint8(tvb
, offset
+1);
78 if (d
!= '"' && d
!= '}') {
86 dissect_tplink_smarthome_message(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
,
90 proto_tree
*tplink_smarthome_tree
;
94 int32_t len
= tvb_captured_length(tvb
);
96 switch (pinfo
->ptype
) { /* look at the IP port type */
107 if (!test_tplink_smarthome(pinfo
, tvb
, start
, data
)) {
111 col_set_str(pinfo
->cinfo
, COL_PROTOCOL
, "TPLINK-SMARTHOME"); /* show the protocol name of what we're dissecting */
112 col_clear(pinfo
->cinfo
, COL_INFO
); /* and clear anything that might be in the Info field on the UI */
114 ti
= proto_tree_add_item(tree
, proto_tplink_smarthome
, tvb
, 0, -1, ENC_NA
); /* create display subtree for this protocol */
115 tplink_smarthome_tree
= proto_item_add_subtree(ti
, ett_tplink_smarthome
); /* and add it to the display tree */
117 if (pinfo
->ptype
== PT_TCP
) {
118 proto_tree_add_item(tplink_smarthome_tree
, hf_tplink_smarthome_Len
,
119 tvb
, 0, FRAME_HEADER_LEN
, ENC_BIG_ENDIAN
); /* decode the 4 byte message length field pre-pended in a TCP message, */
121 int i_offset
= start
;
123 int decode_len
= len
- start
;
124 char *ascii_buffer
= (char *)wmem_alloc(pinfo
->pool
, 1 + len
- start
); /* create a buffer for the decoded (JSON) message */
126 for (; o_offset
< decode_len
; i_offset
++, o_offset
++) { /* decrypt 'Autokey XOR' message (into ASCII) */
127 c
= tvb_get_uint8(tvb
, i_offset
);
128 d
= c
^ key
; /* XOR the byte with the key to get the decoded byte */
129 key
= c
; /* then use that decoded byte as the value for the next key */
130 *(ascii_buffer
+ o_offset
) = g_ascii_isprint(d
) ? d
: '.'; /* buffer a printable version (for display and JSON decoding) */
132 *(ascii_buffer
+ o_offset
) = '\0';
134 char *mtype
; /* categorize the message's intent: */
135 if (pinfo
->destport
== TPLINK_SMARTHOME_PORT
) { mtype
= "Cmd"; } /* 'Cmd' - if it's TO the TP_Link port */
136 else if (pinfo
->srcport
== TPLINK_SMARTHOME_PORT
) { mtype
= "Rsp"; } /* 'Rsp' - if it's FROM the TP_Link port */
137 else { mtype
= "Msg"; } /* impossible... because we're registered on this port so src or dest must have matched */
139 proto_tree_add_string_format(tplink_smarthome_tree
, hf_tplink_smarthome_Msg
, tvb
,
140 start
, -1, ascii_buffer
, "%s: %s", mtype
, ascii_buffer
); /* add the decrypted data to the subtree so you can 'expand' on it */
142 tvbuff_t
*next_tvb
= tvb_new_child_real_data(tvb
, (uint8_t *)ascii_buffer
, decode_len
, decode_len
); /* create a new TVB and insert the decrypted ASCII string, and */
143 add_new_data_source(pinfo
, next_tvb
, "JSON Message"); /* add it so you can click on this JSON entry and see the decoded buffer */
144 call_dissector(find_dissector("json"), next_tvb
, pinfo
, ti
); /* and decode/dissect it as JSON so you can drill down into it as well */
146 col_add_fstr(pinfo
->cinfo
, COL_INFO
, "%s %s: %s",
147 (pinfo
->ptype
== PT_UDP
) ? "UDP" : "TCP",
148 mtype
, ascii_buffer
); /* add the decoded string to the INFO column for a quick and easy read */
150 return tvb_captured_length(tvb
); /* finally return the amount of data this dissector was able to dissect */
154 get_tplink_smarthome_message_len(packet_info
*pinfo _U_
, tvbuff_t
*tvb
, int offset
, void *data _U_
)
155 { /* the PDU size is... the value in the length field */
156 return (unsigned)tvb_get_ntohl(tvb
, offset
) + FRAME_HEADER_LEN
; /* plus the 'size of' the length field itself */
160 dissect_tplink_smarthome(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data
)
162 conversation_t
*conv
= find_or_create_conversation(pinfo
);
163 if (!conversation_get_proto_data(conv
, proto_tplink_smarthome
)) {
164 if (!test_tplink_smarthome(pinfo
, tvb
, FRAME_HEADER_LEN
, data
)) {
167 conversation_add_proto_data(conv
, proto_tplink_smarthome
, GUINT_TO_POINTER(1));
169 tcp_dissect_pdus(tvb
, pinfo
, tree
, true, FRAME_HEADER_LEN
,
170 get_tplink_smarthome_message_len
, dissect_tplink_smarthome_message
, data
);
171 return tvb_captured_length(tvb
);
174 /* Register the protocol with Wireshark. */
177 proto_register_tplink_smarthome(void)
179 static hf_register_info hf
[] = { /* setup list of header fields */
180 { &hf_tplink_smarthome_Len
,
181 { "Len", "tplink_smarthome.len",
182 FT_UINT32
, BASE_DEC
, NULL
, 0,
183 "Message Length", HFILL
}
185 { &hf_tplink_smarthome_Msg
,
186 { "Msg", "tplink_smarthome.msg",
187 FT_STRING
, BASE_NONE
, NULL
, 0,
192 static int *ett
[] = { /* setup protocol subtree array */
193 &ett_tplink_smarthome
196 proto_tplink_smarthome
= proto_register_protocol("TP-Link Smart Home Protocol", /* register the protocol name and description */
197 "TPLINK-SMARTHOME", "tplink-smarthome");
198 tplink_smarthome_handle
= register_dissector("tplink-smarthome",
199 dissect_tplink_smarthome
, proto_tplink_smarthome
);
200 tplink_smarthome_message_handle
= register_dissector("tplink-smarthome-message",
201 dissect_tplink_smarthome_message
, proto_tplink_smarthome
);
203 proto_register_field_array(proto_tplink_smarthome
, hf
, array_length(hf
)); /* register the header fields */
204 proto_register_subtree_array(ett
, array_length(ett
)); /* and subtrees */
208 proto_reg_handoff_tplink_smarthome(void)
211 dissector_add_uint_with_preference("tcp.port", TPLINK_SMARTHOME_PORT
, tplink_smarthome_handle
);
212 dissector_add_uint_with_preference("udp.port", TPLINK_SMARTHOME_PORT
, tplink_smarthome_message_handle
);
216 * Editor modelines - https://www.wireshark.org/tools/modelines.html
221 * indent-tabs-mode: t
224 * vi: set shiftwidth=4 tabstop=8 noexpandtab:
225 * :indentSize=4:tabSize=8:noTabs=false: