dorothy2 0.0.3 → 1.0.0
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.
- data/README.md +30 -6
- data/TODO +21 -0
- data/bin/dorothy_start +7 -6
- data/bin/dparser_start +13 -1
- data/etc/ddl/dorothive.ddl +2 -31
- data/lib/doroParser.rb +30 -23
- data/lib/dorothy2/BFM.rb +5 -13
- data/lib/dorothy2/do-utils.rb +4 -5
- data/lib/dorothy2/version.rb +1 -1
- data/lib/dorothy2.rb +5 -8
- data/lib/mu/xtractr/about.rb +57 -0
- data/lib/mu/xtractr/content.rb +68 -0
- data/lib/mu/xtractr/field.rb +178 -0
- data/lib/mu/xtractr/flow.rb +162 -0
- data/lib/mu/xtractr/flows.rb +118 -0
- data/lib/mu/xtractr/host.rb +87 -0
- data/lib/mu/xtractr/packet.rb +138 -0
- data/lib/mu/xtractr/packets.rb +122 -0
- data/lib/mu/xtractr/service.rb +77 -0
- data/lib/mu/xtractr/stream/http.rb +103 -0
- data/lib/mu/xtractr/stream.rb +132 -0
- data/lib/mu/xtractr/term.rb +73 -0
- data/lib/mu/xtractr/test/stream/tc_http.rb +53 -0
- data/lib/mu/xtractr/test/tc_field.rb +140 -0
- data/lib/mu/xtractr/test/tc_flow.rb +79 -0
- data/lib/mu/xtractr/test/tc_flows.rb +94 -0
- data/lib/mu/xtractr/test/tc_host.rb +116 -0
- data/lib/mu/xtractr/test/tc_packet.rb +110 -0
- data/lib/mu/xtractr/test/tc_packets.rb +84 -0
- data/lib/mu/xtractr/test/tc_service.rb +66 -0
- data/lib/mu/xtractr/test/tc_stream.rb +56 -0
- data/lib/mu/xtractr/test/tc_term.rb +59 -0
- data/lib/mu/xtractr/test/tc_views.rb +118 -0
- data/lib/mu/xtractr/test/tc_xtractr.rb +151 -0
- data/lib/mu/xtractr/test/test.rb +19 -0
- data/lib/mu/xtractr/views.rb +204 -0
- data/lib/mu/xtractr.rb +257 -0
- metadata +32 -4
@@ -0,0 +1,178 @@
|
|
1
|
+
# "THE BEER-WARE LICENSE" (Revision 42):
|
2
|
+
# Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
|
3
|
+
# notice you can do whatever you want with this stuff. If we meet some day,
|
4
|
+
# and you think this stuff is worth it, you can buy us a beer in return.
|
5
|
+
#
|
6
|
+
# All about pcapr
|
7
|
+
# * http://www.pcapr.net
|
8
|
+
# * http://groups.google.com/group/pcapr-forum
|
9
|
+
# * http://twitter.com/pcapr
|
10
|
+
#
|
11
|
+
# Mu Dynamics
|
12
|
+
# * http://www.mudynamics.com
|
13
|
+
# * http://labs.mudynamics.com
|
14
|
+
|
15
|
+
module Mu
|
16
|
+
class Xtractr
|
17
|
+
# = Field
|
18
|
+
# Field represents a named packet field in the <b>xtractr</b> index. Each field
|
19
|
+
# contains tokenized terms along with the frequency at which they occur. A
|
20
|
+
# field is a queryable object and can be used to iterate over flows or packets
|
21
|
+
# that match the Field::Value. See Flow or Packet for more information on the
|
22
|
+
# fields that are stored in the index.
|
23
|
+
#
|
24
|
+
# xtractr.field('http.request.uri').each_packet { |packet ... }
|
25
|
+
#
|
26
|
+
# The fields are useful for quick analysis on top trending terms across the
|
27
|
+
# entire index. Here are the top HTTP fields that have NOP slides in them:
|
28
|
+
#
|
29
|
+
# xtractr.fields(/^http/).select do |field|
|
30
|
+
# not field.terms(/AAAAAA/i).empty?
|
31
|
+
# end
|
32
|
+
class Field
|
33
|
+
include Enumerable
|
34
|
+
|
35
|
+
attr_reader :xtractr # :nodoc:
|
36
|
+
|
37
|
+
# The name of the field.
|
38
|
+
attr_reader :name
|
39
|
+
|
40
|
+
# = Field::Value
|
41
|
+
# Field::Value represents an instance of a field with a concrete value that
|
42
|
+
# can further used for fine grained searches.
|
43
|
+
class Value
|
44
|
+
attr_reader :xtractr # :nodoc:
|
45
|
+
|
46
|
+
# Return the field object.
|
47
|
+
attr_reader :field
|
48
|
+
|
49
|
+
# Return the value of the field object.
|
50
|
+
attr_reader :value
|
51
|
+
|
52
|
+
def initialize xtractr, json # :nodoc:
|
53
|
+
@xtractr = xtractr
|
54
|
+
@field = Field.new(xtractr, json['key'])
|
55
|
+
@value = json['value']
|
56
|
+
end
|
57
|
+
|
58
|
+
def q # :nodoc:
|
59
|
+
"#{field.name}:\"#{value}\""
|
60
|
+
end
|
61
|
+
|
62
|
+
# Fetch the list of packets that contain this Field::Value. If the
|
63
|
+
# optional query is given, it's AND'd to the query that matches this
|
64
|
+
# Field::Value.
|
65
|
+
# value.packets.each { |pkt| ... }
|
66
|
+
# value.packets('dns.qry.name:apple').each { |pkt| ... }
|
67
|
+
def packets _q=nil
|
68
|
+
q2 = q
|
69
|
+
q2 << " #{_q}" if _q
|
70
|
+
Packets.new xtractr, :q => q2
|
71
|
+
end
|
72
|
+
|
73
|
+
# Iterate over each packet that contains this field value. This is
|
74
|
+
# a convenience function used primiarily in method chaining.
|
75
|
+
def each_packet(_q=nil, &blk) # :yields: packet
|
76
|
+
packets(_q).each(&blk)
|
77
|
+
return self
|
78
|
+
end
|
79
|
+
|
80
|
+
# Count the unique values of the specified field amongst all the packets
|
81
|
+
# that matched the query.
|
82
|
+
# value.count('http.request.method')
|
83
|
+
def count _field
|
84
|
+
which = field.name =~ /^flow\./ ? 'flows' : 'packets'
|
85
|
+
Views.count xtractr, _field, "/api/#{which}/report", :q => q
|
86
|
+
end
|
87
|
+
|
88
|
+
# Sum the unique numeric values of vfield, keyed by the unique values of
|
89
|
+
# kfield.
|
90
|
+
# value.sum('flow.src', 'flow.bytes')
|
91
|
+
def sum kfield, vfield
|
92
|
+
which = field.name =~ /^flow\./ ? 'flows' : 'packets'
|
93
|
+
Views.sum xtractr, kfield, vfield, "/api/#{which}/report", :q => q
|
94
|
+
end
|
95
|
+
|
96
|
+
def inspect # :nodoc:
|
97
|
+
"#<value:#{field.name} #{value}>"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def initialize xtractr, name # :nodoc:
|
102
|
+
@xtractr = xtractr
|
103
|
+
@name = name
|
104
|
+
end
|
105
|
+
|
106
|
+
# Fetch the terms and their packet frequencies (in packets) for this field.
|
107
|
+
# If the optional start term is given, then the term enumeration starts
|
108
|
+
# from the specified term.
|
109
|
+
# field.each { |term| ... }
|
110
|
+
# field.each('mozilla') { |term| ... }
|
111
|
+
def each_term(start='') # :yields: term
|
112
|
+
opts = {}
|
113
|
+
opts[:start] = start
|
114
|
+
opts[:limit] = 101
|
115
|
+
|
116
|
+
while true
|
117
|
+
result = xtractr.json "api/field/#{name}/terms", opts
|
118
|
+
rows = result['rows']
|
119
|
+
break if rows.empty?
|
120
|
+
|
121
|
+
rows[0, 100].each do |row|
|
122
|
+
term = Term.new self, row
|
123
|
+
yield term
|
124
|
+
end
|
125
|
+
|
126
|
+
break if rows.size < 101
|
127
|
+
opts[:start] = rows[100]['key']
|
128
|
+
end
|
129
|
+
|
130
|
+
return self
|
131
|
+
end
|
132
|
+
|
133
|
+
# Fetch the list of <em>all</em> the unique terms for this field, sorted by the
|
134
|
+
# frequency of occurence in the packets. This can be used for some quick
|
135
|
+
# trend analysis to see which term of a given field appears most amongst
|
136
|
+
# all packets in the index. Here's an example to print out the top 10 terms
|
137
|
+
# of <em>http.request.uri</em>.
|
138
|
+
# p xtractr.field('http.request.uri').terms[0..10]
|
139
|
+
def terms regex=nil
|
140
|
+
regex = Regexp.new(regex, Regexp::IGNORECASE) if regex.is_a? String
|
141
|
+
t = regex ? entries.select { |name| name =~ regex } : entries
|
142
|
+
t.sort { |a, b| b.frequency <=> a.frequency }
|
143
|
+
end
|
144
|
+
|
145
|
+
# Find the term for this field which has the name and the packet frequency.
|
146
|
+
# field.term 'mozilla'
|
147
|
+
def [] which
|
148
|
+
result = xtractr.json "api/field/#{name}/terms", :start => which, :limit => 1
|
149
|
+
rows = result['rows']
|
150
|
+
if rows.empty? || rows[0]['key'] != which
|
151
|
+
raise ArgumentError, "Unknown term #{which} for field #{name}"
|
152
|
+
end
|
153
|
+
return Term.new(self, rows[0])
|
154
|
+
end
|
155
|
+
|
156
|
+
# Find out all the unique values of this field with an optional query.
|
157
|
+
# xtractr.field('http.user.agent').count('flow.src:192.168.1.1')
|
158
|
+
def count q='*'
|
159
|
+
Views.count xtractr, self, "api/flows/report", :q => q
|
160
|
+
end
|
161
|
+
|
162
|
+
# Return a list of Field::Value objects for this field, sorted by their
|
163
|
+
# frequency. This is a convenience method to use the resulting Field::Value
|
164
|
+
# objects in method chaining.
|
165
|
+
# xtractr.field('http.user.agent').values.first.packets.slice('foo.pcap')
|
166
|
+
def values q='*'
|
167
|
+
count(q).map { |c| c.object }
|
168
|
+
end
|
169
|
+
|
170
|
+
def inspect # :nodoc:
|
171
|
+
"#<field:#{name}>"
|
172
|
+
end
|
173
|
+
|
174
|
+
alias_method :each, :each_term
|
175
|
+
alias_method :term, :[]
|
176
|
+
end
|
177
|
+
end # Xtractr
|
178
|
+
end # Mu
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# "THE BEER-WARE LICENSE" (Revision 42):
|
2
|
+
# Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
|
3
|
+
# notice you can do whatever you want with this stuff. If we meet some day,
|
4
|
+
# and you think this stuff is worth it, you can buy us a beer in return.
|
5
|
+
#
|
6
|
+
# All about pcapr
|
7
|
+
# * http://www.pcapr.net
|
8
|
+
# * http://groups.google.com/group/pcapr-forum
|
9
|
+
# * http://twitter.com/pcapr
|
10
|
+
#
|
11
|
+
# Mu Dynamics
|
12
|
+
# * http://www.mudynamics.com
|
13
|
+
# * http://labs.mudynamics.com
|
14
|
+
|
15
|
+
module Mu
|
16
|
+
class Xtractr
|
17
|
+
# = Flow
|
18
|
+
# Flow represents a single conversation between two hosts. Depending on whether
|
19
|
+
# it's IP, UDP or TCP, xtractr uses different information from the IP header
|
20
|
+
# of the packets (src/dst addresses and ports) to logically group them
|
21
|
+
# together. Each flow also has the duration (difference in the timestamp between
|
22
|
+
# the first and last packet in the conversation), the total bytes that were
|
23
|
+
# exchanged as well as the logical messages that were exchanged. For example:
|
24
|
+
#
|
25
|
+
# <b>Identify hosts that performed TCP port scans</b>
|
26
|
+
#
|
27
|
+
# xtractr.flows('flow.proto:6 flow.cmsgs:0 flow.smsgs:0').count('flow.src')
|
28
|
+
#
|
29
|
+
# <b>Identify DNS queries with no response (possibly timed out)</b>
|
30
|
+
#
|
31
|
+
# xtractr.flows('flow.service:DNS flow.smsgs:0').count('dns.qry.name')
|
32
|
+
class Flow
|
33
|
+
include Enumerable
|
34
|
+
|
35
|
+
attr_reader :xtractr # :nodoc:
|
36
|
+
|
37
|
+
# The unique ID of the flow.
|
38
|
+
attr_reader :id
|
39
|
+
|
40
|
+
# The timestamp of the flow, determined by the first packet in the flow.
|
41
|
+
attr_reader :time
|
42
|
+
|
43
|
+
# The duration of the flow, determined by the first and last packet in the flow.
|
44
|
+
attr_reader :duration
|
45
|
+
|
46
|
+
# The source host of the flow.
|
47
|
+
attr_reader :src
|
48
|
+
|
49
|
+
# The destination host of the flow.
|
50
|
+
attr_reader :dst
|
51
|
+
|
52
|
+
# The IP protocol of the flow.
|
53
|
+
attr_reader :proto
|
54
|
+
|
55
|
+
# The source port of the flow (if applicable).
|
56
|
+
attr_reader :sport
|
57
|
+
|
58
|
+
# The destination port of the flow (if applicable).
|
59
|
+
attr_reader :dport
|
60
|
+
|
61
|
+
# The service of the flow (like DNS or HTTP).
|
62
|
+
attr_reader :service
|
63
|
+
|
64
|
+
# The title of the flow.
|
65
|
+
attr_reader :title
|
66
|
+
|
67
|
+
# The total ##packets in the flow.
|
68
|
+
attr_reader :packets
|
69
|
+
|
70
|
+
# The total ##bytes (request and response) in the flow.
|
71
|
+
attr_reader :bytes
|
72
|
+
|
73
|
+
# The logical client messages (payloads) in the flow.
|
74
|
+
attr_reader :cmsgs
|
75
|
+
|
76
|
+
# The logical server messages (payloads) in the flow.
|
77
|
+
attr_reader :smsgs
|
78
|
+
|
79
|
+
def initialize xtractr, json # :nodoc:
|
80
|
+
@xtractr = xtractr
|
81
|
+
@id = json['id']
|
82
|
+
@time = json['time']
|
83
|
+
@duration = json['duration']
|
84
|
+
@src = Host.new xtractr, json['src']
|
85
|
+
@dst = Host.new xtractr, json['dst']
|
86
|
+
@proto = json['proto']
|
87
|
+
@sport = json['sport']
|
88
|
+
@dport = json['dport']
|
89
|
+
@service = Service.new xtractr, json['service']
|
90
|
+
@title = json['title']
|
91
|
+
@packets = json['packets']
|
92
|
+
@bytes = json['bytes']
|
93
|
+
@cmsgs = json['cmsgs']
|
94
|
+
@smsgs = json['smsgs']
|
95
|
+
@first_id = json['first']
|
96
|
+
@last_id = json['last']
|
97
|
+
@iterator = Packets.new(xtractr, :q => "pkt.flow:#{id}")
|
98
|
+
end
|
99
|
+
|
100
|
+
# Return the first packet for this flow. Together the first and last
|
101
|
+
# packets make up the span of the flow. Read this
|
102
|
+
# blog[http://labs.mudynamics.com/2010/09/30/visualizing-application-flows-with-xtractr/]
|
103
|
+
# to see how these spans enable flow visualization.
|
104
|
+
# xtractr.flow(1).first.bytes
|
105
|
+
def first
|
106
|
+
@first ||= xtractr.packet @first_id
|
107
|
+
end
|
108
|
+
|
109
|
+
# Return the last packet for this flow. Together the first and last
|
110
|
+
# packets make up the span of the flow. Read this
|
111
|
+
# blog[http://labs.mudynamics.com/2010/09/30/visualizing-application-flows-with-xtractr/]
|
112
|
+
# to see how these spans enable flow visualization.
|
113
|
+
# xtractr.flow(2).last.bytes
|
114
|
+
def last
|
115
|
+
@last ||= xtractr.packet @last_id
|
116
|
+
end
|
117
|
+
|
118
|
+
# Iterate over each packet in this flow.
|
119
|
+
# flow.each { |pkt| ... }
|
120
|
+
def each_packet(&blk) # :yields: packet
|
121
|
+
@iterator.each(&blk)
|
122
|
+
return self
|
123
|
+
end
|
124
|
+
|
125
|
+
# Reassemble the TCP stream for this flow (assuming it's a TCP flow) and
|
126
|
+
# return the stream. This is the basis for doing content extraction from
|
127
|
+
# packets even if the packets span multiple pcaps.
|
128
|
+
# xtractr.service('HTTP').flows.first.stream
|
129
|
+
def stream
|
130
|
+
result = xtractr.json "api/flow/#{id}/stream"
|
131
|
+
return Stream.new(xtractr, self, result)
|
132
|
+
end
|
133
|
+
|
134
|
+
# A convenience method to fetch the stream for this flow, extract the
|
135
|
+
# content and then return an array of contents.
|
136
|
+
# xtractr.flows('flow.service:HTTP favicon.ico').each do |flow|
|
137
|
+
# flow.contents.each { |c| c.save }
|
138
|
+
# end
|
139
|
+
def contents
|
140
|
+
stream.contents
|
141
|
+
end
|
142
|
+
|
143
|
+
# Stich together a pcap made up of all packets containing this flow and
|
144
|
+
# save it to the filename. It's possible for the packets to span multiple
|
145
|
+
# pcaps, but xtractr makes it seamless.
|
146
|
+
# flow.save("foo.pcap")
|
147
|
+
def save filename
|
148
|
+
open(filename, "w") do |ios|
|
149
|
+
pcap = xtractr.get "api/packets/slice", :q => "pkt.flow:#{id}"
|
150
|
+
ios.write pcap
|
151
|
+
end
|
152
|
+
return self
|
153
|
+
end
|
154
|
+
|
155
|
+
def inspect # :nodoc:
|
156
|
+
"#<flow:#{id} #{service.name} #{src.address}:#{sport} > #{dst.address}:#{dport} #{title}"
|
157
|
+
end
|
158
|
+
|
159
|
+
alias_method :each, :each_packet
|
160
|
+
end
|
161
|
+
end # Xtractr
|
162
|
+
end # Mu
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# "THE BEER-WARE LICENSE" (Revision 42):
|
2
|
+
# Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
|
3
|
+
# notice you can do whatever you want with this stuff. If we meet some day,
|
4
|
+
# and you think this stuff is worth it, you can buy us a beer in return.
|
5
|
+
#
|
6
|
+
# All about pcapr
|
7
|
+
# * http://www.pcapr.net
|
8
|
+
# * http://groups.google.com/group/pcapr-forum
|
9
|
+
# * http://twitter.com/pcapr
|
10
|
+
#
|
11
|
+
# Mu Dynamics
|
12
|
+
# * http://www.mudynamics.com
|
13
|
+
# * http://labs.mudynamics.com
|
14
|
+
|
15
|
+
module Mu
|
16
|
+
class Xtractr
|
17
|
+
# Flows is an iterator for the flows in the index, based on a given query.
|
18
|
+
# The default query for this iterator is '*', implying that it will iterate
|
19
|
+
# over <em>all</em> the flows in the index.
|
20
|
+
class Flows
|
21
|
+
include Enumerable
|
22
|
+
|
23
|
+
attr_reader :xtractr # :nodoc:
|
24
|
+
|
25
|
+
MAX_PAGE_SIZE = 100 # :nodoc:
|
26
|
+
|
27
|
+
def initialize xtractr, opts # :nodoc:
|
28
|
+
@xtractr = xtractr
|
29
|
+
@opts = opts.dup
|
30
|
+
@opts[:q] ||= '*'
|
31
|
+
end
|
32
|
+
|
33
|
+
def q # :nodoc:
|
34
|
+
@opts[:q]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Iterate over each flow that matches the search criteria. It's always
|
38
|
+
# better to use this with a fine-grained query instead of flows.to_a
|
39
|
+
# because it's going to try and load *all* flows from the index.
|
40
|
+
# xtractr.flows("flow.src:192.168.1.1").each { |flow| ... }
|
41
|
+
def each_flow() # :yields: flow
|
42
|
+
_opts = @opts.dup
|
43
|
+
_opts[:start] ||= 1
|
44
|
+
_opts[:limit] ||= MAX_PAGE_SIZE
|
45
|
+
|
46
|
+
while true
|
47
|
+
result = xtractr.json "api/flows", _opts
|
48
|
+
rows = result['rows']
|
49
|
+
break if rows.empty?
|
50
|
+
|
51
|
+
rows[0, MAX_PAGE_SIZE-1].each do |row|
|
52
|
+
flow = Flow.new xtractr, row
|
53
|
+
yield flow
|
54
|
+
end
|
55
|
+
|
56
|
+
break if rows.size < MAX_PAGE_SIZE
|
57
|
+
_opts[:start] = rows[MAX_PAGE_SIZE-1]['id']
|
58
|
+
end
|
59
|
+
return self
|
60
|
+
end
|
61
|
+
|
62
|
+
# Fetch the first flow that matched the query. This is mainly used for
|
63
|
+
# unit testing, but useful within IRB to experiment with method chaining.
|
64
|
+
# flows.first.save("1.pcap")
|
65
|
+
def first
|
66
|
+
result = xtractr.json "api/flows", :start => 1, :limit => 1, :q => q
|
67
|
+
rows = result['rows']
|
68
|
+
rows.empty? ? nil : Flow.new(xtractr, rows[0])
|
69
|
+
end
|
70
|
+
|
71
|
+
# Count the unique values of the specified field amongst all the flows
|
72
|
+
# that matched the query.
|
73
|
+
# xtractr.flows('index.html').count('http.request.uri')
|
74
|
+
def count field
|
75
|
+
Views.count xtractr, field, '/api/flows/report', @opts
|
76
|
+
end
|
77
|
+
|
78
|
+
# Return a list of Field::Value objects for the specified field, sorted
|
79
|
+
# by their frequency. This is a convenience method used in method chaining.
|
80
|
+
# xtractr.flows('index.html').values('http.request.uri')
|
81
|
+
def values field
|
82
|
+
count(field).map { |c| c.object }
|
83
|
+
end
|
84
|
+
|
85
|
+
# Sum the numeric values of vfield, keyed by the unique values of
|
86
|
+
# kfield.
|
87
|
+
# xtractr.flows('index.html').sum('http.request.uri', 'flow.bytes')
|
88
|
+
def sum kfield, vfield
|
89
|
+
Views.sum xtractr, kfield, vfield, '/api/flows/report', @opts
|
90
|
+
end
|
91
|
+
|
92
|
+
# Save all the packets for this collection of flows into a pcap. It's
|
93
|
+
# possible that the packets for the flows might span multiple indexed
|
94
|
+
# pcaps.
|
95
|
+
# xtractr.flows('flow.service:DNS AAAA').save('dns.pcap')
|
96
|
+
def save filename
|
97
|
+
flow_ids = []
|
98
|
+
each_flow do |flow|
|
99
|
+
flow_ids << flow.id.to_s
|
100
|
+
break if flow_ids.size >= 1024
|
101
|
+
end
|
102
|
+
|
103
|
+
_q = "pkt.flow:(" << flow_ids.join('||') << ')'
|
104
|
+
open(filename, "w") do |ios|
|
105
|
+
pcap = xtractr.get "api/packets/slice", :q => _q
|
106
|
+
ios.write pcap
|
107
|
+
end
|
108
|
+
return self
|
109
|
+
end
|
110
|
+
|
111
|
+
def inspect # :nodoc:
|
112
|
+
"#<flows:#{@opts[:q]}>"
|
113
|
+
end
|
114
|
+
|
115
|
+
alias_method :each, :each_flow
|
116
|
+
end
|
117
|
+
end # Xtractr
|
118
|
+
end # Mu
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# "THE BEER-WARE LICENSE" (Revision 42):
|
2
|
+
# Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
|
3
|
+
# notice you can do whatever you want with this stuff. If we meet some day,
|
4
|
+
# and you think this stuff is worth it, you can buy us a beer in return.
|
5
|
+
#
|
6
|
+
# All about pcapr
|
7
|
+
# * http://www.pcapr.net
|
8
|
+
# * http://groups.google.com/group/pcapr-forum
|
9
|
+
# * http://twitter.com/pcapr
|
10
|
+
#
|
11
|
+
# Mu Dynamics
|
12
|
+
# * http://www.mudynamics.com
|
13
|
+
# * http://labs.mudynamics.com
|
14
|
+
|
15
|
+
module Mu
|
16
|
+
class Xtractr
|
17
|
+
# = Host
|
18
|
+
# Host is a generic entity representing MAC, IPv4 and IPv6 addresses. You can
|
19
|
+
# get a list of all the unique hosts in the index using the root xtractr instance.
|
20
|
+
# xtractr.hosts
|
21
|
+
class Host
|
22
|
+
attr_reader :xtractr # :nodoc:
|
23
|
+
|
24
|
+
# Returns the address of this host.
|
25
|
+
attr_reader :address
|
26
|
+
|
27
|
+
def initialize xtractr, address # :nodoc:
|
28
|
+
@xtractr = xtractr
|
29
|
+
@address = address
|
30
|
+
end
|
31
|
+
|
32
|
+
# Use the host as a server and get a unique list of its clients.
|
33
|
+
# xtractr.hosts(/192.168/).first.clients
|
34
|
+
def clients q=nil
|
35
|
+
_q = role2q :server, 'flow', q
|
36
|
+
Flows.new(xtractr, :q => _q).count('flow.src')
|
37
|
+
end
|
38
|
+
|
39
|
+
# Use the host as a client and get a unique list its servers.
|
40
|
+
# xtractr.hosts(/192.168/).first.servers
|
41
|
+
def servers q=nil
|
42
|
+
_q = role2q :client, 'flow', q
|
43
|
+
Flows.new(xtractr, :q => _q).count('flow.dst')
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get a unique list of the host's services. <em>role</em> can be one of
|
47
|
+
# :any, :client or :server to specify the role.
|
48
|
+
# host.services :server
|
49
|
+
def services role =:any, q=nil
|
50
|
+
_q = role2q role, 'flow', q
|
51
|
+
Flows.new(xtractr, :q => _q).count('flow.service')
|
52
|
+
end
|
53
|
+
|
54
|
+
# Return a flow iterator to iterate over the various flows that contain
|
55
|
+
# this host in the specified role.
|
56
|
+
# host.flows :client
|
57
|
+
def flows role =:any, q=nil
|
58
|
+
_q = role2q role, 'flow', q
|
59
|
+
Flows.new(xtractr, :q => _q)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Return a packet iterator to iterate over the various packets that contain
|
63
|
+
# this host in the specified role.
|
64
|
+
# host.packets :server
|
65
|
+
def packets role =:any, q=nil
|
66
|
+
_q = role2q role, 'pkt', q
|
67
|
+
Packets.new(xtractr, :q => _q)
|
68
|
+
end
|
69
|
+
|
70
|
+
def inspect # :nodoc:
|
71
|
+
"#<host:#{address}>"
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
def role2q role, forp, q=nil # :nodoc:
|
76
|
+
_q = case role
|
77
|
+
when :any: "#{forp}.src|#{forp}.dst:\"#{address}\""
|
78
|
+
when :client: "#{forp}.src:\"#{address}\""
|
79
|
+
when :server: "#{forp}.dst:\"#{address}\""
|
80
|
+
else raise ArgumentError, "Unknown role #{role}"
|
81
|
+
end
|
82
|
+
_q << " #{q}" if q
|
83
|
+
return _q
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end # Xtractr
|
87
|
+
end # Mu
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# "THE BEER-WARE LICENSE" (Revision 42):
|
2
|
+
# Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
|
3
|
+
# notice you can do whatever you want with this stuff. If we meet some day,
|
4
|
+
# and you think this stuff is worth it, you can buy us a beer in return.
|
5
|
+
#
|
6
|
+
# All about pcapr
|
7
|
+
# * http://www.pcapr.net
|
8
|
+
# * http://groups.google.com/group/pcapr-forum
|
9
|
+
# * http://twitter.com/pcapr
|
10
|
+
#
|
11
|
+
# Mu Dynamics
|
12
|
+
# * http://www.mudynamics.com
|
13
|
+
# * http://labs.mudynamics.com
|
14
|
+
|
15
|
+
module Mu
|
16
|
+
class Xtractr
|
17
|
+
# = Packet
|
18
|
+
# A class that represents a single packet in the xtractr index. You can
|
19
|
+
# iterate over all the packets in the index like this:
|
20
|
+
# xtractr.packets('blah').each { |pkt| ... }
|
21
|
+
class Packet
|
22
|
+
include Enumerable
|
23
|
+
|
24
|
+
attr_reader :xtractr # :nodoc:
|
25
|
+
|
26
|
+
# The unique ID of the packet.
|
27
|
+
attr_reader :id
|
28
|
+
|
29
|
+
# The file offset of the packet within the pcap.
|
30
|
+
attr_reader :offset
|
31
|
+
|
32
|
+
# The length of the packet (the entire frame).
|
33
|
+
attr_reader :length
|
34
|
+
|
35
|
+
# The relative timestamp of the packet.
|
36
|
+
attr_reader :time
|
37
|
+
|
38
|
+
# The direction of the packet (if it belongs to a flow).
|
39
|
+
attr_reader :dir
|
40
|
+
|
41
|
+
# The source host of the packet.
|
42
|
+
attr_reader :src
|
43
|
+
|
44
|
+
# The destination host of the packet.
|
45
|
+
attr_reader :dst
|
46
|
+
|
47
|
+
# The service of the packet.
|
48
|
+
attr_reader :service
|
49
|
+
|
50
|
+
# The title of the packet.
|
51
|
+
attr_reader :title
|
52
|
+
|
53
|
+
def initialize xtractr, json # :nodoc:
|
54
|
+
@xtractr = xtractr
|
55
|
+
@id = json['id']
|
56
|
+
@offset = json['offset']
|
57
|
+
@length = json['length']
|
58
|
+
@pcap_id = json['pcap']
|
59
|
+
@flow_id = json['flow']
|
60
|
+
@time = json['time']
|
61
|
+
@dir = json['dir']
|
62
|
+
@src = Host.new xtractr, json['src']
|
63
|
+
@dst = Host.new xtractr, json['dst']
|
64
|
+
@service = Service.new xtractr, json['service']
|
65
|
+
@title = json['title']
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the flow (if any) that this packet belongs to.
|
69
|
+
# xtractr.packets('index.html').first.flow
|
70
|
+
def flow
|
71
|
+
return nil if @flow_id.zero?
|
72
|
+
@flow ||= xtractr.flow @flow_id
|
73
|
+
end
|
74
|
+
|
75
|
+
# Fetch the actual packet data from the index. The return value is a
|
76
|
+
# String (that might contain null characters).
|
77
|
+
# xtractr.packets('index.html').first.bytes
|
78
|
+
def bytes
|
79
|
+
result = xtractr.json "/api/packet/#{id}/bytes"
|
80
|
+
result['bytes'].map { |b| b.chr }.join('')
|
81
|
+
end
|
82
|
+
|
83
|
+
# For UDP/TCP (both IPv4 and IPv6) packets, fetch just the layer4 payload.
|
84
|
+
# Returns an empty string for all other types of packet.
|
85
|
+
# xtractr.packets('http.request.method:GET').each do |pkt|
|
86
|
+
# puts pkt.payload
|
87
|
+
# end
|
88
|
+
def payload
|
89
|
+
result = xtractr.json "/api/packet/#{id}/bytes"
|
90
|
+
bytes = result['bytes']
|
91
|
+
l4size = result['l4size'] || 0
|
92
|
+
bytes[-l4size, l4size].map { |b| b.chr }.join('')
|
93
|
+
end
|
94
|
+
|
95
|
+
# Iterate over each Field::Value in the packet. The various packet fields
|
96
|
+
# are only available if the indexing was done with <em>--mode forensics</em>.
|
97
|
+
# packet.each('ip.ttl') { |fv| ... }
|
98
|
+
def each_field(regex=nil) # :yields: value
|
99
|
+
regex = Regexp.new(regex) if regex.is_a? String
|
100
|
+
result = xtractr.json "/api/packet/#{id}/fields"
|
101
|
+
rows = result['rows']
|
102
|
+
rows = rows.select { |row| row['key'] =~ regex } if regex
|
103
|
+
rows.each do |row|
|
104
|
+
value = Field::Value.new(xtractr, row)
|
105
|
+
yield value
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Fetch the values of the specified field for this packet. Even if there's
|
110
|
+
# only a single value for the field, it's returned as an array of 1 element
|
111
|
+
# packet.field('ip.ttl').each { |ttl| ... }
|
112
|
+
def [] name
|
113
|
+
result = xtractr.json "/api/packet/#{id}/field/#{name}"
|
114
|
+
return result['rows']
|
115
|
+
end
|
116
|
+
|
117
|
+
# Extract just this packet and save it to the specified file as a pcap.
|
118
|
+
# You can also save a collection of packets using Packets#save or a
|
119
|
+
# collection of flows using Flows#save.
|
120
|
+
# packet.save("foo.pcap")
|
121
|
+
def save filename
|
122
|
+
open(filename, "w") do |ios|
|
123
|
+
pcap = xtractr.get "api/packet/#{id}/pcap"
|
124
|
+
ios.write pcap
|
125
|
+
end
|
126
|
+
return self
|
127
|
+
end
|
128
|
+
|
129
|
+
def inspect # :nodoc:
|
130
|
+
"#<pkt:#{id} #{src.address} > #{dst.address} #{service.name} #{title}"
|
131
|
+
end
|
132
|
+
|
133
|
+
alias_method :each, :each_field
|
134
|
+
alias_method :fields, :entries
|
135
|
+
alias_method :field, :[]
|
136
|
+
end
|
137
|
+
end # Xtractr
|
138
|
+
end # Mu
|