venerdì 8 marzo 2019

Libsodium sealed boxes: multiple (32) working secret keys for one public key

Hi Everybody,
starting from the strange fact where, during Cr1pT0r RE, I found 3 different but valid secret keys, I wanted to dig a bit more.

And yes I was wrong. For a single public key there are 32 valid keys!



Figure out how the key pair are generated is the most obvious start point.
From Libsodium Documentation:
digging into the sources we face the crypto_box_keypair(*pk,*sk)


take a look into the crypto_scalarmult_curve25519_ref10_base. This function is going to create our public key.
The first step is to copy the secret key (previously created by randombytes_buf(sk, 32);) into t array.
Next, before to create the public key by calling other methods some math is applied:
t[0] &= 248;
t[31] &= 127;
t[31] |= 64;

the first byte of our secret key undergoes a bitwise AND operation with 248dec (0xF8).

Take a look more closely at how it works starting from my old keypairs:
>>> publicbob.hex()
'3d3f78633ea6a799c4dcf2522d9021c51031de6ba3ebcf061cc5caf8f843c52f'
>>> privbob.hex()
'dba2d474c0b72b620ecdc87f43eaab2e2465009174dc03b422c848301f19dd78'


At the time it was invoked, crypto_scalarmult_curve25519_ref10_base function had
t[0] = 0xdb;
Let's reproduce the bitwise and in binary:

11011010  0xDB

‭11111000‬   0xF8
_____________& result
11011000   0xD8


Now, do the same for the others 2 bytes which were working: 0xDD 0xDC

11011001  0xDD

‭11111000‬   0xF8
_____________& result
11011000   0xD8


And the last one:
11011000  0xDC

‭11111000‬   0xF8
_____________& result
11011000   0xD8


Bitwise AND is a lossy transformation, by doing such operation we face a mask that leaves us free to rewrite the last 3 bits as we prefer!
3 bits = 8 possibilites -> so, the public key is valid for 8 different secret keys!! 

But we're not done yet! Remember?
t[31] &= 127;
t[31] |= 64;


A similar application over the last byte is valid! 
Bring back the last byte in the example key: 0x78, in binary:

‭01111000‬  0x78

‭01111111‬  0x7f
_____________&result
‭01111000‬  0x78
‭00111000‬  0x38
‭01111000‬  0x78
‭00111000‬  0x38
_____________| result
01000000  0x40 


Same logic applied before: which bytes we're free to change in order of having a valid secret key?
The first bitwise AND operation leaves us free to change the first bit (MSB) only but, the OR operation extends our possibilities up to the second bit.
So accepted bytes in this case are:
01111000‬  0x78
10111000  0xb8
11111000  0xf8
00111000  0x38


2 bits, 4 possibilites: 00,01,10,11.

So we know that for a single public key up to 8 keys * 4 = 32 different keys could be valid and acceptable!
Starting from the source code I wrote in the last article, let's brute the first and last byte of the sk:





32 different valid secret keys! 👀👀

In the context of a bruteforce those valid keys are substantially adjacent, so in the set of reducing the time spent during bruteforce the whole key it does not help, because remains extremely long and not practicable, but it is a step forward within the general knowledge

UPDATE:
Frank Denis was kind giving motivations for this behavior.

I'm glad to receive his answer.

 


Cheers,
RE Solver



mercoledì 6 marzo 2019

DE-Cr1pt0r tool - The Cr1pt0r ransomware decompiled decryption routine

Hello Everybody,
after so many articles( 1 - 2 - 3 ) about my research on this Cr1ptor ransomware finally there is a tiny way to decrypt your files.

SPOILER ALERT:
This is a very early alpha release, is destined to programmers not directly to the victims.
Calm down, this will not be quick and/or easy at all but there is only a theorical chance. Probably you'll need few months, years, your son's life of computational work to brute the key. This is not a solution.

Let's start from the beginning:
as I wrote in the last article I got chance to have a pair of valid keys to run some tests on my Raspberry PI VM.
Before to talk about the source code, I need you to focus on the encrypted files's structure:
Basically this ransomware append after encryption 0x7A bytes. This is important because of this:
This is how the Decryption routine looks initially (where I made some gusses..):

