.cap file format of Microsoft Network Monitor, v2.x: Ruby parsing library

Microsoft Network Monitor (AKA Netmon) is a proprietary Microsoft's network packet sniffing and analysis tool. It can save captured traffic as .cap files, which usually contain the packets and may contain some additional info - enhanced network info, calculated statistics, etc.

There are at least 2 different versions of the format: v1 and v2. Netmon v3 seems to use the same file format as v1.

Application

Microsoft Network Monitor, v2.x

File extension

cap

KS implementation details

License: CC0-1.0
Minimal Kaitai Struct required: 0.7

References

This page hosts a formal specification of .cap file format of Microsoft Network Monitor, v2.x using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All parsing code for Ruby generated by Kaitai Struct depends on the Ruby runtime library. You have to install it before you can parse data.

The Ruby runtime library can be installed from RubyGems:

gem install kaitai-struct

Code

Parse a local file and get structure in memory:

data = MicrosoftNetworkMonitorV2.from_file("path/to/local/file.cap")

Or parse structure from a string of bytes:

bytes = "\x00\x01\x02..."
data = MicrosoftNetworkMonitorV2.new(Kaitai::Struct::Stream.new(bytes))

After that, one can get various attributes from the structure by invoking getter methods like:

data.version_minor # => Format version (minor), BCD
data.frame_table # => Index that is used to access individual captured frames

Ruby source code to parse .cap file format of Microsoft Network Monitor, v2.x

microsoft_network_monitor_v2.rb

# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

require 'kaitai/struct/struct'

unless Gem::Version.new(Kaitai::Struct::VERSION) >= Gem::Version.new('0.9')
  raise "Incompatible Kaitai Struct Ruby API: 0.9 or later is required, but you have #{Kaitai::Struct::VERSION}"
end


