Skip to content

Instantly share code, notes, and snippets.

@alvinhochun
Last active April 28, 2021 09:35
Show Gist options
  • Save alvinhochun/b7fb10bdfc7647c22847 to your computer and use it in GitHub Desktop.
Save alvinhochun/b7fb10bdfc7647c22847 to your computer and use it in GitHub Desktop.
Script to process NDS WiFi communication in Wireshark (WIP)
--
-- DeSmuME and NDS Local WiFi Dissector script
--
-- * Last Modified: 2014-06-06
-- * Author: Alvin Wong (alvinhochun, alvinhochun-at-gmail-com)
--
-- This LUA script is designed to be used with Wireshark to inspect the detail
-- of the NDS WiFi local multiplayer protocol.
--
-- This script contains of three parts:
--
-- 1. A dissector to dissect the udp multicast packets that DeSmuME emits,
-- and chains to the 802.11 dissector to dissect the 802.11 frames.
-- This is to be used with the "Ad-hoc" mode of DeSmuME. It is currently
-- assumed to be using port 3763 (different from existing code) but you
-- can change it.
-- 2. Post-dissectors to show some details of the Nintendo multiplayer WiFi
-- packets.
-- These works with both the above mode and normal WLAN capture, which
-- means you can also use this to analyse real 802.11 packets sent by real
-- NDS hardware.
-- 3. Utility functions to decode human-readable formats of some data.
--
-- Notes:
--
-- This is a work-in-progress. I originally wrote this script to help debug
-- the WiFi emulation of DeSmuME, but later noticed that it can be extended
-- to analyse also the real traffic of the NDS hardware, which in turn also
-- helps debugging DeSmuME.
--
-- The packets may contain a 4-byte FCS, so you may need to change the
-- settings for the IEEE 802.11 protocol to enable "Assume packets have
-- FCS".
--
-- Wireshark may identify some 802.11 packets to have LLC and/or SNA and
-- will indicate them as malformed frames, therefore you should disable
-- these two protocols before using this script.
--
-- TODO:
--
-- - Make the fields actual fields so that filters can work.
-- - Analyse the protocol more in-depth.
--
-- Credits:
--
-- - Stephen Stair (sgstair), for his early work on reverse-engineering the
-- NDS WiFi hardware and helping me.
-- - GBATEK, for the extensive documentation on NDS hardware.
-- - DeSmuME Team, for DeSmuME, of course.
-- - Luigi__ (?), for his work on DeSmuME WiFi emulation.
-- - Wireshark, for the awesome Wireshark software.
-- - http://dev-scene.com/NDS/Network_WMB_Beacons
-- - http://archive.today/PPjUl
--
-- Copyright:
--
-- Alvin Wong (c) 2014, All rights reserved.
-- Please contact the author for more information.
--
local dsmPort = 3763
--------------------------------------------------------------------------------
-- NDS magics
--------------------------------------------------------------------------------
-- The 4-byte field in the beacon frames which appears to differ depending on
-- the game, so I guess this might be a game id.
function gameId1_to_name(gid)
if gid == 0x25000000 then
return "Mario Kart DS"
elseif gid == 0x50014000 then
return "New Super Mario Bros."
elseif gid == 0x11004000 then
return "Super Mario 64 DS"
elseif gid == 0x24004000 then
return "Puyo Pop Fever"
end
return "<Unknown>"
end
--------------------------------------------------------------------------------
-- NDS local multiplayer WiFi :)
--------------------------------------------------------------------------------
local wlan_sa_f = Field.new("wlan.sa")
local wlan_da_f = Field.new("wlan.da")
local wlan_bssid_f = Field.new("wlan.bssid")
local wlan_type_subtype_f = Field.new("wlan.fc.type_subtype")
local wlan_seq_f = Field.new("wlan.seq")
local wlan_bi_f = Field.new("wlan_mgt.fixed.beacon")
local wlan_status_f = Field.new("wlan_mgt.fixed.status_code")
local wlan_aid_f = Field.new("wlan_mgt.fixed.aid")
local wlan_ssid_f = Field.new("wlan_mgt.ssid")
local wlan_tag_oui_f = Field.new("wlan_mgt.tag.oui")
local wlan_tag_vendor_f = Field.new("wlan_mgt.tag.vendor.data")
local data_f = Field.new("data.data")
local ndsDlPlayAd_proto = Proto("NDS-DLPLAY-AD", "NDS Download Play Advert")
function ndsDlPlayAd_proto_dissector(buf, pinfo, tree)
local subtree = tree:add(ndsDlPlayAd_proto, buf(), "NDS Download Play Advert (Segmented)")
subtree:add(buf(0, 4), "Game ID: 0x" .. buf(0, 4) .. ", Guessed: " .. gameId1_to_name(buf(0, 4):uint()))
if buf(4, 1):uint() == 0x02 then
subtree:add(buf(4, 1), "Last of Segmented Packets")
elseif buf(4, 1):uint() == 0x00 then
subtree:add(buf(4, 1), "Not Last of Segmented Packets")
else
subtree:add(buf(4, 1), "Unknown segment flag!")
end
subtree:add(buf(5, 1), "* Unknown header")
subtree:add(buf(6, 1), "Connected Players: " .. buf(6, 1):uint())
subtree:add(buf(7, 1), "Sequence Number: " .. buf(7, 1):uint())
subtree:add(buf(8, 2), "Checksum: 0x" .. buf(8, 2) .. " (Not checked)")
if buf(4, 1):uint() == 0x02 then
subtree:add(buf(10, 1), "Maximum Players Count: " .. buf(10, 1):uint())
else
subtree:add(buf(10, 1), "Sequence Number: " .. buf(10, 1):uint())
end
subtree:add(buf(11, 1), "Segmented Packet Count: " .. buf(11, 1):uint())
subtree:add(buf(12, 2), "Length: " .. buf(12, 2):le_uint())
subtree:add(buf(14, buf(12, 2):le_uint()), "Data")
end
local ndsWifiBrd_proto = Proto("NDSWIFI-BRD", "NDS Local Multiplayer Wifi Broadcast")
function ndsWifiBrd_proto.dissector(buf, pinfo, tree)
--local wlan_sa = wlan_sa_f()
--local wlan_da = wlan_da_f()
local wlan_isBeacon = (wlan_type_subtype_f().value == 0x08)
local wlan_seq = wlan_seq_f()
local wlan_bi = wlan_bi_f()
local wlan_tag_oui = wlan_tag_oui_f()
local wlan_tag_vendor = wlan_tag_vendor_f()
--local data = data_f()
if wlan_isBeacon and wlan_tag_oui.value == 0x0009bf and wlan_tag_vendor then
local dbuf = wlan_tag_vendor.range:tvb()
local subtree = tree:add(ndsWifiBrd_proto, dbuf(), "NDS local multiplayer WiFi game broadcast beacon")
pinfo.cols.protocol = "NDS/BRD"
pinfo.cols.info = "Game Broadcast, GID=" .. dbuf(9, 4) .. ", SID=" .. dbuf(13, 2) .. ", 802.11 SN=" .. wlan_seq.value .. ", BI=" .. wlan_bi.value
-- TODO: Figure out what're these headers
subtree:add(dbuf(0, 9), "* Unknown headers")
-- Seems to be the same for the same game (or ROM, at least)
-- Looks like this is used in the SSID of Association request
subtree:add(dbuf(9, 4), "Game ID: 0x" .. dbuf(9, 4) .. ", Guessed: " .. gameId1_to_name(dbuf(9, 4):uint()))
-- Looks like this is also used in the SSID of Association request
subtree:add(dbuf(13, 2), "Stream ID: 0x" .. dbuf(13, 2))
local size = dbuf(15, 1):uint()
subtree:add(dbuf(15, 1), "Length: " .. size)
-- TODO: Check whether these are true or just fsck these off
--local bittree = subtree:add(dbuf(16, 1), "Guessed bitfield")
--if dbuf(16, 1):bitfield(6) == 1 then
-- bittree:add(dbuf(16, 1), ".... ..1. = Download Play (Guessed)")
--else
-- bittree:add(dbuf(16, 1), ".... ..0. = Not Download Play (Guessed)")
--end
local brdTypeTree = subtree:add(dbuf(16, 1), "Type (guessed): ")
if dbuf(16, 1):uint() == 0x01 then
brdTypeTree:append_text("Multiplayer (0x01)")
elseif dbuf(16, 1):uint() == 0x0b then
brdTypeTree:append_text("Download Play (0x0b)")
else
brdTypeTree:append_text("Unknown Yet (0x" .. dbuf(16, 1) .. ")")
end
-- TODO: Figure out these crabs
subtree:add(dbuf(17, 4), "* Still more unknown headers")
if size > 0 then
subtree:add(dbuf(21, size), "Actual Data: " .. dbuf(21, size))
end
if dbuf(16, 1):uint() == 0x0b then
ndsDlPlayAd_proto_dissector(dbuf(21, size), pinfo, tree)
end
end
end
register_postdissector(ndsWifiBrd_proto)
local ndsWifi_proto = Proto("NDSWIFI-COMM", "NDS Local Multiplayer Wifi Communication")
function ndsWifi_proto.dissector(buf, pinfo, tree)
local wlan_sa = wlan_sa_f()
local wlan_da = wlan_da_f()
local wlan_bssid = wlan_bssid_f()
local wlan_type_subtype = wlan_type_subtype_f()
local wlan_isBeacon = (wlan_type_subtype.value == 0x08)
local wlan_seq = wlan_seq_f()
local wlan_ssid = wlan_ssid_f()
local wlan_status = wlan_status_f()
local wlan_aid = wlan_aid_f()
local data = data_f()
if wlan_sa and wlan_da and not wlan_isBeacon then --and tostring(wlan_sa):sub(1, 8) == "00:09:bf" then
if tostring(wlan_da):sub(1, 8) == "03:09:bf" then
local subtree = tree:add(ndsWifi_proto, "NDS local multiplayer WiFi communication")
-- TODO: Figure out what these actually are and confirm
if tostring(wlan_da):sub(10, 17) == "00:00:00" then
if wlan_type_subtype.value == 0x22 then -- Data + CF-Poll
pinfo.cols.protocol = "NDS/M>S"
pinfo.cols.info = "Master -> Slave, Data + Poll, 802.11 SN=" .. wlan_seq.value
subtree:append_text(" (Master -> Slave)")
local dbuf = data.range:tvb()
subtree:add(dbuf(0, 2), "Some sort of ID: 0x" .. dbuf(0, 2))
local stree = subtree:add(dbuf(2, 2), "Target Slaves: 0x" .. dbuf(3, 1) .. dbuf(2, 1))
for i = 7, 0, -1 do
local b = dbuf(2, 1):bitfield(i, 1)
if b == 1 then
stree:add(dbuf(2, 1), "Slave " .. (7 - i))
end
end
for i = 7, 0, -1 do
local b = dbuf(3, 1):bitfield(i, 1)
if b == 1 then
stree:add(dbuf(3, 1), "Slave " .. (15 - i))
end
end
else
pinfo.cols.protocol = "NDS/M>S?"
subtree:append_text(" (Master -> Slave) (unexpected format)")
error("Unexpected packet format")
end
elseif tostring(wlan_da):sub(10, 17) == "00:00:03" then
if wlan_type_subtype.value == 0x21 then -- Data + CF-Ack
pinfo.cols.protocol = "NDS/M:ack"
pinfo.cols.info = "Master out, Data + Ack, 802.11 SN=" .. wlan_seq.value
subtree:append_text(" (Master ack (guessed))")
else
pinfo.cols.protocol = "NDS/M:ack?"
subtree:append_text(" (Master ack (guessed)) (unexpected format)")
error("Unexpected packet format")
end
elseif tostring(wlan_da):sub(10, 17) == "00:00:10" then
if wlan_type_subtype.value == 0x21 then -- Data + CF-Ack
pinfo.cols.protocol = "NDS/S>M"
pinfo.cols.info = "Slave -> Master, Data + Ack, 802.11 SN=" .. wlan_seq.value
subtree:append_text(" (Slave -> Master)")
elseif wlan_type_subtype.value == 0x25 then -- Ack (No data)
pinfo.cols.protocol = "NDS/S>M"
pinfo.cols.info = "Slave -> Master, Ack (No data), 802.11 SN=" .. wlan_seq.value
subtree:append_text(" (Slave -> Master)")
else
pinfo.cols.protocol = "NDS/S>M?"
subtree:append_text(" (Slave -> Master) (unexpected format)")
error("Unexpected packet format")
end
else
pinfo.cols.protocol = "NDS/MP?"
subtree:append_text(" (unexpected format)")
error("Unexpected packet format")
end
if data then
subtree:add(data.range, "Data: " .. tostring(data.value))
end
elseif tostring(wlan_bssid):sub(1, 8) == "00:09:bf" then
if wlan_type_subtype.value == 0x0b then -- Authentication
pinfo.cols.protocol = "NDS/Auth"
elseif wlan_type_subtype.value == 0x00 then -- Assoc req
local subtree = tree:add(ndsWifi_proto, "NDS local multiplayer WiFi association request")
pinfo.cols.protocol = "NDS/Assoc"
if wlan_ssid then
local ssidBuf = wlan_ssid.range:tvb()
pinfo.cols.info = "Assoc. Request, GID=" .. ssidBuf(0, 4) .. ", SID=" .. ssidBuf(4, 2) .. ", 802.11 SN=" .. wlan_seq.value
-- See above
subtree:add(ssidBuf(0, 4), "Game ID: 0x" .. ssidBuf(0, 4) .. ", Guessed: " .. gameId1_to_name(ssidBuf(0, 4):uint()))
subtree:add(ssidBuf(4, 2), "Stream ID: 0x" .. ssidBuf(4, 2))
subtree:add(ssidBuf(6), "Additional Data: " .. ssidBuf(6))
else
subtree:append_text(" (missing SSID)")
error("Missing SSID")
end
elseif wlan_type_subtype.value == 0x01 then -- Assoc response
local subtree = tree:add(ndsWifi_proto, "NDS local multiplayer WiFi association response")
pinfo.cols.protocol = "NDS/Assoc"
if wlan_status and wlan_status.value == 0 then
local aidBuf = wlan_aid.range:tvb()
local slaveId = aidBuf(0, 1):bitfield(4, 4)
pinfo.cols.info = "Assoc. Response, Status=Successful, SlaveID=" .. slaveId .. ", 802.11 SN=" .. wlan_seq.value
subtree:add(aidBuf(0, 1), "Assigned Slave ID: " .. slaveId)
else
pinfo.cols.info = "Assoc. Response, Status=Unsuccessful, 802.11 SN=" .. wlan_seq.value
end
end
end
end
end
register_postdissector(ndsWifi_proto)
--------------------------------------------------------------------------------
-- DeSmuME NDSWIFI
--------------------------------------------------------------------------------
local wlan_dissector = Dissector.get("wlan")
local dsmAdhoc_proto = Proto("DeSmuME", "DeSmuME NDSWIFI")
function dsmAdhoc_proto.dissector(buf, pinfo, tree)
--if buf(0, 8):uint64() == 5639724267731306752 then
--if buf(8, 2):uint() == 1 then
pinfo.cols.protocol = "DeSmuME WIFI v1"
local subtree = tree:add(dsmAdhoc_proto, buf(0, 12), "DeSmuME NDSWIFI protocol v1")
subtree:add(buf(0, 8), "Magic: NDSWIFI.")
subtree:add(buf(8, 2), "Version: 0x" .. buf(8, 2))
local pkLen = buf(10, 2):le_uint() -- - 4
subtree:add(buf(10, 2), "Packet Length: " .. buf(10, 2):le_uint() .. " (including FCS)") -- - 4 = " .. pkLen)
wlan_dissector:call(buf(12, pkLen):tvb(), pinfo, tree)
--end
--end
end
local udp_table = DissectorTable.get("udp.port")
udp_table:add(dsmPort, dsmAdhoc_proto)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment