metasploit-aggregator 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,13 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: metasploit/aggregator/aggregator.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ require 'metasploit/aggregator/messages_pb'
7
+ Google::Protobuf::DescriptorPool.generated_pool.build do
8
+ end
9
+
10
+ module Metasploit
11
+ module Aggregator
12
+ end
13
+ end
@@ -0,0 +1,36 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # Source: metasploit/aggregator/aggregator.proto for package 'metasploit.aggregator'
3
+
4
+ require 'grpc'
5
+ require 'metasploit/aggregator/aggregator_pb'
6
+
7
+ module Metasploit
8
+ module Aggregator
9
+ module Pb
10
+ class Service
11
+
12
+ include GRPC::GenericService
13
+
14
+ self.marshal_class_method = :encode
15
+ self.unmarshal_class_method = :decode
16
+ self.service_name = 'metasploit.aggregator.Pb'
17
+
18
+ rpc :available, Message::No_params, Message::Result
19
+ rpc :version, Message::No_params, Message::String_array
20
+ rpc :sessions, Message::No_params, Message::Result_map
21
+ rpc :cables, Message::No_params, Message::String_array
22
+ rpc :obtain_session, Message::String_array, Message::Result
23
+ rpc :release_session, Message::String_array, Message::Result
24
+ rpc :session_details, Message::String_array, Message::Result_map
25
+ rpc :add_cable, Message::Cable_def, Message::Result
26
+ rpc :remove_cable, Message::String_array, Message::Result
27
+ rpc :register_default, Message::Register, Message::Result
28
+ rpc :default, Message::No_params, Message::String_array
29
+ rpc :available_addresses, Message::No_params, Message::String_array
30
+ rpc :process, stream(Message::Response), stream(Message::Request)
31
+ end
32
+
33
+ Stub = Service.rpc_stub_class
34
+ end
35
+ end
36
+ end
@@ -1,4 +1,5 @@
1
1
  require 'openssl'
2
+ require 'singleton'
2
3
  require 'socket'
3
4
 
4
5
  require 'metasploit/aggregator/logger'
@@ -10,6 +11,7 @@ module Metasploit
10
11
  module Aggregator
11
12
 
12
13
  class ConnectionManager
14
+ include Singleton
13
15
 
14
16
  def initialize
15
17
  @cables = []
@@ -119,7 +121,7 @@ module Metasploit
119
121
  next unless cable.forwarder.connections.include?(payload)
120
122
  # TODO: improve how time is exposed for live connections
121
123
  time = cable.forwarder.connection_info(payload)['TIME']
122
- detail_map['LAST_SEEN'] = Time.now - time unless time.nil?
124
+ detail_map['LAST_SEEN'] = (Time.now - time).to_s unless time.nil?
123
125
  end
124
126
  detail_map
125
127
  end
@@ -0,0 +1,9 @@
1
+ module Metasploit
2
+ module Aggregator
3
+ class Error < RuntimeError
4
+ end
5
+
6
+ class CompatibilityError < Error
7
+ end
8
+ end
9
+ end
@@ -21,7 +21,7 @@ module Metasploit
21
21
  @time = Time.now
22
22
  @router = Router.instance
23
23
  @session_service = SessionDetailService.instance
24
- @pending_requests = nil
24
+ @pending_request = nil
25
25
  end
26
26
 
27
27
  def process_requests
@@ -0,0 +1,51 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: metasploit/aggregator/messages.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ Google::Protobuf::DescriptorPool.generated_pool.build do
7
+ add_message "metasploit.aggregator.message.No_params" do
8
+ end
9
+ add_message "metasploit.aggregator.message.Request" do
10
+ repeated :headers, :string, 1
11
+ optional :body, :bytes, 2
12
+ end
13
+ add_message "metasploit.aggregator.message.Cable_def" do
14
+ optional :type, :string, 1
15
+ optional :host, :string, 2
16
+ optional :port, :int32, 3
17
+ optional :pem, :bytes, 4
18
+ end
19
+ add_message "metasploit.aggregator.message.Register" do
20
+ optional :uuid, :string, 1
21
+ repeated :payloads, :string, 2
22
+ end
23
+ add_message "metasploit.aggregator.message.Response" do
24
+ optional :uuid, :string, 1
25
+ optional :response, :message, 2, "metasploit.aggregator.message.Request"
26
+ end
27
+ add_message "metasploit.aggregator.message.Result_map" do
28
+ map :map, :string, :string, 1
29
+ end
30
+ add_message "metasploit.aggregator.message.String_array" do
31
+ repeated :value, :string, 1
32
+ end
33
+ add_message "metasploit.aggregator.message.Result" do
34
+ optional :answer, :bool, 1
35
+ end
36
+ end
37
+
38
+ module Metasploit
39
+ module Aggregator
40
+ module Message
41
+ No_params = Google::Protobuf::DescriptorPool.generated_pool.lookup("metasploit.aggregator.message.No_params").msgclass
42
+ Request = Google::Protobuf::DescriptorPool.generated_pool.lookup("metasploit.aggregator.message.Request").msgclass
43
+ Cable_def = Google::Protobuf::DescriptorPool.generated_pool.lookup("metasploit.aggregator.message.Cable_def").msgclass
44
+ Register = Google::Protobuf::DescriptorPool.generated_pool.lookup("metasploit.aggregator.message.Register").msgclass
45
+ Response = Google::Protobuf::DescriptorPool.generated_pool.lookup("metasploit.aggregator.message.Response").msgclass
46
+ Result_map = Google::Protobuf::DescriptorPool.generated_pool.lookup("metasploit.aggregator.message.Result_map").msgclass
47
+ String_array = Google::Protobuf::DescriptorPool.generated_pool.lookup("metasploit.aggregator.message.String_array").msgclass
48
+ Result = Google::Protobuf::DescriptorPool.generated_pool.lookup("metasploit.aggregator.message.Result").msgclass
49
+ end
50
+ end
51
+ end
@@ -60,7 +60,6 @@ module Metasploit
60
60
  @queue_by_uuid[uuid] << request.pop
61
61
  end
62
62
  end
63
- [] unless @queue_by_uuid[uuid].length > 0
64
63
  [@queue_by_uuid[uuid], nil, uuid]
65
64
  end
66
65
  end
@@ -9,267 +9,267 @@ module Metasploit
9
9
  module Tlv
10
10
  class UUID
11
11
 
12
- include Rex::Arch
13
- #
14
- # Constants
15
- #
12
+ include Rex::Arch
13
+ #
14
+ # Constants
15
+ #
16
16
 
17
- Architectures = {
18
- 0 => nil,
19
- 1 => ARCH_X86,
20
- 2 => ARCH_X64, # removed ARCH_X86_64, now consistent across the board
21
- 3 => ARCH_X64,
22
- 4 => ARCH_MIPS,
23
- 5 => ARCH_MIPSLE,
24
- 6 => ARCH_MIPSBE,
25
- 7 => ARCH_PPC,
26
- 8 => ARCH_PPC64,
27
- 9 => ARCH_CBEA,
28
- 10 => ARCH_CBEA64,
29
- 11 => ARCH_SPARC,
30
- 12 => ARCH_ARMLE,
31
- 13 => ARCH_ARMBE,
32
- 14 => ARCH_CMD,
33
- 15 => ARCH_PHP,
34
- 16 => ARCH_TTY,
35
- 17 => ARCH_JAVA,
36
- 18 => ARCH_RUBY,
37
- 19 => ARCH_DALVIK,
38
- 20 => ARCH_PYTHON,
39
- 21 => ARCH_NODEJS,
40
- 22 => ARCH_FIREFOX,
41
- 23 => ARCH_ZARCH,
42
- 24 => ARCH_AARCH64,
43
- 25 => ARCH_MIPS64,
44
- 26 => ARCH_PPC64LE
45
- }
17
+ Architectures = {
18
+ 0 => nil,
19
+ 1 => ARCH_X86,
20
+ 2 => ARCH_X64, # removed ARCH_X86_64, now consistent across the board
21
+ 3 => ARCH_X64,
22
+ 4 => ARCH_MIPS,
23
+ 5 => ARCH_MIPSLE,
24
+ 6 => ARCH_MIPSBE,
25
+ 7 => ARCH_PPC,
26
+ 8 => ARCH_PPC64,
27
+ 9 => ARCH_CBEA,
28
+ 10 => ARCH_CBEA64,
29
+ 11 => ARCH_SPARC,
30
+ 12 => ARCH_ARMLE,
31
+ 13 => ARCH_ARMBE,
32
+ 14 => ARCH_CMD,
33
+ 15 => ARCH_PHP,
34
+ 16 => ARCH_TTY,
35
+ 17 => ARCH_JAVA,
36
+ 18 => ARCH_RUBY,
37
+ 19 => ARCH_DALVIK,
38
+ 20 => ARCH_PYTHON,
39
+ 21 => ARCH_NODEJS,
40
+ 22 => ARCH_FIREFOX,
41
+ 23 => ARCH_ZARCH,
42
+ 24 => ARCH_AARCH64,
43
+ 25 => ARCH_MIPS64,
44
+ 26 => ARCH_PPC64LE
45
+ }
46
+
47
+ Platforms = {
48
+ 0 => nil,
49
+ 1 => 'windows',
50
+ 2 => 'netware',
51
+ 3 => 'android',
52
+ 4 => 'java',
53
+ 5 => 'ruby',
54
+ 6 => 'linux',
55
+ 7 => 'cisco',
56
+ 8 => 'solaris',
57
+ 9 => 'osx',
58
+ 10 => 'bsd',
59
+ 11 => 'openbsd',
60
+ 12 => 'bsdi',
61
+ 13 => 'netbsd',
62
+ 14 => 'freebsd',
63
+ 15 => 'aix',
64
+ 16 => 'hpux',
65
+ 17 => 'irix',
66
+ 18 => 'unix',
67
+ 19 => 'php',
68
+ 20 => 'js',
69
+ 21 => 'python',
70
+ 22 => 'nodejs',
71
+ 23 => 'firefox'
72
+ }
46
73
 