Studying the code from here is completely INSANE..........well, I did it anyway (tens hours of hard work) helped by the libsodium documentation I figured out the exact pseudo code of the decompilation routine built in the ransomware and what do it exactly do.
After a few IDA corrections here is where I landed:



Most of functions now looks familiar, especially those concerning libsodium and files manipulation.
Starting from here, I've ported the code into a C application to reproduce the decryption.Once figured out which kind of encryption the ransomware adopted, I've started to write a C program and from the libsodium documentation there was something interesting:
Well, this looks similar to our ransomware implementation, except for the fact that he's doing some manipulation on the top of the pseudo code, in fact this code example is not sufficient.
A sealed box implementation seems to anticipate the code we seen:

Good. We now have so many pieces of the puzzle. Its time to put them togheter.
What do we need to decrypt the files?

Take a closer look at the "crypto_box_seal_open" function.
Do you remember the encrypted structure?
CIPHERTEXT_LEN, from the bottom of file is 0x50. We have it.
recipient_pk, from the bottom of the file and is 0x20. We have it.
recipient_sk, from the end of.....No. Unfortunaly we haven't the secret key.
The result decrypted array is then used to decrypt the rest of the file more or less as decribed on the libsodium documentation secret-key_cryptography -> "Stream encryption/file encryption" on github.
To procede with
crypto_secretstream_xchacha20poly1305_init_pull(&st, header, key) != 0)
we need the header and the key. The header is actually stored into the encrypted file as the same as the example shows. So we have it.
the key....the key is the "decrypted" crypto_box_seal_open resoult! We have it.

Since the fact I had a working keypair, I had everything I need to run some tests with the good old DEV-C++ IDE.
Once set up the code, I found a very strange behaviour of libsodium which brings me to a correct decryption with 3 different private keys!(!?!?!?!?!?) O.o


Is due to a libsodium bug?! IDK!
I hope some of you knows (and tells me) the reason of such behaviour, by the way victims does not have the private key and this strange behaviour of libsodium motivated me to implement a brute force routine into the code (to MAYBE find a working decryption sk with humanly acceptable timing).

And here is the result (skipping the brute force routine)!

(and yes, the original file was filled by a junky 0xAA 😊)Here is the main source (I do not share the encryption routine to avoid a Cr1pt0r x86 porting)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define UINT64_MAX (18446744073709551615ULL)
#define CHUNK_SIZE 4096

#define crypto_stream_chacha20_ietf_KEYBYTES 32U

void rvereseArray(unsigned char *arr, int start, int end) 
{ 
    while (start < end) 
    { 
        unsigned char temp = arr[start];  
        arr[start] = arr[end]; 
        arr[end] = temp; 
        start++; 
        end--; 
    }  
}      
void printArray(unsigned char arr[], int size) 
{ 
  int i; 
  for (i=0; i < size; i++) 
    printf("%02x ", arr[i]); 
  
  printf("\n"); 
}  

static int
decrypt(const char *target_file, const char *source_file, const unsigned char key[crypto_secretstream_xchacha20poly1305_KEYBYTES])
{
    unsigned char  buf_in[CHUNK_SIZE + crypto_secretstream_xchacha20poly1305_ABYTES];
    unsigned char  buf_out[CHUNK_SIZE];
    unsigned char  header[crypto_secretstream_xchacha20poly1305_HEADERBYTES];
    crypto_secretstream_xchacha20poly1305_state st;

    FILE          *fp_t, *fp_s, *fp_s1;
    unsigned long long out_len;
    size_t         rlen;
    int            eof;
    int            ret = -1;
    unsigned char  tag = 0x0;


#define MESSAGE (const unsigned char *) "Message"

#define MESSAGE_LEN 15
#define CIPHERTEXT_LEN (crypto_box_SEALBYTES + MESSAGE_LEN)




unsigned char recipient_pk[crypto_box_PUBLICKEYBYTES];
//My pk
//unsigned char recipient_pk[crypto_box_PUBLICKEYBYTES];={0x3D , 0x3F , 0x78 , 0x63 , 0x3E , 0xA6 , 0xA7 , 0x99 , 0xC4 , 0xDC , 0xF2 , 0x52 , 0x2D , 0x90 , 0x21 , 0xC5 , 0x10 , 0x31 , 0xDE , 0x6B , 0xA3 , 0xEB , 
              //0xCF , 0x06 , 0x1C , 0xC5 , 0xCA , 0xF8 , 0xF8 , 0x43 , 0xC5 , 0x2F};//; /* Bob's public key */
//
//recipient_sk decrypt the files also with recipient_sk[0]=0xDB (the original byte) than 0xDD and also 0xDC
// unsigned char recipient_sk[crypto_box_SECRETKEYBYTES]={ 0xDB , 0xA2 , 0xD4 , 0x74 , 0xC0 , 0xB7 , 0x2B , 0x62 , 0x0E , 0xCD , 0xC8 , 0x7F , 0x43 , 0xEA , 0xAB , 0x2E , 0x24 , 
//0x65 , 0x00 , 0x91 , 0x74 , 0xDC , 0x03 , 0xB4 , 0x22 , 0xC8 , 0x48 , 0x30 , 0x1F , 0x19 , 0xDD , 0x78 }; //; /* Bob's secret key */
unsigned char recipient_sk[32]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
   
unsigned char ciphertext[80];
//var50h

long filelen;
fp_s1 = fopen("enc", "rb");
   if(fp_s1==0){ printf("Encrypted files not found"); return 1;}
    fseek(fp_s1, 0xFFFFFFD6, SEEK_END);          
    //filelen = ftell(fp_s1);
  fread(recipient_pk, 1, 0x20, fp_s1);
  fseek(fp_s1, 0xFFFFFF86, SEEK_END); 
  
  fread(ciphertext, 1, 0x50, fp_s1);

//because the new key is 32dec bytes long
unsigned char decrypted[32];



//brute start
//Bruteforce the 32byte key. Thanks to GeDaMo from IRC ##programming @ freenode
//
 uint64_t i[4] = { 0, 0,0,0 };
// uint64_t i[4] = {0xDBA2D474C0B72B62 ,    0x0ECDC87F43EAAB2E ,    0x2465009174DC03B4 ,    0x22C848301F19DD78 };
      int exit=0;
 printf("DE-Cr1pt0r Tool By RE-Solver @solver_re:\r\n Bruteforcing \r\n ");
  do {
   i[1] = 0;
  do {
   i[2] = 0;
 do {
  i[3] = 0;
      do {

memcpy(&recipient_sk[23],&i[3],sizeof(i[3]));
rvereseArray(&recipient_sk[23],0,8);

memcpy(&recipient_sk[15],&i[2],sizeof(i[2]));
rvereseArray(&recipient_sk[15],0,8);

memcpy(&recipient_sk[7],&i[1],sizeof(i[1]));
rvereseArray(&recipient_sk[7],0,8);

memcpy(&recipient_sk,&i[0],sizeof(i[0]));
rvereseArray(recipient_sk,0,7);

//printArray(recipient_sk, 32);
if(crypto_box_seal_open(decrypted, ciphertext, 0x50u, recipient_pk, recipient_sk) == 0)
{printf("Found: ");printArray(recipient_sk, 32);exit=1;break;}


           
           } while (i[3]++ < UINT64_MAX&&exit==0);
        } while (i[2]++ < UINT64_MAX&&exit==0);
   } while (i[1]++ < UINT64_MAX&&exit==0);
 } while (i[0]++ < UINT64_MAX&&exit==0);
 //END brute
//from the decompilated program this was the original routine, because of the bruteforce is added as a comment now
/*
if (crypto_box_seal_open(decrypted, ciphertext, 0x50u, recipient_pk, recipient_sk) != 0) {
    // message corrupted or not intended for this recipient 
     printf ("message corrupted or not intended for this recipient %s",decrypted);}
*/     

    fp_s = fopen(source_file, "rb");
    fp_t = fopen(target_file, "wb");
    if(fp_s==0 || fp_t==0){printf("Encrypted files not found");
 goto ret;}
    fread(header, 1, 0x18, fp_s);
    if (crypto_secretstream_xchacha20poly1305_init_pull(&st, header, decrypted) != 0) {
        goto ret; /* incomplete header */
    }
    do {
        rlen = fread(buf_in, 1, 0x1011, fp_s);
        eof = feof(fp_s);
        //tag = 0x0;
        int value=crypto_secretstream_xchacha20poly1305_pull(&st, buf_out, &out_len, &tag, buf_in, rlen, NULL,0);
        if (value != 0) {
            goto ret; /* corrupted chunk */
        }
        if (tag == 3 && ! eof) { //crypto_secretstream_xchacha20poly1305_TAG_FINAL -> 3
            goto ret; /* premature end (end of file reached before the end of the stream) */
        }
        fwrite(buf_out, 1, (size_t) out_len, fp_t);
    } while (! eof);

    ret = 0;
ret:
    fclose(fp_t);
    fclose(fp_s);
    fclose(fp_s1);
    return ret;
}

