venerdì 31 luglio 2020

Linksys RE6500 - Unauthenticated RCE: Full Disclosure

Linksys RE6500 is a pretty new range extender build by Linksys, well, more properly by Belkin. An USA product built just a few thousand km east in the "suicide factory" (the Foxconn factory, China)

My goal was to archive a personal need a telnet access, I never expected to come across such a big security hole holes, more properly because between poorly implemented backdoor ( goform/j78G-DFdg_24Mhw3?password= ) and lousy code, in the end I discovered a few security flaws.


li a0, "RCE"


martedì 31 marzo 2020

TP-Link RE200 config.bin decryption and manipulation

A very quick article to share with you how to decrypt the TP-Link RE200 config.bin

In this article we take up the concepts seen in the previous one jumping straight into Ghidra to take a look at the _tddp_UploadFlashData routine in httpd ELF.

The way the config is being uploaded is pretty clear, basically the uploaded file is entirely decrypted, after which the md5 hashing is performed starting from the 0x10 offset until the end of the file. Finally, it verifies whether the first byte 0x10 coincide in this md5.
The encryption has been performed with a DES symmetric-key algorithm (ECB)


Openssl can reach the goal by using this command:
# openssl enc -d -des-ecb -nopad -K 478DA50BF9E3D2CF -in config.bin > decrypted.bin

alternatively you can use any other application, demonslay335's CryptoTester for example:

 copy and paste the decrypted data into an hxd

Do a manually md5 check by selecting all the bytes from 0x10 to the end of the file.

You can now modify by a text editor the decrypted confing, retrive and or change the admin password, recalculate the new md5, encrypt the whole file back and upload it by the web interface. That's all. :)

Follow me on Twitter


sabato 28 marzo 2020

TP-Link RE200 aka AC750: Unpack, repack, validate image by md5 hashing and upload YOUR OWN version!

This article demonstrates how "easy" may be build a potentially malicious firmware. 
This way should be valid for EVERY TP-Link firmware header version 1 (identified by the very first 4 bytes in the header, in little endian!)

A TP-Link RE200 is a widespread cheap Range Extender.
I love having root shell on my devices, so long story short:
I've started to decrypt RE200 config.bin (which I'll publish soon Edit: here!), to find a vector to inject code and get a root shell.
Unfortunatelly, for this time, the firmware is (not really, but enough) well written so I couldn't see any code injection possibilities from the browser and/or from the nvram config file and (almost) every stack smash chance are avoided by reimplemented TP-Link functions.

Ok, excluding a phisical attack, a custom firmware is the common route (among the same family devices) to get the shell.

0 - rcS script
1 - firmware header structure - httpd Analysis with Ghidra
2 - firmware customization and squashfs recompression
3 - firmware file reconstruction
  3.1 - Uncompress the image
  3.2 - Modify
  3.3 - Re-Compress the image
  3.4 - Put the things togheter
4 - Firmware mod new hash
5 - Upload and verify

0 - rcS script

Once unpacked the firmware by using binwalk ($ binwalk  -e firmware.bin ) the first thing to take a look at RunControlSingle user script (rcS).

Pretty clear,  TP-Link has commented the telnet row command, disabling the daemon.
In the end we'll see the httpd start command.

1 - firmware header structure - httpd Analysis with Ghidra

Let's start to examinate the firmware header in Ghidra. Our focus lands on upgradeFirmware(void *param_1,uint param_2)

I will try to be as brief as possible. Additional explainations requests in comments are welcome
1- In a similar way we seen in the config.bin [here], the file offset 0x4c contanins the md5 hash signature
2- int variable in 0x94 indicates if our firmware conains bootloader. If yes, the md5 signature uses a different salt.
3- md5_veryfy_digest. No explaination, this works in the same way as in the config.bin. Getting the md5  from the file in a variable, it replaces the same file address readen 0x4c with the 0x10 bytes with the right salt on point 2. It calculates the md5, and compare with the one originally readen.
4- Country code check @ 0x98 len=3chars: in case we'll change the firmware from a different country, we need to restore the same country code the device is factured with.

 md5key_withoutbootloader = DC D7 3A A5 C3 95 98 FB DD F9 E7 F4 0E AE 47 38
 md5key_withbootloader =      8C EF 33 5B D5 C5 CE FA A7 9C 28 DA B2 E9 0F 42