47
- Platforms = {
48
- 0 => nil,
49
- 1 => 'windows',
50
- 2 => 'netware',
51
- 3 => 'android',
52
- 4 => 'java',
53
- 5 => 'ruby',
54
- 6 => 'linux',
55
- 7 => 'cisco',
56
- 8 => 'solaris',
57
- 9 => 'osx',
58
- 10 => 'bsd',
59
- 11 => 'openbsd',
60
- 12 => 'bsdi',
61
- 13 => 'netbsd',
62
- 14 => 'freebsd',
63
- 15 => 'aix',
64
- 16 => 'hpux',
65
- 17 => 'irix',
66
- 18 => 'unix',
67
- 19 => 'php',
68
- 20 => 'js',
69
- 21 => 'python',
70
- 22 => 'nodejs',
71
- 23 => 'firefox'
72
- }
74
+ # The raw length of the UUID structure
75
+ RawLength = 16
73
76
 
74
- # The raw length of the UUID structure
75
- RawLength = 16
77
+ # The base64url-encoded length of the UUID structure
78
+ UriLength = 22
76
79
 
77
- # The base64url-encoded length of the UUID structure
78
- UriLength = 22
80
+ # Validity constraints for UUID timestamps in UTC
81
+ TimestampMaxFuture = Time.now.utc.to_i + (30*24*3600) # Up to 30 days in the future
82
+ TimestampMaxPast = 1420070400 # Since 2015-01-01 00:00:00 UTC
79
83
 
80
- # Validity constraints for UUID timestamps in UTC
81
- TimestampMaxFuture = Time.now.utc.to_i + (30*24*3600) # Up to 30 days in the future
82
- TimestampMaxPast = 1420070400 # Since 2015-01-01 00:00:00 UTC
84
+ #
85
+ # Class Methods
86
+ #
83
87
 
84
- #
85
- # Class Methods
86
- #
88
+ #
89
+ # Parse a raw 16-byte payload UUID and return the payload ID, platform, architecture, and timestamp
90
+ #
91
+ # @param raw [String] The raw 16-byte payload UUID to parse
92
+ # @return [Hash] A hash containing the Payload ID, platform, architecture, and timestamp
93
+ #
94
+ def self.parse_raw(raw)
95
+ if raw.to_s.length < 16
96
+ raise ArgumentError, "Raw UUID must be at least 16 bytes"
97
+ end
87
98
 
