packetman 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a30c3ba57b035cf5927580b722a5651aefc55317
4
- data.tar.gz: 68c4aa0507610985b538da8e6b8b432ffbd6aa49
3
+ metadata.gz: 6ee06237cff9b2ffb77984c3d3ed1447e9fe4ba8
4
+ data.tar.gz: 58b85bd5f40726dbe58271425c6247556659dcfe
5
5
  SHA512:
6
- metadata.gz: 976a934d36e13ceb1593728d7067198bbbe78d036da0a600ffa58703483709ca35c813feb3198aa3575ed9f6c9ca680a96572c31a05358ee542438c5d248fec3
7
- data.tar.gz: 727bf261f5a87940af9abd8e636272dce8d1e2de7746976b2079b2b6dff2717da849fbd780b0da17df47ee2fed1a981f48ae0e7ce7cdb52ad7e61cd532f8354f
6
+ metadata.gz: cfa7fe1df88846174e93d98003b4399da5b97162b3ccf8c9a23895d87e4d16a69da654c1da5772dd98eb0ce06afe97033b1adbb36913e3e1c470efd3a3dfe964
7
+ data.tar.gz: 48890d5a660428531b7d17f6338bcba85f142ec6b76ce09519632fddd2a7536f9079432e89065f817a5de5a1e07fc997bd24e3ee5ec9b45c5d70211314353a68
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Packetman
2
2
 
3
- Advanced tcpdump and Wireshark capture generator.
3
+ Advanced tcpdump and Wireshark filter string generator.
4
4
 