2 - firmware customization and squashfs recompression
We have now enough informations about the firmware header which allows us to grant writing on the device flash.
On the official TP-Link firmware, we have 2 headers, the first at 0x0 and a second one at 0x20200 (in this case). The first section contains the uboot, after the second header kernel + rootfs.

The second header contains the signature made from 0x20200 to EOF with the md5key_withoutbootloader salt
The first md5 signature is made with  md5key_withbootloader from 0x0 to EOF, (thus also including the second image)

As seen in httpd, we have 2 allowed kind of images: the first, is the whole image uboot+kernel+rootfs and the second way is to write just the kernel+rootfs which leaves the uboot uneffected (read: if I fail I can still de-brick by using a serial TTL :) )
That's why I've splitted the firmware.

3 - Firmware file reconstruction
 3.1 Uncompress the image
First of all, from the firmware, we're going to extract the squasfs file and uncompress by using unsquashfs:

Note: binwalk is an amazing swiss-knife, keep in mind the compression xz (LZMA2) and the inodes

By dd tool, squashfs has been extracted skipping, from the firmware file, the first 0x100000 (1048576 in decimal) bytes.
Next step was to uncompress the image with simply with # unsquashfs filename

 3.2 Modify
I've used nano to modify the rcS row number 33
#telnetd -l /bin/login &
telnetd -l /bin/login &
Forgot to say: /etc/shadow file contains a different password then the one we use on the web.
Create your new /etc/shadow file!
The TP-Link credential hardcoded are root:shoadmin
(Thank you 6c2e6e2e ) <-wrong password here

(Thank you Openwrt)

3.3 Re-Compress the image
now recompress time with: mksquashfs squashfsdir/ fileout.squashfs -comp xz -no-duplicates
Those last two options are vital. The first indicates to mksquashfs to use LZMA2 algo(the same algo we know the stock firmware is using, showed by binwalk), the second one prevents mksquashfs to delete duplicates, guaranteeing the correct restoration of inodes and file number.

3.4 Put the things togheter 
Anyway the file size is reasonably different different. Anyway we can have a bigger (or smaller) image, anyway not bigger than 0x6C0000 calculated from my reduced (kernel+squashfs) stock firmware (EOF) 0x7C0000 - 0x100000 (initial squashfs magic number).
In the re200_imagecut.bin (
obtained in paragraph 2, see image) we overwrite into the file starting from 0x100000 our new squashfs image by keeping the SAME original file size, if needed padding the file adding or removing 0xFF up to 0x7C0000-1

4 - Firmware mod new hash
We have now recreated the right file structure. The last step is to rewrite the new md5 at 0x4c-0x5c.
In my case, bootloaderless, my key will be md5key_withoutbootloader:

By keeping in mind what we read at the paragraph n.2, once replaced the md5 you'll have the new md5 (see the picture at the bottom) wich we will use to replace the salt (in red) from 0x4c to 0x5c.

5 - Upload and verify

Let's upload and try to login with Putty.....
BOOM .... it worked like a charm on the first try!!! 😎

Modified firmware image here.
(no uboot, telnet enabled: default telnet credentials root:sohoadmin)


giovedì 26 marzo 2020

Tp-Link CPE-510/520 "new" Config.bin structure: Decryption, modify, re-encryption

So far, seems nobody has yet relased a tool to decrypt such kind of config structure.
By doing only static analysis we'll take a look how to figure out the new TP-Link CPE-510/520  (probably also TP-Link CPE-210/2200 and others) config.bin structure by using Ghidra.
I neither have the CPE :)

0 - Hardware overview and firmware extracting
1 - uclited ELF static analysis with Ghidra
  1.1 - config.bin file structure
  1.2 - Extracting decryption key
  1.3 - TP-Link MD5 config signature tricky calculation
2 - config.bin decryption script
3 - Re-creation of a new modified config.bin
4 - Login md5 trick

0 - Hardware overview and firmware extracting

OpenWrt wiki provide us many hardware details:
CPE-510 is based on tipically mips_24kc instruction set, the bootloader is not the well known Uboot, because TP-Link has used their SafeLoader. This will be important later.