88
- #
89
- # Parse a raw 16-byte payload UUID and return the payload ID, platform, architecture, and timestamp
90
- #
91
- # @param raw [String] The raw 16-byte payload UUID to parse
92
- # @return [Hash] A hash containing the Payload ID, platform, architecture, and timestamp
93
- #
94
- def self.parse_raw(raw)
95
- if raw.to_s.length < 16
96
- raise ArgumentError, "Raw UUID must be at least 16 bytes"
99
+ puid, plat_xor, arch_xor, plat_id, arch_id, tstamp = raw.unpack('a8C4N')
100
+ plat = find_platform_name(plat_xor ^ plat_id)
101
+ arch = find_architecture_name(arch_xor ^ arch_id)
102
+ time_xor = [plat_xor, arch_xor, plat_xor, arch_xor].pack('C4').unpack('N').first
103
+ time = time_xor ^ tstamp
104
+ { puid: puid, platform: plat, arch: arch, timestamp: time, xor1: plat_xor, xor2: arch_xor }
97
105
  end
98
106
 
99
- puid, plat_xor, arch_xor, plat_id, arch_id, tstamp = raw.unpack('a8C4N')
100
- plat = find_platform_name(plat_xor ^ plat_id)
101
- arch = find_architecture_name(arch_xor ^ arch_id)
102
- time_xor = [plat_xor, arch_xor, plat_xor, arch_xor].pack('C4').unpack('N').first
103
- time = time_xor ^ tstamp
104
- { puid: puid, platform: plat, arch: arch, timestamp: time, xor1: plat_xor, xor2: arch_xor }
105
- end
107
+ #
108
+ # Filter out UUIDs with obviously invalid fields and return either
109
+ # a validated UUID or a UUID with the arch, platform, and timestamp
110
+ # fields strippped out.
111
+ #
112
+ # @param uuid [Hash] The UUID in hash format
113
+ # @return [Hash] The filtered UUID in hash format
114
+ #
115
+ def self.filter_invalid(uuid)
116
+ # Verify the UUID fields and return just the Payload ID unless the
117
+ # timestamp is within our constraints and the UUID has either a
118
+ # valid architecture or platform
119
+ if uuid[:timestamp] > TimestampMaxFuture ||
120
+ uuid[:timestamp] < TimestampMaxPast ||
121
+ (uuid[:arch].nil? && uuid[:platform].nil?)
122
+ return { puid: uuid[:puid] }
123
+ end
124
+ uuid
125
+ end
106
126
 
107
- #
108
- # Filter out UUIDs with obviously invalid fields and return either
109
- # a validated UUID or a UUID with the arch, platform, and timestamp
110
- # fields strippped out.
111
- #
112
- # @param uuid [Hash] The UUID in hash format
113
- # @return [Hash] The filtered UUID in hash format
114
- #
115
- def self.filter_invalid(uuid)
116
- # Verify the UUID fields and return just the Payload ID unless the
117
- # timestamp is within our constraints and the UUID has either a
118
- # valid architecture or platform
119
- if uuid[:timestamp] > TimestampMaxFuture ||
120
- uuid[:timestamp] < TimestampMaxPast ||
121
- (uuid[:arch].nil? && uuid[:platform].nil?)
122
- return { puid: uuid[:puid] }
127
+ #
128
+ # Look up the numeric platform ID given a string or PlatformList as input
129
+ #
130
+ # @param platform [String] The name of the platform to lookup
131
+ # @return [Fixnum] The integer value of this platform
132
+ #
133
+ def self.find_platform_id(platform)
134
+ name = name.first if name.kind_of? ::Array
135
+ ( Platforms.keys.select{ |k|
136
+ Platforms[k] == name
137
+ }.first || Platforms[0] ).to_i
123
138
  end
124
- uuid
125
- end
126
139
 
127
- #
128
- # Look up the numeric platform ID given a string or PlatformList as input
129
- #
130
- # @param platform [String] The name of the platform to lookup
131
- # @return [Fixnum] The integer value of this platform
132
- #
133
- def self.find_platform_id(platform)
134
- name = name.first if name.kind_of? ::Array
135
- ( Platforms.keys.select{ |k|
136
- Platforms[k] == name
137
- }.first || Platforms[0] ).to_i
138
- end
140
+ #
141
+ # Look up the numeric architecture ID given a string as input
142
+ #
143
+ # @param name [String] The name of the architecture to lookup
144
+ # @return [Fixnum] The integer value of this architecture
145
+ #
146
+ def self.find_architecture_id(name)
147
+ name = name.first if name.kind_of? ::Array
148
+ ( Architectures.keys.select{ |k|
149
+ Architectures[k] == name
150
+ }.first || Architectures[0] ).to_i
151
+ end
139
152
 
