Last active
April 28, 2021 09:35
-
-
Save alvinhochun/b7fb10bdfc7647c22847 to your computer and use it in GitHub Desktop.
Script to process NDS WiFi communication in Wireshark (WIP)
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
-- | |
-- 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