int
main(void)
{
    unsigned char key[crypto_secretstream_xchacha20poly1305_KEYBYTES];

    if (sodium_init() != 0) {
        return 1;
    }
    crypto_secretstream_xchacha20poly1305_keygen(key);
  
    if (decrypt("enc.outtmp", "enc.tmp", 0x0) != 0) {
     printf("Something goes wrong.");
        return 1;
    }
    printf("Decrypted! RE Solver");
    return 0;
}

IDE: DEV-C++
libsodium library: libsodium-1.0.17-mingw.tar.gz
Remember to link the library into the Project/Project Options

Compiled tool: https://www.sendspace.com/file/275c70
sha256: 4066fa0d402a8458f7784e89ba979929ee1d7efd761b3cabe9705784aa8af865

usage: Copy an encrypted file into the same folder of the tool and rename it as enc (with no extensions). Copy the same encrypted file and rename it as enc.tmp and strip the last 0x7A from the end of the file. If you're lucky within some weeks you'll have the key printed on the console and the encrypted.outtmp decrypted file created on the same folder dir.

Next step: create a file named privkey and write the hex key (with no spaces) into a text file and put it in the Cr1pt0r folder. From the same folder, rename the file pubkey as pubkey_backup and turn on your D-Link nas again.

Note: My GF is waiting me since days, she has been so patient. A special Thanks to her. 😊
I'm sorry but I do not support the tool usage or others kind of requests.Since the fact that code is released under GPL, everyone can compile, improve, modify the code. (And I hope it happens).

Follow me on Twitter @solver_re
Hire me! Job offers are welcome.
Cheers,
RE Solver


martedì 5 marzo 2019

Cr1pt0r Ransomware Analysis Libsodium/NaCl Encryption, Decryption, paramenters and (sad) conclusions

Hi Everybody,
After my two posts here and here  this is the third about Cr1pt0r.

Cr1pt0r main function decompiled by using RecDec plugin (graph view representation is huge to be seen in a picture):

Usage: ./cr1pt0r /mountPointToEncryptOrDecrypt
The ELF looks for a keyfile, in this order:
1 looks for a "privkey" file
2 looks for the "pubkey" file.
No matter if privkey is present, if the public key file is found, the ransomware starts to encrypt, if privatekey only is present, the ransomware decrypts the files.
In all the cases to walk trought the directories the included nftw function is invoked.

Defined parameters (still digging on them):
-g --> read the keyfile as bin2hex(bytes)
-k --> bin2hex + seed; probably generates a keypair?! - Don't know yet.
-e --> force encryption
-d --> force decryption
-t --> test? crypto has_sha512 final maybe does not overwrite the file? - Don't know yet.
/mountpoint --> the directory to be encrypted by the .sh start script is defined to be /mnt

Another thing I have to look at is why encrypted files , "_Cr1pt0r_" signature a part, are longer than the original ones. Also @demonslay335 has noticied the same. Could it be the answare?
 From PyNaCl documentation:


A note:
arm assembly SVC -> "SVC (formerly SWI) that generates a supervisor call. Supervisor calls are normally used to request privileged operations or access to system resources from an operating system." From ARM documentation.
Some of syscalls has been identified manually, binding the ARM instructions to the EAX value. The x86 reference website https://syscalls.kernelgrok.com/ gave me the connection I needed to identify the exit functions and few others.

To avoid the deleted files recovery possibility the ransomware during encryption creates the sameNameFile+".tmp" where stores the encrypted stream, then replace the source file with the encrypted data overwriting the source file itself.
The encryption algo used appear be crypto_onetimeauth_poly1305.  
I'm embarassed. This was completly incorrect. I wrote that during late night, I apologize.😰
Thanks to Frank Denis.

