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