/src/wireshark/epan/dissectors/packet-tplink-smarthome.c
Line | Count | Source |
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/conversation.h> |
36 | | #include <epan/wmem_scopes.h> |
37 | | #include "packet-tcp.h" |
38 | | |
39 | 30 | #define TPLINK_SMARTHOME_PORT 9999 /* Not IANA registered */ |
40 | | /* TP-Link Smart Home devices use this port on both TCP and UDP */ |
41 | 2 | #define FRAME_HEADER_LEN 4 /* 4 bytes of TCP frame length header info */ |
42 | | |
43 | | /* Prototypes */ |
44 | | /* (Required to prevent [-Wmissing-prototypes] warnings */ |
45 | | |
46 | | void proto_reg_handoff_tplink_smarthome(void); |
47 | | void proto_register_tplink_smarthome(void); |
48 | | |
49 | | static dissector_handle_t tplink_smarthome_handle; |
50 | | static dissector_handle_t tplink_smarthome_message_handle; |
51 | | |
52 | | /* Initialize the protocol and registered fields */ |
53 | | |
54 | | static int proto_tplink_smarthome; |
55 | | static int ett_tplink_smarthome; /* Initialize the subtree pointers */ |
56 | | |
57 | | static int hf_tplink_smarthome_Len; |
58 | | static int hf_tplink_smarthome_Msg; |
59 | | |
60 | | static bool |
61 | | test_tplink_smarthome(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_) |
62 | 4 | { |
63 | 4 | uint8_t key = 171; |
64 | 4 | uint8_t c, d; |
65 | 4 | if (tvb_captured_length_remaining(tvb, offset) < 2) { |
66 | 1 | return false; |
67 | 1 | } |
68 | | |
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 | 3 | c = tvb_get_uint8(tvb, offset); |
73 | 3 | d = c ^ key; |
74 | 3 | if (d != '{') { |
75 | 3 | return false; |
76 | 3 | } |
77 | 0 | d = c ^ tvb_get_uint8(tvb, offset+1); |
78 | 0 | if (d != '"' && d != '}') { |
79 | 0 | return false; |
80 | 0 | } |
81 | | |
82 | 0 | return true; |
83 | 0 | } |
84 | | |
85 | | static int |
86 | | dissect_tplink_smarthome_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, |
87 | | void *data _U_) |
88 | 2 | { |
89 | 2 | proto_item *ti; |
90 | 2 | proto_tree *tplink_smarthome_tree; |
91 | 2 | int8_t start = 0; |
92 | 2 | uint8_t c, d; |
93 | 2 | uint8_t key = 171; |
94 | 2 | int32_t len = tvb_captured_length(tvb); |
95 | | |
96 | 2 | switch (pinfo->ptype) { /* look at the IP port type */ |
97 | 2 | case PT_UDP: |
98 | 2 | start = 0; |
99 | 2 | break; |
100 | 0 | case PT_TCP: |
101 | 0 | start = 4; |
102 | 0 | break; |
103 | 0 | default: |
104 | 0 | return 0; |
105 | 2 | } |
106 | | |
107 | 2 | if (!test_tplink_smarthome(pinfo, tvb, start, data)) { |
108 | 2 | return 0; |
109 | 2 | } |
110 | | |
111 | 0 | col_set_str(pinfo->cinfo, COL_PROTOCOL, "TPLINK-SMARTHOME"); /* show the protocol name of what we're dissecting */ |
112 | 0 | col_clear(pinfo->cinfo, COL_INFO); /* and clear anything that might be in the Info field on the UI */ |
113 | |
|
114 | 0 | ti = proto_tree_add_item(tree, proto_tplink_smarthome, tvb, 0, -1, ENC_NA); /* create display subtree for this protocol */ |
115 | 0 | tplink_smarthome_tree = proto_item_add_subtree(ti, ett_tplink_smarthome); /* and add it to the display tree */ |
116 | |
|
117 | 0 | if (pinfo->ptype == PT_TCP) { |
118 | 0 | proto_tree_add_item(tplink_smarthome_tree, hf_tplink_smarthome_Len, |
119 | 0 | tvb, 0, FRAME_HEADER_LEN, ENC_BIG_ENDIAN); /* decode the 4 byte message length field pre-pended in a TCP message, */ |
120 | 0 | } |
121 | 0 | int i_offset = start; |
122 | 0 | int o_offset = 0; |
123 | 0 | int decode_len = len - start; |
124 | 0 | char *ascii_buffer = (char *)wmem_alloc(pinfo->pool, 1 + len - start); /* create a buffer for the decoded (JSON) message */ |
125 | |
|
126 | 0 | for (; o_offset < decode_len; i_offset++, o_offset++) { /* decrypt 'Autokey XOR' message (into ASCII) */ |
127 | 0 | c = tvb_get_uint8(tvb, i_offset); |
128 | 0 | d = c ^ key; /* XOR the byte with the key to get the decoded byte */ |
129 | 0 | key = c; /* then use that decoded byte as the value for the next key */ |
130 | 0 | *(ascii_buffer + o_offset) = g_ascii_isprint(d) ? d : '.'; /* buffer a printable version (for display and JSON decoding) */ |
131 | 0 | } |
132 | 0 | *(ascii_buffer + o_offset) = '\0'; |
133 | |
|
134 | 0 | char *mtype; /* categorize the message's intent: */ |
135 | 0 | if (pinfo->destport == TPLINK_SMARTHOME_PORT) { mtype = "Cmd"; } /* 'Cmd' - if it's TO the TP_Link port */ |
136 | 0 | else if (pinfo->srcport == TPLINK_SMARTHOME_PORT) { mtype = "Rsp"; } /* 'Rsp' - if it's FROM the TP_Link port */ |
137 | 0 | else { mtype = "Msg"; } /* impossible... because we're registered on this port so src or dest must have matched */ |
138 | |
|
139 | 0 | proto_tree_add_string_format(tplink_smarthome_tree, hf_tplink_smarthome_Msg, tvb, |
140 | 0 | start, -1, ascii_buffer, "%s: %s", mtype, ascii_buffer); /* add the decrypted data to the subtree so you can 'expand' on it */ |
141 | |
|
142 | 0 | 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 | 0 | 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 | 0 | call_dissector(find_dissector("json"), next_tvb, pinfo, ti); /* and decode/dissect it as JSON so you can drill down into it as well */ |
145 | |
|
146 | 0 | col_add_fstr(pinfo->cinfo, COL_INFO, "%s %s: %s", |
147 | 0 | (pinfo->ptype == PT_UDP) ? "UDP" : "TCP", |
148 | 0 | mtype, ascii_buffer); /* add the decoded string to the INFO column for a quick and easy read */ |
149 | |
|
150 | 0 | return tvb_captured_length(tvb); /* finally return the amount of data this dissector was able to dissect */ |
151 | 2 | } |
152 | | |
153 | | static unsigned |
154 | | get_tplink_smarthome_message_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_) |
155 | 0 | { /* the PDU size is... the value in the length field */ |
156 | 0 | return (unsigned)tvb_get_ntohl(tvb, offset) + FRAME_HEADER_LEN; /* plus the 'size of' the length field itself */ |
157 | 0 | } |
158 | | |
159 | | static int |
160 | | dissect_tplink_smarthome(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) |
161 | 2 | { |
162 | 2 | conversation_t *conv = find_or_create_conversation(pinfo); |
163 | 2 | if (!conversation_get_proto_data(conv, proto_tplink_smarthome)) { |
164 | 2 | if (!test_tplink_smarthome(pinfo, tvb, FRAME_HEADER_LEN, data)) { |
165 | 2 | return 0; |
166 | 2 | } |
167 | 0 | conversation_add_proto_data(conv, proto_tplink_smarthome, GUINT_TO_POINTER(1)); |
168 | 0 | } |
169 | 0 | tcp_dissect_pdus(tvb, pinfo, tree, true, FRAME_HEADER_LEN, |
170 | 0 | get_tplink_smarthome_message_len, dissect_tplink_smarthome_message, data); |
171 | 0 | return tvb_captured_length(tvb); |
172 | 2 | } |
173 | | |
174 | | /* Register the protocol with Wireshark. */ |
175 | | |
176 | | void |
177 | | proto_register_tplink_smarthome(void) |
178 | 15 | { |
179 | 15 | static hf_register_info hf[] = { /* setup list of header fields */ |
180 | 15 | { &hf_tplink_smarthome_Len, |
181 | 15 | { "Len", "tplink_smarthome.len", |
182 | 15 | FT_UINT32, BASE_DEC, NULL, 0, |
183 | 15 | "Message Length", HFILL } |
184 | 15 | }, |
185 | 15 | { &hf_tplink_smarthome_Msg, |
186 | 15 | { "Msg", "tplink_smarthome.msg", |
187 | 15 | FT_STRING, BASE_NONE, NULL, 0, |
188 | 15 | "Message", HFILL } |
189 | 15 | } |
190 | 15 | }; |
191 | | |
192 | 15 | static int *ett[] = { /* setup protocol subtree array */ |
193 | 15 | &ett_tplink_smarthome |
194 | 15 | }; |
195 | | |
196 | 15 | proto_tplink_smarthome = proto_register_protocol("TP-Link Smart Home Protocol", /* register the protocol name and description */ |
197 | 15 | "TPLINK-SMARTHOME", "tplink-smarthome"); |
198 | 15 | tplink_smarthome_handle = register_dissector("tplink-smarthome", |
199 | 15 | dissect_tplink_smarthome, proto_tplink_smarthome); |
200 | 15 | tplink_smarthome_message_handle = register_dissector("tplink-smarthome-message", |
201 | 15 | dissect_tplink_smarthome_message, proto_tplink_smarthome); |
202 | | |
203 | 15 | proto_register_field_array(proto_tplink_smarthome, hf, array_length(hf)); /* register the header fields */ |
204 | 15 | proto_register_subtree_array(ett, array_length(ett)); /* and subtrees */ |
205 | 15 | } |
206 | | |
207 | | void |
208 | | proto_reg_handoff_tplink_smarthome(void) |
209 | 15 | { |
210 | | |
211 | 15 | dissector_add_uint_with_preference("tcp.port", TPLINK_SMARTHOME_PORT, tplink_smarthome_handle); |
212 | 15 | dissector_add_uint_with_preference("udp.port", TPLINK_SMARTHOME_PORT, tplink_smarthome_message_handle); |
213 | 15 | } |
214 | | |
215 | | /* |
216 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
217 | | * |
218 | | * Local variables: |
219 | | * c-basic-offset: 4 |
220 | | * tab-width: 8 |
221 | | * indent-tabs-mode: t |
222 | | * End: |
223 | | * |
224 | | * vi: set shiftwidth=4 tabstop=8 noexpandtab: |
225 | | * :indentSize=4:tabSize=8:noTabs=false: |
226 | | */ |