aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/notes.md9
-rw-r--r--wireshark/ethers8
-rw-r--r--wireshark/ieee.lua17
-rw-r--r--wireshark/main.lua9
-rw-r--r--wireshark/pictochat.lua235
-rw-r--r--wireshark/util.lua80
-rwxr-xr-xwireshark/wireshark6
7 files changed, 238 insertions, 126 deletions
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
+<!-- #### MAC addresses
-The following table of addresses may be visible in the packet captures. An
-[ethers](../wireshark/ethers) file is provided in the wireshark folder, which
-may be symlinked to `~/.config/wireshark/ethers` to make Wireshark resolve the
-MAC addresses to human-readable names.
+The following table of addresses may be visible in the packet captures.
|address|label|source|
|-|-|-|
@@ -217,7 +214,7 @@ MAC addresses to human-readable names.
In melonDS's settings, the firmware MAC addresses for `lork` and `lork2` are
set to `11:00:de:ad:be:ef` and `22:00:de:ad:be:ef` respectively, but these
always get mangled and sent as the addresses noted in the table above. I have
-no idea why this happens.
+no idea why this happens. -->
#### 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","","","",""' \
"$@"