##
# Microsoft Network Monitor (AKA Netmon) is a proprietary Microsoft's
# network packet sniffing and analysis tool. It can save captured
# traffic as .cap files, which usually contain the packets and may
# contain some additional info - enhanced network info, calculated
# statistics, etc.
# 
# There are at least 2 different versions of the format: v1 and
# v2. Netmon v3 seems to use the same file format as v1.
# @see https://docs.microsoft.com/en-us/windows/win32/netmon2/capturefile-header-values Source
class MicrosoftNetworkMonitorV2 < Kaitai::Struct::Struct

  LINKTYPE = {
    0 => :linktype_null_linktype,
    1 => :linktype_ethernet,
    3 => :linktype_ax25,
    6 => :linktype_ieee802_5,
    7 => :linktype_arcnet_bsd,
    8 => :linktype_slip,
    9 => :linktype_ppp,
    10 => :linktype_fddi,
    50 => :linktype_ppp_hdlc,
    51 => :linktype_ppp_ether,
    100 => :linktype_atm_rfc1483,
    101 => :linktype_raw,
    104 => :linktype_c_hdlc,
    105 => :linktype_ieee802_11,
    107 => :linktype_frelay,
    108 => :linktype_loop,
    113 => :linktype_linux_sll,
    114 => :linktype_ltalk,
    117 => :linktype_pflog,
    119 => :linktype_ieee802_11_prism,
    122 => :linktype_ip_over_fc,
    123 => :linktype_sunatm,
    127 => :linktype_ieee802_11_radiotap,
    129 => :linktype_arcnet_linux,
    138 => :linktype_apple_ip_over_ieee1394,
    139 => :linktype_mtp2_with_phdr,
    140 => :linktype_mtp2,
    141 => :linktype_mtp3,
    142 => :linktype_sccp,
    143 => :linktype_docsis,
    144 => :linktype_linux_irda,
    147 => :linktype_user0,
    148 => :linktype_user1,
    149 => :linktype_user2,
    150 => :linktype_user3,
    151 => :linktype_user4,
    152 => :linktype_user5,
    153 => :linktype_user6,
    154 => :linktype_user7,
    155 => :linktype_user8,
    156 => :linktype_user9,
    157 => :linktype_user10,
    158 => :linktype_user11,
    159 => :linktype_user12,
    160 => :linktype_user13,
    161 => :linktype_user14,
    162 => :linktype_user15,
    163 => :linktype_ieee802_11_avs,
    165 => :linktype_bacnet_ms_tp,
    166 => :linktype_ppp_pppd,
    169 => :linktype_gprs_llc,
    170 => :linktype_gpf_t,
    171 => :linktype_gpf_f,
    177 => :linktype_linux_lapd,
    187 => :linktype_bluetooth_hci_h4,
    189 => :linktype_usb_linux,
    192 => :linktype_ppi,
    195 => :linktype_ieee802_15_4,
    196 => :linktype_sita,
    197 => :linktype_erf,
    201 => :linktype_bluetooth_hci_h4_with_phdr,
    202 => :linktype_ax25_kiss,
    203 => :linktype_lapd,
    204 => :linktype_ppp_with_dir,
    205 => :linktype_c_hdlc_with_dir,
    206 => :linktype_frelay_with_dir,
    209 => :linktype_ipmb_linux,
    215 => :linktype_ieee802_15_4_nonask_phy,
    220 => :linktype_usb_linux_mmapped,
    224 => :linktype_fc_2,
    225 => :linktype_fc_2_with_frame_delims,
    226 => :linktype_ipnet,
    227 => :linktype_can_socketcan,
    228 => :linktype_ipv4,
    229 => :linktype_ipv6,
    230 => :linktype_ieee802_15_4_nofcs,
    231 => :linktype_dbus,
    235 => :linktype_dvb_ci,
    236 => :linktype_mux27010,
    237 => :linktype_stanag_5066_d_pdu,
    239 => :linktype_nflog,
    240 => :linktype_netanalyzer,
    241 => :linktype_netanalyzer_transparent,
    242 => :linktype_ipoib,
    243 => :linktype_mpeg_2_ts,
    244 => :linktype_ng40,
    245 => :linktype_nfc_llcp,
    247 => :linktype_infiniband,
    248 => :linktype_sctp,
    249 => :linktype_usbpcap,
    250 => :linktype_rtac_serial,
    251 => :linktype_bluetooth_le_ll,
    253 => :linktype_netlink,
    254 => :linktype_bluetooth_linux_monitor,
    255 => :linktype_bluetooth_bredr_bb,
    256 => :linktype_bluetooth_le_ll_with_phdr,
    257 => :linktype_profibus_dl,
    258 => :linktype_pktap,
    259 => :linktype_epon,
    260 => :linktype_ipmi_hpm_2,
    261 => :linktype_zwave_r1_r2,
    262 => :linktype_zwave_r3,
    263 => :linktype_wattstopper_dlm,
    264 => :linktype_iso_14443,
  }
  I__LINKTYPE = LINKTYPE.invert
  def initialize(_io, _parent = nil, _root = self)
    super(_io, _parent, _root)
    _read
  end

  def _read
    @signature = @_io.read_bytes(4)
    raise Kaitai::Struct::ValidationNotEqualError.new([71, 77, 66, 85].pack('C*'), signature, _io, "/seq/0") if not signature == [71, 77, 66, 85].pack('C*')
    @version_minor = @_io.read_u1
    @version_major = @_io.read_u1
    @mac_type = Kaitai::Struct::Stream::resolve_enum(LINKTYPE, @_io.read_u2le)
    @time_capture_start = WindowsSystemtime.new(@_io)
    @frame_table_ofs = @_io.read_u4le
    @frame_table_len = @_io.read_u4le
    @user_data_ofs = @_io.read_u4le
    @user_data_len = @_io.read_u4le
    @comment_ofs = @_io.read_u4le
    @comment_len = @_io.read_u4le
    @statistics_ofs = @_io.read_u4le
    @statistics_len = @_io.read_u4le
    @network_info_ofs = @_io.read_u4le
    @network_info_len = @_io.read_u4le
    @conversation_stats_ofs = @_io.read_u4le
    @conversation_stats_len = @_io.read_u4le
    self
  end
  class FrameIndex < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @entries = []
      i = 0
      while not @_io.eof?
        @entries << FrameIndexEntry.new(@_io, self, @_root)
        i += 1
      end
      self
    end
    attr_reader :entries
  end

  ##
  # Each index entry is just a pointer to where the frame data is
  # stored in the file.
  class FrameIndexEntry < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @ofs = @_io.read_u4le
      self
    end

    ##
    # Frame body itself
    def body
      return @body unless @body.nil?
      io = _root._io
      _pos = io.pos
      io.seek(ofs)
      @body = Frame.new(io, self, @_root)
      io.seek(_pos)
      @body
    end

    ##
    # Absolute pointer to frame data in the file
    attr_reader :ofs
  end

  ##
  # A container for actually captured network data. Allow to
  # timestamp individual frames and designates how much data from
  # the original packet was actually written into the file.
  # @see https://docs.microsoft.com/en-us/windows/win32/netmon2/frame Source
  class Frame < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @ts_delta = @_io.read_u8le
      @orig_len = @_io.read_u4le
      @inc_len = @_io.read_u4le
      case _root.mac_type
      when :linktype_ethernet
        @_raw_body = @_io.read_bytes(inc_len)
        _io__raw_body = Kaitai::Struct::Stream.new(@_raw_body)
        @body = EthernetFrame.new(_io__raw_body)
      else
        @body = @_io.read_bytes(inc_len)
      end
      self
    end

    ##
    # Time stamp - usecs since start of capture
    attr_reader :ts_delta

    ##
    # Actual length of packet
    attr_reader :orig_len

    ##
    # Number of octets captured in file
    attr_reader :inc_len

    ##
    # Actual packet captured from the network
    attr_reader :body
    attr_reader :_raw_body
  end

  ##
  # Index that is used to access individual captured frames
  def frame_table
    return @frame_table unless @frame_table.nil?
    _pos = @_io.pos
    @_io.seek(frame_table_ofs)
    @_raw_frame_table = @_io.read_bytes(frame_table_len)
    _io__raw_frame_table = Kaitai::Struct::Stream.new(@_raw_frame_table)
    @frame_table = FrameIndex.new(_io__raw_frame_table, self, @_root)
    @_io.seek(_pos)
    @frame_table
  end
  attr_reader :signature

  ##
  # Format version (minor), BCD
  attr_reader :version_minor

  ##
  # Format version (major), BCD
  attr_reader :version_major

  ##
  # Network topology type of captured data
  attr_reader :mac_type

  ##
  # Timestamp of capture start
  attr_reader :time_capture_start
  attr_reader :frame_table_ofs
  attr_reader :frame_table_len
  attr_reader :user_data_ofs
  attr_reader :user_data_len
  attr_reader :comment_ofs
  attr_reader :comment_len
  attr_reader :statistics_ofs
  attr_reader :statistics_len
  attr_reader :network_info_ofs
  attr_reader :network_info_len
  attr_reader :conversation_stats_ofs
  attr_reader :conversation_stats_len
  attr_reader :_raw_frame_table
end