Skip to content

Instantly share code, notes, and snippets.

@DaniloNC
Last active April 22, 2022 20:35
Show Gist options
  • Save DaniloNC/4a5122cdfbb8ed5b5b1dfe2e588d5592 to your computer and use it in GitHub Desktop.
Save DaniloNC/4a5122cdfbb8ed5b5b1dfe2e588d5592 to your computer and use it in GitHub Desktop.
Wireshark dissector for Supercell protocol - no decryption support

Install dissector on Linux

mkdir -p  ~/.local/lib/wireshark/plugins
cd ~/.local/lib/wireshark/plugins
wget https://gist.githubusercontent.com/DaniloNC/4a5122cdfbb8ed5b5b1dfe2e588d5592/raw/84769e49834f870b51d32595c8868fec1b671bee/sc.lua

Note

Each game has its own protocol types, this dissector started from a protocol type for CoC but is being modified for BrawlStars

local debug_level = {
DISABLED = 0,
LEVEL_1 = 1,
LEVEL_2 = 2
}
local DEBUG = debug_level.LEVEL_1
local default_settings =
{
debug_level = debug_level.LEVEL_2,
enabled = true, -- whether this dissector is enabled or not
port = 9339, -- default TCP port number
max_msg_len = 65535, -- max length of message
-- subdissect = true, -- whether to call sub-dissector or not
-- subdiss_type = wtap.NETLINK, -- the encap we get the subdissector for
}
SC_HDR_SIZE = 7
local dprint = function() end
local dprint2 = function() end
local function resetDebugLevel()
if default_settings.debug_level > debug_level.DISABLED then
dprint = function(...)
print(...)
end
if default_settings.debug_level > debug_level.LEVEL_1 then
dprint2 = dprint
end
else
dprint = function() end
dprint2 = dprint
end
end
-- call it now
resetDebugLevel()
scdata_protocol = Proto("scdata", "Supercell Protocol - BrawlStars")
-- original list from https://github.com/clugh/cocdp/wiki/Protocol-Messages
local message_types = {
[ 10100 ] = "ClientHello",
[ 10101 ] = "Login",
[ 10102 ] = "LoginUsingSession",
[ 10103 ] = "CreateAccount",
[ 10107 ] = "ClientCapabilities",
[ 10108 ] = "Ping",
[ 10112 ] = "AuthenticationCheck",
[ 10113 ] = "SetDeviceToken",
[ 10116 ] = "ResetAccount",
[ 10117 ] = "ReportUser",
[ 10118 ] = "AccountSwitched",
[ 10150 ] = "AppleBillingRequest",
[ 10151 ] = "GoogleBillingRequest",
[ 10200 ] = "CreateAvatar",
[ 10201 ] = "SelectAvatar",
[ 10206 ] = "SendChatToAvatar",
[ 10212 ] = "ChangeAvatarName",
[ 10501 ] = "AcceptFriend",
[ 10502 ] = "AddFriend",
[ 10503 ] = "AskForAddableFriends",
[ 10504 ] = "AskForFriendList",
[ 10506 ] = "RemoveFriend",
[ 10507 ] = "AddFriendByEmail",
[ 10509 ] = "AddFriendByAvatarNameAndCode",
[ 10512 ] = "AskForPlayingGamecenterFriends",
[ 10513 ] = "AskForPlayingFacebookFriends",
[ 10599 ] = "xx_GetSuggestedFriends?",
[ 10901 ] = "AskForMailList",
[ 10904 ] = "TakeMailAttachments",
[ 14101 ] = "AttackResult",
[ 14102 ] = "EndClientTurn",
[ 14104 ] = "AskForTargetHomeList",
[ 14106 ] = "AttackHome",
[ 14108 ] = "ChangeHomeName",
[ 14113 ] = "VisitHome",
[ 14114 ] = "HomeBattleReplay",
[ 14123 ] = "AttackMatchedHome",
[ 14134 ] = "AttackNpc",
[ 14201 ] = "BindFacebookAccount",
[ 14211 ] = "UnbindFacebookAccount",
[ 14212 ] = "BindGamecenterAccount",
[ 14262 ] = "BindGoogleServiceAccount",
[ 14301 ] = "CreateAlliance",
[ 14302 ] = "AskForAllianceData",
[ 14303 ] = "AskForJoinableAlliancesList",
[ 14305 ] = "JoinAlliance",
[ 14306 ] = "ChangeAllianceMemberRole",
[ 14307 ] = "KickAllianceMember",
[ 14308 ] = "LeaveAlliance",
[ 14309 ] = "AskForAllianceUnitDonations",
[ 14310 ] = "DonateAllianceUnit",
[ 14315 ] = "ChatToAllianceStream",
[ 14316 ] = "ChangeAllianceSettings",
[ 14317 ] = "RequestJoinAlliance",
[ 14321 ] = "RespondToAllianceJoinRequest",
[ 14322 ] = "SendAllianceInvitation",
[ 14323 ] = "JoinAllianceUsingInvitation",
[ 14324 ] = "SearchAlliances",
[ 14325 ] = "AskForAvatarProfile",
[ 14330 ] = "SendAllianceMail",
[ 14331 ] = "HomeShareReplay",
[ 14401 ] = "AskForAllianceRankingList",
[ 14403 ] = "AskForAvatarRankingList",
[ 14404 ] = "AskForAvatarLocalRankingList",
[ 14405 ] = "AskForAvatarStream",
[ 14418 ] = "RemoveAvatarStreamEntry",
[ 14503 ] = "AskForLeagueMemberList",
[ 14715 ] = "SendGlobalChatLine",
[ 16000 ] = "LogicDeviceLinkCodeRequest",
[ 16001 ] = "LogicDeviceLinkMenuClosed",
[ 16002 ] = "LogicDeviceLinkEnterCode",
[ 16003 ] = "LogicDeviceLinkConfirmYes",
[ 20000 ] = "Encryption",
[ 20100 ] = "ServerHello",
[ 20101 ] = "CreateAccountResult",
[ 20103 ] = "LoginFailed",
[ 20104 ] = "LoginOk",
[ 20105 ] = "FriendList",
[ 20106 ] = "FriendListUpdate",
[ 20107 ] = "AddableFriends",
[ 20108 ] = "Pong",
[ 20109 ] = "FriendOnlineStatus",
[ 20110 ] = "FriendLoggedIn",
[ 20111 ] = "FriendLoggedOut",
[ 20117 ] = "ReportUserStatus",
[ 20118 ] = "ChatAccountBanStatus",
[ 20121 ] = "BillingRequestFailed",
[ 20151 ] = "AppleBillingProcessedByServer",
[ 20152 ] = "GoogleBillingProcessedByServer",
[ 20161 ] = "ShutdownStarted",
[ 20171 ] = "PersonalBreakStarted",
[ 20199 ] = "xx_FriendSuggestions?",
[ 20201 ] = "AvatarData",
[ 20202 ] = "CreateAvatarFailed",
[ 20203 ] = "CreateAvatarOk",
[ 20205 ] = "AvatarNameChangeFailed",
[ 20801 ] = "Notification",
[ 20903 ] = "MailList",
[ 24101 ] = "OwnHomeData",
[ 24103 ] = "AttackHomeFailed",
[ 24104 ] = "OutOfSync",
[ 24105 ] = "TargetHomeList",
[ 24106 ] = "AttackReportList",
[ 24107 ] = "EnemyHomeData",
[ 24109 ] = "HomeStatusList",
[ 24111 ] = "AvailableServerCommand",
[ 24112 ] = "WaitingToGoHome",
[ 24113 ] = "VisitedHomeData",
[ 24114 ] = "HomeBattleReplayData",
[ 24115 ] = "ServerError",
[ 24116 ] = "HomeBattleReplayFailed",
[ 24133 ] = "NpcData",
[ 24201 ] = "FacebookAccountBound",
[ 24202 ] = "FacebookAccountAlreadyBound",
[ 24211 ] = "GamecenterAccountBound",
[ 24212 ] = "GamecenterAccountAlreadyBound",
[ 24214 ] = "FacebookAccountUnbound",
[ 24261 ] = "GoogleServiceAccountBound",
[ 24262 ] = "GoogleServiceAccountAlreadyBound",
[ 24301 ] = "AllianceData",
[ 24302 ] = "AllianceJoinFailed",
[ 24303 ] = "AllianceJoinOk",
[ 24304 ] = "JoinableAllianceList",
[ 24305 ] = "AllianceLeaveOk",
[ 24306 ] = "ChangeAllianceMemberRoleOk",
[ 24307 ] = "KickAllianceMemberOk",
[ 24308 ] = "AllianceMember",
[ 24309 ] = "AllianceMemberRemoved",
[ 24310 ] = "AllianceList",
[ 24311 ] = "AllianceStream",
[ 24312 ] = "AllianceStreamEntry",
[ 24318 ] = "AllianceStreamEntryRemoved",
[ 24319 ] = "AllianceJoinRequestOk",
[ 24320 ] = "AllianceJoinRequestFailed",
[ 24321 ] = "AllianceInvitationSendFailed",
[ 24322 ] = "AllianceInvitationSentOk",
-- [ 24324 ] = "",
[ 24333 ] = "AllianceChangeFailed",
[ 24334 ] = "AvatarProfile",
-- [ 24335 ] = "",
-- [ 24402 ] = "",
[ 24404 ] = "AvatarLocalRankingList",
[ 24411 ] = "AvatarStream",
[ 24412 ] = "AvatarStreamEntry",
[ 24418 ] = "AvatarStreamEntryRemoved",
[ 24555 ] = "xx_FriendPresence?",
[ 24503 ] = "LeagueMemberList",
[ 24715 ] = "GlobalChatLine",
[ 25892 ] = "Disconnected",
[ 26002 ] = "LogicDeviceLinkCodeResponse",
[ 26003 ] = "LogicDeviceLinkNewDeviceLinked",
[ 26004 ] = "LogicDeviceLinkCodeDeactivated",
[ 26005 ] = "LogicDeviceLinkResponse",
[ 26007 ] = "LogicDeviceLinkDone",
[ 26008 ] = "LogicDeviceLinkError"
}
-- Header fields
-- packet_type = ProtoField.uint16("scdata.type", "Packet Type", base.DEC)
packet_type = ProtoField.new("Packet Type", "scdata.type", ftypes.UINT16, message_types, base.DEC)
packet_size = ProtoField.uint24("scdata.size", "Packet Size", base.DEC)
packet_version = ProtoField.uint16("scdata.version", "Packet Version", base.DEC)
-- Payload fields
packet_data = ProtoField.bytes("scdata.data", "Packet Data")
scdata_protocol.fields = {
packet_type,
packet_size,
packet_version,
packet_data
}
function scdata_protocol.dissector(tvbuf, pktinfo, root)
local pktlen = tvbuf:len()
local bytes_consumed = 0
while bytes_consumed < pktlen do
local pdu_bytes_consumed = scdata_protocol_pdu_dissect(tvbuf, pktinfo, root, bytes_consumed)
if pdu_bytes_consumed > 0 then
-- a pdu was successfully parsed
bytes_consumed = bytes_consumed + pdu_bytes_consumed
elseif pdu_bytes_consumed == 0 then
-- error parsing the pdu, ignore the rest of the packet
return 0
else
-- need more bytes to parse the pdu
-- set the desgment_offset to what we consumed so far
-- and desgment_len to how many bytes we need
pktinfo.desegment_offset = bytes_consumed
pdu_bytes_consumed = -pdu_bytes_consumed
pktinfo.desegment_len = pdu_bytes_consumed
return pktlen
end
end
return bytes_consumed
end
function get_message_size(tvbuf)
local size = tvbuf(2, 3):uint()
return size
end
-- The following function is used to dissect a PDU.
-- If the return is positive, it means that the PDU was dissected and consumed the return number of bytes
-- If the return is negative, it means it needs the return number of bytes to dissect the PDU
-- If the return is 0, it means it cannot dissect the PDU and the message should be ignored.
function scdata_protocol_pdu_dissect(tvbuf, pktinfo, root, offset)
local msglen = tvbuf:len() - offset
if msglen ~= tvbuf:reported_length_remaining(offset) then
dprint2("[*] SCDATA: Captured packet was shorter than original, can't reassmble")
return 0
end
if msglen < SC_HDR_SIZE then
dprint2("[*] SCDATA: Need more bytes to parse the header")
return -DESEGMENT_ONE_MORE_SEGMENT
end
local pdulen = tvbuf:range(offset + 2, 3):uint()
if (pdulen + SC_HDR_SIZE) > msglen then
dprint2("[*] SCDATA: Need more bytes to parse the packet")
return msglen - (pdulen + SC_HDR_SIZE)
end
-- We have enough bytes to parse a pdu
local subroot = root:add(scdata_protocol, tvbuf:range(offset, pdulen + SC_HDR_SIZE))
subroot:add(packet_type, tvbuf(offset, 2))
subroot:add(packet_size, tvbuf(offset + 2, 3))
subroot:add(packet_version, tvbuf(offset + 5, 2))
subroot:add(packet_data, tvbuf(offset + 7, pdulen))
return pdulen + SC_HDR_SIZE
end
local function enableDissector()
-- using DissectorTable:set() removes existing dissector(s), whereas the
-- DissectorTable:add() one adds ours before any existing ones, but
-- leaves the other ones alone, which is better
DissectorTable.get("tcp.port"):add(default_settings.port, scdata_protocol)
end
-- call it now, because we're enabled by default
enableDissector()
local function disableDissector()
DissectorTable.get("tcp.port"):remove(default_settings.port, scdata_protocol)
end
local debug_pref_enum = {
{ 1, "Disabled", debug_level.DISABLED },
{ 2, "Level 1", debug_level.LEVEL_1 },
{ 3, "Level 2", debug_level.LEVEL_2 },
}
scdata_protocol.prefs.enabled = Pref.bool("Enabled", default_settings.enabled, "Enable the scdata protocol dissector")
scdata_protocol.prefs.debug = Pref.enum("Debug", default_settings.debug, "Enable debug messages", debug_pref_enum)
function scdata_protocol.prefs_changed()
dprint2("scdata_protocol.prefs_changed")
default_settings.debug_level = scdata_protocol.prefs.debug
resetDebugLevel()
if default_settings.enabled ~= scdata_protocol.prefs.enabled then
default_settings.enabled = scdata_protocol.prefs.enabled
if sdefault_settings.enabled then
enableDissector()
else
disableDissector()
end
reload()
end
end
dprint2("SCDATA: Loaded")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment