Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add add support for lightbulbs #6

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 37 additions & 59 deletions tplink-smarthome.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
-- For decrypting local network traffic between TP-Link
-- Smart Home Devices and the Kasa Smart Home App
--
-- Install under:
-- (Windows) %APPDATA%\Wireshark\plugins\
-- (Linux, Mac) $HOME/.wireshark/plugins
-- Install in the location listed in About Wireshark/Folders/Personal Plugins
--
-- by Lubomir Stroetmann
-- Copyright 2016 softScheck GmbH
Expand All @@ -24,65 +22,45 @@
--

-- Create TP-Link Smart Home protocol and its fields
p_tplink = Proto ("TPLink-SmartHome","TP-Link Smart Home Protocol")
hs1x0_proto_TCP = Proto ("TPLink-SmartHome-TCP", "TP-Link Smart Home Protocol (TCP")
hs1x0_proto_UDP = Proto ("TPLink-SmartHome-UDP", "TP-Link Smart Home Protocol (UDP)")

-- Dissector function
function p_tplink.dissector (buf, pkt, root)
-- Validate packet length
if buf:len() == 0 then return end
pkt.cols.protocol = p_tplink.name

-- Decode data
local ascii = ""
local hex = ""

-- Skip first 4 bytes (header)
start = 4
endPosition = buf:len() - 1

-- Decryption key is -85 (256-85=171)
local key = 171

-- Decrypt Autokey XOR
-- Save results as ascii and hex
for index = start, endPosition do
local c = buf(index,1):uint()
-- XOR first byte with key
d = bit32.bxor(c,key)
-- Use byte as next key
key = c

hex = hex .. string.format("%x", d)
-- Convert to printable characters
if d >= 0x20 and d <= 0x7E then
ascii = ascii .. string.format("%c", d)
else
-- Use dot for non-printable bytes
ascii = ascii .. "."
end
end
-- Decrypt string Autokey XOR to ByteArray
function tpdecode(buf, start)
local key = 171
local size = buf:len()-1
local decoded = ""
for i=start,size do
local c = buf(i,1):uint()
decoded = decoded .. string.format("%x", bit.bxor(c,key))
key = c
end
return ByteArray.new(decoded)
end


-- Create subtree
subtree = root:add(p_tplink, buf(0))

-- Add data to subtree
subtree:add(ascii)
-- Description of payload
function hs1x0_proto_TCP.dissector (buf, pkt, root)
pkt.cols.protocol = "TPLink-SmartHome (TCP)"
local subtree = root:add(hs1x0_proto_TCP, buf() ,"TPLink-SmartHome")
local decoded = tpdecode(buf, 4)
subtree:add(decoded:raw())
subtree:append_text(" (decrypted)")

-- Call JSON Dissector with decrypted data
local b = ByteArray.new(hex)
local tvb = ByteArray.tvb(b, "JSON TVB")
local tvb = ByteArray.tvb(decoded, "JSON TVB")
Dissector.get("json"):call(tvb, pkt, root)

end

-- Initialization routine
function p_tplink.init()

function hs1x0_proto_UDP.dissector (buf, pkt, root)
pkt.cols.protocol = "TPLink-SmartHome (UDP)"
local subtree = root:add(hs1x0_proto_UDP, buf() ,"TPLink-SmartHome")
local decoded = tpdecode(buf, 0)
subtree:add(decoded:raw())
subtree:append_text(" (decrypted)")
local tvb = ByteArray.tvb(decoded, "JSON TVB")
Dissector.get("json"):call(tvb, pkt, root)
end

-- Register a chained dissector for port 9999
local tcp_dissector_table = DissectorTable.get("tcp.port")
dissector = tcp_dissector_table:get_dissector(9999)
tcp_dissector_table:add(9999, p_tplink)

tcp_table = DissectorTable.get ("tcp.port")
udp_table = DissectorTable.get ("udp.port")

-- register the protocol to port 9999
tcp_table:add (9999, hs1x0_proto_TCP)
udp_table:add (9999, hs1x0_proto_UDP)
36 changes: 24 additions & 12 deletions tplink-smartplug.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,30 @@ def validIP(ip):
'reset' : '{"system":{"reset":{"delay":1}}}'
}

light_commands = {
'on': '{"smartlife.iot.smartbulb.lightingservice":{"transition_light_state":{"on_off":1,"transition_period":0}}}',
'off': '{"smartlife.iot.smartbulb.lightingservice":{"transition_light_state":{"on_off":0,"transition_period":0}}}'
}

# Encryption and Decryption of TP-Link Smart Home Protocol
# XOR Autokey Cipher with starting key = 171
def encrypt(string):
def encrypt(string, doHeader=True):
key = 171
result = "\0\0\0\0"
if doHeader:
result = "\0\0\0\0"
else:
result = ""
for i in string:
a = key ^ ord(i)
key = a
result += chr(a)
return result

def decrypt(string):
def decrypt(string, doHeader=True):
key = 171
result = ""
if doHeader:
string = string[4:]
for i in string:
a = key ^ ord(i)
key = ord(i)
Expand All @@ -70,6 +80,7 @@ def decrypt(string):
# Parse commandline arguments
parser = argparse.ArgumentParser(description="TP-Link Wi-Fi Smart Plug Client v" + str(version))
parser.add_argument("-t", "--target", metavar="<ip>", required=True, help="Target IP Address", type=validIP)
parser.add_argument("-l", "--lightbulb", dest="lightbulb", help="Enable lightbulb mode", action="store_true")
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("-c", "--command", metavar="<command>", help="Preset command to send. Choices are: "+", ".join(commands), choices=commands)
group.add_argument("-j", "--json", metavar="<JSON string>", help="Full JSON string of command to send")
Expand All @@ -81,19 +92,20 @@ def decrypt(string):
if args.command is None:
cmd = args.json
else:
cmd = commands[args.command]


if args.lightbulb:
cmd = light_commands[args.command]
else:
cmd = commands[args.command]

# Send command and receive reply
try:
sock_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock_tcp.connect((ip, port))
sock_tcp.send(encrypt(cmd))
data = sock_tcp.recv(2048)
sock_tcp.close()
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM if args.lightbulb else socket.SOCK_STREAM)
sock.settimeout(1)
sock.sendto(encrypt(cmd, not args.lightbulb), (ip, port))
data = decrypt(sock.recv(2048), not args.lightbulb)
sock.close()

print "Sent: ", cmd
print "Received: ", decrypt(data[4:])
print "Received: ", data
except socket.error:
quit("Cound not connect to host " + ip + ":" + str(port))