From cef0cbd29a903e023ad5730b98beceb40baa6bf0 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Wed, 28 Aug 2024 16:06:20 +0200 Subject: more WIP --- docs/notes.md | 5 +++ wireshark/ieee.lua | 96 +++++++++++++++++++++++++++++++++++++++++++++++++ wireshark/melon.lua | 20 +++++++---- wireshark/nifi.lua | 57 ----------------------------- wireshark/pictochat.lua | 63 ++++++++++---------------------- wireshark/rxhdr.lua | 50 ++++++++++++++++++++++++++ wireshark/txhdr.lua | 47 ++++++++++++++++++++++++ wireshark/util.lua | 5 +++ wireshark/wireshark | 6 ++-- 9 files changed, 239 insertions(+), 110 deletions(-) create mode 100644 wireshark/ieee.lua delete mode 100644 wireshark/nifi.lua create mode 100644 wireshark/rxhdr.lua create mode 100644 wireshark/txhdr.lua create mode 100644 wireshark/util.lua diff --git a/docs/notes.md b/docs/notes.md index 204ac57..c50a390 100644 --- a/docs/notes.md +++ b/docs/notes.md @@ -304,6 +304,11 @@ messages from the system that joined later) |e|`#0030fb` | |f|`#2800fb` | +## More interesting sources / link dump + +- +- + ## Unsure/notes - Is the endianness of the DS properly emulated? diff --git a/wireshark/ieee.lua b/wireshark/ieee.lua new file mode 100644 index 0000000..bbdc397 --- /dev/null +++ b/wireshark/ieee.lua @@ -0,0 +1,96 @@ +require "util" +local bit = require "bit" + +local p = Proto("ieee", "IEEE802.11 frame header") + +-- based off + +p.fields.ctl = ProtoField.new("Frame control", "ieee.ctl", ftypes.BYTES) +p.fields.ctl_ver = ProtoField.uint16("ieee.ctl.ver", "Protocol version", base.DEC, { + [0] = "Current", + [1] = "Reserved", + [2] = "Reserved", + [3] = "Reserved", +}, bits(0, 2)) +p.fields.ctl_type = ProtoField.uint16("ieee.ctl.type", "Type", base.DEC, { + [0] = "Management", + [1] = "Control", + [2] = "Data", + [3] = "Reserved", +}, bits(2, 2)) +p.fields.ctl_subtype = ProtoField.uint16("ieee.ctl.subtype", "Subtype", base.DEC, nil, bits(4, 4)) +p.fields.ctl_to_ds = ProtoField.bool("ieee.ctl.tods", "To DS", base.DEC, nil, bits(8)) +p.fields.ctl_from_ds = ProtoField.bool("ieee.ctl.fromds", "From DS", base.DEC, nil, bits(9)) +p.fields.ctl_fragment = ProtoField.bool("ieee.ctl.fragment", "More fragments", base.DEC, nil, bits(10)) +p.fields.ctl_retry = ProtoField.bool("ieee.ctl.retry", "Retry", base.DEC, nil, bits(11)) +p.fields.ctl_power = ProtoField.bool("ieee.ctl.power", "Power management", base.DEC, nil, bits(12)) +p.fields.ctl_data = ProtoField.bool("ieee.ctl.data", "More data", base.DEC, nil, bits(13)) +p.fields.ctl_wep = ProtoField.bool("ieee.ctl.wep", "WEP encrypt", base.DEC, nil, bits(14)) +p.fields.ctl_order = ProtoField.bool("ieee.ctl.order", "Order", base.DEC, nil, bits(15)) + +p.fields.duration = ProtoField.uint16("ieee.duration", "Duration / ID") +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.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)) + +p.fields.body = ProtoField.bytes("ieee.body", "Body") + +local pc_dissector = Dissector.get("pictochat") + +function p.dissector(buffer, pinfo, tree) + local buffer_len = buffer:len() + -- invalid + if buffer_len < 10 then return 0 end + + -- pretty wireshark shit + pinfo.cols.protocol = p.name + + -- MAC header is (usually) 0x18 bytes, but also sometimes contains values in + -- the trailer. The 0x18 here is so wireshark only highlights the MAC header + -- when clicking this item in the dissection tree. + local subtree = tree:add(p, buffer(0x00, 0x18), p.description) + local trailer_size = 0 + + 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) + ctl_tree:add_le(p.fields.ctl_subtype, buffer(0x00, 2)) + 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)) + ctl_tree:add_le(p.fields.ctl_retry, buffer(0x00, 2)) + ctl_tree:add_le(p.fields.ctl_power, buffer(0x00, 2)) + ctl_tree:add_le(p.fields.ctl_data, buffer(0x00, 2)) + ctl_tree:add_le(p.fields.ctl_wep, buffer(0x00, 2)) + 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)) + 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)) + + if ctl_type ~= 0 then + trailer_size = 4 -- Frame Check Sequence (FCS) (hardware-generated CRC32) + end + + buffer = buffer(0x18) -- seek forward + + local body_size = buffer:len() - trailer_size + subtree:add(p.fields.body, buffer(0, body_size)) + pc_dissector:call(buffer(0, body_size):tvb(), pinfo, tree) + + if trailer_size == 0 then + return buffer_len + end + + buffer = buffer(body_size) + + return buffer_len +end diff --git a/wireshark/melon.lua b/wireshark/melon.lua index a431781..265af01 100644 --- a/wireshark/melon.lua +++ b/wireshark/melon.lua @@ -1,5 +1,4 @@ -local p = Proto("melon", "MelonDS packet header") -local dt = DissectorTable.new("melon") +local p = Proto("melon", "MelonDS Ni-Fi header") p.fields.magic = ProtoField.uint32("melon.magic", "Magic", base.HEX) p.fields.src = ProtoField.int32("melon.src", "Instance ID", base.DEC) @@ -16,11 +15,15 @@ p.fields.timestamp = ProtoField.uint64("melon.timestamp", "Timestamp", base.DEC) local p_type_enum_field = Field.new("melon.type.enum") +local txhdr_dissector = Dissector.get("txhdr") +local rxhdr_dissector = Dissector.get("rxhdr") + function p.dissector(buffer, pinfo, tree) local header_size = 0x18 - + -- check buffer size + if buffer:len() < header_size then return 0 end -- check magic ("NIFI") - if buffer(0x00, 4):uint() ~= 0x4e494649 then return end + if buffer(0x00, 4):uint() ~= 0x4e494649 then return 0 end local subtree = tree:add(p, buffer(0, header_size), string.format("%s: %d bytes", p.description, header_size)) subtree:add(p.fields.magic, buffer(0x00, 4)) @@ -40,9 +43,12 @@ function p.dissector(buffer, pinfo, tree) pinfo.cols.src = string.format("instance %d", instance) pinfo.cols.info = p_type_enum_field().display - -- melonds packets always contain NIFI packets, I use 0 as the pattern - -- because this function doesn't seem to like nil - dt:try(0, buffer(header_size):tvb(), pinfo, tree) + -- melonds packets always contain NIFI packets + local next_dissector = txhdr_dissector + -- if instance ~= 0 then + -- next_dissector = rxhdr_dissector + -- end + next_dissector:call(buffer(header_size):tvb(), pinfo, tree) return header_size end diff --git a/wireshark/nifi.lua b/wireshark/nifi.lua deleted file mode 100644 index 94a563c..0000000 --- a/wireshark/nifi.lua +++ /dev/null @@ -1,57 +0,0 @@ -local p = Proto("nifi", "Ni-Fi header") -local dt = DissectorTable.new("nifi") - -p.fields.frame_type = ProtoField.uint16("nifi.type", "Frame type", base.DEC, { - [0] = "Normal", -- Used for actual messages, ack packets - [1] = "Announcement", -- TODO: send broadcast??? -}) -p.fields.original = ProtoField.bool("nifi.original", "Original") -- only 0 or 2 -p.fields.gameid = ProtoField.uint16("nifi.gid", "Game ID", base.HEX, { - [0xfffd] = "PictoChat (CMD)", - [0x7dcb] = "PictoChat (ACK)", - [0x5a5a] = "Mario Kart DS (CMD)", - [0x7c02] = "Mario Kart DS (ACK)", -}) -p.fields.magic = ProtoField.bytes("nifi.magic", "Magic") -p.fields.length = ProtoField.uint16("nifi.len", "Remaining message length") - -local melon_type_enum_field = Field.new("melon.type.enum") - -function p.init() - -- register nifi as a subdissector for melon - DissectorTable.get("melon"):add(0, p) -end - -function p.dissector(buffer, pinfo, tree) - local header_size = 12 - - local subtree = tree:add(p, buffer(0, header_size), string.format("%s: %d bytes", p.description, header_size)) - subtree:add_le(p.fields.frame_type, buffer(0x00, 2)) - subtree:add_le(p.fields.original, buffer(0x02, 2)) - local original = buffer(0x02, 2):le_uint() > 0 - - local melon_type = melon_type_enum_field()() - local gameid_buf = buffer(0x04, 4) - local gameid = 0 - if melon_type == 1 then -- CMD - gameid_buf = gameid_buf(2, 2) - elseif melon_type == 3 then -- ACK - gameid_buf = gameid_buf(0, 2) - end - gameid = gameid_buf():le_uint() - subtree:add_le(p.fields.gameid, gameid_buf) - - subtree:add_le(p.fields.magic, buffer(0x08, 2)) -- const 0x1401 - subtree:add_le(p.fields.length, buffer(0x0a, 2)) - local length = buffer(0x0a, 2):le_uint() - - -- pretty wireshark shit - pinfo.cols.protocol = p.name - pinfo.cols.info = string.format("GID %04x %s, %s", gameid, pinfo.cols.info, (original and "Original" or "Resend")) - - -- try to call subdissector for gameid - dt:try(gameid, buffer(header_size, length):tvb(), pinfo, tree) - - return header_size -end - diff --git a/wireshark/pictochat.lua b/wireshark/pictochat.lua index ba16ee0..79345f6 100644 --- a/wireshark/pictochat.lua +++ b/wireshark/pictochat.lua @@ -12,10 +12,6 @@ p.fields.msg_type = ProtoField.uint8("pictochat.msg_type", "Message type", base. [86] = "Message body", -- contains tile data for drawing [184] = "???", -- user leave?? }) -p.fields.length = ProtoField.uint16("pictochat.length", "Length") -p.fields.host = ProtoField.ether("pictochat.host", "Room host") -p.fields.src = ProtoField.ether("pictochat.src", "Source") -p.fields.dst = ProtoField.ether("pictochat.dst", "Destination") 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") @@ -49,45 +45,26 @@ 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") -local pc_src_field = Field.new("pictochat.src") -local pc_dst_field = Field.new("pictochat.dst") -local data_remaining = 0 - local state = {} -function p.init() - -- register pictochat as a subdissector for nifi - local dt = DissectorTable.get("nifi") - dt:add(0xfffd, p) -- CMD messages - -- dt:add(0x7dcb, p) -- ACK messages -end - function p.dissector(buffer, pinfo, tree) local subtree = tree:add(p, buffer(), string.format("%s: %d bytes", p.description, buffer():len())) - subtree:add_le(p.fields.unknown, buffer(0x00, 2)) - subtree:add_le(p.fields.unknown, buffer(0x02, 2)) - subtree:add_le(p.fields.dst, buffer(0x04, 6)) - subtree:add_le(p.fields.src, buffer(0x0a, 6)) - subtree:add_le(p.fields.host, buffer(0x10, 6)) - subtree:add_le(p.fields.sequence, buffer(0x16, 2)) - subtree:add_le(p.fields.unknown, buffer(0x18, 2)) - subtree:add_le(p.fields.unknown, buffer(0x1a, 2)) - subtree:add_le(p.fields.msg_type, buffer(0x1c, 1)) - local msg_type = buffer(0x1c, 1):le_uint() - subtree:add_le(p.fields.unknown, buffer(0x1d, 1)) - subtree:add_le(p.fields.unknown, buffer(0x1e, 2)) - buffer = buffer(0x20) + + subtree:add_le(p.fields.unknown, buffer(0x00, 2)) + subtree:add_le(p.fields.unknown, buffer(0x02, 2)) + subtree:add_le(p.fields.msg_type, buffer(0x04, 1)) + local msg_type = buffer(0x04, 1):le_uint() + 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 - pinfo.cols.src = tostring(pc_src_field()) - pinfo.cols.dst = tostring(pc_dst_field()) 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") - local buffer_next = buffer(payload_length) if msg_type == 48 -- user join @@ -105,10 +82,10 @@ function p.dissector(buffer, pinfo, tree) msg_type == 10 -- msg start then payload:add_le(p.fields.msg_start_len, buffer(0x04, 2)) - data_remaining = buffer(0x04, 2):le_uint() - local segment = buffer(0):bytes() - local buf = ByteArray.tvb(segment, "Complete message???") + state.data_remaining = buffer(0x04, 2):le_uint() + state.buf = ByteArray.new() + state.want_next = 0 end if @@ -119,32 +96,30 @@ function p.dissector(buffer, pinfo, tree) payload:add_le(p.fields.data_len, buffer(0x02, 1)) local data_length = buffer(0x02, 1):le_uint() - if data_remaining > 0 then - data_remaining = data_remaining - data_length - pinfo.cols.info = string.format("Message body [remaining 0x%04x (%d) bytes]", data_remaining, data_remaining) - end - 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(0x04, 2)) - buffer = buffer(0x06) end - - buffer = buffer_next -- after payload - subtree:add_le(p.fields.magic_trailer, buffer(0x02, 2)) -- const 0xb8b6 end diff --git a/wireshark/rxhdr.lua b/wireshark/rxhdr.lua new file mode 100644 index 0000000..3924603 --- /dev/null +++ b/wireshark/rxhdr.lua @@ -0,0 +1,50 @@ +require "util" + +local p = Proto("rxhdr", "Hardware RX header") + +-- based off + +p.fields.unknown = ProtoField.bytes("rxhdr.unknown", "Unknown") + +p.fields.flags = ProtoField.new("Flags", "rxhdr.flags", ftypes.BYTES) +p.fields.flag_type = ProtoField.uint16("rxhdr.flags.type", "Frame type", base.DEC, nil, bits(0, 4)) +p.fields.flag_more = ProtoField.bool("rxhdr.flags.more", "More fragments", base.DEC, nil, bits(8)) + +p.fields.new = ProtoField.bool("rxhdr.new", "New frame") +p.fields.magic = ProtoField.bytes("rxhdr.magic", "Magic") +p.fields.channel = ProtoField.uint8("rxhdr.channel", "802.11 channel") + +p.fields.rate = ProtoField.uint16("rxhdr.rate", "Transfer rate", base.HEX, { + [0x0a] = "1 Mbit/s", + [0x14] = "2 Mbit/s", +}) +p.fields.length = ProtoField.uint16("rxhdr.len", "Remaining message length") + +local ieee_dissector = Dissector.get("ieee") + +function p.dissector(buffer, pinfo, tree) + local header_size = 12 + -- check buffer size + if buffer:len() < header_size then return 0 end + + local subtree = tree:add(p, buffer(0, header_size), string.format("%s: %d bytes", p.description, header_size)) + + local flags_tree = subtree:add(p.fields.flags, buffer(0x00, 2)) + flags_tree:add_le(p.fields.flag_type, buffer(0x00, 2)) + flags_tree:add_le(p.fields.flag_more, buffer(0x00, 2)) + + subtree:add(p.fields.unknown, buffer(0x02, 2)) + subtree:add(p.fields.unknown, buffer(0x04, 2)) + + subtree:add_le(p.fields.rate, buffer(0x06, 2)) + subtree:add_le(p.fields.length, buffer(0x08, 2)) + local length = buffer(0x08, 2):le_uint() + + -- pretty wireshark shit + pinfo.cols.protocol = p.name + + ieee_dissector:call(buffer(header_size, length):tvb(), pinfo, tree) + + return header_size +end + diff --git a/wireshark/txhdr.lua b/wireshark/txhdr.lua new file mode 100644 index 0000000..fc8db21 --- /dev/null +++ b/wireshark/txhdr.lua @@ -0,0 +1,47 @@ +local p = Proto("txhdr", "Hardware TX header") + +-- based off + +p.fields.unknown = ProtoField.bytes("txhdr.unknown", "Unknown") + +p.fields.status = ProtoField.uint16("txhdr.status", "Status", base.DEC, { + [0x00] = "Retrying?", + [0x01] = "OK", + [0x03] = "Failed", + [0x05] = "Failed", +}, 0x00ff) +p.fields.new = ProtoField.bool("txhdr.new", "New frame") +p.fields.rate = ProtoField.uint8("txhdr.rate", "Transfer rate", base.HEX, { + [0x0a] = "1 Mbit/s", + [0x14] = "2 Mbit/s", +}) +p.fields.channel = ProtoField.uint8("txhdr.channel", "802.11 channel") +p.fields.length = ProtoField.uint16("txhdr.len", "Remaining message length") + +local ieee_dissector = Dissector.get("ieee") + +function p.dissector(buffer, pinfo, tree) + local header_size = 12 + -- check buffer size + if buffer:len() < header_size then return 0 end + + local subtree = tree:add(p, buffer(0, header_size), string.format("%s: %d bytes", p.description, header_size)) + + subtree:add_le(p.fields.status, buffer(0x00, 2)) + subtree:add_le(p.fields.new, buffer(0x02, 2)) + subtree:add_le(p.fields.unknown, buffer(0x04, 1)) + subtree:add_le(p.fields.unknown, buffer(0x05, 1)) + subtree:add_le(p.fields.unknown, buffer(0x06, 2)) + subtree:add_le(p.fields.rate, buffer(0x08, 1)) + subtree:add_le(p.fields.channel, buffer(0x09, 1)) + subtree:add_le(p.fields.length, buffer(0x0a, 2)) + local length = buffer(0x0a, 2):le_uint() + + -- pretty wireshark shit + pinfo.cols.protocol = p.name + + ieee_dissector:call(buffer(header_size, length):tvb(), pinfo, tree) + + return header_size +end + diff --git a/wireshark/util.lua b/wireshark/util.lua new file mode 100644 index 0000000..e23f958 --- /dev/null +++ b/wireshark/util.lua @@ -0,0 +1,5 @@ +function bits(idx, len) + len = len or 1 + return ((2^len)-1) * 2^idx +end + diff --git a/wireshark/wireshark b/wireshark/wireshark index d6a285b..94d64d9 100755 --- a/wireshark/wireshark +++ b/wireshark/wireshark @@ -2,9 +2,11 @@ # simple wrapper to load lua scripts for DLT_USER0 here="$(dirname "$0")" exec wireshark \ - -X "lua_script:$here/melon.lua" \ - -X "lua_script:$here/nifi.lua" \ -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" \ -o 'uat:user_dlts:"User 0 (DLT=147)","melon","","","",""' \ "$@" -- cgit v1.2.3