aboutsummaryrefslogtreecommitdiffstats
path: root/epan/dissectors/packet-tplink-smarthome.c
blob: 3a1392a0bce01ee2bff5f26de19d2f9e2eb4e5d0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/* packet-tplink-smarthome.c
 *
 * Routines for TP-Link Smart Home Protocol dissection
 *
 * Copyright 2020-2021, Fulko Hew <fulko.hew@gmail.com>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

/*
 * TP-Link Smart Home Protocol (Port 9999) Wireshark Dissector
 * For decrypting local network traffic between TP-Link Smart Home Devices (such as a KP400)
 * and the Kasa Smart Home App (or equivalent)
 *
 * Protocol	Message
 *
 *		+--+--+--+--+--+--+--+--+--+--+
 *  UDP		| Autokey XOR'ed message ...  |
 *		+--+--+--+--+--+--+--+--+--+--+
 *
 *		+-------+-------+-------+-------+--+--+--+--+--+--+--+--+--+--+
 *  TCP		| Big-endian 32-bit byte count  + Autokey XOR'ed message ...  |
 *		+-------+-------+-------+-------+--+--+--+--+--+--+--+--+--+--+
 *
 * I.e. They are both the same except TCP is prefixed with a byte count.
 */

#include <config.h>
#include <epan/packet.h>
#include <epan/address.h>
#include <epan/wmem/wmem.h>
#include "packet-tcp.h"

#define TPLINK_SMARTHOME_PORT	9999			/* TP-Link Smart Home devices use this port on both TCP and UDP */
#define FRAME_HEADER_LEN	4			/* 4 bytes of TCP frame length header info */

	/* Prototypes */
	/* (Required to prevent [-Wmissing-prototypes] warnings */

void proto_reg_handoff_tplink_smarthome(void);
void proto_register_tplink_smarthome(void);

		/* Initialize the protocol and registered fields */

static int	proto_tplink_smarthome	= -1;
static gint	ett_tplink_smarthome	= -1;		/* Initialize the subtree pointers */

static int	hf_tplink_smarthome_Len	= -1;
static int	hf_tplink_smarthome_Msg	= -1;

static int
dissect_tplink_smarthome_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
		void *data _U_)
{
	proto_item	*ti;
	proto_tree	*tplink_smarthome_tree;
	gint8		start;
	gint32		len = tvb_captured_length(tvb);

	switch (pinfo->ptype) {									/* look at the IP port type */
		case PT_UDP:									/* UDP */
			if (len < 2) return 0;							/* don't dissect unless it contains enough to hold minimum valid JSON i.e. '{}' */
			start = 0;								/* and the JSON message starts immediately */
			break;
		case PT_TCP:									/* TCP */
			if (len < (4 + 2)) return 0;						/* don't dissect unless it has the msg length plus minimal JSON */
			start = 4;								/* and the JSON message starts after that */
			break;
		default:
			return 0;
	}

	col_set_str(pinfo->cinfo, COL_PROTOCOL, "TPLINK-SMARTHOME");				/* show the protocol name of what we're dissecting */
	col_clear(pinfo->cinfo, COL_INFO);							/* and clear anything that might be in the Info field on the UI */

	ti = proto_tree_add_item(tree, proto_tplink_smarthome, tvb, 0, -1, ENC_NA);		/* create display subtree for this protocol */
	tplink_smarthome_tree = proto_item_add_subtree(ti, ett_tplink_smarthome);		/* and add it to the display tree */

	if (pinfo->ptype == PT_TCP) {
		proto_tree_add_item(tplink_smarthome_tree, hf_tplink_smarthome_Len,
					tvb, 0, FRAME_HEADER_LEN, ENC_BIG_ENDIAN);		/* decode the the 4 byte message length field pre-pended in a TCP message, */
	}
	guint8	c, d;
	guint8	key		= 171;
	gint	i_offset	= start;
	gint	o_offset	= 0;
	gint	decode_len	= len - start;
	char	*ascii_buffer	= (char *)wmem_alloc(pinfo->pool, 1 + len - start);		/* create a buffer for the decoded (JSON) message */

	for (; o_offset < decode_len; i_offset++, o_offset++) {					/* decrypt 'Autokey XOR' message (into ASCII) */
		c	= tvb_get_guint8(tvb, i_offset);
		d	= c ^ key;								/* XOR the byte with the key to get the decoded byte */
		key	= c;									/* then use that decoded byte as the value for the next key */
		*(ascii_buffer + o_offset) = g_ascii_isprint(d) ? d : '.';			/* buffer a printable version (for display and JSON decoding) */
	}
	*(ascii_buffer + o_offset) = '\0';

	char *mtype;										/* categorize the message's intent: */
	if	(pinfo->destport == TPLINK_SMARTHOME_PORT)	{ mtype = "Cmd"; }		/*	'Cmd' - if it's  TO  the TP_Link port */
	else if	(pinfo->srcport  == TPLINK_SMARTHOME_PORT)	{ mtype = "Rsp"; }		/*	'Rsp' - if it's FROM the TP_Link port */
	else							{ mtype = "Msg"; }		/* impossible... because we're registered on this port so src or dest must have matched */

	proto_tree_add_string_format(tplink_smarthome_tree, hf_tplink_smarthome_Msg, tvb,
					start, -1, ascii_buffer, "%s: %s", mtype, ascii_buffer);	    /* add the decrypted data to the subtree so you can 'expand' on it */

	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 */
	add_new_data_source(pinfo, next_tvb, "JSON Message");					    	/* add it so you can click on this JSON entry and see the decoded buffer */
	call_dissector(find_dissector("json"), next_tvb, pinfo, ti);			    		/* and decode/dissect it as JSON so you can drill down into it as well */

	col_add_fstr(pinfo->cinfo, COL_INFO, "%s %s: %s",
		(pinfo->ptype == PT_UDP) ? "UDP" : "TCP",
		mtype, ascii_buffer);									/* add the decoded string to the INFO column for a quick and easy read */

	return tvb_captured_length(tvb);								/* finally return the amount of data this dissector was able to dissect */
}

