From 6ffd75cb0a4abbc4c20956abe0e5ecc472d7f3cf Mon Sep 17 00:00:00 2001 From: wisp3rwind <17089248+wisp3rwind@users.noreply.github.com> Date: Mon, 30 Sep 2024 17:20:21 +0200 Subject: [PATCH 1/2] fixes for recent spotify and wireshark 4.2 - GRegex -> rex_pcre2 - replace the removed package.prepend_path - add some more details to the info column - add a PID suffix to pcap filenames to avoid concurrent writes from different processes corrupting them --- dissect/dissect.sh | 2 +- .../protobuf_dissector/modules/ast/ast.lua | 4 +- .../protobuf_dissector/modules/compiler.lua | 4 +- .../modules/front_end/cursor.lua | 4 +- .../modules/front_end/file_reader.lua | 6 +-- .../modules/front_end/lexer.lua | 14 +++--- .../modules/front_end/parser.lua | 4 +- .../modules/front_end/token.lua | 4 +- dissect/protobuf_dissector/modules/prefs.lua | 10 ++++- dissect/protobuf_dissector/modules/syntax.lua | 4 +- dissect/protobuf_dissector/protobuf.lua | 3 +- dissect/spotify.lua | 44 ++++++++++++++++--- dump/dump.c | 10 ++++- 13 files changed, 80 insertions(+), 33 deletions(-) diff --git a/dissect/dissect.sh b/dissect/dissect.sh index d05fdf3..f00a708 100755 --- a/dissect/dissect.sh +++ b/dissect/dissect.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -eux -exec wireshark-gtk $1 \ +exec wireshark $1 \ -X lua_script:protobuf_dissector/protobuf.lua \ -X lua_script:spotify.lua \ -X lua_script:mercury.lua \ diff --git a/dissect/protobuf_dissector/modules/ast/ast.lua b/dissect/protobuf_dissector/modules/ast/ast.lua index 2eb4824..76fb15b 100644 --- a/dissect/protobuf_dissector/modules/ast/ast.lua +++ b/dissect/protobuf_dissector/modules/ast/ast.lua @@ -62,8 +62,8 @@ if not _G['protbuf_dissector'] then return end -- make sure wireshark is new enough -if not GRegex then - return nil, "Wireshark is too old: no GRegex library" +if not rex_pcre2 then + return nil, "Wireshark is too old: no rex_pcre2 library" end diff --git a/dissect/protobuf_dissector/modules/compiler.lua b/dissect/protobuf_dissector/modules/compiler.lua index 6f355dc..d964377 100644 --- a/dissect/protobuf_dissector/modules/compiler.lua +++ b/dissect/protobuf_dissector/modules/compiler.lua @@ -32,8 +32,8 @@ if not _G['protbuf_dissector'] then return end -- make sure wireshark is new enough -if not GRegex then - return nil, "Wireshark is too old: no GRegex library" +if not rex_pcre2 then + return nil, "Wireshark is too old: no rex_pcre2 library" end diff --git a/dissect/protobuf_dissector/modules/front_end/cursor.lua b/dissect/protobuf_dissector/modules/front_end/cursor.lua index b4cd3c9..134b04c 100644 --- a/dissect/protobuf_dissector/modules/front_end/cursor.lua +++ b/dissect/protobuf_dissector/modules/front_end/cursor.lua @@ -94,8 +94,8 @@ function Cursor:nextLine() end -local wspace_rgx = GRegex.new("^([ \t]++)|^(\n)", "s") -local chunk_rgx = GRegex.new("^([^\n]++)|^(\n)", "s") +local wspace_rgx = rex_pcre2.new("^([ \t]++)|^(\n)", "s") +local chunk_rgx = rex_pcre2.new("^([^\n]++)|^(\n)", "s") local len = string.len local sub = string.sub diff --git a/dissect/protobuf_dissector/modules/front_end/file_reader.lua b/dissect/protobuf_dissector/modules/front_end/file_reader.lua index 6bdc90a..fb086f2 100644 --- a/dissect/protobuf_dissector/modules/front_end/file_reader.lua +++ b/dissect/protobuf_dissector/modules/front_end/file_reader.lua @@ -24,8 +24,8 @@ if not _G['protbuf_dissector'] then return end -- make sure wireshark is new enough -if not GRegex then - return nil, "Wireshark is too old: no GRegex library" +if not rex_pcre2 then + return nil, "Wireshark is too old: no rex_pcre2 library" end @@ -46,7 +46,7 @@ end function FileReader:read(name) end -local filepath_rgx = GRegex.new("^(.*)([^/\\\\]+)$", "U") +local filepath_rgx = rex_pcre2.new("^(.*)([^/\\\\]+)$", "U") -------------------------------------------------------------------------------- diff --git a/dissect/protobuf_dissector/modules/front_end/lexer.lua b/dissect/protobuf_dissector/modules/front_end/lexer.lua index 25504be..460f027 100644 --- a/dissect/protobuf_dissector/modules/front_end/lexer.lua +++ b/dissect/protobuf_dissector/modules/front_end/lexer.lua @@ -50,8 +50,8 @@ if not _G['protbuf_dissector'] then return end -- make sure wireshark is new enough -if not GRegex then - return nil, "Wireshark is too old: no GRegex library" +if not rex_pcre2 then + return nil, "Wireshark is too old: no rex_pcre2 library" end local Settings = require "settings" @@ -83,7 +83,7 @@ function Lexer:generateVariableMatchPatterns() for _, token in ipairs(self.Syntax.token_info.VARIABLE) do if not token.value then dprint2("creating new variable regex for: ", token.ttype) - tbl[token.ttype] = GRegex.new("^" .. token.pattern .. "$") + tbl[token.ttype] = rex_pcre2.new("^" .. token.pattern .. "$") end end self.variable_token_regexes = tbl @@ -97,7 +97,7 @@ function Lexer:generateStringMatchPatterns() for _, token in ipairs(self.Syntax.token_info[category]) do if not token.value then dprint2("creating new string regex for: ", token.ttype) - tbl[token.ttype] = GRegex.new("^" .. token.pattern .. "$") + tbl[token.ttype] = rex_pcre2.new("^" .. token.pattern .. "$") end end end @@ -112,7 +112,7 @@ function Lexer:generateSkipPatterns() for _, token in ipairs(self.Syntax.token_info[category]) do if token.skip then dprint2("creating new skip regex for: ", token.ttype) - tbl[token.ttype] = GRegex.new(token.skip, token.skip_flags) + tbl[token.ttype] = rex_pcre2.new(token.skip, token.skip_flags) if not tbl[token.ttype] then error("Could not compile regex for: " .. token.ttype) end @@ -173,7 +173,7 @@ function Lexer:compilePatterns() local t = {} for ttype, str in pairs(self.pattern_strings) do -- Gregex will raise a Lua error if this doesn't succeed - t[ttype] = GRegex.new(str) + t[ttype] = rex_pcre2.new(str) end self.pattern = t end @@ -226,7 +226,7 @@ function Lexer:tokenize(chunk, filename) -- normally we'd use Gregex.gmatch() in a for-loop to do this, but we need to -- skip quoted strings inside the for-loop, and that can't be done with gmatch; -- so we're going to do it the slower way by creating lots of substrings :( - -- also, GRegex.match() doesn't consider subsequent iterations to match a + -- also, rex_pcre2.match() doesn't consider subsequent iterations to match a -- pattern with a "^" anchor, which is unfortunate, so by creating substrings -- we get to use "^" to prevent skipping unmatched words/tokens diff --git a/dissect/protobuf_dissector/modules/front_end/parser.lua b/dissect/protobuf_dissector/modules/front_end/parser.lua index d03e1a9..7799034 100644 --- a/dissect/protobuf_dissector/modules/front_end/parser.lua +++ b/dissect/protobuf_dissector/modules/front_end/parser.lua @@ -40,8 +40,8 @@ if not _G['protbuf_dissector'] then return end -- make sure wireshark is new enough -if not GRegex then - return nil, "Wireshark is too old: no GRegex library" +if not rex_pcre2 then + return nil, "Wireshark is too old: no rex_pcre2 library" end diff --git a/dissect/protobuf_dissector/modules/front_end/token.lua b/dissect/protobuf_dissector/modules/front_end/token.lua index 5f2b32c..259a9ae 100644 --- a/dissect/protobuf_dissector/modules/front_end/token.lua +++ b/dissect/protobuf_dissector/modules/front_end/token.lua @@ -93,8 +93,8 @@ function Token:isNativeType() end -local hex_rgx = GRegex.new("^0[xX]([a-fA-F0-9]+)$") -local oct_rgx = GRegex.new("^0([0-7]+)$") +local hex_rgx = rex_pcre2.new("^0[xX]([a-fA-F0-9]+)$") +local oct_rgx = rex_pcre2.new("^0([0-7]+)$") function Token:convertToNumber() diff --git a/dissect/protobuf_dissector/modules/prefs.lua b/dissect/protobuf_dissector/modules/prefs.lua index ba5cdda..28f66f8 100644 --- a/dissect/protobuf_dissector/modules/prefs.lua +++ b/dissect/protobuf_dissector/modules/prefs.lua @@ -14,6 +14,12 @@ if not _G['protbuf_dissector'] then return end +-- make sure wireshark is new enough +if not rex_pcre2 then + return nil, "Wireshark is too old: no rex_pcre2 library" +end + + local Settings = require "settings" local dprint = Settings.dprint local dprint2 = Settings.dprint2 @@ -24,10 +30,10 @@ local derror = Settings.derror local Prefs = {} -local range_rgx = GRegex.new("([0-9]+)\\s*(?:-\\s*([0-9]+))?") +local range_rgx = rex_pcre2.new("([0-9]+)\\s*(?:-\\s*([0-9]+))?") local function getRange(range) local t = {} - for first, second in GRegex.gmatch(range, range_rgx) do + for first, second in rex_pcre2.gmatch(range, range_rgx) do if first then first = tonumber(first) if second then diff --git a/dissect/protobuf_dissector/modules/syntax.lua b/dissect/protobuf_dissector/modules/syntax.lua index 7dce9e2..9fa9738 100644 --- a/dissect/protobuf_dissector/modules/syntax.lua +++ b/dissect/protobuf_dissector/modules/syntax.lua @@ -26,8 +26,8 @@ if not _G['protbuf_dissector'] then return end -- make sure wireshark is new enough -if not GRegex then - error("Wireshark is too old: no GRegex library - upgrade to version 1.12 or higher.") +if not rex_pcre2 then + error("Wireshark is too old: no rex_pcre2 library - upgrade to version 1.12 or higher.") end diff --git a/dissect/protobuf_dissector/protobuf.lua b/dissect/protobuf_dissector/protobuf.lua index 9898c28..c38960c 100644 --- a/dissect/protobuf_dissector/protobuf.lua +++ b/dissect/protobuf_dissector/protobuf.lua @@ -40,7 +40,8 @@ _G['protbuf_dissector'] = { -- help wireshark find our modules -package.prepend_path("modules") +-- package.prepend_path("modules") +package.path = __DIR__ .. __DIR_SEPARATOR__ .. "modules" .. __DIR_SEPARATOR__ .. "?.lua;" .. package.path -- load our settings diff --git a/dissect/spotify.lua b/dissect/spotify.lua index 66ab1db..78bdc80 100644 --- a/dissect/spotify.lua +++ b/dissect/spotify.lua @@ -28,16 +28,50 @@ function spotify.dissector(buffer, pinfo, tree) subtree:add(f.cmd, cmd) subtree:add(f.length, length) - if cmd:uint() == 0xab then + if false then + elseif cmd:uint() == 0x02 then + pinfo.cols.info = "SecretBlock" + elseif cmd:uint() == 0x04 then + pinfo.cols.info = "Ping: " .. payload(0, 4):uint() + elseif cmd:uint() == 0x08 then + pinfo.cols.info = "StreamChunk" + elseif cmd:uint() == 0x09 then + pinfo.cols.info = "StreamChunkRes" + elseif cmd:uint() == 0x0a then + pinfo.cols.info = "ChannelError" + elseif cmd:uint() == 0x0b then + pinfo.cols.info = "ChannelAbort" + elseif cmd:uint() == 0x0c then + pinfo.cols.info = "RequestKey: " .. payload() + elseif cmd:uint() == 0x0d then + pinfo.cols.info = "AesKey: " .. payload() + elseif cmd:uint() == 0x0e then + pinfo.cols.info = "AesKeyError" + elseif cmd:uint() == 0x19 then + pinfo.cols.info = "Image" + elseif cmd:uint() == 0x1b then + pinfo.cols.info = "CountryCode: " .. payload() + elseif cmd:uint() == 0x49 then + pinfo.cols.info = "Pong" + elseif cmd:uint() == 0x4a then + pinfo.cols.info = "PongAck" + elseif cmd:uint() == 0x4b then + pinfo.cols.info = "Pause" + -- elseif cmd:uint() == 0x50 then + -- pinfo.cols.info = "ProductInfo" + elseif cmd:uint() == 0x69 then + pinfo.cols.info = "LegacyWelcome" + elseif cmd:uint() == 0x76 then + pinfo.cols.info = "LicenseVersion" + elseif cmd:uint() == 0xab then DissectorTable.get("protobuf"):try("ClientResponseEncrypted", payload, pinfo, tree) + pinfo.cols.info = "Login" elseif cmd:uint() == 0xac then DissectorTable.get("protobuf"):try("APWelcome", payload, pinfo, tree) + pinfo.cols.info = "APWelcome" elseif cmd:uint() == 0xad then DissectorTable.get("protobuf"):try("APLoginFailed", payload, pinfo, tree) - elseif cmd:uint() == 0x0c then - pinfo.cols.info = "Req AudioKey: " .. payload() - elseif cmd:uint() == 0x0d then - pinfo.cols.info = "Got AudioKey: " .. payload() + pinfo.cols.info = "APLoginFailed" else DissectorTable.get("spotify.cmd"):try(cmd:uint(), payload, pinfo, tree) end diff --git a/dump/dump.c b/dump/dump.c index de486d1..3fe60c0 100644 --- a/dump/dump.c +++ b/dump/dump.c @@ -15,6 +15,7 @@ #include #include #include +#include #ifdef __APPLE__ #include @@ -103,11 +104,16 @@ static void find_shn_heuristic(void *text_start, size_t text_size, void **p_shn_ } static void patch_shn(void) { - dump_fd = open("dump.pcap", O_CREAT | O_RDWR | O_TRUNC, 0644); + char *fname = (char *) malloc(64 * sizeof(char)); + pid_t pid = getpid(); + snprintf(fname, 64, "dump-%ld.pcap", (long) pid); + + dump_fd = open(fname, O_CREAT | O_RDWR | O_TRUNC, 0644); pcap_write_header(dump_fd, PCAP_DLT_USER0); - printf("Patching ...\n"); + + printf("Patching ... (PID = %ld)\n", (long) pid); size_t text_size = 0; void *text_start = NULL; From 1ca2f4b1b5095f79af3b7d885ac556d8010b7402 Mon Sep 17 00:00:00 2001 From: wisp3rwind <17089248+wisp3rwind@users.noreply.github.com> Date: Sat, 19 Oct 2024 14:56:00 +0200 Subject: [PATCH 2/2] dump: create capture file lazily to avoid creating empty files --- dump/dump.c | 66 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/dump/dump.c b/dump/dump.c index 3fe60c0..d086ebf 100644 --- a/dump/dump.c +++ b/dump/dump.c @@ -25,6 +25,7 @@ #include "pcap.h" #include "shn.h" +static int init_pcap_file(); static void patch_function(void *src, const void *dst); static void *memrmem(const void *haystack, size_t haystack_size, const void *needle, size_t needle_size); @@ -32,16 +33,40 @@ static void *memrmem(const void *haystack, size_t haystack_size, #define DIRECTION_SEND 0 #define DIRECTION_RECV 1 -static int dump_fd; +static int dump_fd = -1; + +/* + * The capture file is opened lazily just before the first write. This prevents + * writing empty pcap files which contain only the header from subprocesses + * that don't handle the shn en/decryption. + */ +static int init_pcap_file() { + if (dump_fd == -1) { + const size_t FNAME_CAP = 64; + char fname[FNAME_CAP]; + pid_t pid = getpid(); + snprintf(fname, FNAME_CAP, "dump-%ld.pcap", (long) pid); + + dump_fd = open(fname, O_CREAT | O_RDWR | O_TRUNC, 0644); + + pcap_write_header(dump_fd, PCAP_DLT_USER0); + } + + return dump_fd; +} static void my_shn_encrypt(shn_ctx * c, UCHAR * buf, int nbytes) { - struct timeval tv; - gettimeofday(&tv, NULL); - pcap_write_packet_header(dump_fd, &tv, 1 + nbytes); + int fd = init_pcap_file(); + + if (fd > 0) { + struct timeval tv; + gettimeofday(&tv, NULL); + pcap_write_packet_header(fd, &tv, 1 + nbytes); - uint8_t direction = DIRECTION_SEND; - write(dump_fd, &direction, 1); - write(dump_fd, buf, nbytes); + uint8_t direction = DIRECTION_SEND; + write(fd, &direction, 1); + write(fd, buf, nbytes); + } shn_encrypt(c, buf, nbytes); } @@ -59,14 +84,17 @@ static void my_shn_decrypt(shn_ctx * c, UCHAR * buf, int nbytes) { memcpy(&header, buf, 3); } else { if (nbytes == ntohs(header.length)) { - struct timeval tv; - gettimeofday(&tv, NULL); - pcap_write_packet_header(dump_fd, &tv, 4 + nbytes); - - uint8_t direction = DIRECTION_RECV; - write(dump_fd, &direction, 1); - write(dump_fd, &header, 3); - write(dump_fd, buf, nbytes); + int fd = init_pcap_file(); + if (fd > 0) { + struct timeval tv; + gettimeofday(&tv, NULL); + pcap_write_packet_header(fd, &tv, 4 + nbytes); + + uint8_t direction = DIRECTION_RECV; + write(fd, &direction, 1); + write(fd, &header, 3); + write(fd, buf, nbytes); + } } header.cmd = 0; @@ -104,15 +132,7 @@ static void find_shn_heuristic(void *text_start, size_t text_size, void **p_shn_ } static void patch_shn(void) { - char *fname = (char *) malloc(64 * sizeof(char)); pid_t pid = getpid(); - snprintf(fname, 64, "dump-%ld.pcap", (long) pid); - - dump_fd = open(fname, O_CREAT | O_RDWR | O_TRUNC, 0644); - - pcap_write_header(dump_fd, PCAP_DLT_USER0); - - printf("Patching ... (PID = %ld)\n", (long) pid); size_t text_size = 0;