1 /* packet-tplink-smarthome.c
2 *
3 * Routines for TP-Link Smart Home Protocol dissection
4 *
5 * Copyright 2020-2021, Fulko Hew <fulko.hew@gmail.com>
6 *
7 * Wireshark - Network traffic analyzer
8 * By Gerald Combs <gerald@wireshark.org>
9 * Copyright 1998 Gerald Combs
10 *
11 * SPDX-License-Identifier: GPL-2.0-or-later
12 */
13
14 /*
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)
18 *
19 * Protocol Message
20 *
21 * +--+--+--+--+--+--+--+--+--+--+
22 * UDP | Autokey XOR'ed message ... |
23 * +--+--+--+--+--+--+--+--+--+--+
24 *
25 * +-------+-------+-------+-------+--+--+--+--+--+--+--+--+--+--+
26 * TCP | Big-endian 32-bit byte count + Autokey XOR'ed message ... |
27 * +-------+-------+-------+-------+--+--+--+--+--+--+--+--+--+--+
28 *
29 * I.e. They are both the same except TCP is prefixed with a byte count.
30 */
31
32 #include <config.h>
33 #include <epan/packet.h>
34 #include <epan/address.h>
35 #include <epan/wmem_scopes.h>
36 #include "packet-tcp.h"
37
38 #define TPLINK_SMARTHOME_PORT 9999 /* TP-Link Smart Home devices use this port on both TCP and UDP */
39 #define FRAME_HEADER_LEN 4 /* 4 bytes of TCP frame length header info */
40
41 /* Prototypes */
42 /* (Required to prevent [-Wmissing-prototypes] warnings */
43
44 void proto_reg_handoff_tplink_smarthome(void);
45 void proto_register_tplink_smarthome(void);
46
47 /* Initialize the protocol and registered fields */
48
49 static int proto_tplink_smarthome = -1;
50 static gint ett_tplink_smarthome = -1; /* Initialize the subtree pointers */
51
52 static int hf_tplink_smarthome_Len = -1;
53 static int hf_tplink_smarthome_Msg = -1;
54
55 static int
dissect_tplink_smarthome_message(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,void * data _U_)56 dissect_tplink_smarthome_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
57 void *data _U_)
58 {
59 proto_item *ti;
60 proto_tree *tplink_smarthome_tree;
61 gint8 start;
62 gint32 len = tvb_captured_length(tvb);
63
64 switch (pinfo->ptype) { /* look at the IP port type */
65 case PT_UDP: /* UDP */
66 if (len < 2) return 0; /* don't dissect unless it contains enough to hold minimum valid JSON i.e. '{}' */
67 start = 0; /* and the JSON message starts immediately */
68 break;
69 case PT_TCP: /* TCP */
70 if (len < (4 + 2)) return 0; /* don't dissect unless it has the msg length plus minimal JSON */
71 start = 4; /* and the JSON message starts after that */
72 break;
73 default:
74 return 0;
75 }
76
77 col_set_str(pinfo->cinfo, COL_PROTOCOL, "TPLINK-SMARTHOME"); /* show the protocol name of what we're dissecting */
78 col_clear(pinfo->cinfo, COL_INFO); /* and clear anything that might be in the Info field on the UI */
79
80 ti = proto_tree_add_item(tree, proto_tplink_smarthome, tvb, 0, -1, ENC_NA); /* create display subtree for this protocol */
81 tplink_smarthome_tree = proto_item_add_subtree(ti, ett_tplink_smarthome); /* and add it to the display tree */
82
83 if (pinfo->ptype == PT_TCP) {
84 proto_tree_add_item(tplink_smarthome_tree, hf_tplink_smarthome_Len,
85 tvb, 0, FRAME_HEADER_LEN, ENC_BIG_ENDIAN); /* decode the the 4 byte message length field pre-pended in a TCP message, */
86 }
87 guint8 c, d;
88 guint8 key = 171;
89 gint i_offset = start;
90 gint o_offset = 0;
91 gint decode_len = len - start;
92 char *ascii_buffer = (char *)wmem_alloc(pinfo->pool, 1 + len - start); /* create a buffer for the decoded (JSON) message */
93
94 for (; o_offset < decode_len; i_offset++, o_offset++) { /* decrypt 'Autokey XOR' message (into ASCII) */
95 c = tvb_get_guint8(tvb, i_offset);
96 d = c ^ key; /* XOR the byte with the key to get the decoded byte */
97 key = c; /* then use that decoded byte as the value for the next key */
98 *(ascii_buffer + o_offset) = g_ascii_isprint(d) ? d : '.'; /* buffer a printable version (for display and JSON decoding) */
99 }
100 *(ascii_buffer + o_offset) = '\0';
101
102 char *mtype; /* categorize the message's intent: */
103 if (pinfo->destport == TPLINK_SMARTHOME_PORT) { mtype = "Cmd"; } /* 'Cmd' - if it's TO the TP_Link port */
104 else if (pinfo->srcport == TPLINK_SMARTHOME_PORT) { mtype = "Rsp"; } /* 'Rsp' - if it's FROM the TP_Link port */
105 else { mtype = "Msg"; } /* impossible... because we're registered on this port so src or dest must have matched */
106
107 proto_tree_add_string_format(tplink_smarthome_tree, hf_tplink_smarthome_Msg, tvb,
108 start, -1, ascii_buffer, "%s: %s", mtype, ascii_buffer); /* add the decrypted data to the subtree so you can 'expand' on it */
109
110 tvbuff_t *next_tvb = tvb_new_child_real_data(tvb, (guint8 *)ascii_buffer, decode_len, decode_len); /* create a new TVB and insert the decrypted ASCII string, and */
111 add_new_data_source(pinfo, next_tvb, "JSON Message"); /* add it so you can click on this JSON entry and see the decoded buffer */
112 call_dissector(find_dissector("json"), next_tvb, pinfo, ti); /* and decode/dissect it as JSON so you can drill down into it as well */
113
114 col_add_fstr(pinfo->cinfo, COL_INFO, "%s %s: %s",
115 (pinfo->ptype == PT_UDP) ? "UDP" : "TCP",
116 mtype, ascii_buffer); /* add the decoded string to the INFO column for a quick and easy read */
117
118 return tvb_captured_length(tvb); /* finally return the amount of data this dissector was able to dissect */
119 }
120
121 static guint
get_tplink_smarthome_message_len(packet_info * pinfo _U_,tvbuff_t * tvb,int offset,void * data _U_)122 get_tplink_smarthome_message_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_)
123 { /* the PDU size is... the value in the length field */
124 return (guint)tvb_get_ntohl(tvb, offset) + FRAME_HEADER_LEN; /* plus the 'size of' the length field itself */
125 }
126
127 static int
dissect_tplink_smarthome(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,void * data)128 dissect_tplink_smarthome(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
129 {
130 if (pinfo->ptype == PT_UDP) {
131 dissect_tplink_smarthome_message(tvb, pinfo, tree, data);
132 } else {
133 tcp_dissect_pdus(tvb, pinfo, tree, TRUE, FRAME_HEADER_LEN,
134 get_tplink_smarthome_message_len, dissect_tplink_smarthome_message, data);
135 }
136 return tvb_captured_length(tvb);
137 }
138
139 /* Register the protocol with Wireshark. */
140
141 void
proto_register_tplink_smarthome(void)142 proto_register_tplink_smarthome(void)
143 {
144 static hf_register_info hf[] = { /* setup list of header fields */
145 { &hf_tplink_smarthome_Len,
146 { "Len", "tplink_smarthome.len",
147 FT_UINT32, BASE_DEC, NULL, 0,
148 "Message Length", HFILL }
149 },
150 { &hf_tplink_smarthome_Msg,
151 { "Msg", "tplink_smarthome.msg",
152 FT_STRING, BASE_NONE, NULL, 0,
153 "Message", HFILL }
154 }
155 };
156
157 static gint *ett[] = { /* setup protocol subtree array */
158 &ett_tplink_smarthome
159 };
160
161 proto_tplink_smarthome = proto_register_protocol("TP-Link Smart Home Protocol", /* register the protocol name and description */
162 "TPLINK-SMARTHOME", "tplink-smarthome");
163
164 proto_register_field_array(proto_tplink_smarthome, hf, array_length(hf)); /* register the header fields */
165 proto_register_subtree_array(ett, array_length(ett)); /* and subtrees */
166 }
167
168 void
proto_reg_handoff_tplink_smarthome(void)169 proto_reg_handoff_tplink_smarthome(void)
170 {
171 dissector_handle_t tplink_smarthome_handle;
172
173 tplink_smarthome_handle = create_dissector_handle(dissect_tplink_smarthome, proto_tplink_smarthome);
174 dissector_add_uint_with_preference("tcp.port", TPLINK_SMARTHOME_PORT, tplink_smarthome_handle);
175 dissector_add_uint_with_preference("udp.port", TPLINK_SMARTHOME_PORT, tplink_smarthome_handle);
176 }
177
178 /*
179 * Editor modelines - https://www.wireshark.org/tools/modelines.html
180 *
181 * Local variables:
182 * c-basic-offset: 4
183 * tab-width: 8
184 * indent-tabs-mode: t
185 * End:
186 *
187 * vi: set shiftwidth=4 tabstop=8 noexpandtab:
188 * :indentSize=4:tabSize=8:noTabs=false:
189 */
190