Skip to content

Instantly share code, notes, and snippets.

@schierlm
Last active May 6, 2017 19:00
Show Gist options
  • Save schierlm/a10b89e011f8baf646cb0cb674a48241 to your computer and use it in GitHub Desktop.
Save schierlm/a10b89e011f8baf646cb0cb674a48241 to your computer and use it in GitHub Desktop.
Eulora Protocol dissector for Wireshark

Eulora Protocol dissector for Wireshark

Usage

Start Wireshark like this:

wireshark -X lua_script:/path/to/eulora.lua

Then capture or load a capture file. Eulora packets will be automatically dissected if they are on UDP port 13331, else use the "Decode as..." option in context menu

Status

Single packets and multipackets are supported; fragmented packets are not (yet) supported.

Detail dissectors are available for the following message types:

    [1] = "PING",
    [2] = "AUTHENTICATE",
    [3] = "PREAUTHENTICATE",
    [4] = "PREAUTHAPPROVED",
    [5] = "AUTHAPPROVED",
    [7] = "DISCONNECT",
    [9] = "CHANNEL_JOIN",
    [10] = "CHANNEL_JOINED",
    [13] = "USERCMD",
    [14] = "SYSTEM",
    [20] = "USERACTION",

For other message types, only the message type and the raw hex data are shown (so far).

See below for a sanitized .pcap file - all AUTHENTICATE messages have been removed, but not their replies.

If you'd like certain message types to be added, feel free to comment here :)

