Skip to content

Instantly share code, notes, and snippets.

@foxt
Created January 28, 2024 13:58
Show Gist options
  • Save foxt/a48753d23d4c52a77aa95c87f15eb54c to your computer and use it in GitHub Desktop.
Save foxt/a48753d23d4c52a77aa95c87f15eb54c to your computer and use it in GitHub Desktop.
TP-Link Easy Smart Management Wireshark Dissector
local TPL_KEY = {191,155,227,202,99,162,79,104,49,18,190,164,30,76,189,131,23,52,86,106,207,125,126,169,196,28,172,58,188,132,160,3,36,120,144,168,12,231,116,44,41,97,108,213,42,198,32,148,218,107,247,112,204,14,66,68,91,224,206,235,33,130,203,178,1,134,199,78,249,123,7,145,73,208,209,100,74,115,72,118,8,22,243,147,64,96,5,87,60,113,233,152,31,219,143,174,232,153,245,158,254,70,170,75,77,215,211,59,71,133,214,157,151,6,46,81,94,136,166,210,4,43,241,29,223,176,67,63,186,137,129,40,248,255,55,15,62,183,222,105,236,197,127,54,179,194,229,185,37,90,237,184,25,156,173,26,187,220,2,225,0,240,50,251,212,253,167,17,193,205,177,21,181,246,82,226,38,101,163,182,242,92,20,11,95,13,230,16,121,124,109,195,117,39,98,239,84,56,139,161,47,201,51,135,250,10,19,150,45,111,27,24,142,80,85,83,234,138,216,57,93,65,154,141,122,34,140,128,238,88,89,9,146,171,149,53,102,61,114,69,217,175,103,228,35,180,252,200,192,165,159,221,244,110,119,48}
-- Crypto functions courtesy of
-- https://github.com/koraa/smrtlink_terrible_crypto
function generate_keystream(key, length)
local out = {}
local q = 0
for ix = 0, length do
local h = (ix + 1) % 256
q = (q + key[h+1]) % 256
local temp = key[h+1]
key[h+1] = key[q+1]
key[q+1] = temp
local w = (key[h+1] + key[q+1]) % 256
local z = key[w+1]
table.insert(out, z)
end
return out
end
function crypt(key, data)
local ks = generate_keystream(key, #data)
local out = {}
for ix = 1, #data do
table.insert(out, bit.bxor(data[ix], ks[ix]))
end
return out
end
function bytearraytoarray(bytearray)
local out = {}
for ix = 0, bytearray:len() - 1 do
table.insert(out, bytearray:get_index(ix))
end
return out
end
function arraytostring(array)
local out = ""
for ix = 1, #array do
out = out .. string.char(array[ix])
end
return out
end
function toMac(array, start)
local string = ""
for ix = 0, 5 do
string = string .. string.format("%02x", array[start + ix])
if ix < 5 then
string = string .. ":"
end
end
return Address.ether(string)
end
function toHex(array)
local string = ""
for ix = 1, #array do
string = string .. string.format("%02x", array[ix])
end
return string
end
function isPrintable(string)
for ix = 1, #string-1 do
local char = string:sub(ix, ix)
if char < " " or char > "~" then
return false
end
end
return true
end
local tpl_proto = Proto("tpl_easysmart", "TP-Link Easy Smart Protocol")
local decryptedField = ProtoField.bytes("tpl_easysmart.decrypted", "Decrypted Data")
local versionField = ProtoField.uint8("tpl_easysmart.version", "Version", base.DEC)
local opcodeField = ProtoField.uint8("tpl_easysmart.opcode", "Opcode", base.DEC)
local swMacField = ProtoField.ether("tpl_easysmart.sw_mac", "Switch MAC")
local pcMacField = ProtoField.ether("tpl_easysmart.pc_mac", "PC MAC")
local seqNoField = ProtoField.uint16("tpl_easysmart.seq_no", "Sequence Number", base.DEC)
local errNoField = ProtoField.uint32("tpl_easysmart.err_no", "Error Number", base.DEC)
local lengthField = ProtoField.uint16("tpl_easysmart.length", "Length", base.DEC)
local fragOffsetField = ProtoField.uint16("tpl_easysmart.frag_offset", "Fragment Offset", base.DEC)
local tokenIdField = ProtoField.uint16("tpl_easysmart.token_id", "Token ID", base.DEC)
local tlvEntryField = ProtoField.uint16("tpl_easysmart.tlv_entry", "TLV Entry")
local tlvTypeField = ProtoField.uint16("tpl_easysmart.tlv_type", "TLV Type", base.DEC)
local tlvLengthField = ProtoField.uint16("tpl_easysmart.tlv_length", "TLV Length", base.DEC)
local tlvDataField = ProtoField.string("tpl_easysmart.tlv_data", "TLV Data")
local tlvDataStringField = ProtoField.string("tpl_easysmart.tlv_data_string", "TLV Data String")
tpl_proto.fields = { decryptedField, versionField, opcodeField, swMacField, pcMacField, seqNoField, errNoField, lengthField, fragOffsetField, tokenIdField, tlvEntryField, tlvTypeField, tlvLengthField, tlvDataField, tlvDataStringField }
local tlvTypeNames = {
[1] = "Hardware Model",
[2] = "System Description",
[3] = "MAC Address",
[4] = "IP Address",
[5] = "Subnet Mask",
[6] = "Gateway",
[7] = "Firmware",
[8] = "Hardware Revision",
[9] = "DHCP Enabled",
[10] = "Port Number",
[11] = "MAC VLAN",
[12] = "LED Enabled",
[13] = "Autosaving",
[14] = "Factory",
[15] = "Switch flash type",
[16] = "Port Order",
[512] = "Username",
[513] = "New Password",
[514] = "Password",
[773] = "Save Config/Reboot",
[768] = "Read Config File",
[780] = "Write Config File",
[1280] = "Reset Config",
[1536] = "Firmware Upgrade",
[2305] = "Token",
[4352] = "IGMP Snooping Enabled",
[4354] = "IGMP Report Mesage Suppression",
[4353] = "IGMP Multicast IP",
[4096] = "Port",
[4608] = "Trunk",
[8192] = "VLAN MTU",
[8448] = "Port-based VLAN status",
[8449] = "Port-based VLAN",
[8704] = "802.1q VLAN status",
[8705] = "802.1q VLAN",
[8706] = "PVID",
[8707] = "VLAN Support",
[12288] = "QOS Mode",
[12289] = "QOS Priority",
[12545] = "Bandwidth Egress Limit",
[12544] = "Bandwidth Ingress Limit",
[13568] = "POE Status",
[13824] = "POE Config",
[14080] = "POE Global Recovery",
[14336] = "POE Recovery",
[14592] = "POE Extend",
[16384] = "Port Statistics",
[16640] = "Port Mirroring",
[16896] = "Cable Test",
[17152] = "Loop Prevention",
[65535] = "End"
}
function tpl_proto.dissector(pktData, packet, tree)
local length = pktData:len()
if length == 0 then return end
packet.cols.protocol = tpl_proto.name
local subtree = tree:add(tpl_proto, pktData(), "TP-Link Easy Smart Protocol")
local dataRange = pktData:range()
local data = pktData:range(dataRange:offset(), dataRange:len()):bytes()
local decrypted = crypt({table.unpack(TPL_KEY)}, bytearraytoarray(data))
local decryptedData = arraytostring(decrypted)
subtree:add(decryptedField, dataRange, decryptedData)
local version = decrypted[1]
subtree:add(versionField, version)
local opcode = decrypted[2]
subtree:add(opcodeField, opcode)
local swMac = toMac(decrypted, 3)
subtree:add(swMacField, swMac)
local pcMac = toMac(decrypted, 9)
subtree:add(pcMacField, pcMac)
local seqNo = decrypted[15] * 256 + decrypted[16]
subtree:add(seqNoField, seqNo)
local errNo = decrypted[17] * 16777216 + decrypted[18] * 65536 + decrypted[19] * 256 + decrypted[20]
subtree:add(errNoField, errNo)
local length = decrypted[21] * 256 + decrypted[22]
subtree:add(lengthField, length)
local fragOffset = decrypted[23] * 256 + decrypted[24]
subtree:add(fragOffsetField, fragOffset)
local tokenId = decrypted[25] * 256 + decrypted[26]
subtree:add(tokenIdField, tokenId)
local i = 33
packet.cols.info:set("");
while i < #decrypted do
local fieldType = decrypted[i] * 256 + decrypted[i+1]
local fieldLength = decrypted[i+2] * 256 + decrypted[i+3]
local fieldData = {}
for j = 0, fieldLength - 1 do
table.insert(fieldData, decrypted[i+4+j])
print(decrypted[i+4+j])
end
print("---")
local fieldString = arraytostring(fieldData)
local field = subtree:add(tlvEntryField, fieldType)
local name = fieldType
if tlvTypeNames[fieldType] ~= nil then
field:append_text(" (" .. tlvTypeNames[fieldType] .. ")")
name = tlvTypeNames[fieldType]
end
packet.cols.info:append(name)
if fieldType ~= 65535 then
packet.cols.info:append(", ")
end
field:add(tlvTypeField, fieldType)
field:add(tlvLengthField, fieldLength)
field:add(tlvDataField, toHex(fieldData))
if isPrintable(fieldString) then
field:add(tlvDataStringField, fieldString)
end
i = i + 4 + fieldLength
end
end
local udp_dissector_table = DissectorTable.get("udp.port")
udp_dissector_table:add(29808, tpl_proto)
udp_dissector_table:add(29809, tpl_proto)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment