Ransomware Decryption: So Long, and Thanks For All the CrypMIC

This post discusses how one can recover from a CrypMIC ransomware infection with a small amount of preparation work and some lazy programming. Please note that this will not help you if you have been infected and you didn’t manage to capture the post-infection network traffic from the malware.

Introduction

In this post, we will discuss how one can recover from a CrypMIC ransomware infection with a small amount of preparation work and some lazy programming. Please note that this will not help you if you have been infected and you didn’t manage to capture the post-infection network traffic from the malware. Brute-forcing the AES-256 key for CrypMIC is well beyond the scope of this discussion.

Allow me to introduce the CryptXXX copycat.

So, there I was, with a bad case of post-vacation-itis, examining the network traffic emitted by a bunch of CrypMIC samples.

For those that have not done this, a little backstory is in order.

The CrypMIC ransomware is modified code based on the earlier CryptXXX ransomware. In particular, a few of the weaknesses of CryptXXX were:

  1. It used RC4 for encryption, so that malware researchers were able to release decryption tools. This allowed the victims to recover from the attack without having to pay the ransom.
  2. It used a custom network protocol on 443/tcp, attempting to look like HTTPS to the uninitiated, but actually being nothing like you would expect.

In this latter way, CrypMIC is still weak. One could make the argument that CryptXXX and CrypMIC are honorable (in the ‘among-thieves’ sense) malware, because they leave lots of breadcrumbs for the technically proficient to recover from, and only truly victimize the helpless.

Let’s examine these breadcrumbs.

There are four packets that the malware sends off to its C&C server, and it gets four responses. The first one is some kind of mystery packet. The second and third result in responses containing the ransom note text. The fourth packet is also a mystery; and does not get sent until some time later, after the encryption has happened.

Let's call them "Hello", "Text Ransom", "HTML Ransom", and "Confirmation", in that order. So, I was gazing at a bunch of these "Hello" packets and responses, and I noticed something curious. The response packet was the "Hello command", and 48 bytes of seemingly random crap.

Groggily, the number 48 wandered through my mind, in search of something to connect with.

I read a few blog posts about CrypMIC, such as “Side-by-side comparisons of the CrypMIC and CryptXXX Ransomware Infections”. They all mentioned that one of the major changes between CryptXXX and CrypMIC was the switch from RSA keypairs to AES-256 for the encryption.

The phrase "AES-256" wandered through my mind in search of something to connect with.

Fifteen seconds later, I realized that 32 plus 16 is 48; 32 bytes being the length of the AES-256 key and 16 bytes being the length of the initialization vector. I thought, "This can't be this easy, can it?"

So, I sat down in the mud and wrote up a couple of quick snort signatures to capture the traffic I needed:

alert tcp any any -> any 443 (content:"|32000000|"; rawbytes; offset:0; depth:8; \
  content:"|060102b1090004000000000055303030303039005c00520045000000|"; \
  rawbytes; offset:24; depth:40; \
  msg: "HDSI CrypMIC Post-Infection Traffic, key request"; \
  flowbits:set,crypmic; \
  classtype:trojan-activity; priority:1; rev:1; sid:4123886230;)

alert tcp any 443 -> any any (content:"|32000000|; rawbytes; offset:0; depth:8; \ 
  flowbits:isset,crypmic; \
  msg: "HDSI CrypMIC Post-Infection ENCRYPTION KEY CAPTURED! SAVE THIS PACKET!"; \
  classtype:trojan-activity; priority:1; rev:1; sid:4123886231;)

Now, I had 48 bytes to work with. Below is what the packet payload looks like, in hex:

00000000  32 00 00 00 8b 00 8e 08  37 00 00 c5 11 91 c6 00   
00000010  fe 82 51 e3 92 72 68 1c  3b 58 83 3c a1 c5 04 24   
00000020  ff 75 98 23 99 b8 94 a6  4c db 8b 7b c0 fe 8b 13   
00000030  bc e6 10 32

The "0x32000000" is the "Hello" command, or more likely the "Key Request" command. Now, what about the rest of that stuff? We'll take a guess that it's (KEY,IV) and run with it. (I got lucky here, it could have been the other way, or obfuscated somehow. It wasn't. Maybe in the next release.)

We grabbed a couple files off the now-encrypted machine, and computed the MD5 and SHA1 hashes for the originals, so I'd know what success looked like.

Time to do a little python.

#
# PoC for CrypMIC decryptor
#

import sys

keystring = "\x8b\x00\x8e\x08\x37\x00\x00\xc5\x11\x91\xc6\x00\xfe\x82\x51\xe3\
x92\x72\x68\x1c\x3b\x58\x83\x3c\xa1\xc5\x04\x24\xff\x75\x98\x23\x99\xb8\x94\xa6\
x4c\xdb\x8b\x7b\xc0\xfe\x8b\x13\xbc\xe6\x10\x32"
KEY=keystring[0:32]
IV=keystring[32:]

# so... let's cut and paste some code off stackoverflow.... (modern programming at its finest!)
# easy_install pycrypto
from Crypto import Random
from Crypto.Cipher import AES

BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[0:-ord(s[-1])]

class AESwrap:
    def __init__( self, key, iv ):
        self.key = key
        self.iv = iv
    def encrypt( self, raw ):
        raw = pad(raw)
        cipher = AES.new( self.key, AES.MODE_CBC, self.iv )
        return  self.iv + cipher.encrypt( raw ) 
    def decrypt( self, enc ):
        cipher = AES.new(self.key, AES.MODE_CBC, self.iv )
        return unpad(cipher.decrypt( enc[32:] ))

crypmic = AESwrap(KEY, IV)

f = open(sys.argv[1], "r")
g = open(sys.argv[2], "w")

x = f.read()
decrypted = crypmic.decrypt(x)
g.write(decrypted)

f.close()
g.close()





$ python decryptmic.py encrypted_pil_win-amd64-2.7.zip PIL_win-amd64-2.7.zip

$ python decryptmic.py encrypted_python-2.7.11.amd64.msi python-2.7.11.amd64.msi

$ md5 PIL_win-amd64-2.7.zip ; openssl sha1 PIL_win-amd64-2.7.zip
MD5 (PIL_win-amd64-2.7.zip) = 0ae891336ad7ca0492e6d1f83c0e9bd7
SHA1(PIL_win-amd64-2.7.zip)= 278510ad1c53c0734e03c9c0c624c3401fdb26a0

$ md5 python-2.7.11.amd64.msi ; openssl sha1 python-2.7.11.amd64.msi
MD5 (python-2.7.11.amd64.msi) = 25acca42662d4b02682eee0df3f3446d
SHA1(python-2.7.11.amd64.msi)= 14846d20f2c7c819d7543538681e199f2e6f62d6

$ unzip -t PIL_win-amd64-2.7.zip | tail -1
No errors detected in compressed data of PIL_win-amd64-2.7.zip.

$

Success.

In Conclusion

Hopefully you’ll find this tool to be a helpful in an emergency -- in case you discover that your backups have failed and your patch policy didn’t prevent the infection in the first place, but you did happen to get lucky and manage to capture the key exchange. Also, pulling some junk out of a network capture and decrypting an end-user’s system makes you look like a wizard.



Close off Canvas Menu