From 537a0c4f0d82f2743353ebddffef1af564a7e4b3 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Sun, 13 Jan 2019 13:11:59 +0100 Subject: file-tar: basic POSIX tar archive dissector Support the POSIX tar format only and not other dialects. --- lua/file-tar.lua | 217 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 lua/file-tar.lua diff --git a/lua/file-tar.lua b/lua/file-tar.lua new file mode 100644 index 0000000..ee768dc --- /dev/null +++ b/lua/file-tar.lua @@ -0,0 +1,217 @@ +-- +-- Tar dissector +-- Author: Peter Wu +-- +-- Information about the file format: +-- https://www.gnu.org/software/tar/manual/html_node/Standard.html +-- https://en.wikipedia.org/wiki/Tar_(computing) +-- +-- Requires at least Wireshark 2.4 (due to the use of ftypes.CHAR) +-- + +-- +-- Dissection of Tar file contents +-- + +--[[ + +// From https://www.gnu.org/software/tar/manual/html_node/Standard.html +struct posix_header +{ /* byte offset */ + char name[100]; /* 0 */ + char mode[8]; /* 100 */ + char uid[8]; /* 108 */ + char gid[8]; /* 116 */ + char size[12]; /* 124 */ + char mtime[12]; /* 136 */ + char chksum[8]; /* 148 */ + char typeflag; /* 156 */ + char linkname[100]; /* 157 */ + char magic[6]; /* 257 */ + char version[2]; /* 263 */ + char uname[32]; /* 265 */ + char gname[32]; /* 297 */ + char devmajor[8]; /* 329 */ + char devminor[8]; /* 337 */ + char prefix[155]; /* 345 */ + /* 500 */ +}; + +# Convert above headers to +fields=$(xsel | sed -re 's# *char ([a-z]+)\[([0-9]+)\]; */\* +([0-9]+) *\*/#\1 \3 \2#' -e 's# *char ([a-z]+); */\* +([0-9]+) *\*/#\1 \2 1#') +# Convert to hf definitions (and fixup mtime and typeflag) +echo "$fields" | awk '{ printf " %-12s= ProtoField.string(\"tar.%s\", \"%s\"),\n", $1, $1, $1 }' +# Convert to dissection calls. +echo "$fields" | awk '{ printf " htree:add(hf.%-10s tvb(offset + %d, %d))\n", $1",", $2, $3 }' +--]] + +local typeflag_values = { + [0] = "Regular file", + [string.byte("0")] = "Regular file", + [string.byte("1")] = "Hard link", + [string.byte("2")] = "Symbolic link", + [string.byte("3")] = "Character special", + [string.byte("4")] = "Block special", + [string.byte("5")] = "Directory", + [string.byte("6")] = "FIFO special", + [string.byte("7")] = "Reserved", + [string.byte("g")] = "Global extended header", + [string.byte("x")] = "Extended header", +} + +local proto_tar = Proto.new("tar_archive", "Tar Archive") +local hf = { + header = ProtoField.none("tar.header", "POSIX header"), + file_data = ProtoField.bytes("tar.file_data", "File data"), + -- posix_header + name = ProtoField.string("tar.name", "name"), + mode = ProtoField.string("tar.mode", "mode"), + uid = ProtoField.string("tar.uid", "uid"), + gid = ProtoField.string("tar.gid", "gid"), + size = ProtoField.string("tar.size", "size"), + --mtime = ProtoField.string("tar.mtime", "mtime"), + mtime = ProtoField.absolute_time("tar.mtime", "mtime", base.UTC), + chksum = ProtoField.string("tar.chksum", "chksum"), + typeflag = ProtoField.new("typeflag", "tar.typeflag", ftypes.CHAR, typeflag_values), + linkname = ProtoField.string("tar.linkname", "linkname"), + magic = ProtoField.string("tar.magic", "magic"), + version = ProtoField.string("tar.version", "version"), + uname = ProtoField.string("tar.uname", "uname"), + gname = ProtoField.string("tar.gname", "gname"), + devmajor = ProtoField.string("tar.devmajor", "devmajor"), + devminor = ProtoField.string("tar.devminor", "devminor"), + prefix = ProtoField.string("tar.prefix", "prefix"), +} +proto_tar.fields = hf + +local function dissect_one(tvb, offset, pinfo, tree) + local htree = tree:add(hf.header, tvb(offset, 500)) + htree:add(hf.name, tvb(offset + 0, 100)) + local name = tvb:raw(offset, 100):gsub("\0+$", "") + htree:append_text(string.format(" %s", name)) + htree:add(hf.mode, tvb(offset + 100, 8)) + htree:add(hf.uid, tvb(offset + 108, 8)) + htree:add(hf.gid, tvb(offset + 116, 8)) + htree:add(hf.size, tvb(offset + 124, 12)) + local mtime = tonumber(tvb:raw(offset + 136, 12):gsub("\0+$", ""), 8) + htree:add(hf.mtime, tvb(offset + 136, 12), NSTime(mtime)) + htree:add(hf.chksum, tvb(offset + 148, 8)) + htree:add(hf.typeflag, tvb(offset + 156, 1)) + local ftype = typeflag_values[tvb(offset + 156, 1):uint()] + if ftype then htree:append_text(string.format(" (%s)", ftype)) end + htree:add(hf.linkname, tvb(offset + 157, 100)) + htree:add(hf.magic, tvb(offset + 257, 6)) + htree:add(hf.version, tvb(offset + 263, 2)) + htree:add(hf.uname, tvb(offset + 265, 32)) + htree:add(hf.gname, tvb(offset + 297, 32)) + htree:add(hf.devmajor, tvb(offset + 329, 8)) + htree:add(hf.devminor, tvb(offset + 337, 8)) + htree:add(hf.prefix, tvb(offset + 345, 155)) + + local size = tonumber(tvb:raw(offset + 124, 12):gsub("\0+$", ""), 8) + offset = offset + 512 + if size and size > 0 then + htree:append_text(string.format(" (%d bytes)", size)) + tree:add(hf.file_data, tvb(offset, size)) + offset = offset + size + end + if offset % 512 ~= 0 then + offset = offset - (offset % 512) + 512 + end + -- Detect end of archive (at least two consecutive zero-filled records). + while offset + 512 <= tvb:len() and tvb:raw(offset, 500):match("^\0+$") do + offset = offset + 512 + end + return offset +end + +function proto_tar.dissector(tvb, pinfo, tree) + pinfo.cols.protocol = "tar" + --pinfo.cols.info = "" + + local next_offset = 0 + while next_offset and next_offset < tvb:len() do + next_offset = dissect_one(tvb, next_offset, pinfo, tree) + end + return next_offset +end + +local function tar_heur(tvb, pinfo, tree) + if tvb:len() < 512 or tvb:raw(257, 6) ~= "ustar\0" then + return false + end + + proto_tar.dissector(tvb, pinfo, tree) + return true +end + +-- Register MIME types in case a Tar file appears over HTTP. +DissectorTable.get("media_type"):add("application/x-tar", proto_tar) + +-- Ensure that files can directly be opened (after any FileHandler has accepted +-- it, see below). +proto_tar:register_heuristic("wtap_file", tar_heur) + + +-- +-- File handler (for directly interpreting opening a Tar file in Wireshark) +-- Actually, all it does is recognizing a Tar file and passing one packet to the +-- MIME dissector. +-- + +local tar_fh = FileHandler.new("Tar", "tar", "Tar archive file reader", "rms") + +-- Check if file is really a tar file (return true if it is) +function tar_fh.read_open(file, cinfo) + -- Detect UStar format + if not (file:seek("set", 257) and file:read(6) == "ustar\0") then + return false + end + + -- Find end of file and rewind. + local endpos, err = file:seek("end") + if not endpos then error("Error while finding end! " .. err) end + local ok, err = file:seek("set", 0) + if not ok then error("Non-seekable file! " .. err) end + + cinfo.encap = wtap_encaps.MIME + cinfo.private_table = { + endpos = endpos, + } + + return true +end + +-- Read next packet (returns begin offset or false on error) +local function tar_fh_read(file, cinfo, finfo) + local p = cinfo.private_table + local curpos = file:seek("cur") + + -- Fal on EOF + if curpos >= p.endpos then return false end + + finfo.original_length = p.endpos - curpos + finfo.captured_length = p.endpos - curpos + + if not finfo:read_data(file, finfo.captured_length) then + -- Partial read? + print("Hmm, partial read, curpos=" .. curpos .. ", len: " .. finfo.captured_length) + return false + end + + return curpos +end +tar_fh.read = tar_fh_read + +-- Reads packet at offset (returns true on success and false on failure) +function tar_fh.seek_read(file, cinfo, finfo, offset) + file:seek("set", offset) + -- Return a boolean since WS < 2.4 has an undocumented "feature" where + -- strings (including numbers) are treated as data. + return tar_fh_read(file, cinfo, finfo) ~= false +end + +-- Hints for when to invoke this dissector. +tar_fh.extensions = "tar" + +register_filehandler(tar_fh) -- cgit v1.2.1