static guint
get_tplink_smarthome_message_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_)
{													/* the PDU size is... the value in the length field */
    return (guint)tvb_get_ntohl(tvb, offset) + FRAME_HEADER_LEN;					/* plus the 'size of' the length field itself */
}

static int
dissect_tplink_smarthome(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
{
	if (pinfo->ptype == PT_UDP) {
		dissect_tplink_smarthome_message(tvb, pinfo, tree, data);
	} else {
		tcp_dissect_pdus(tvb, pinfo, tree, TRUE, FRAME_HEADER_LEN,
			get_tplink_smarthome_message_len, dissect_tplink_smarthome_message, data);
	}
	return tvb_captured_length(tvb);
}

	/* Register the protocol with Wireshark. */

void
proto_register_tplink_smarthome(void)
{
	static hf_register_info hf[] = {								/* setup list of header fields */
		{ &hf_tplink_smarthome_Len,
			{ "Len", "tplink_smarthome.len",
				FT_UINT32, BASE_DEC, NULL, 0,
				"Message Length", HFILL }
		},
		{ &hf_tplink_smarthome_Msg,
			{ "Msg", "tplink_smarthome.msg",
				FT_STRING, BASE_NONE, NULL, 0,
				"Message", HFILL }
		}
	};

	static gint *ett[] = {										/* setup protocol subtree array */
		&ett_tplink_smarthome
	};

	proto_tplink_smarthome = proto_register_protocol("TP-Link Smart Home Protocol",			/* register the protocol name and description */
			"TPLINK-SMARTHOME", "tplink-smarthome");

	proto_register_field_array(proto_tplink_smarthome, hf, array_length(hf));			/* register the header fields */
	proto_register_subtree_array(ett, array_length(ett));						/* and subtrees */
}

void
proto_reg_handoff_tplink_smarthome(void)
{
	dissector_handle_t tplink_smarthome_handle;

	tplink_smarthome_handle = create_dissector_handle(dissect_tplink_smarthome, proto_tplink_smarthome);
	dissector_add_uint_with_preference("tcp.port", TPLINK_SMARTHOME_PORT, tplink_smarthome_handle);
	dissector_add_uint_with_preference("udp.port", TPLINK_SMARTHOME_PORT, tplink_smarthome_handle);
}

/*
 * Editor modelines - https://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 4
 * tab-width: 8
 * indent-tabs-mode: t
 * End:
 *
 * vi: set shiftwidth=4 tabstop=8 noexpandtab:
 * :indentSize=4:tabSize=8:noTabs=false:
 */