Coverage Report

Created: 2026-07-12 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
 */