Let's download the TP-Link CPE-510 rev 3.0 firmware and examine it with binwalk:

We're interested in the squashfs partition, so let's quickly extract the image by using -e paramenter on binwalk and take a look on what the router once turned on by looking at the rcS script:
Before the httpd, a lower software surface, uclited , is executed.

1 - uclited ELF static analysis with Ghidra

I'm not a Ghidra big fan, but sometimes it's powerfull, expecially when I have a missing decompiler on IDA Pro. lol.
So, load the ELF on Ghidra and start having fun!

1.1 - config.bin file structure
Before proceeding and examining in ghidra we have to take a look on how the encrypted config.bin looks like:
In the file header there are just a few words in plaintext and at 0x94 (keep it in mind) we have undeadable informations. From my experience, this may be where the encrypted stuff starts.

1.2 - Extracting decryption key

There are several approaches to finding the routine we are interested in.
I'll skip how to find the routine (it's too long to write here), so go straight at the routine labled as
usrconf_load @ 0x004272d0 which is the decryption config.bin routine.

The routine is almost self explanatory, anyway let's go over the highlights copared to the config.bin hex:

In the image above:
From 0x0 to 0x3 bytes are the file size. (4 bytes)
Next 0x10 bytes are the MD5 file hash. (0x10 bytes)
From 0x4 to 0x94 are the file header (device info, rev info, and other things). (0x90 bytes)
From 0x94 to EOF is the DES Encrypted data

The DES - ECB  routine  des_min_do(__src + 0x90,size - 0x90,__ptr,0x20000,(const_DES_cblock *)desConfigKey,0); act an encrytion with last paramenter as int 1 or decryption if set 0.

desConfigKey is the pointer to the key: 47 8d a5 0b f9 e3 d2 cf 
We can assume "__src + 0x90"  as our file pointer which is moved 4 bytes forward (the bytes of the file size at the beginning of the file)

We have now all the informations we need to decrypt the file.

1.3 - TP-Link MD5 config signature tricky calculation 

In the previous paragraph I deliberately highlighted the MD5 in blue for a specific reason, I do not understand why, but TP-Link do a particular thing in the calculation of the MD5 which will be:
Understanding those steps are esential to recreate the modified file:
1 step: the routine saves the 0x10 bytes readen from file offset 0x4 to 0x14. This is the final md5 that will be compared
2 step: the routine replaces 0x10 bytes with "47 8d a5 0b f9 e3 d2 cf 88 19 83 9d 4c 06 14 45"
3rd step: the routine calculates the md5 from 0x4 to EOF with the first 0x10 bytes shortly before replaced
4 step: the md5 result is compared to the one readen in the original file (saved during the step 1)

2 - config.bin decryption script

dd bs=1 skip=148 if=config.bin of=config_cut.bin 
openssl enc -d -des-ecb -nopad -K 478DA50BF9E3D2CF -in config_cut.bin >compressed.bin 
zlib-flate -uncompress  config.json
where 148=0x94 do you remember?... 
the config.json contains all the router informations including the login ones.

3 - Re-creation of a new modified config.bin

You can do it by yourself. Having all the informations you need, you can just recompress the modified .json, encrypt it again with openss, calculate back the new md5 as seen in the 1.3 paragraph and add the current file size + 0x4 and replace the first 4 bytes with the calculated value.

4 - Login md5 trick 
 If you forgot your router login password, in the config you'll have no plaintext password but the md5(password).
Do you need to brute the plaintext password which generates the corresponding MD5?

Considering that the browser, in local javascript, calculates and send the MD5 to the router, you dont need to waste your time.
A firefox Web console is enough to inject the md5 and get the login.



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()
>>> privbob.hex()

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
‭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

Frank Denis was kind giving motivations for this behavior.

I'm glad to receive his answer.


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.

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)

#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; 
void printArray(unsigned char arr[], int size) 
  int i; 
  for (i=0; i < size; i++) 
    printf("%02x ", arr[i]); 

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

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];

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 {





//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;
    return ret;

    unsigned char key[crypto_secretstream_xchacha20poly1305_KEYBYTES];

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

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

Compiled tool:
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.
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 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.

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()
>>> privbob.hex()

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
RE Solver