From e055b564a861f5bcbd032d48e9a370d1b4f69b9d Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Thu, 29 Aug 2024 13:49:52 +0200 Subject: more WIP --- docs/notes.md | 9 +- wireshark/ethers | 8 -- wireshark/ieee.lua | 17 ++-- wireshark/main.lua | 9 ++ wireshark/pictochat.lua | 235 ++++++++++++++++++++++++++++-------------------- wireshark/util.lua | 80 +++++++++++++++++ wireshark/wireshark | 6 +- 7 files changed, 238 insertions(+), 126 deletions(-) delete mode 100644 wireshark/ethers create mode 100644 wireshark/main.lua diff --git a/docs/notes.md b/docs/notes.md index c50a390..e3d1659 100644 --- a/docs/notes.md +++ b/docs/notes.md @@ -196,12 +196,9 @@ Notable: - Message length **can not** be used to consistently check which packets contain PictoChat message data. -#### MAC addresses + #### Analysis diff --git a/wireshark/ethers b/wireshark/ethers deleted file mode 100644 index b60d4ec..0000000 --- a/wireshark/ethers +++ /dev/null @@ -1,8 +0,0 @@ -00:09:bf:11:22:33 Default_firmware_MAC -03:09:bf:00:00:00 Multiplayer_CMD -03:09:bf:00:00:10 Multiplayer_Reply -03:09:bf:00:00:03 Multiplayer_ACK -00:f0:77:77:77:77 Access_point -10:00:de:ad:be:ef Instance_1_(lork) -20:00:de:ae:02:ff Instance_2_(lork2) -ff:ff:ff:ff:ff:ff Broadcast diff --git a/wireshark/ieee.lua b/wireshark/ieee.lua index 97000db..9e08cf3 100644 --- a/wireshark/ieee.lua +++ b/wireshark/ieee.lua @@ -1,5 +1,4 @@ require "util" -local bit = require "bit" local p = Proto("ieee", "IEEE802.11 frame header") local dslmp = DissectorTable.new("dslmp") -- DS Local Multi-Player @@ -30,9 +29,9 @@ p.fields.ctl_wep = ProtoField.bool("ieee.ctl.wep", "WEP encrypt", base.DEC, nil, p.fields.ctl_order = ProtoField.bool("ieee.ctl.order", "Order", base.DEC, nil, bits(15)) p.fields.duration = ProtoField.uint16("ieee.duration", "Duration / ID", base.HEX, nil, 0xffff) -p.fields.addr1 = ProtoField.ether("ieee.addr1", "Address 1") -p.fields.addr2 = ProtoField.ether("ieee.addr2", "Address 2") -p.fields.addr3 = ProtoField.ether("ieee.addr3", "Address 3") +p.fields.addr1 = ProtoField.bytes("ieee.addr1", "Address 1", base.COLON) +p.fields.addr2 = ProtoField.bytes("ieee.addr2", "Address 2", base.COLON) +p.fields.addr3 = ProtoField.bytes("ieee.addr3", "Address 3", base.COLON) p.fields.seq = ProtoField.uint16("ieee.seq", "Sequence control") p.fields.seq_frag = ProtoField.uint16("ieee.seq.frag", "Fragment", base.DEC, nil, bits(0, 4)) p.fields.seq_num = ProtoField.uint16("ieee.seq.num", "Sequence number", base.DEC, nil, bits(4, 12)) @@ -58,9 +57,9 @@ function p.dissector(buffer, pinfo, tree) local ctl_tree = subtree:add_le(p.fields.ctl, buffer(0x00, 2)) ctl_tree:add_le(p.fields.ctl_ver, buffer(0x00, 2)) ctl_tree:add_le(p.fields.ctl_type, buffer(0x00, 2)) - local ctl_type = bit.rshift(bit.band(buffer(0x00, 2):le_uint(), bits(2, 2)), 2) + local ctl_type = buffer(0x00, 2):bitfield(14, 2) ctl_tree:add_le(p.fields.ctl_subtype, buffer(0x00, 2)) - local ctl_subtype = bit.rshift(bit.band(buffer(0x00, 2):le_uint(), bits(4, 4)), 4) + local ctl_subtype = buffer(0x00, 2):bitfield(12, 4) ctl_tree:add_le(p.fields.ctl_to_ds, buffer(0x00, 2)) ctl_tree:add_le(p.fields.ctl_from_ds, buffer(0x00, 2)) ctl_tree:add_le(p.fields.ctl_fragment, buffer(0x00, 2)) @@ -71,9 +70,9 @@ function p.dissector(buffer, pinfo, tree) ctl_tree:add_le(p.fields.ctl_order, buffer(0x00, 2)) subtree:add_le(p.fields.duration, buffer(0x02, 2)) - subtree:add(p.fields.addr1, buffer(0x04, 6)) - subtree:add(p.fields.addr2, buffer(0x0a, 6)) - subtree:add(p.fields.addr3, buffer(0x10, 6)) + add_addr(subtree, p.fields.addr1, buffer(0x04, 6)) + add_addr(subtree, p.fields.addr2, buffer(0x0a, 6)) + add_addr(subtree, p.fields.addr3, buffer(0x10, 6)) local seq_tree = subtree:add_le(p.fields.seq, buffer(0x16, 2)) seq_tree:add_le(p.fields.seq_frag, buffer(0x16, 2)) seq_tree:add_le(p.fields.seq_num, buffer(0x16, 2)) diff --git a/wireshark/main.lua b/wireshark/main.lua new file mode 100644 index 0000000..91323da --- /dev/null +++ b/wireshark/main.lua @@ -0,0 +1,9 @@ +require "pictochat" + +require "ieee" + +require "txhdr" +-- require "rxhdr" + +require "melon" + diff --git a/wireshark/pictochat.lua b/wireshark/pictochat.lua index 9f8cad0..b64832a 100644 --- a/wireshark/pictochat.lua +++ b/wireshark/pictochat.lua @@ -2,20 +2,76 @@ require "util" local p = Proto("pictochat", "PictoChat") +function p.init() + local dt = DissectorTable.get("dslmp") + dt:add(GAMEID.PICTOCHAT, p) +end + +local MSG_TYPE = { + INIT = 0, + MSG_START = 10, + MSG_END = 24, + USER_JOIN = 48, + STATUS = 52, + MSG_BODY = 86, +} + +local MSG_TYPE_MAP = { + [MSG_TYPE.INIT] = "Init", -- sent once (init?) + [MSG_TYPE.MSG_START] = "Message start", -- sent before a message packet + [MSG_TYPE.MSG_END] = "Message end", -- sent after multi-frame message body packets + [MSG_TYPE.USER_JOIN] = "User join", -- these packets contain a username + [MSG_TYPE.STATUS] = "Status", -- constantly spammed (status?) + [MSG_TYPE.MSG_BODY] = "Message body", -- contains tile data for drawing +} + p.fields.unknown = ProtoField.uint16("pictochat.unknown", "Unknown") -p.fields.magic = ProtoField.bytes("pictochat.magic", "Magic") -p.fields.new = ProtoField.bool("pictochat.new", "New") -p.fields.msg_type = ProtoField.uint8("pictochat.msg_type", "Message type", base.DEC, { - [0] = "???", - [1] = "???", - [10] = "Message start", -- sent before a message packet - [24] = "Message end", -- sent after multi-frame message body packets - [48] = "User join", -- these packets contain a username - [52] = "Keepalive", -- constantly spammed (keep alive?) - [86] = "Message body", -- contains tile data for drawing - [184] = "???", -- user leave?? -}) +local function dissect_payload(buffer, pinfo, tree) + tree:add_le(p.fields.payload_len, buffer(0x00, 2)) + local payload_length = buffer(0x00, 2):le_uint() + buffer = buffer(0x02) + return buffer, tree:add(buffer(0x00, payload_length), "Payload: " .. payload_length .. " bytes") +end + +local dissect_msg_type = { } + +p.fields.msg_start_len = ProtoField.uint8("pictochat.msg_start.len", "Total length") +local state = {} +dissect_msg_type[MSG_TYPE.MSG_START] = function (buffer, pinfo, tree) + local payload + buffer, payload = dissect_payload(buffer, pinfo, tree) + + payload:add_le(p.fields.msg_start_len, buffer(0x04, 2)) + local start_len = buffer(0x04, 2):le_uint() + + state.data_remaining = start_len + state.buf = ByteArray.new() + state.want_next = 0 +end + +p.fields.user_addr = ProtoField.bytes("pictochat.user.addr", "Address", base.COLON) +p.fields.user_name = ProtoField.string("pictochat.user.name", "Nickname") +p.fields.user_msg = ProtoField.string("pictochat.user.msg", "Message") +p.fields.user_color = ProtoField.uint16("pictochat.user.color", "Color", base.DEC, PROFILE_COLOR_MAP) +p.fields.user_bday_month = ProtoField.uint8("pictochat.user.bday_month", "Month") +p.fields.user_bday_day = ProtoField.uint8("pictochat.user.bday_day", "Day") +dissect_msg_type[MSG_TYPE.USER_JOIN] = function (buffer, pinfo, tree) + local payload + buffer, payload = dissect_payload(buffer, pinfo, tree) + + add_addr_le(payload, p.fields.user_addr, buffer(0x0a, 6)) + local user_name = buffer(0x10, 20):le_ustring() + payload:add(p.fields.user_name, buffer(0x10, 20), user_name) + register_addr_le(buffer(0x0a, 6):raw(), user_name) + pinfo.cols.info = string.format("%s (%s)", pinfo.cols.info, user_name) + payload:add(p.fields.user_msg, buffer(0x24, 52), buffer(0x24, 52):le_ustring()) + payload:add_le(p.fields.user_color, buffer(0x58, 2)) + local bday = payload:add(buffer(0x5a, 2), string.format("Birthday: %02d/%02d", buffer(0x5a, 1):uint(), buffer(0x5b, 1):uint())) + bday:add(p.fields.user_bday_month, buffer(0x5a, 1)) + bday:add(p.fields.user_bday_day, buffer(0x5b, 1)) +end + p.fields.payload_len = ProtoField.uint16("pictochat.payload_len", "Payload length") p.fields.data_len = ProtoField.uint8("pictochat.data_len", "Data length") p.fields.data_end = ProtoField.bool("pictochat.data_end", "Data end") @@ -23,111 +79,94 @@ p.fields.data_offset = ProtoField.uint16("pictochat.data_offset", "Data offset") p.fields.data_sequence = ProtoField.uint16("pictochat.data_sequence", "Data sequence") p.fields.data = ProtoField.bytes("pictochat.data", "Data") p.fields.sequence = ProtoField.uint16("pictochat.sequence", "Packet sequence") -p.fields.user_mac = ProtoField.ether("pictochat.user.mac", "Address") -p.fields.user_name = ProtoField.string("pictochat.user.name", "Nickname") -p.fields.user_msg = ProtoField.string("pictochat.user.msg", "Message") -p.fields.user_color = ProtoField.uint16("pictochat.user.color", "Color", base.DEC, { - [0] = "Greyish blue", - [1] = "Brown", - [2] = "Red", - [3] = "Light pink", - [4] = "Orange", - [5] = "Yellow", - [6] = "Lime", - [7] = "Light green", - [8] = "Dark green", - [9] = "Turqoise", - [10] = "Light blue", - [11] = "Blue", - [12] = "Dark blue", - [13] = "Dark purple", - [14] = "Light purple", - [15] = "Dark pink", -}) -p.fields.user_bday_month = ProtoField.uint8("pictochat.user.bday_month", "Month") -p.fields.user_bday_day = ProtoField.uint8("pictochat.user.bday_day", "Day") -p.fields.msg_start_len = ProtoField.uint8("pictochat.msg.start_len", "Total length") +dissect_msg_type[MSG_TYPE.MSG_BODY] = function (buffer, pinfo, tree) + local payload + buffer, payload = dissect_payload(buffer, pinfo, tree) + + payload:add_le(p.fields.unknown, buffer(0x00, 2)) + payload:add_le(p.fields.data_len, buffer(0x02, 1)) + local data_length = buffer(0x02, 1):le_uint() + + payload:add_le(p.fields.data_end, buffer(0x03, 1)) + -- This appears to be some kind of offset for indicating where to store the + -- current frame's data in a larger buffer. Messages sent in multiple parts + -- increment this value by 160 for each new original (p.fields.original == + -- True) message. + payload:add_le(p.fields.data_offset, buffer(0x04, 2)) + local data_offset = buffer(0x04, 2):le_uint() + payload:add_le(p.fields.unknown, buffer(0x06, 2)) -- usually 0 -local state = {} + buffer = buffer(0x08) + -- This appears to be the actual message data (the drawing) sent as an + -- array of 8x8 tiles. + payload:add(p.fields.data, buffer(0, data_length)) + if state.want_next == data_offset then + state.want_next = data_offset + data_length + state.data_remaining = state.data_remaining - data_length + state.buf:append(buffer(0, data_length):bytes()) + pinfo.cols.info = "Message body" + ByteArray.tvb(state.buf, "Complete message") + end + buffer = buffer(data_length) -function p.init() - local dt = DissectorTable.get("dslmp") - dt:add(GAMEID.PICTOCHAT, p) + payload:add_le(p.fields.data_sequence, buffer(0x00, 2)) + payload:add_le(p.fields.unknown, buffer(0x02, 2)) -- copy +end + +p.fields.status_addr = ProtoField.bytes("pictochat.status.addr", "Address", base.COLON) +dissect_msg_type[MSG_TYPE.STATUS] = function (buffer, pinfo, tree) + local payload + buffer, payload = dissect_payload(buffer, pinfo, tree) + + payload:add_le(p.fields.unknown, buffer(0x00, 2)) + payload:add_le(p.fields.unknown, buffer(0x02, 2)) + + buffer = buffer(0x04) + + for _ = 0, 15 do + -- payload:add(p.fields.status_addr, buffer(0, 6)) + add_addr_le(payload, p.fields.status_addr, buffer(0, 6)) + buffer = buffer(6) + end + + payload:add_le(p.fields.unknown, buffer(0x00, 2)) + payload:add_le(p.fields.unknown, buffer(0x02, 2)) end +p.fields.magic = ProtoField.bytes("pictochat.magic", "Magic") +p.fields.new = ProtoField.bool("pictochat.new", "New") +p.fields.msg_type = ProtoField.uint8("pictochat.msg_type", "Message type", base.DEC, MSG_TYPE_MAP) function p.dissector(buffer, pinfo, tree) + -- check magic + if buffer(0x00, 2):uint() ~= GAMEID.PICTOCHAT then return 0 end + local subtree = tree:add(p, buffer(), string.format("%s: %d bytes", p.description, buffer():len())) + local buffer_len = buffer():len() subtree:add_le(p.fields.magic, buffer(0x00, 2)) subtree:add_le(p.fields.new, buffer(0x02, 2)) subtree:add_le(p.fields.msg_type, buffer(0x04, 1)) local msg_type = buffer(0x04, 1):le_uint() + local msg_type_str = MSG_TYPE_MAP[msg_type] subtree:add_le(p.fields.unknown, buffer(0x05, 1)) - subtree:add_le(p.fields.unknown, buffer(0x06, 2)) - buffer = buffer(0x08) -- pretty wireshark shit pinfo.cols.protocol = p.name - - subtree:add_le(p.fields.payload_len, buffer(0x00, 2)) - local payload_length = buffer(0x00, 2):le_uint() - buffer = buffer(0x02) - local payload = subtree:add(buffer(0x00, payload_length), "Payload: " .. payload_length .. " bytes") - - if - msg_type == 48 -- user join - then - payload:add_le(p.fields.user_mac, buffer(0x0a, 6)) -- endianness is fucked - payload:add(p.fields.user_name, buffer(0x10, 20), buffer(0x10, 20):le_ustring()) - payload:add(p.fields.user_msg, buffer(0x24, 52), buffer(0x24, 52):le_ustring()) - payload:add_le(p.fields.user_color, buffer(0x58, 2)) - local bday = payload:add(buffer(0x5a, 2), string.format("Birthday: %02d/%02d", buffer(0x5a, 1):uint(), buffer(0x5b, 1):uint())) - bday:add(p.fields.user_bday_month, buffer(0x5a, 1)) - bday:add(p.fields.user_bday_day, buffer(0x5b, 1)) + if msg_type_str ~= nil then + pinfo.cols.info = msg_type_str end - if - msg_type == 10 -- msg start - then - payload:add_le(p.fields.msg_start_len, buffer(0x04, 2)) - - state.data_remaining = buffer(0x04, 2):le_uint() - state.buf = ByteArray.new() - state.want_next = 0 + if msg_type == 0 then + -- no more content + return buffer_len end - if - msg_type == 24 or -- msg end - msg_type == 86 -- msg body - then - payload:add_le(p.fields.unknown, buffer(0x00, 2)) - payload:add_le(p.fields.data_len, buffer(0x02, 1)) - local data_length = buffer(0x02, 1):le_uint() - - payload:add_le(p.fields.data_end, buffer(0x03, 1)) - -- This appears to be some kind of offset for indicating where to store the - -- current frame's data in a larger buffer. Messages sent in multiple parts - -- increment this value by 160 for each new original (p.fields.original == - -- True) message. - payload:add_le(p.fields.data_offset, buffer(0x04, 2)) - local data_offset = buffer(0x04, 2):le_uint() - payload:add_le(p.fields.unknown, buffer(0x06, 2)) -- usually 0 - - buffer = buffer(0x08) - -- This appears to be the actual message data (the drawing) sent as an - -- array of 8x8 tiles. - payload:add(p.fields.data, buffer(0, data_length)) - if state.want_next == data_offset then - state.want_next = data_offset + data_length - state.data_remaining = state.data_remaining - data_length - state.buf:append(buffer(0, data_length):bytes()) - pinfo.cols.info = "Message body" - ByteArray.tvb(state.buf, "Complete message") - end - buffer = buffer(data_length) - - payload:add_le(p.fields.data_sequence, buffer(0x00, 2)) - payload:add_le(p.fields.unknown, buffer(0x02, 2)) -- copy + subtree:add_le(p.fields.unknown, buffer(0x06, 2)) + buffer = buffer(0x08) + + local subdissector = dissect_msg_type[msg_type] + if subdissector ~= nil then + subdissector(buffer, pinfo, subtree) end end diff --git a/wireshark/util.lua b/wireshark/util.lua index 140308b..6051351 100644 --- a/wireshark/util.lua +++ b/wireshark/util.lua @@ -3,8 +3,88 @@ function bits(idx, len) return ((2^len)-1) * 2^idx end +function fix_addr_endianness(addr) + local fixed = ByteArray.new() + fixed:set_size(6) + fixed:set_index(0, string.byte(addr, 2)) + fixed:set_index(1, string.byte(addr, 1)) + fixed:set_index(2, string.byte(addr, 4)) + fixed:set_index(3, string.byte(addr, 3)) + fixed:set_index(4, string.byte(addr, 6)) + fixed:set_index(5, string.byte(addr, 5)) + return fixed:raw() +end + +room_user_addrs = { } +DS_SYSTEM_ADDRS = { + ["\x00\x00\x00\x00\x00\x00"] = "Null / empty", + ["\x00\x09\xbf\x11\x22\x33"] = "Default firmware MAC", + ["\x03\x09\xbf\x00\x00\x00"] = "Multiplayer CMD", + ["\x03\x09\xbf\x00\x00\x10"] = "Multiplayer Reply", + ["\x03\x09\xbf\x00\x00\x03"] = "Multiplayer ACK", + ["\x00\xf0\x77\x77\x77\x77"] = "Access point", + ["\xff\xff\xff\xff\xff\xff"] = "Broadcast", +} + +function get_addr_label(addr) + local label = DS_SYSTEM_ADDRS[addr] + if label ~= nil then + return label + end + + label = room_user_addrs[addr] + if label ~= nil then + return string.format("User: %s", label) + end + + return nil +end + +function add_addr(tree, field, buffer, value) + value = value or buffer:raw() + local treeitem = tree:add(field, buffer, value) + + local label = get_addr_label(value) + if label ~= nil then + treeitem:append_text(string.format(" (%s)", label)) + end + + return treeitem +end + +function add_addr_le(tree, field, buffer) + return add_addr(tree, field, buffer, fix_addr_endianness(buffer:raw())) +end + +function register_addr(addr, label) + room_user_addrs[addr] = label +end + +function register_addr_le(addr, label) + room_user_addrs[fix_addr_endianness(addr)] = label +end + GAMEID = { PICTOCHAT = 0xe603, MARIOKART = 0xbe01, } +PROFILE_COLOR_MAP = { + [0] = "greyish blue", + [1] = "brown", + [2] = "red", + [3] = "light pink", + [4] = "orange", + [5] = "yellow", + [6] = "lime", + [7] = "light green", + [8] = "dark green", + [9] = "turqoise", + [10] = "light blue", + [11] = "blue", + [12] = "dark blue", + [13] = "dark purple", + [14] = "light purple", + [15] = "dark pink", +} + diff --git a/wireshark/wireshark b/wireshark/wireshark index 94d64d9..ae07982 100755 --- a/wireshark/wireshark +++ b/wireshark/wireshark @@ -2,11 +2,7 @@ # simple wrapper to load lua scripts for DLT_USER0 here="$(dirname "$0")" exec wireshark \ - -X "lua_script:$here/pictochat.lua" \ - -X "lua_script:$here/ieee.lua" \ - -X "lua_script:$here/txhdr.lua" \ - -X "lua_script:$here/rxhdr.lua" \ - -X "lua_script:$here/melon.lua" \ + -X "lua_script:$here/main.lua" \ -o 'uat:user_dlts:"User 0 (DLT=147)","melon","","","",""' \ "$@" -- cgit v1.2.3