5
5
  [![Gem Version](https://badge.fury.io/rb/packetman.svg)](http://badge.fury.io/rb/packetman)
6
6
  [![Test Coverage](https://codeclimate.com/github/jescholl/packetman/badges/coverage.svg)](https://codeclimate.com/github/jescholl/packetman/coverage)
@@ -38,7 +38,40 @@ Or install it yourself as:
38
38
 
39
39
  ## Usage
40
40
 
41
- TODO: Write usage instructions here
41
+ $ packetman -h
42
+
43
+ Usage: packetman [OPTIONS] FILTER_STRING
44
+ -p, --protocol PROTO Transport Protocol (tcp,udp,icmp)
45
+ -t, --transport OFFSET starts at transport header instead of data payload
46
+ -r, --radix RADIX Treat FILTER_STRING as RADIX instead of String
47
+ -o, --offset OFFSET Offset in bits
48
+ -b, --byte-offset Use 8-bit bytes instead of bits for offset
49
+ -w, --wildcard [CHARACTER=?] Treat CHARACTER as single-character wildcard
50
+ -v, --version Show version
51
+
52
+ Create and use a filter string to capture all HTTP GET requests to `/foo/bar`
53
+
54
+ $ sudo tcpdump -nA `packetman GET /foo/bar`
55
+ tcpdump: data link type PKTAP
56
+ tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
57
+ listening on pktap, link-type PKTAP (Packet Tap), capture size 262144 bytes
58
+ 16:49:04.516409 IP 127.0.0.1.54662 > 127.0.0.1.80: Flags [P.], seq 1488105913:1488105994, ack 1397163988, win 4121, options [nop,nop,TS val 875380202 ecr 2751916352], length 81: HTTP: GET /foo/bar HTTP/1.1
59
+ .....b....j...E.....@.@..S..
60
+ ..:.....PX...SG......75.....
61
+ 4-=....@GET /foo/bar HTTP/1.1
62
+ Host: localhost
63
+ User-Agent: curl/7.43.0
64
+ Accept: */*
65
+
66
+ Hexadecimal string with wildcards
67
+
68
+ $ packetman -r 16 -w '?' "A8C401???C200A"
69
+ tcp[((tcp[12:1] & 0xf0) >> 2) + 0:4] & 0xffffff00 = 0xa8c40100 && tcp[((tcp[12:1] & 0xf0) >> 2) + 4:2] & 0x0fff = 0x0c20 && tcp[((tcp[12:1] & 0xf0) >> 2) + 6:1] & 0xff = 0x0a
70
+
71
+ Base 4 string with wildcards and offset beginning at start of the TCP header
72
+
73
+ $ packetman -t -o 3 -r 4 -w i 1223iiii2212
74
+ tcp[0:4] & 0x1fe01fe0 = 0x0d6014c0
42
75
 
43
76
  ## Development
44
77
 
@@ -1,29 +1,56 @@
1
1
  ---
2
2
  tcp:
3
3
  table:
4
- Source Port: 16
5
- Destination Port: 16
6
- Sequence Number: 32
7
- Acknowledgement Number: 32
8
- Data Offset: 4
9
- RESERVED: 3
10
- ECN: 3
11
- Control Bits: 6
12
- Window: 16
13
- Checksum: 16
14
- Urgent Pointer: 16
15
- Options and Padding: 32
4
+ - - :value: Source Port
5
+ :colspan: 16
6
+ - :value: Destination Port
7
+ :colspan: 16
8
+ - :separator
9
+ - - :value: Sequence Number
10
+ :colspan: 32
11
+ - :separator
12
+ - - :value: Acknowledgement Number
13
+ :colspan: 32
14
+ - :separator
15
+ - - :value: Data Offset
16
+ :colspan: 4
17
+ - :value: RESERVED
18
+ :colspan: 3
19
+ - :value: ECN
20
+ :colspan: 3
21
+ - :value: Control Bits
22
+ :colspan: 6
23
+ - :value: Window
24
+ :colspan: 16
25
+ - :separator
26
+ - - :value: Checksum
27
+ :colspan: 16
28
+ - :value: Urgent Pointer
29
+ :colspan: 16
30
+ - :separator
31
+ - - :value: Options and Padding
32
+ :colspan: 32
16
33
  payload_query: '((tcp[12:1] & 0xf0) >> 2)'
17
34
  udp:
18
35
  table:
19
- Source Port: 16
20
- Destination Port: 16
21
- Length: 16
22
- Checksum: 16
36
+ - - :value: Source Port
37
+ :colspan: 16
38
+ - :value: Destination Port
39
+ :colspan: 16
40
+ - :separator
41
+ - - :value: Length
42
+ :colspan: 16
43
+ - :value: Checksum
44
+ :colspan: 16
23
45
  payload_query: 8
24
46
  icmp:
25
47
  table:
26
- Type: 8
27
- Code: 8
28
- Checksum: 16
29
- Type Specific Options: 32
48
+ - - :value: Type
49
+ :colspan: 8
50
+ - :value: Code
51
+ :colspan: 8
52
+ - :value: Checksum
53
+ :colspan: 16
54
+ - :separator
55
+ - - :value: Type Specific Options
56
+ :colspan: 32
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'packetman'
3
3
 
4
- search_str = Packetman.config.parse_opts
4
+ catch :exit do
5
+ search_str = Packetman.config.parse_opts
5
6
 
6
- puts Packetman::Filter.new(search_str)
7
+ puts Packetman::Filter.new(search_str)
8
+ end
@@ -2,24 +2,31 @@ module Packetman
2
2
  class Clause
3
3
  include ConfigMethods
4
4
 
5
- attr_accessor :search, :mask, :offset
5
+ attr_accessor :search, :mask, :start_bit
6
6
 
7
- def initialize(search, mask, offset)
7
+ def initialize(search, mask, start_bit)
8
8
  self.search = search
9
9
  self.mask = mask
10
- self.offset = offset
10
+ self.start_bit = start_bit
11
11
  end
12
12
 
13
- def start_byte(start_bit)
14
- "#{config.payload_query} + #{(config.offset_bits + start_bit)/8}"
13
+ # Address of first byte
14
+ def start_byte
15
+ [config.payload_query, (config.offset_bits + start_bit)/8].compact.join(' + ')
15
16
  end
16
17
 
17
- def data_address(start_bit, data_bits)
18
- "#{config.transport}[#{start_byte(start_bit)}:#{data_bits/8}]"
18
+ def num_bytes
19
+ Filter.bit_length(search)/8
19
20
  end
20
21
 
22
+ # Full address of the query data (eg. `tcp[0:4]`)
23
+ def data_address
24
+ "#{config.transport}[#{start_byte}:#{num_bytes}]"
25
+ end
26
+
27
+ # The whole filter clause fully assembled
21
28
  def to_s
22
- "#{data_address(offset, Filter.bit_length(search))} & #{mask} = #{search}"
29
+ "#{data_address} & #{mask} = #{search}"
23
30
  end
24
31
 
25
32
  end
@@ -2,11 +2,12 @@ require 'optparse'
2
2
 
3
3
  module Packetman
4
4
  class Config
5
- attr_accessor :transport, :application, :use_bytes, :radix, :start_with_transport, :offset, :wildcard
5
+ attr_accessor :transport, :application, :offset_type, :radix, :start_with_transport, :offset, :wildcard
6
6
 
7
7
  def initialize
8
- @transport = "tcp"
9
- @offset = 0
8
+ self.transport = "tcp"
9
+ self.offset = 0
10
+ self.offset_type = :bits
10
11
  end
11
12
 
12
13
  def protocols
@@ -18,7 +19,7 @@ module Packetman
18
19
  end
19
20
 
20
21
  def offset_bits
21
- if use_bytes
22
+ if offset_type == :bytes
22
23
  offset*8
23
24
  else
24
25
  offset
@@ -28,22 +29,24 @@ module Packetman
28
29
  def opts
29
30
  @opts ||= OptionParser.new do |opt|
30
31
  opt.banner = "Usage: #{File.basename($PROGRAM_NAME)} [OPTIONS] FILTER_STRING"
31
- opt.on("-p", "--protocol PROTO", protocols.keys, "Transport Protocol ( #{protocols.keys.join(',')})") { |v| self.transport = v }
32
+ opt.on("-p", "--protocol PROTO", protocols.keys, "Transport Protocol (#{protocols.keys.join(',')})") { |v| self.transport = v }
32
33
  opt.on("-t", "--transport", "OFFSET starts at transport header instead of data payload") { |v| self.start_with_transport = v }
33
34
  opt.on("-r", "--radix RADIX", Integer, "Treat FILTER_STRING as RADIX instead of String") { |v| self.radix = v }
34
35
  opt.on("-o", "--offset OFFSET", Integer, "Offset in bits") { |v| self.offset = v }
35
- opt.on("-b", "--byte-offset", "Use 8-bit bytes instead of bits for offset") { |v| self.use_bytes = v }
36
- opt.on("-w", "--wildcard [CHARACTER=?]", "Treat CHARACTER as single-character wildcard") { |v| raise "invalid wildcard" if v.to_s.length > 1; self.wildcard = v || '?' }
37
- opt.on("-v", "--version", "Show version") { puts Packetman::VERSION; exit }
36
+ opt.on("-b", "--byte-offset", "Use 8-bit bytes instead of bits for offset") { |v| self.offset_type = :bytes if v }
37
+ opt.on("-w", "--wildcard CHARACTER", "Treat CHARACTER as single-character wildcard") { |v| raise "invalid wildcard" if v.to_s.length > 1; self.wildcard = v }
38
+ opt.on("--table", "Show transport header table") { puts Packetman::Table.new; throw :exit }
39
+ opt.on("-v", "--version", "Show version") { puts Packetman::VERSION; throw :exit }
38
40
  end
39
41
  end
40
42
 
41
-
42
43
  def parse_opts
43
- filter_str = ARGV.pop
44
- raise "Invalid command line arguments" unless filter_str
45
- opts.parse!
46
- filter_str
44
+ unparsed_opts = opts.parse!
45
+ if unparsed_opts.length < 1
46
+ puts opts
47
+ throw :exit
48
+ end
49
+ unparsed_opts.join(" ")
47
50
  end
48
51
 
49
52
  end
@@ -13,33 +13,36 @@ module Packetman
13
13
  case num
14
14
  when /^0x/
15
15
  $'.length * bit_density(16)
16
- when /^0b/
17
- $'.length * bit_density(2)
18
16
  else
19
17
  nil
20
18
  end
21
19
  end
22
20
 
23
21
  def self.bit_density(radix=config.radix)
24
- (radix.nil?) ? 8 : Math.log2(radix).to_i
22
+ (radix.nil?) ? 8 : Math.log2(radix).ceil
25
23
  end
26
24
 
27
25
  def map_chr
28
- pad_right(input.scan(/./).map{ |chr| yield chr }.join)
26
+ shift_and_pad(input.scan(/./).map{ |chr| yield chr }.join)
29
27
  end
30
28
 
31
- def pad_right(bin_str)
32
- bin_str.ljust(desired_length, '0')
29
+ def shift_and_pad(bin_str)
30
+ #shift
31
+ bin_str.ljust(target_bit_length, '0').
32
+ #pad
33
+ rjust(target_bit_length + config.offset_bits % 8, '0')
33
34
  end
34
35
 
35
- def desired_length
36
- ((input.length + config.offset_bits)/8.to_f).ceil*8 - config.offset_bits
36
+ def target_bit_length
37
+ ((input.length*self.class.bit_density + config.offset_bits)/8.to_f).ceil*8 - config.offset_bits
37
38
  end
38
39
 
40
+ # Mask for 1 character of current radix
39
41
  def radix_mask
40
42
  ("1"*self.class.bit_density).to_i(2)
41
43
  end
42
44
 
45
+ # Mask string for _chr_ substituting wildcards as necessary
43
46
  def mask_chr(chr)
44
47
  if chr == config.wildcard
45
48
  0
@@ -48,6 +51,7 @@ module Packetman
48
51
  end.to_s(2).rjust(self.class.bit_density, '0')
49
52
  end
50
53
 
54
+ # Binary string for _chr_ substituting wildcards as necessary
51
55
  def bin_chr(chr)
52
56
  chr = '0' if chr == config.wildcard
53
57
 
@@ -67,6 +71,7 @@ module Packetman
67
71
  hex_encode(map_chr{ |c| bin_chr(c) })
68
72
  end
69
73
 
74
+ # Transform _bin_str_ to array of 32, 16, and 8 bit hex encoded strings
70
75
  def hex_encode(bin_str)
71
76
  bin_str.reverse.scan(/.{1,4}/).map{ |chunk|
72
77
  chunk.reverse.to_i(2).to_s(16)
@@ -75,14 +80,18 @@ module Packetman
75
80
  }
76
81
  end
77
82
 
78
- def to_s
79
- offset = 0
83
+ def clauses
84
+ start_bit = 0
80
85
  [].tap do |filter|
81
86
  search_hex.zip(mask_hex).each do |search, mask|
82
- filter << Packetman::Clause.new(search, mask, offset)
83
- offset += self.class.bit_length(search)
87
+ filter << Packetman::Clause.new(search, mask, start_bit)
88
+ start_bit += self.class.bit_length(search)
84
89
  end
85
- end.map{ |b| b.to_s }.join(' && ')
90
+ end
91
+ end
92
+
93
+ def to_s
94
+ clauses.map{ |clause| clause.to_s }.join(' && ')
86
95
  end
87
96
 
88
97
  end
@@ -1,57 +1,28 @@
1
+ require 'terminal-table'
2
+
1
3
  module Packetman
2
4
  class Table
3
5
  include ConfigMethods
4
6
 
5
- attr_reader :line_h, :line_v
6
- attr_accessor :columns
7
-
8
- def initialize(cols = 32)
9
- @line_v = '|'
10
- @line_h = '-'
11
- @columns = cols
12
- end
13
-
14
- def line_h=(value)
15
- raise "Invalid character" if value.length != 1
16
- @line_h = value
17
- end
18
-
19
- def line_v=(value)
20
- raise "Invalid character" if value.length != 1
21
- @line_v = value
22
- end
23
-
24
- def column_width
25
- (columns-1).to_s.length
26
- end
7
+ def initialize
27
8
 
28
- def horizontal_bar
29
- line_v + line_h*(table_width - 2) + line_v + "\n"
9
+ @term_table = Terminal::Table.new(headings: headings, rows: rows, style: style)
30
10
  end
31
11
 
32
- def table_width
33
- columns*(column_width + 1) + 1
12
+ def headings
13
+ [*0..31].map{ |c| "%02d" % c }
34
14
  end
35
15
 
36
- def cell_size(field_size)
37
- field_size*(column_width + 1) - 1
16
+ def rows
17
+ protocols[config.transport]['table']
38
18
  end
39
19
 
40
- def header_row
41
- line_v + columns.times.map{ |n| sprintf "%0#{column_width}d", n }.join(line_v) + line_v + "\n"
20
+ def style
21
+ { alignment: :center, padding_left: 0, padding_right: 0}
42
22
  end
43
23
 
44
24
  def to_s
45
- output = horizontal_bar + header_row + horizontal_bar
46
-
47
- protocols[config.transport]['table'].each do |label, size|
48
- output += sprintf "%s%.#{cell_size(size)}s", line_v, label.center(cell_size(size))
49
- if output.split("\n").last.length == (table_width - 1)
50
- output += line_v + "\n"
51
- output += horizontal_bar
52
- end
53
- end
54
- output
25
+ @term_table.to_s
55
26
  end
56
27
 
57
28
  end
@@ -1,3 +1,3 @@
1
1
  module Packetman
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.3"
3
3
  end
@@ -19,6 +19,8 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ["lib"]
21
21
 
22
+ spec.add_dependency "terminal-table", "~> 1.5.2"
23
+
22
24
  spec.add_development_dependency "bundler", "~> 1"
23
25
  spec.add_development_dependency "rake", "~> 10.0"
24
26
  spec.add_development_dependency "rspec", "~> 3.3"
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: packetman
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Scholl
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-11-28 00:00:00.000000000 Z
11
+ date: 2015-11-30 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: terminal-table
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.5.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.5.2
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bundler
15
29
  requirement: !ruby/object:Gem::Requirement