Those calls to crypto_onetimeauth_poly1305 takes us to NaCl One-time authentication.
More info on one-time authentication (Poly1305): (PDF) Daniel J. Bernstein, "Cryptography in NaCl", 45pp.
(0x000160BC)

Python and PyNaCl library helped me to create a keypair based on the standard implementation of  the library itself.
import nacl.utils
from nacl.public import PrivateKey, Box
skbob = PrivateKey.generate()
pkbob = skbob.public_key

>>> publicbob.hex()
'3d3f78633ea6a799c4dcf2522d9021c51031de6ba3ebcf061cc5caf8f843c52f'
>>> privbob.hex()
'dba2d474c0b72b620ecdc87f43eaab2e2465009174dc03b422c848301f19dd78'


Using this key pairs, I tryied to encrypt and decrypt a folder of my VM and the result has been positive.



To bruteforce such kind of key nowdays is pratically impossible.
Mathematics don't know good and bad, it is a rigid discipline with rules and even if in this situation where we would like to break the rules for the good, we can't.
I hope someone may have in the future some brilliant ideas on how reduce the keyspace for example some specific bug on random generation on this specific device and kernel (/dev/random is connected to this crypto algo) or maybe NaCl's creator Daniel J. Bernstein can share with us some ideas.


If some of you have ideas, feel free to contact me on Twitter @solver_re
Cheers,
RE Solver


lunedì 4 marzo 2019

Cr1pt0r ransomware: FireEye FLARE idb2pat.py script to build your IDA Pro FLIRT signatures

Hi Everybody,
from my previous post in order to have an human approch to analyze the stripped ELF Cr1pt0r I wrote just a few words about IDA Pro feature to build FLIRT signature.
What is FLIRT?
"The unique F.L.I.R.T. technology allows IDA to recognize standard function calls, and enhance the disassembly output." From Hex-Rays website

The FLIRT Technology is the thing which makes IDA so special during decompiling. Ida knows, by the .sig files thousand standard calls identified by a pattern, renaming the subroutines which matches automatically during the autoanalysis (or by invoking the specific signature later).
Unfortunatly, in this specific condition, where we had a stripped ELF, static compiled, ARM executable, IDA does not have the specific FLIRT signatures and I had to build my owns to make my life better. 💖

The process of creating FLIRT signatures usually requires a number of prerequisite conditions to exist:
  • A pattern file must be created via either pelf or similar, followed by use of sigmake
  • A compiled, relocatable library containing the functions and associated names, of which signatures are to be generated against, must exist
  • The library must be a recognised format and with a supported instruction set


We're not able in this situation to satisfy everything so, Fire Eye’s IDB2PAT tool, created by the FLARE the division of Fire Eye is the tool which makes possible to extract the pattern starting from an IDB:
This script allows you to easily generate function patterns from an existing IDB database that can then be turned into FLIRT signatures to help identify similar functions in new files.
https://www.fireeye.com/blog/threat-research/2015/01/flare_ida_pro_script.html
Source

 
Instead of cross compiling examples to have as much functions in the signature (which has been the better solution in order to maybe avoid many collisions), I thought it was faster to extract the patterns directly from  Debian pre-compiled libraries! 😎
 packages.debian.org  gave me the armel (arm little endian) packages I needed. (libsodium, libc and others)
Once opened the libsodium.so.23.2.so (Shared Object), into IDA , starting the FireEye script, the pat file has been wrote easly on the same directory.


 

  ./sigmake element.pat  element.sig (sigmake is part of FLAIR tools provided by Hex-Rays to produce the .sig file).
And here we go, we got signatures, simply push the files into %IDA_INSTALL_PATH%/sig and let's see if it works!

From the 0 signature we had from the initial load, now we have few hundreds of them recognized!

I am fully informed that starting from the libraries, in addition precompiled, without accurately verifying the collisions, the chances of inaccurate recognition are high. But I don't care! This is not an huge ELF and I'll keep it in mind during the analysis 😂

What is important in this article is the desire to share this technique with you so that it can help you in situations like this or, for example, by decompiling subsequent or previous versions of the same malware, in all those situations where you already have a structured idb, even if I already know that you will quote me bindiff, creating the signatures, will maybe an help to speed-up the analysis.

My heart is branded Hex-Rays. 💘
Cheers,
RE Solver