Skip to content

Instantly share code, notes, and snippets.

@schierlm
Last active May 6, 2017 19:00

Revisions

  1. schierlm created this gist May 6, 2017.
    37 changes: 37 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,37 @@
    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 :)
    350 changes: 350 additions & 0 deletions eulora.lua
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,350 @@
    ----------------------------------------
    -- 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)
    Binary file added sanitized.pcapng.xz
    Binary file not shown.