After my last post about CTB-Locker I received a lot of e-mails from people asking for a complete analysis of the malware. Most of them wanted to know if it’s possible to restore the compromised files without paying the ransom. The answer is simple: it’s impossible without knowing the Master key! That key resides on the malicious server and it’s the only way to restore every single compromised file.

360 Mobile Vision - North & South Carolina Security products and Systems Installations for Commercial and Residential - $55 Hourly Rate. ACCESS CONTROL, INTRUSION ALARM, ACCESS CONTROLLED GATES, INTERCOMS AND CCTV INSTALL OR REPAIR 360 Mobile Vision - is committed to excellence in every aspect of our business. We uphold a standard of integrity bound by fairness, honesty and personal responsibility. Our distinction is the quality of service we bring to our customers. Accurate knowledge of our trade combined with ability is what makes us true professionals. Above all, we are watchful of our customers interests, and make their concerns the basis of our business.

There are a some articles on the net about CTB-Locker’s modus-operandi. Everyone knows that ZLib is used, AES is used but only few of them mention the use of SHA256+Curve. To explain everything in details I’ll show you how encryption/decryption is done, step by step.

HiddenInfo file is the core of the malware, it’s full of precious data. There’s no need to explain every field of the file, a closer look at the first part of it would suffice because it has an important part in the encryption/decryption scheme.

DCE1C1   call    ds:CryptGenRandom
DCE1C7   lea     eax, [ebp+systemTimeAsFileTime]
DCE1CA   push    eax
DCE1CB   call    ds:GetSystemTimeAsFileTime
DCE1D1   call    ds:GetTickCount
DCE1D7   mov     [ebp+gettickcountVal], eax
DCE1DA   call    ds:GetCurrentThreadId
DCE1E0   mov     esi, eax         
DCE1E2   rol     esi, 10h                      ; Shift ThreadID
DCE1E5   call    ds:GetCurrentProcessId 
DCE1EB   xor     eax, esi                      ; ThreadID and ProcessID values inside the same dword
DCE1ED   mov     [ebp+threadID_processID], eax
DCE1F0   mov     esi, 0EB7910h
DCE1F5   lea     edi, [ebp+machineGuid]
DCE1F8   movsd                                 ; Move MachineGUID
DCE1F9   movsd
DCE1FA   movsd
DCE1FB   lea     eax, [ebp+random]             ; Random sequence of bytes
DCE1FE   push    34h                           ; Number of bytes to hash
DCE200   push    eax                           ; Sequence of bytes to hash
DCE201   mov     ecx, ebx                      ; Output buffer
DCE203   movsd
DCE204   call    SHA256                        ; SHA256(random)
DCE209   mov     al, [ebx+1Fh]
DCE20C   and     byte ptr [ebx], 0F8h
DCE20F   push    0E98718h                      ; Basepoint
DCE214   and     al, 3Fh
DCE216   push    ebx                           ; SHA256(random)
DCE217   push    [ebp+outputBuffer]            ; Public key
DCE21A   or      al, 40h
DCE21C   mov     [ebx+1Fh], al
DCE21F   call    curve_25519     

The snippet is part of a procedure I called GenSecretAndPublicKeys. The secret key is obtained applying SHA256 to a random sequence of 0x34 bytes composed by:

   0x14 bytes: from CryptGenRandom function
   0x08 bytes: from GetSystemTimeAsFileTime
   0x04 bytes: from GetTickCount
   0x04 bytes: from (ThreadID ^ ProcessID)
   0x10 bytes: MachineGuid