----------------------------------------
-- Wireshark Lua dissector for Eulora protocol
--
-- author: Michael Schierl <schierlm at gmx dot de>, or <mihi> @ freenode
--
-- Based on the Lua example dissector script by Hadriel Kaplan <hadrielk at yahoo dot com> which is
-- Copyright (c) 2014, Hadriel Kaplan
-- This code is in the Public Domain, or the BSD (3 clause) license if Public Domain does not apply
-- in your country.
--
----------------------------------------
local eulora = Proto("eulora","Eulora Protocol")
local msgtypes = {
[1] = "PING",
[2] = "AUTHENTICATE",
[3] = "PREAUTHENTICATE",
[4] = "PREAUTHAPPROVED",
[5] = "AUTHAPPROVED",
[6] = "AUTHREJECTED",
[7] = "DISCONNECT",
[8] = "CHAT",
[9] = "CHANNEL_JOIN",
[10] = "CHANNEL_JOINED",
[11] = "CHANNEL_LEAVE",
[12] = "GUILDCMD",
[13] = "USERCMD",
[14] = "SYSTEM",
[15] = "CHARREJECT",
[16] = "DEAD_RECKONING",
[17] = "FORCE_POSITION",
[18] = "CELPERSIST",
[19] = "CONFIRMQUESTION",
[20] = "USERACTION",
[21] = "ADMINCMD",
[22] = "GUIINTERACT",
[23] = "GUIINVENTORY",
[24] = "VIEW_ITEM",
[25] = "VIEW_CONTAINER",
[26] = "VIEW_SKETCH",
[27] = "VIEW_ACTION_LOCATION",
[28] = "READ_BOOK",
[29] = "WRITE_BOOK",
[30] = "UPDATE_ITEM",
[31] = "MODE",
[32] = "WEATHER",
[33] = "NEWSECTOR",
[34] = "GUIGUILD",
[35] = "EQUIPMENT",
[36] = "GUIEXCHANGE",
[37] = "EXCHANGE_REQUEST",
[38] = "EXCHANGE_ADD_ITEM",
[39] = "EXCHANGE_REMOVE_ITEM",
[40] = "EXCHANGE_ACCEPT",
[41] = "EXCHANGE_STATUS",
[42] = "EXCHANGE_END",
[43] = "EXCHANGE_AUTOGIVE",
[44] = "EXCHANGE_MONEY",
[45] = "GUIMERCHANT",
[46] = "GUISTORAGE",
[47] = "GROUPCMD",
[48] = "GUIGROUP",
[49] = "STATDRUPDATE",
[50] = "SPELL_BOOK",
[51] = "GLYPH_REQUEST",
[52] = "GLYPH_ASSEMBLE",
[53] = "PURIFY_GLYPH",
[54] = "SPELL_CAST",
[55] = "SPELL_CANCEL",
[56] = "EFFECT",
[57] = "EFFECT_STOP",
[58] = "NPCAUTHENT",
[59] = "NPCLIST",
[60] = "GUITARGETUPDATE",
[61] = "MAPLIST",
[62] = "NPCCOMMANDLIST",
[63] = "NPCREADY",
[64] = "ALLENTITYPOS",
[65] = "PERSIST_ALL_ENTITIES",
[66] = "NEW_NPC",
[67] = "PETITION",
[68] = "MSGSTRINGS",
[69] = "CHARACTERDATA",
[70] = "AUTHCHARACTER",
[71] = "AUTHCHARACTERAPPROVED",
[72] = "CHAR_CREATE_CP",
[73] = "COMBATEVENT",
[74] = "LOOT",
[75] = "LOOTITEM",
[76] = "LOOTREMOVE",
[77] = "GUISKILL",
[78] = "OVERRIDEACTION",
[79] = "QUESTLIST",
[80] = "QUESTINFO",
[81] = "GMGUI",
[82] = "WORKCMD",
[83] = "BUDDY_LIST",
[84] = "BUDDY_STATUS",
[85] = "MOTD",
[86] = "MOTDREQUEST",
[87] = "QUESTION",
[88] = "QUESTIONRESPONSE",
[89] = "SLOT_MOVEMENT",
[90] = "QUESTIONCANCEL",
[91] = "GUILDMOTDSET",
[92] = "PLAYSOUND",
[93] = "PLAYVOICE",
[94] = "CHARACTERDETAILS",
[95] = "CHARDETAILSREQUEST",
[96] = "CHARDESCUPDATE",
[97] = "FACTION_INFO",
[98] = "QUESTREWARD",
[99] = "NAMECHANGE",
[100] = "GUILDCHANGE",
[101] = "LOCKPICK",
[102] = "GMSPAWNITEMS",
[103] = "GMSPAWNTYPES",
[104] = "GMSPAWNITEM",
[105] = "ADVICE",
[106] = "ACTIVEMAGIC",
[107] = "GROUPCHANGE",
[108] = "MAPACTION",
[109] = "CLIENTSTATUS",
[110] = "TUTORIAL",
[111] = "BANKING",
[112] = "CMDDROP",
[113] = "REQUESTMOVEMENTS",
[114] = "MOVEINFO",
[115] = "MOVEMOD",
[116] = "MOVELOCK",
[117] = "CHAR_DELETE",
[118] = "CHAR_CREATE_PARENTS",
[119] = "CHAR_CREATE_CHILDHOOD",
[120] = "CHAR_CREATE_LIFEEVENTS",
[121] = "CHAR_CREATE_UPLOAD",
[122] = "CHAR_CREATE_VERIFY",
[123] = "CHAR_CREATE_NAME",
[124] = "PERSIST_WORLD_REQUEST",
[125] = "PERSIST_WORLD",
[126] = "PERSIST_ACTOR_REQUEST",
[127] = "PERSIST_ACTOR",
[128] = "PERSIST_ITEM",
[129] = "PERSIST_ACTIONLOCATION",
[130] = "PERSIST_ALL",
[131] = "REMOVE_OBJECT",
[132] = "CHANGE_TRAIT",
[133] = "DAMAGE_EVENT",
[134] = "DEATH_EVENT",
[135] = "TARGET_EVENT",
[136] = "ZPOINT_EVENT",
[137] = "BUY_EVENT",
[138] = "SELL_EVENT",
[139] = "PICKUP_EVENT",
[140] = "DROP_EVENT",
[141] = "LOOT_EVENT",
[142] = "CONNECT_EVENT",
[143] = "MOVEMENT_EVENT",
[144] = "GENERIC_EVENT",
[145] = "SOUND_EVENT",
[146] = "CHAR_CREATE_TRAITS",
[147] = "STATS",
[148] = "PET_COMMAND",
[149] = "PET_SKILL",
[150] = "CRAFT_INFO",
[151] = "PETITION_REQUEST",
[152] = "HEART_BEAT",
[153] = "NPC_COMMAND",
[154] = "MINIGAME_STARTSTOP",
[155] = "MINIGAME_BOARD",
[156] = "MINIGAME_UPDATE",
[157] = "ENTRANCE",
[158] = "GMEVENT_LIST",
[159] = "GMEVENT_INFO",
[160] = "SEQUENCE",
[161] = "NPCRACELIST",
[162] = "INTRODUCTION",
[163] = "CACHEFILE",
[164] = "DIALOG_MENU",
[165] = "SIMPLE_STRING",
[166] = "ORDEREDTEST",
[167] = "GENERICCMD",
[168] = "CRAFT_CANCEL",
[169] = "MUSICAL_SHEET",
[170] = "PLAY_SONG",
[171] = "STOP_SONG",
[172] = "SIMPLE_RENDER_MESH",
[173] = "NPC_WORKDONE",
[174] = "PATH_NETWORK",
[175] = "LOCATION",
[176] = "MECS_ACTIVATE",
[177] = "NPC_DELETED"
}
local pf_msgtype = ProtoField.uint8("eulora.msgtype", "Message type", base.DEC, msgtypes)
local pf_ping_id = ProtoField.uint32("eulora.ping.id", "ID", base.HEX)
local pf_ping_flags = ProtoField.uint8("eulora.ping.flags", "Flags", base.HEX)
local pf_auth_version = ProtoField.uint32("eulora.auth.version", "Version", base.DEC)
local pf_auth_user = ProtoField.stringz("eulora.auth.user", "User name", base.ASCII)
eulora.fields = { pf_msgtype, pf_ping_id, pf_ping_flags, pf_auth_version, pf_auth_user }
local msgtype_field = Field.new("eulora.msgtype")
local ping_id_field = Field.new("eulora.ping.id")
local ping_flags_field = Field.new("eulora.ping.flags")
local function getStringLength(buffer, offset)
return buffer(offset):stringz():len()
end
local function parseString(ctx, label)
local val = ctx.tvbuf:range(ctx.offs):stringz()
ctx.parent:add(ctx.tvbuf:range(ctx.offs, val:len()), label, val)
ctx.offs = ctx.offs + val:len() + 1
end
local function dissectPacket(tvbuf,pktinfo,tree)
local pktlen = tvbuf:len()
if pktlen < 15 then
tree:add("Packet too short");
return
end
local msgsize = tvbuf:range(8,4):le_uint()
local pktsize = tvbuf:range(12,2):le_uint()
local msgflags = tvbuf:range(14,1):uint()
tree:add(tvbuf:range(0,4), string.format("Packet ID: 0x%08x", tvbuf:range(0,4):le_uint()))
tree:add(tvbuf:range(4,4), "Offset:", tvbuf:range(4,4):le_uint())
tree:add(tvbuf:range(8,4), "Message Size:", msgsize)
tree:add(tvbuf:range(12,2), "Packet Size:", pktsize)
tree:add(tvbuf:range(14,1), string.format("Flags: 0x%02x", msgflags))
if pktlen == 15 and pktsize == 0 then
pktinfo.cols.info:set("ACK packet")
return
end
if msgsize ~= pktsize then
pktinfo.cols.info:set("Fragmented packet [not supported yet]")
return
end
if pktlen < 18 or pktlen ~= msgsize + 15 then
return
end
if msgflags == 2 or msgflags == 3 then
local offs=15
while pktlen > offs + 14 do
local mpsize = tvbuf:range(offs+12,2):le_uint()
local subpacket = tree:add("Subpacket")
dissectPacket(tvbuf:range(offs,mpsize+15):tvb(),pktinfo,subpacket)
offs = offs + 15 + mpsize
end
pktinfo.cols.info:set("Multipacket")
return
end
tree:add(pf_msgtype, tvbuf:range(15,1))
local msgtype_value = tvbuf:range(15,1):uint()
pktinfo.cols.info:set(msgtype_field().display)
local payloadsize_value = tvbuf:range(16,2):le_uint()
tree:add(tvbuf:range(16,2), "Payload size:", payloadsize_value)
if payloadsize_value ~= msgsize-3 then
return
end
if msgtype_value == 1 and payloadsize_value == 5 then
local payload_tree = tree:add(tvbuf:range(18, 5), "PING Payload")
payload_tree:add_le(pf_ping_id, tvbuf:range(18,4))
payload_tree:add(pf_ping_flags, tvbuf:range(22,1))
pktinfo.cols.info:append(": "..ping_id_field().display.." ["..ping_flags_field().display.."]")
elseif msgtype_value == 2 then
local payload_tree = tree:add(tvbuf:range(18, payloadsize_value), "AUTH Payload")
payload_tree:add_le(pf_auth_version, tvbuf:range(18,4))
local l = getStringLength(tvbuf, 22)
payload_tree:add(pf_auth_user, tvbuf:range(22, l))
local ctx = {offs = 22 + l + 1, tvbuf = tvbuf, parent = payload_tree}
parseString(ctx, "Password:")
parseString(ctx, "Operating System:")
parseString(ctx, "Graphics Card:")
parseString(ctx, "Graphics Version:")
parseString(ctx, "Password256:")
pktinfo.cols.info:append(": "..tvbuf:range(22):stringz())
elseif msgtype_value == 3 and payloadsize_value == 4 then
local payload_tree = tree:add(tvbuf:range(18, 4), "PREAUTH Payload")
payload_tree:add(tvbuf:range(18,4), "Net version: ", tvbuf:range(18,4):le_uint())
elseif msgtype_value == 4 and payloadsize_value == 4 then
local payload_tree = tree:add(tvbuf:range(18, 4), "PREAUTH_APPROVED Payload")
payload_tree:add(tvbuf:range(18,4), "Client number: ", tvbuf:range(18,4):le_uint())
elseif msgtype_value == 5 and payloadsize_value > 8 then
local payload_tree = tree:add(tvbuf:range(18, payloadsize_value), "AUTH_APPROVED Payload")
payload_tree:add(tvbuf:range(18,4), "ClientValidToken: ", tvbuf:range(18,4):le_uint())
payload_tree:add(tvbuf:range(22,4), "Player ID: ", tvbuf:range(22,4):le_uint())
payload_tree:add(tvbuf:range(26,1), "Number of Characters: ", tvbuf:range(26,1):uint())
local ctx = {offs = 27, tvbuf = tvbuf, parent = payload_tree}
while ctx.offs < 18 + payloadsize_value do
parseString(ctx, "Value:")
end
elseif msgtype_value == 7 and payloadsize_value > 4 then
local payload_tree = tree:add(tvbuf:range(18, payloadsize_value), "DISCONECT Payload")
payload_tree:add(tvbuf:range(18,4), "Actor: ", tvbuf:range(18,4):le_uint())
parseString({offs = 22, tvbuf = tvbuf, parent = payload_tree}, "Reason:")
pktinfo.cols.info:append(": "..tvbuf:range(22):stringz())
elseif msgtype_value == 9 then
local payload_tree = tree:add(tvbuf:range(18, payloadsize_value), "CHANNEL_JOIN Payload")
parseString({offs = 18, tvbuf = tvbuf, parent = payload_tree}, "Channel:")
pktinfo.cols.info:append(": "..tvbuf:range(18):stringz())
elseif msgtype_value == 10 then
local payload_tree = tree:add(tvbuf:range(18, payloadsize_value), "CHANNEL_JOINED Payload")
local ctx = {offs = 18, tvbuf = tvbuf, parent = payload_tree}
parseString(ctx, "Channel:")
payload_tree:add(tvbuf:range(ctx.offs, 2), "Channel ID:", tvbuf:range(ctx.offs, 2):le_uint())
pktinfo.cols.info:append(": "..tvbuf:range(18):stringz())
elseif msgtype_value == 13 then
local payload_tree = tree:add(tvbuf:range(18, payloadsize_value), "USERCMD Payload")
parseString({offs = 18, tvbuf = tvbuf, parent = payload_tree}, "Command:")
pktinfo.cols.info:append(": "..tvbuf:range(18):stringz())
elseif msgtype_value == 14 and payloadsize_value > 4 then
local payload_tree = tree:add(tvbuf:range(18, payloadsize_value), "SYSTEM Payload")
payload_tree:add(tvbuf:range(18,4), "Type: ", tvbuf:range(18,4):le_uint())
parseString({offs = 22, tvbuf = tvbuf, parent = payload_tree}, "Message:")
pktinfo.cols.info:append(": "..tvbuf:range(22):stringz())
elseif msgtype_value == 20 and payloadsize_value > 5 then
local payload_tree = tree:add(tvbuf:range(18, payloadsize_value), "USERACTION Payload")
payload_tree:add(tvbuf:range(18,4), "Target: ", tvbuf:range(18,4):le_uint())
local ctx = {offs = 22, tvbuf = tvbuf, parent = payload_tree}
parseString(ctx, "Action:")
parseString(ctx, "Default behaviors:")
pktinfo.cols.info:append(": "..tvbuf:range(22):stringz())
elseif payloadsize_value == 0 then
tree:add(tvbuf:range(18, 0), "Empty Payload")
else
tree:add(tvbuf:range(18, payloadsize_value), "Payload")
pktinfo.cols.info:append(": [details not supported yet]")
end
end
function eulora.dissector(tvbuf,pktinfo,root)
pktinfo.cols.protocol:set("Eulora")
local pktlen = tvbuf:reported_length_remaining()
local tree = root:add(eulora, tvbuf:range(0,pktlen))
dissectPacket(tvbuf:range(0,pktlen):tvb(),pktinfo,tree)
return pktlen
end
DissectorTable.get("udp.port"):add(13331, eulora)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment