pstream 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/pstream +11 -39
- data/lib/pstream.rb +126 -20
- data/lib/pstream/cipher_negotiation.rb +56 -0
- data/lib/pstream/stream.rb +34 -7
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ee81e7ea543491338bb800254b0b2cc4c0850c3
|
4
|
+
data.tar.gz: d877d6ff3c9deca5cc7b3ef7e07531e9e1f587de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f65be907ced9b779793e85607d5b0e7a80306d8527dd7981890c0585da482cd4ab35a2de2ede23aa53b5c73b3ed224ec02e20aa76bc6c6a7f4a5a42d23ee5447
|
7
|
+
data.tar.gz: 6cace69f3b2e5eaa02ffc81dffb7a90bda72cd9dfe6413547f532f4ed289916fa2ca92558853679a9e442e2858300963e456e7e75eb7e628f351641174f0cdca
|
data/bin/pstream
CHANGED
@@ -92,51 +92,23 @@ end
|
|
92
92
|
options = parse(ARGV)
|
93
93
|
|
94
94
|
begin
|
95
|
-
pstream = PStream.new(
|
95
|
+
pstream = PStream.new(
|
96
|
+
options["pcap"],
|
97
|
+
!String.disable_colorization
|
98
|
+
)
|
96
99
|
|
97
100
|
if (options["stream"])
|
98
|
-
pstream.get_stream(
|
101
|
+
puts pstream.get_stream(
|
99
102
|
options["stream"].to_i,
|
100
103
|
options["prot"]
|
101
|
-
).
|
102
|
-
m = line.match(/([0-9A-Fa-f]{8}) (.*) (.{17})/)
|
103
|
-
puts [
|
104
|
-
m[1].light_blue,
|
105
|
-
m[2].light_green,
|
106
|
-
m[3].white
|
107
|
-
].join(" ")
|
108
|
-
end
|
104
|
+
).to_s
|
109
105
|
elsif (options["ciphers"])
|
110
|
-
|
111
|
-
|
112
|
-
pstream.to_s.split("\n").each do |line|
|
113
|
-
case line
|
114
|
-
when /.*:$/
|
115
|
-
# Headers
|
116
|
-
puts line.white
|
117
|
-
when /<->/
|
118
|
-
# Streams
|
119
|
-
m = line.match(/([0-9]+) \| (.+) \| ([0-9]+ Frames)/)
|
120
|
-
puts [
|
121
|
-
m[1].light_blue,
|
122
|
-
m[2].light_green,
|
123
|
-
m[3].light_white
|
124
|
-
].join(" | ")
|
125
|
-
else
|
126
|
-
case line
|
127
|
-
when /Unknown/
|
128
|
-
puts line.light_yellow
|
129
|
-
when /NULL|MD5|RC4|anon/
|
130
|
-
# Bad cipher suites
|
131
|
-
puts line.light_red
|
132
|
-
when /E?(EC)?DHE?|AES_256/
|
133
|
-
# Great cipher suites
|
134
|
-
puts line.light_green
|
135
|
-
else
|
136
|
-
puts line.white
|
137
|
-
end
|
138
|
-
end
|
106
|
+
pstream.cipher_negotiations.each do |negotiation|
|
107
|
+
puts negotiation.to_s
|
139
108
|
end
|
109
|
+
else
|
110
|
+
# Summarize
|
111
|
+
puts pstream.to_s
|
140
112
|
end
|
141
113
|
rescue PStream::Error => e
|
142
114
|
$stderr.puts e.message.red
|
data/lib/pstream.rb
CHANGED
@@ -4,13 +4,115 @@ require "scoobydoo"
|
|
4
4
|
class PStream
|
5
5
|
attr_reader :streams
|
6
6
|
|
7
|
-
def
|
7
|
+
def cipher_negotiations
|
8
|
+
negotiations = Hash.new
|
9
|
+
|
8
10
|
# List ciphers during ssl handshake
|
9
11
|
out = %x(
|
10
12
|
tshark -r #{@pcap} -Y ssl.handshake.ciphersuite -V 2>&1 \
|
11
|
-
| \grep -E "Internet
|
13
|
+
| \grep -E "(Handshake|Internet) Prot|Cipher Suite"
|
12
14
|
)
|
13
|
-
|
15
|
+
|
16
|
+
negotiation = nil
|
17
|
+
hello = nil
|
18
|
+
out.split("\n").each do |line|
|
19
|
+
case line.gsub(/^ +/, "")
|
20
|
+
when /^Cipher Suite:/
|
21
|
+
m = line.match(/Cipher Suite: ([^ ]+) (.*)$/)
|
22
|
+
case hello
|
23
|
+
when "Client"
|
24
|
+
case m[1]
|
25
|
+
when "Unknown"
|
26
|
+
negotiation.suites.push("#{m[1]} #{m[2]}")
|
27
|
+
else
|
28
|
+
negotiation.suites.push(m[1])
|
29
|
+
end
|
30
|
+
when "Server"
|
31
|
+
id = "#{negotiation.dst} <-> #{negotiation.src}"
|
32
|
+
# Ignore partial handshakes that are server side
|
33
|
+
# only
|
34
|
+
if (negotiations[id])
|
35
|
+
case m[1]
|
36
|
+
when "Unknown"
|
37
|
+
negotiations[id].suite = "#{m[1]} #{m[2]}"
|
38
|
+
else
|
39
|
+
negotiations[id].suite = m[1]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
negotiation = nil
|
43
|
+
end
|
44
|
+
when /^Cipher Suites Length:/
|
45
|
+
m = line.match(/Cipher Suites Length: ([0-9]+)$/)
|
46
|
+
negotiation.length = m[1].to_i
|
47
|
+
when /^Handshake Protocol:/
|
48
|
+
m = line.match(/Handshake Protocol: ([^ ]+) Hello$/)
|
49
|
+
hello = m[1]
|
50
|
+
when /^Internet Protocol Version/
|
51
|
+
if (negotiation)
|
52
|
+
id = "#{negotiation.src} <-> #{negotiation.dst}"
|
53
|
+
negotiations[id] = negotiation
|
54
|
+
end
|
55
|
+
|
56
|
+
m = line.gsub("Internet Protocol Version", "").match(
|
57
|
+
/(4|6), Src: ([^,]+), Dst: (.*)$/
|
58
|
+
)
|
59
|
+
|
60
|
+
ipv = m[1]
|
61
|
+
src = m[2]
|
62
|
+
dst = m[3]
|
63
|
+
|
64
|
+
negotiation = PStream::CipherNegotiation.new(
|
65
|
+
self,
|
66
|
+
ipv,
|
67
|
+
src,
|
68
|
+
dst,
|
69
|
+
@colorize
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Keep parital handshakes that are client side only
|
75
|
+
if (negotiation)
|
76
|
+
id = "#{negotiation.src} <-> #{negotiation.dst}"
|
77
|
+
negotiations[id] = negotiation
|
78
|
+
end
|
79
|
+
|
80
|
+
return negotiations.values
|
81
|
+
end
|
82
|
+
|
83
|
+
def colorize_cipher_suite(suite)
|
84
|
+
return suite if (!@colorize)
|
85
|
+
|
86
|
+
case suite
|
87
|
+
when /Unknown/
|
88
|
+
# Unknown
|
89
|
+
return suite.light_yellow
|
90
|
+
when /NULL|MD5|RC4|anon/
|
91
|
+
# Bad cipher suites
|
92
|
+
return suite.light_red
|
93
|
+
when /E?(EC)?DHE?|AES_256/
|
94
|
+
# Great cipher suites
|
95
|
+
return suite.light_green
|
96
|
+
else
|
97
|
+
# Maybe OK
|
98
|
+
return suite.light_white
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def colorize_header(header)
|
103
|
+
return header if (!@colorize)
|
104
|
+
return header.light_cyan
|
105
|
+
end
|
106
|
+
|
107
|
+
def colorize_stream(stream)
|
108
|
+
if (!@colorize)
|
109
|
+
return "#{stream.id} | #{stream.desc} | #{stream.frames}"
|
110
|
+
end
|
111
|
+
return [
|
112
|
+
"#{stream.id}".light_blue,
|
113
|
+
stream.desc.light_green,
|
114
|
+
stream.frames.light_white
|
115
|
+
].join(" | ")
|
14
116
|
end
|
15
117
|
|
16
118
|
def get_stream(stream, prot = "tcp")
|
@@ -60,7 +162,16 @@ class PStream
|
|
60
162
|
count = 0
|
61
163
|
out.split("\n").each do |line|
|
62
164
|
desc, frames = line.split(" | ")
|
63
|
-
streams.push(
|
165
|
+
streams.push(
|
166
|
+
Stream.new(
|
167
|
+
@pcap,
|
168
|
+
prot,
|
169
|
+
count,
|
170
|
+
desc,
|
171
|
+
frames,
|
172
|
+
@colorize
|
173
|
+
)
|
174
|
+
)
|
64
175
|
count += 1
|
65
176
|
end
|
66
177
|
|
@@ -68,7 +179,9 @@ class PStream
|
|
68
179
|
end
|
69
180
|
private :get_streams
|
70
181
|
|
71
|
-
def initialize(pcap)
|
182
|
+
def initialize(pcap, colorize = false)
|
183
|
+
@colorize = colorize
|
184
|
+
|
72
185
|
if (ScoobyDoo.where_are_you("tshark").nil?)
|
73
186
|
raise PStream::Error::TsharkNotFound.new
|
74
187
|
end
|
@@ -87,31 +200,23 @@ class PStream
|
|
87
200
|
end
|
88
201
|
end
|
89
202
|
|
90
|
-
def negotiated_ciphers
|
91
|
-
f = "ssl.handshake.ciphersuite && ssl.handshake.type == 2"
|
92
|
-
out = %x(
|
93
|
-
tshark -r #{@pcap} -Y "#{f}" -V 2>&1 | \
|
94
|
-
\grep -E "Cipher Suite:" | \
|
95
|
-
sed -r "s|^ +Cipher Suite: ||g" | sort -u
|
96
|
-
)
|
97
|
-
return out.split("\n")
|
98
|
-
end
|
99
|
-
|
100
203
|
def summary
|
101
204
|
ret = Array.new
|
102
205
|
|
103
206
|
# List streams
|
104
207
|
["tcp", "udp"].each do |prot|
|
105
|
-
ret.push("#{prot} streams:")
|
106
|
-
@streams[prot].each do |
|
107
|
-
ret.push(
|
208
|
+
ret.push(colorize_header("#{prot} streams:"))
|
209
|
+
@streams[prot].each do |stream|
|
210
|
+
ret.push(colorize_stream(stream))
|
108
211
|
end
|
109
212
|
ret.push("")
|
110
213
|
end
|
111
214
|
|
112
215
|
# List ciphers that were actually selected
|
113
|
-
ret.push("Ciphers in use:")
|
114
|
-
|
216
|
+
ret.push(colorize_header("Ciphers in use:"))
|
217
|
+
cipher_negotiations.each do |negotiation|
|
218
|
+
ret.push(colorize_cipher_suite(negotiation.suite))
|
219
|
+
end
|
115
220
|
|
116
221
|
return ret.join("\n")
|
117
222
|
end
|
@@ -122,5 +227,6 @@ class PStream
|
|
122
227
|
end
|
123
228
|
end
|
124
229
|
|
230
|
+
require "pstream/cipher_negotiation"
|
125
231
|
require "pstream/error"
|
126
232
|
require "pstream/stream"
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class PStream::CipherNegotiation
|
2
|
+
attr_accessor :length
|
3
|
+
attr_accessor :suite
|
4
|
+
attr_accessor :suites
|
5
|
+
|
6
|
+
attr_reader :dst
|
7
|
+
attr_reader :ipv
|
8
|
+
attr_reader :pstream
|
9
|
+
attr_reader :src
|
10
|
+
|
11
|
+
def colorize_hosts(src, dst)
|
12
|
+
return "#{src} <-> #{dst}" if (!@colorize)
|
13
|
+
return "#{src} <-> #{dst}".light_cyan
|
14
|
+
end
|
15
|
+
|
16
|
+
def colorize_ipv(ipv)
|
17
|
+
return "IPv#{ipv}" if (!@colorize)
|
18
|
+
return "IPv#{ipv}".light_cyan
|
19
|
+
end
|
20
|
+
|
21
|
+
def colorize_selected_suite(suite)
|
22
|
+
return [
|
23
|
+
"Selected".light_blue,
|
24
|
+
@pstream.colorize_cipher_suite(suite),
|
25
|
+
"from:".light_blue
|
26
|
+
].join(" ")
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(pstream, ipv, src, dst, colorize)
|
30
|
+
@colorize = colorize
|
31
|
+
@dst = dst
|
32
|
+
@ipv = ipv
|
33
|
+
@length = nil
|
34
|
+
@pstream = pstream
|
35
|
+
@src = src
|
36
|
+
@suite = nil
|
37
|
+
@suites = Array.new
|
38
|
+
end
|
39
|
+
|
40
|
+
def summary
|
41
|
+
ret = Array.new
|
42
|
+
ret.push(
|
43
|
+
"#{colorize_ipv(@ipv)} #{colorize_hosts(@src, @dst)}"
|
44
|
+
)
|
45
|
+
ret.push(" #{colorize_selected_suite(@suite)}") if (@suite)
|
46
|
+
@suites.each do |suite|
|
47
|
+
ret.push(" #{@pstream.colorize_cipher_suite(suite)}")
|
48
|
+
end
|
49
|
+
|
50
|
+
return ret.join("\n")
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
return summary
|
55
|
+
end
|
56
|
+
end
|
data/lib/pstream/stream.rb
CHANGED
@@ -3,24 +3,51 @@ class PStream::Stream
|
|
3
3
|
attr_reader :frames
|
4
4
|
attr_reader :id
|
5
5
|
|
6
|
+
def colorize_address(address)
|
7
|
+
return address if (!@colorize)
|
8
|
+
return address.light_blue
|
9
|
+
end
|
10
|
+
|
11
|
+
def colorize_ascii(ascii)
|
12
|
+
return ascii if (!@colorize)
|
13
|
+
return ascii.light_white
|
14
|
+
end
|
15
|
+
|
16
|
+
def colorize_hex(hex)
|
17
|
+
return hex if (!@colorize)
|
18
|
+
return hex.light_green
|
19
|
+
end
|
20
|
+
|
6
21
|
def contents
|
7
22
|
case @prot
|
8
23
|
when /^tcp$/i
|
9
|
-
|
24
|
+
stream=@id
|
10
25
|
when /^udp$/i
|
11
|
-
|
26
|
+
stream=@desc.gsub(" <-> ", ",")
|
12
27
|
else
|
13
28
|
raise PStream::Error::ProtocolNotSupported.new(@prot)
|
14
29
|
end
|
15
30
|
|
16
|
-
|
17
|
-
|
31
|
+
ret = Array.new
|
32
|
+
%x(
|
33
|
+
tshark -r #{@pcap} -z follow,#{@prot},hex,#{stream} | \
|
18
34
|
sed "s|^ ||" | \grep -E "^[0-9A-Fa-f]{8}"
|
19
|
-
)
|
20
|
-
|
35
|
+
).split("\n").each do |line|
|
36
|
+
m = line.match(/([0-9A-Fa-f]{8}) (.*) (.{17})/)
|
37
|
+
ret.push(
|
38
|
+
[
|
39
|
+
colorize_address(m[1]),
|
40
|
+
colorize_hex(m[2]),
|
41
|
+
colorize_ascii(m[3])
|
42
|
+
].join(" ")
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
return ret.join("\n")
|
21
47
|
end
|
22
48
|
|
23
|
-
def initialize(pcap, prot, id, desc, frames)
|
49
|
+
def initialize(pcap, prot, id, desc, frames, colorize = false)
|
50
|
+
@colorize = colorize
|
24
51
|
@desc = desc
|
25
52
|
@frames = frames
|
26
53
|
@id = id
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pstream
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Miles Whittaker
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-03-
|
11
|
+
date: 2016-03-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|
@@ -59,6 +59,7 @@ extra_rdoc_files: []
|
|
59
59
|
files:
|
60
60
|
- bin/pstream
|
61
61
|
- lib/pstream.rb
|
62
|
+
- lib/pstream/cipher_negotiation.rb
|
62
63
|
- lib/pstream/error.rb
|
63
64
|
- lib/pstream/error/pcap_not_found.rb
|
64
65
|
- lib/pstream/error/pcap_not_readable.rb
|