Curve25519 is used to generate the corresponding public key. You can recognize the algo from Basepoint vector because it’s a 0x09 byte followed by a series of 0x00 bytes (For a quick overview over the Elliptic curve algorithm used by the malware take a look here:
GenSecretAndPublicKeys is called two times, so two private and two public keys are created. I name them as ASecret, APublic, BSecret and BPublic.

DCF1E7   mov     [esp+274h+public], offset MasterPublic   ; MPublic key
DCF1EE   push    eax                                      ; BSecret
DCF1EF   lea     eax, [ebp+Shared_1]                      ; Shared_1
DCF1F5   push    eax                                              
DCF1F6   call    curve_25519     
DCF1FB   add     esp, 0Ch
DCF1FE   lea     eax, [ebp+Shared_1]
DCF204   push    20h                                               
DCF206   push    eax                                               
DCF207   lea     ecx, [ebp+aesKey]                        ; Hash is saved here
DCF20A   call    SHA256

SHA256(curve_25519(Shared_1, BSecret, MPublic))
Shared secret computation takes place. MPublic is the Master public key and it’s visible inside the memory address space of the malware. The Master secret key remains on the malicious server. To locate the Master public key is pretty easy because it’s between the information section (sequence of info in various languages) and the “.onion” address. Shared secret is then putted inside SHA256 hash algorithm, and the result is used as a key for AES encryption:

DCF20F   lea     eax, [ebp+aesExpandedKey]
DCF215   push    eax
DCF216   mov     edx, 100h
DCF21B   lea     ecx, [ebp+aesKey]
DCF21E   call    AEXExpandKey
DCF223   add     esp, 0Ch
DCF226   xor     edi, edi
DCF228   lea     ecx, [ebp+aesExpandedKey]
DCF22E   lea     eax, [edi+0EF71ACh]
DCF234   push    ecx
DCF235   push    eax
DCF236   push    eax
DCF237   call    AES_ENCRYPT     ; AES encryption

The malware encrypts a block of bytes (named SecretInfo) composed by:

   ASecret              ; a secret key generated by GenSecretAndPublicKeys
   MachineGuid          ; Used to identify the infected machine
   Various information (fixed value, checksum val among others)

Not so hard but it’s better to outline everything:

     ASecret = SHA256(0x34_random_bytes)
     Curve_25519(APublic, ASecret, BasePoint)
     BSecret = SHA256(0x34_random_bytes)
     Curve_25519(BPublic, BSecret, BasePoint)
     Curve_25519(Shared_1, BSecret, MPublic)
     AES_KEY_1 = SHA256(Shared_1)
     Encrypted_SecretInfo = AES_ENCRYPT(SecretInfo, AES_KEY_1)

Part of these informations are saved inside HiddenInfo file, more precisely at the beginning of it:

   +0x00 offset: APublic
   +0x24 offset: BPublic
   +0x44 offset: Encrypted_SecretInfo

So, two public keys are visible, but private key ASecret is encrypted. It’s impossible to get the real ASecret value without the AES key…
Ok, now that you know how HiddenInfo file is created I can start with the file encryption scheme.

CTB-Locker file encryption

C74834   lea     eax, [ebp+50h+var_124]   ; Hash will be saved here
C7483A   push    eax             
C7483B   lea     eax, [ebp+50h+var_E4]
C74841   push    30h             
C74843   lea     edi, [ebp+50h+var_D4]
C74849   push    eax                      ; 0x30 random bytes
C7484A   rep movsd               
C7484C   call    SHA256Hash
C7486E   push    eax                      ; BasePoint
C7486F   lea     eax, [ebp+50h+var_124]
C74875   push    eax                      ; CSecret: SHA256(0x30_random_bytes)
C74876   lea     eax, [ebp+50h+var_B4]
C74879   push    eax                      ; CPublic
C7487A   call    curve25519               ; Generate a public key
C7487F   push    offset dword_C943B8      ; DPublic: first 32 bytes of HiddenInfo (*)
C74884   lea     eax, [ebp+50h+var_124]
C7488A   push    eax                      ; CSecret
C7488B   lea     eax, [ebp+50h+var_164]
C74891   push    eax                      ; Shared_2
C74892   call    curve25519               ; Generate shared secret
C74897   lea     eax, [ebp+50h+var_144]
C7489D   push    eax
C7489E   lea     eax, [ebp+50h+var_164]
C748A4   push    20h
C748A6   push    eax                      ; Shared_2
C748A7   call    SHA256Hash               ; SHA256(Shared_2)
C74955   push    34h
C74957   push    [ebp+50h+var_18]         ; Compression level: 3
C7495A   lea     eax, [ebp+50h+PointerToFileToEncrypt]
C7495D   push    eax                      ; Original file bytes to compress
C7495E   call    ZLibCompress    
C74B1F   push    [ebp+50h+var_4]
C74B22   lea     ecx, [ebp+50h+expandedKey]   ; SHA256(Share_2) is used as key
C74B28   push    [ebp+50h+var_4]
C74B2B   call    AES_Encrypt                  ; It encrypts 16 bytes per round starting from the first 16 bytes of the ZLib compressed file
C74B30   add     [ebp+50h+var_4], 10h 
C74B34   dec     ebx                          ; Decrease the pointer to the bytes to encrypt
C74B35   jnz     short loc_C74B1F             ; Jump up and encrypt the next 16 bytes

It’s quite easy indeed, it uses the same functions (SHA256, Curve, AES). To understand what’s going on you only have to follow the code. The operations sequence is:

     CSecret = SHA256(0x30_random_bytes)
     Curve_25519(CPublic, CSecret, BasePoint)
     Curve_15519(Shared_2, CSecret, APublic) (*)
     AES_KEY_2 = SHA256(Shared_2)
     ZLibFile = ZLibCompress(OriginalFile)
     Encrypted_File = AES_Encrypt(ZLibFile, AES_KEY2)

(*) DPublic inside the disassembled code is indeed APublic (first 32 bytes of HiddenInfo)

That’s the way how CTB-Locker encrypts the bytes of the orginal file. These bytes are saved into the new compromised file with some more data. A typical compromised file has the next structure:

   +0x00 offset: CPublic
   +0x20 offset: AES_Encrypt(InfoVector, AES_KEY_2)
   +0x30 offset: AES_Encrypt(ZLibFile, AES_KEY2)

InfoVector is a sequence of 16 bytes with the first four equals to “CTB1”, this tag word is used to check the correctness of the key provided by the server during the decryption routine.
The decryption demonstration feature, implemented by the malware to prove that it can restore the file, uses
AES_KEY_2 directly. If you remember the key was saved inside HiddenInfo, there are a total of five saved keys.
In this case if you have CSecret you can easily follow the process described above (remember that APublic comes from the first 32 bytes of HiddenInfo), but without CSecret it’s impossible to restore the original files.

It’s important to note that in the encryption process CTB-Locker doesn’t need an open internet connection. It doesn’t send keys/data to the server, it simply encrypts everything! Internet connection is needed in the decryption part only.

CTB-Locker file decryption
At some point, before the real decryption process, there’s a data exchange between the infected machine and the malicious server. The malware sends a block of bytes (taken from HiddenInfo) to the server and the server replies sending back the unique decryption key. The block is composed by the BPublic key, SecretInfo and some minor things:

   32 bytes: BPublic
   90 bytes: SecretInfo
   16 bytes: general info

The malware uses the key to restore the files. Do you remember the decryption part from my last post? Well, the real decryption scheme is not so different. There’s one more step, the calculation of the AES key. To do that the unique key sent by the server is used to decrypt every file:

     curve_25519(Shared, Unique_Key, first_0x20_byte_from_compromised_file)
     AES_DECRYPTION_KEY = SHA256(Shared)
     ZLibFile = AES_Decrypt(Encrypted_File, AES_DECRYPTION_KEY)
     OriginalFile = ZLibDecompress(ZLibFile)

The unique key is sent just one time, so the decryption method needs only one key to decrypt all the compromised files. How is it possible?

My explanation
From HiddenInfo part I have:
     Curve_25519(APublic, Asecret, BasePoint)
     Curve_25519(BPublic, BSecret, BasePoint)
     Curve_25519(Shared_1, BSecret, MPublic)
     AES_KEY_1 = SHA256(Shared_1)
     Encrypted_SecretInfo = AES_ENCRYPT(SecretInfo, AES_KEY_1)

The server receives DataToServer and it applies the principle of elliptic curve:

     Curve_25519(Shared_1, MSecret, BPublic)

Shared_1 from the server is equal to Shared_1 calculated in the HiddenInfo creation part.
Now, with Shared_1 it AES decrypts SecretInfo obtaining ASecret key. ASecret is the key used to decrypt all the compromised files.

From Encryption part:
     Curve_25519(CPublic, CSecret, BasePoint)
     Curve_15519(Shared_2, CSecret, APublic)
     AES_KEY_2 = SHA256(Shared_2)
     ZLibFile = ZLibCompress(OriginalFile)
     Encrypted_File = AES_Encrypt(ZLibFile, AES_KEY_2)

Saying that, here is how to use ASecret in the decryption process (applying the same EC principle):
     Curve_25519(Shared_2, ASecret, CPublic)
     AES_KEY_2 = SHA256(Shared_2)
     ZLibFile = AES_Decrypt(Encrypted_File, AES_KEY_2)
     OriginalFile =ZLibDecompress(ZLibFile)

So, ASecret is the Unique_Key computed by the server and it’s used to decrypt every file. That means one thing only, without MSecret you can’t restore your original files…

Final thoughts
There’s nothing much to say really. CTB-Locker is dangerous and it will damage systems until people will do double-click over attachments.. sad but true.

Feel free to contact me for comments, criticisms, suggestions, etcetc!

By admin