Created
January 28, 2024 13:58
-
-
Save foxt/a48753d23d4c52a77aa95c87f15eb54c to your computer and use it in GitHub Desktop.
TP-Link Easy Smart Management Wireshark Dissector
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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