140
- #
141
- # Look up the numeric architecture ID given a string as input
142
- #
143
- # @param name [String] The name of the architecture to lookup
144
- # @return [Fixnum] The integer value of this architecture
145
- #
146
- def self.find_architecture_id(name)
147
- name = name.first if name.kind_of? ::Array
148
- ( Architectures.keys.select{ |k|
149
- Architectures[k] == name
150
- }.first || Architectures[0] ).to_i
151
- end
153
+ def self.find_platform_name(num)
154
+ Platforms[num]
155
+ end
152
156
 
153
- def self.find_platform_name(num)
154
- Platforms[num]
155
- end
157
+ def self.find_architecture_name(num)
158
+ Architectures[num]
159
+ end
156
160
 
157
- def self.find_architecture_name(num)
158
- Architectures[num]
159
- end
161
+ #
162
+ # Instance methods
163
+ #
160
164
 
161
- #
162
- # Instance methods
163
- #
165
+ def initialize(opts=nil)
166
+ opts = load_new if opts.nil?
167
+ opts = load_raw(opts[:raw]) if opts[:raw]
164
168
 
165
- def initialize(opts=nil)
166
- opts = load_new if opts.nil?
167
- opts = load_raw(opts[:raw]) if opts[:raw]
169
+ self.puid = opts[:puid]
170
+ self.timestamp = opts[:timestamp]
171
+ self.arch = opts[:arch]
172
+ self.platform = opts[:platform]
173
+ self.xor1 = opts[:xor1]
174
+ self.xor2 = opts[:xor2]
168
175
 
169
- self.puid = opts[:puid]
170
- self.timestamp = opts[:timestamp]
171
- self.arch = opts[:arch]
172
- self.platform = opts[:platform]
173
- self.xor1 = opts[:xor1]
174
- self.xor2 = opts[:xor2]
176
+ # Generate some sensible defaults
177
+ self.puid ||= SecureRandom.random_bytes(8)
178
+ self.xor1 ||= rand(256)
179
+ self.xor2 ||= rand(256)
180
+ self.timestamp ||= Time.now.utc.to_i
181
+ end
175
182
 
176
- # Generate some sensible defaults
177
- self.puid ||= SecureRandom.random_bytes(8)
178
- self.xor1 ||= rand(256)
179
- self.xor2 ||= rand(256)
180
- self.timestamp ||= Time.now.utc.to_i
181
- end
183
+ #
184
+ # Initializes a UUID object given a raw 16+ byte blob
185
+ #
186
+ # @param raw [String] The string containing at least 16 bytes of encoded data
187
+ # @return [Hash] The attributes encoded into this UUID
188
+ #
189
+ def load_raw(raw)
190
+ self.class.filter_invalid(self.class.parse_raw(raw))
191
+ end
182
192
 
183
- #
184
- # Initializes a UUID object given a raw 16+ byte blob
185
- #
186
- # @param raw [String] The string containing at least 16 bytes of encoded data
187
- # @return [Hash] The attributes encoded into this UUID
188
- #
189
- def load_raw(raw)
190
- self.class.filter_invalid(self.class.parse_raw(raw))
191
- end
193
+ def load_new
194
+ self.class.parse_raw(self.class.generate_raw())
195
+ end
192
196
 
193
- def load_new
194
- self.class.parse_raw(self.class.generate_raw())
195
- end
197
+ #
198
+ # Provides a string representation of a UUID
199
+ #
200
+ # @return [String] The human-readable version of the UUID data
201
+ #
202
+ def to_s
203
+ arch_id = self.class.find_architecture_id(self.arch).to_s
204
+ plat_id = self.class.find_platform_id(self.platform).to_s
205
+ [
206
+ self.puid_hex,
207
+ [ self.arch || "noarch", arch_id ].join("="),
208
+ [ self.platform || "noplatform", plat_id ].join("="),
209
+ Time.at(self.timestamp.to_i).utc.strftime("%Y-%m-%dT%H:%M:%SZ")
210
+ ].join("/")
211
+ end
196
212
 
