pstream 0.1.7 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|