virusIn the previous post, we have described how to set up a loft to monitor Dyreza with the help of virtual machines configured with breakpoints at addresses where communications appear in clear text. Configuration file updates can thus be obtained in real-time easily. Another way to monitor this kind of malware using a decentralised architecture is to implement parts of the malicious binary in a thin client, which requires to fully understand its decryption routine details.

Generating the cryptographic items

Here is an example of an encrypted response received from a C&C:

(gdb) hexdump 0x3860020 256
cc 25 03 db 35 41 8a e1 37 3b a4 2a 47 b2 b8 36 |.%..5A..7;.*G..6|
96 2d 47 46 96 a3 4c 19 b3 95 6b 80 12 d4 bb d2 |.-GF..L...k.....|
c1 74 4e ff cc 53 a4 b9 8e 49 43 42 89 11 8a b6 |.tN..S...ICB....|
e6 f0 49 a1 f4 ca b9 d1 56 69 cc 6f c8 76 d1 4e |..I.....Vi.o.v.N|
4d e6 0a 21 2c d4 c3 2e 84 80 9e cf 6a e2 76 0e |M..!,.......j.v.|
f8 7b 75 c0 08 4c c9 7c b5 5c f8 75 80 8d 20 ae |.{u..L.|..u.. .|
ff 81 cd 9e 8e f1 8b 2a 86 6a 5c 06 dd 38 13 09 |.......*.j..8..|
cc eb 15 4b d8 03 b6 15 a2 e4 94 71 f3 b2 ac 3b |...K.......q...;|
a2 86 46 3a 62 f3 33 c7 ea d0 a9 56 95 23 ec ca |..F:b.3....V.#..|
e8 46 6c 54 96 2e e1 23 71 ab 02 5b 3e 41 a7 9e |.FlT...#q..[>A..|
23 9b eb 74 64 c5 f0 dd 37 ba b7 cd 18 c7 4d 2b |#..td...7.....M+|
c2 14 73 fd 24 5a a3 f4 31 bd 71 38 e3 0c 13 3e |..s.$Z..1.q8...>|
7f 01 4b 85 ec 63 48 8a 85 fa 82 a1 a4 3e 6f 34 |..K..cH......>o4|
0b 85 8c 10 31 a8 95 40 1c 7f 1b a5 6c c8 fe 2d |....1..@....l..-|
22 d7 98 6b 79 85 b3 ea d8 95 95 b7 09 6a ba 68 |"..ky........j.h|
e9 a1 26 90 80 de 3a bc de d7 22 89 bd 9b b0 26 |..&...:..."....&|
[...]

Thanks to our previous analysis using the watchpoint technique, we know the address where this content is deciphered. By going a few functions up, we stumble upon the following block containing a call to a hash function, an AES initialisation routine and a deciphering function:

dyreza_aes1

The mySHA256_two_blocks function contains the following code:

dyreza_aes2

It calls a hash function mySHA256_loop on two parts of the encrypted blob:

  • the first 32 bytes, the following ones in our example:
cc 25 03 db 35 41 8a e1 37 3b a4 2a 47 b2 b8 36 |.%..5A..7;.*G..6|
96 2d 47 46 96 a3 4c 19 b3 95 6b 80 12 d4 bb d2 |.-GF..L...k.....|
  • the next 16 bytes, in our example:
c1 74 4e ff cc 53 a4 b9 8e 49 43 42 89 11 8a b6 |.tN..S...ICB....|

This mySHA256_loop function has the following graph:

dyreza_aes3

It performs the following operations (the first 32 bytes of the HTTP response are used here as an example):

  • Step 1: computing the hash of the input and using the result as the current buffer
2b d0 40 1d dc 74 df 49 39 3c e5 48 5f 9d 90 e5 |+.@..t.I9<.H_...|
0b d5 b5 a2 f3 71 6a a7 fc 05 e1 e8 99 7c e8 4f |.....qj......|.O|

Python confirms the hash function used by Dyreza as being SHA256:

>>> hashlib.sha256("cc2503db35418ae1373ba42a47b2b836962d474696a34c19b3956b8012d4bbd2".decode("hex")).hexdigest()
'2bd0401ddc74df49393ce5485f9d90e50bd5b5a2f3716aa7fc05e1e8997ce84f'
  • Step 2: from the first 16 bytes of the current buffer, creating 16 new bytes, each of them being the increment of the corresponding byte in the current buffer
2c d1 41 1e dd 75 e0 4a 3a 3d e6 49 60 9e 91 e6 |,.A..u.J:=.I`...|
  • Step 3: computing the hash of the 48 bytes composed of the 32 bytes of the current buffer followed by these 16 incremented bytes
2b d0 40 1d dc 74 df 49 39 3c e5 48 5f 9d 90 e5 |+.@..t.I9<.H_...|
0b d5 b5 a2 f3 71 6a a7 fc 05 e1 e8 99 7c e8 4f |.....qj......|.O|
2c d1 41 1e dd 75 e0 4a 3a 3d e6 49 60 9e 91 e6 |,.A..u.J:=.I`...|
// hash of these 48 bytes:
49 6e 2b 4c a5 96 56 93 d7 fa 24 6a 0e 1f ac 3c |In+L..V...$j...<|
5e e5 4d 95 a7 1a 06 a6 2f ae 08 ff 79 4b 4f ef |^.M...../...yKO.|
  • Step 4: loop to step 2 depending of the value of esi, using the hash computed in step 3 as the new current buffer

As far as the loop iterations are concerned, it can be seen in the previous figure that the esi register is taken from eax which is defined in the parent function mySHA256_two_blocks:

  • 1st call: eax = esi + esi
  • 2nd call: eax = esi

Before calling mySHA256_two_blocks, esi is copied from ecx whose value is fixed to 0x40 via a PUSH/POP primitive:

dyreza_aes4

The iterations number is therefore 0x80, then 0x40 when mySHA256_two_blocks calls mySHA256_loop twice.

At the end of the mySHA256_loop function, N bytes are copied from the current buffer to the output buffer depending on a function parameter:

  • 1st call: 0x20 bytes copied
  • 2nd call: 0x10 bytes copied

In our example, the following results are obtained:

  • 1st call to mySHA256_loop: computing the first 32 bytes of the encrypted HTTP response, 0x80 SHA256 loops and 0x20-byte output
80 3d b3 9c 12 88 fe b8 61 12 8a 63 df f6 a0 64 |.=......a..c...d|
5e fd 2e eb ef e4 a5 37 08 e7 75 ce 4c c2 a4 42 |^......7..u.L..B|
  • 2nd call to mySHA256_loop: computing the next 16 bytes of the encrypted HTTP response, 0x40 SHA256 loops and 0x10-byte ouput
1f 56 c1 63 50 79 fb 9a f2 ea d1 97 5c 7f 40 4d |.V.cPy.......@M|

AES deciphering

Now that the mySHA256_two_blocks function is understood, we can study the myAES_keyschedule function which implements the AES key schedule. Like we did for Kronos, we indeed notice the AES key schedule core followed by four XOR with the S-box, the latter being specific to AES-256 :

dyreza_aes5

At the end of the block, the 240-byte round keys have been generated:

(gdb) hexdump 0x34df898 240
80 3d b3 9c 12 88 fe b8 61 12 8a 63 df f6 a0 64 |.=......a..c...d|
5e fd 2e eb ef e4 a5 37 08 e7 75 ce 4c c2 a4 42 |^......7..u.L..B|
a4 74 9f b5 b6 fc 61 0d d7 ee eb 6e 08 18 4b 0a |.t....a....n..K.|
6e 50 9d 8c 81 b4 38 bb 89 53 4d 75 c5 91 e9 37 |nP....8..SMu...7|
27 6a 05 13 91 96 64 1e 46 78 8f 70 4e 60 c4 7a |'j....d.Fx.pN`.z|
41 80 81 56 c0 34 b9 ed 49 67 f4 98 8c f6 1d af |A..V.4..Ig......|
61 ce 7c 77 f0 58 18 69 b6 20 97 19 f8 40 53 63 |a.|w.X.i. ...@Sc|
00 89 6c ad c0 bd d5 40 89 da 21 d8 05 2c 3c 77 |..l....@..!..,<w|
18 25 89 1c e8 7d 91 75 5e 5d 06 6c a6 1d 55 0f |.%...}.u^].l..U.|
24 2d 90 db e4 90 45 9b 6d 4a 64 43 68 66 58 34 |$-....E.mJdChfX4|
3b 4f 91 59 d3 32 00 2c 8d 6f 06 40 2b 72 53 4f |;O.Y.2.,.o.@+rSO|
d5 6d 7d 5f 31 fd 38 c4 5c b7 5c 87 34 d1 04 b3 |.m}_1.8...4...|
25 bd fc 41 f6 8f fc 6d 7b e0 fa 2d 50 92 a9 62 |%..A...m{..-P..b|
86 22 ae f5 b7 df 96 31 eb 68 ca b6 df b9 ce 05 |.".....1.h......|
33 36 97 df c5 b9 6b b2 be 59 91 9f ee cb 38 fd |36....k..Y....8.|

The first 32 bytes are the AES-256 key and we can recognize the result of the first call to mySHA256_loop.

In the next function named myAES, we can notice the loop deciphering a block and XORing the result with the previous ciphered block, that is to say the CBC mode:

dyreza_aes6

The first XOR iteration provides the IV:

(gdb) hexdump 0x308fdb8 16
1f 56 c1 63 50 79 fb 9a f2 ea d1 97 5c 7f 40 4d |.V.cPy.......@M|

This is the result of the second call to mySHA256_loop.

This analysis shows that Dyreza’s communications can be deciphered without any other secret that… the ciphered communications themselves!

Implementation

The following Python script implements the way Dyreza computes the AES key and initialisation vector from the first 48 bytes of the communication, and uses them to decipher the rest of the content:

#! /usr/bin/env python

import sys
from hashlib import sha256
from Crypto.Cipher import AES

def get_crypto(buffer, loops):
    current = sha256(buffer).digest()
    for i in range(loops):
        plusone = "".join([chr(((ord(x) + 1) & 0xff)) for x in current[:16]])
        current = sha256(current + plusone).digest()
    return current

data = open("dyre.bin").read()
key = get_crypto(data[:32]  , 0x80)[:32]
iv  = get_crypto(data[32:48], 0x40)[:16]

print "Dyreza decryption tool, CERT-LEXSI 2015"
print "Key " + key.encode("hex")
print "IV  " + iv.encode("hex")
print "Decrypting buffer..."

aes = AES.new(key, AES.MODE_CBC, iv)
print aes.decrypt(data[48:])

Let’s check if it works:

$ ./dyreza.py
Dyreza decryption tool, CERT-LEXSI 2015
Key 803db39c1288feb861128a63dff6a0645efd2eebefe4a53708e775ce4cc2a442
IV  1f56c1635079fb9af2ead1975c7f404d
Decrypting buffer...

<rpci>
*.ebanking-services.com/*
85.17.123.70/redirect.php
</rpci>
<rpci>
*.ebanking-services.com/*/favicon.ico[?]*
85.17.123.70/redirect.php
</rpci>
<rpci>
*/cgi-bin/hbproxy.exe/*
85.17.123.70/redirect.php
</rpci>
<rpci>
*/cgi-bin/hbproxy.exe/favicon.ico[?]*
85.17.123.70/redirect.php
</rpci>
<rpci>
[...]

This script can decrypt all Dyreza communications as well as information needed before any communication, such as the list of C&Cs and the campaign identifier (please refer to the previous post for more information on these items).

Thin loft

The loft described in the previous post uses a real Dyreza sample executing in a virtual machine. This technique is easily set up inasmuch as it doesn’t require any knowledge of the decryption process, only sample depacking and a quick analysis to determine the relevant breakpoints addresses. Thanks to the static decryption method presented above, Dyreza can also be monitored via a thin Python script instead of virtual machines. Both solutions have their own pros and cons:

  • virtual machine: easy to set up but may suffer from potential stability issues (ex: malicious binary update)
  • script: easy to deploy but must be maintained (ex: encryption algorithm update)

Conclusion

The Dyreza communication analysis performed by CERT-LEXSI shows they can be trivially decrypted because they contain enough information to instantly compute the AES-256 key and IV they are encrypted with. Configuration file updates are therefore easily monitored via a few lines of Python without using a virtual machine.