197
- #
198
- # Provides a string representation of a UUID
199
- #
200
- # @return [String] The human-readable version of the UUID data
201
- #
202
- def to_s
203
- arch_id = self.class.find_architecture_id(self.arch).to_s
204
- plat_id = self.class.find_platform_id(self.platform).to_s
205
- [
206
- self.puid_hex,
207
- [ self.arch || "noarch", arch_id ].join("="),
208
- [ self.platform || "noplatform", plat_id ].join("="),
209
- Time.at(self.timestamp.to_i).utc.strftime("%Y-%m-%dT%H:%M:%SZ")
210
- ].join("/")
211
- end
213
+ #
214
+ # Return a string that represents the Meterpreter arch/platform
215
+ #
216
+ def session_type
217
+ # mini-patch for x86 so that it renders x64 instead. This is
218
+ # mostly to keep various external modules happy.
219
+ arch = self.arch
220
+ if arch == ARCH_X86_64
221
+ arch = ARCH_X64
222
+ end
223
+ "#{arch}/#{self.platform}"
224
+ end
212
225
 
213
- #
214
- # Return a string that represents the Meterpreter arch/platform
215
- #
216
- def session_type
217
- # mini-patch for x86 so that it renders x64 instead. This is
218
- # mostly to keep various external modules happy.
219
- arch = self.arch
220
- if arch == ARCH_X86_64
221
- arch = ARCH_X64
226
+ #
227
+ # Provides a hash representation of a UUID
228
+ #
229
+ # @return [Hash] The hash representation of the UUID suitable for creating a new one
230
+ #
231
+ def to_h
232
+ {
233
+ puid: self.puid,
234
+ arch: self.arch, platform: self.platform,
235
+ timestamp: self.timestamp,
236
+ xor1: self.xor1, xor2: self.xor2
237
+ }
222
238
  end
223
- "#{arch}/#{self.platform}"
224
- end
225
239
 
226
- #
227
- # Provides a hash representation of a UUID
228
- #
229
- # @return [Hash] The hash representation of the UUID suitable for creating a new one
230
- #
231
- def to_h
232
- {
233
- puid: self.puid,
234
- arch: self.arch, platform: self.platform,
235
- timestamp: self.timestamp,
236
- xor1: self.xor1, xor2: self.xor2
237
- }
238
- end
240
+ #
241
+ # Provides a raw byte representation of a UUID
242
+ #
243
+ # @return [String] The 16-byte raw encoded version of the UUID
244
+ #
245
+ def to_raw
246
+ self.class.generate_raw(self.to_h)
247
+ end
239
248
 
240
- #
241
- # Provides a raw byte representation of a UUID
242
- #
243
- # @return [String] The 16-byte raw encoded version of the UUID
244
- #
245
- def to_raw
246
- self.class.generate_raw(self.to_h)
247
- end
249
+ #
250
+ # Provides a hex representation of the Payload UID of the UUID
251
+ #
252
+ # @return [String] The 16-byte hex string representing the Payload UID
253
+ #
254
+ def puid_hex
255
+ self.puid.unpack('H*').first
256
+ end
248
257
 
249
- #
250
- # Provides a hex representation of the Payload UID of the UUID
251
- #
252
- # @return [String] The 16-byte hex string representing the Payload UID
253
- #
254
- def puid_hex
255
- self.puid.unpack('H*').first
256
- end
258
+ #
259
+ # Clears the two random XOR keys used for obfuscation
260
+ #
261
+ def xor_reset
262
+ self.xor1 = self.xor2 = nil
263
+ self
264
+ end
257
265
 
258
- #
259
- # Clears the two random XOR keys used for obfuscation
260
- #
261
- def xor_reset
262
- self.xor1 = self.xor2 = nil
263
- self
266
+ attr_accessor :arch
267
+ attr_accessor :platform
268
+ attr_accessor :timestamp
269
+ attr_accessor :puid
270
+ attr_accessor :xor1
271
+ attr_accessor :xor2
264
272
  end
265
-
266
- attr_accessor :arch
267
- attr_accessor :platform
268
- attr_accessor :timestamp
269
- attr_accessor :puid
270
- attr_accessor :xor1
271
- attr_accessor :xor2
272
- end
273
273
  end
274
274
  end
275
275
  end