A Tweet posted recently by AVG researcher, Jakub Kroustek, suggested that a new ransomware, written entirely in Python, had been found in the wild, joining the emerging trend for Pysomwares such as the latest HolyCrypt, Fs0ciety Locker and others.
This Python executable comprises two main files. One is called boot_common.py and the otherencryptor.py. The first is responsible for error-logging on Windows platforms, while the second, the encryptor, is the actual locker. Within the encryptor are a number of functions including two calls to the C&C server. The C&C is hidden behind a compromised web server located in Israel. The Israeli server was compromised using a known vulnerability in a content management system called Magento, which allowed the threat actors to upload a PHP shell script as well as additional files that assist them in streaming data from the ransomware to the C&C and back.
A notable point to mention is that the server was also used for phishing attacks, and contained Paypal phishing pages. There are strong indications that a Hebrew-speaking threat actor was behind these phishing attacks. The stolen Paypal credentials were forwarded to another remote server located in Mexico and which contains the same arbitrary file upload technique, only with a different content management.
It is a known practice for attackers to look for low-hanging fruit into which they can inject their code in order to hide their C&C server. One such example was the CTB-Locker for web servers reported last March.
Size: 5.22 MB
To reverse the executable one should first conduct a number of checks using a convenient debugger. The universal steps for unpacking an unknown packer start with trying to set a memory breakpoint on popular functions that packers use, such as VirtualAlloc.
If the breakpoint hits, the next step involves switching to user mode and setting a hardware breakpoint (on access). That will assist in inspecting where exactly the program initializes the memory block. In most cases, an executable magic header (MZ) should appear in the memory block. However, in this case the following screenshot shows the readable data that was allocated to that memory block:
After the data was allocated to the memory block, it appeared to be using VM code (python vm) to execute the code. For those who are not familiar with the term, VM code is the process of creating new instruction sets based on the author’s request. The CPU uses those instruction sets to understand the instructions.
py2exe simply converts the code to x86 assembly, the architecture used on the CPU for communication, and, by loading a python DLLs, loads all the modules into the memory.
We found that the executable file was generated using py2exe. The first indicator was a stack PUSH instruction to add the string – PY2EXE_VERBOSE: a module that compiles Python scripts to Microsoft Windows executables.
PY2EXE module string disclosure
A module that reverse the operation of the py2exe can be found in Github and is called unpy2exe. This module will revert the executable back to its origin Python compiled code (i.e. .pyc file). From that format, another step will be required to fully revert to the original code. We randomly chose to useEasyPythonDecompiler.
Fully decompiled Python scripts
In it’s current state, the executable fails to encrypt the file system, simply because the threat actors must have migrated from the current server to another. By doing so, they deleted the remaining traces of the PHP files they used for data collection from a victim’s machine. The following is the log file that is generated upon exception:
Error log file being generated by the boot_common.py
The scripts in Python use two files:
- Name: boot_common.py
This file throws an “error” to show that the program failed to execute if there is a problem.
- Name: encryptor.py
This script encrypts the victim’s files.
The ransomware disables the following features from the compromised machine by overwriting the registry policies it disables Registry Tools, Task Manager, CMD and Run.
list of registry manipulations
It then continues with changing bcdedit to disable recovery and ignore boot status policy.
Upon successful encryption, the ransomware will encrypt the following file extensions:
*.mid, *.wma, *.flv, *.mkv, *.mov, *.avi, *.asf, *.mpeg, *.vob, *.mpg, *.wmv, *.fla, *.swf, *.wav, *.qcow2, *.vdi, *.vmdk, *.vmx, *.gpg, *.aes, *.ARC, *.PAQ, *.tar.bz2, *.tbk, *.bak, *.tar, *.tgz, *.rar, *.zip, *.djv, *.djvu, *.svg, *.bmp, *.png, *.gif, *.raw, *.cgm, *.jpeg, *.jpg, *.tif, *.tiff, *.NEF, *.psd, *.cmd, *.class, *.jar, *.java, *.asp, *.brd, *.sch, *.dch, *.dip, *.vbs, *.asm, *.pas, *.cpp, *.php, *.ldf, *.mdf, *.ibd, *.MYI, *.MYD, *.frm, *.odb, *.dbf, *.mdb, *.sql, *.SQLITEDB, *.SQLITE3, *.asc, *.lay6, *.lay, *.ms11 (Security copy), *.sldm, *.sldx, *.ppsm, *.ppsx, *.ppam, *.docb, *.mml, *.sxm, *.otg, *.odg, *.uop, *.potx, *.potm, *.pptx, *.pptm, *.std, *.sxd, *.pot, *.pps, *.sti, *.sxi, *.otp, *.odp, *.wks, *.xltx, *.xltm, *.xlsx, *.xlsm, *.xlsb, *.slk, *.xlw, *.xlt, *.xlm, *.xlc, *.dif, *.stc, *.sxc, *.ots, *.ods, *.hwp, *.dotm, *.dotx, *.docm, *.docx, *.DOT, *.max, *.xml, *.txt, *.CSV, *.uot, *.RTF, *.pdf, *.XLS, *.PPT, *.stw, *.sxw, *.ott, *.odt, *.DOC, *.pem, *.csr, *.crt, *.key and wallet.dat to encrypt crypto currency wallets
The files are encrypted using AES with CBC mode for the following paths:
*userhome - The current user home directory
When the encryption step is done, the ransomware will remove the restore points and write theREADME_FOR_DECRYPT.txt file and execute it. The following screenshot is the ransom note:
CryPy Ransomware Note embedded in the Python code
The threat actor behind the attack asks the victim to contact it via email, and to send a request to the following two email addresses to receive the decryption program:
Note that the ransom note contains mistakes, implying that it has been written by a non-English speaker. First, the headline is missing a ‘T’ in “IMPORTAN INFORMATION”. Second, the sentence “Decrypting of your files…” is syntatically wrong. Native speakers will be able to find additional mistakes.
The threat actor claims that files will be deleted every 6 hours, which reflects the approach of more advanced ransomwares. However, it forgets to mention proof of decryption or a channel that can be used in cases where the payment process is not responsive. This points to the executable being at an early stage of development.
The ransomware survives a reboot by adding the following keys to the registry:
The code for adding the values to the registry is located on the functions autorun() and autorun2().
Right before launching the ransom note, the script calls a delete_shadow() function that takes no arguments, and simply executes the following command line code to remove all shadow copies and prevent recovery from backup:
os.system("vssadmin Delete shadows /all /Quiet")
Lastly, the file calls autorun2() fuction that copies the ransomware from its current location to C:\\Users\\\\AppData\\Local with hardcoded name:
The ransomware hides behind an Israeli web server which was compromised using Shell script arbitrary upload written in PHP. The compromise and upload were possible because the server carried a vulnerable Magento CMS.
The executable transfers data over an unencrypted HTTP channel in clear-text. This allows for easy traffic inspection using a network listener. The following screenshot is the traffic being sent to the server:
Inspecting the Magento exploit and the compromised server, we found that the origin of the upload carries the title Pak Haxor – Auto Xploiter and the email ardiansyah09996@gmail[.]com and that the file was uploaded in August 2016, which aligns with the case in subject. The following screenshot reveals how attackers are using massive exploiters that scan for vulnerable web servers and exploit the vulnerability, which they later visit to expand their control over the server:
Part of such an exploitation technique is dropping additional PHP scripts to refine a more sophisticated attack, such as the CryPy ransomware.
A call to one of those scripts script can be found hard-coded in the CryPy Python code, in the form of a GET request. The request is sent with two parameters to a script that was uploaded using the Auto Xploiter and carries the name victim.php. By reviewing the Python code it is easier to understand the type of data being presented in Base64 encoding format.
As seen in the screenshot above, the configurl parameter accepts a URL querystring where thevictim_info input value of the info parameter is derived from the platform module.
uname() is used when one wants to return a tuple of system, node, release, version, machine and processor values. These are encoded with Base64.
The next parameter is ip which contains the socket.gethostname() which basically collects an IP address.
The querystring is then sent to urllib.urlopen(), which will send a GET request to the selected server and read the reponse content into glob_config.
The response contains a JSON format payload which is checked for the following keys:
x_ID – the victim’s unique ID to request their decryption keys after payment.
x_UDP – Not used; perhaps saved for future use.
x_PDP – Not used; perhaps saved for future use.
The second call is implemented in a function called generate_file() which is responsible for fetching a unique key for each file before encryption.
We have seen in recent lockers that, in order to demonstrate trust and integrity, the victim is able to decrypt one/two files before processing the payment. This proves decryptor validity. In order to randomly choose a file, the attacker must first generate a unique token for each one. The second PHP script found in the code is savekey.php which is described in the following screenshot and is suspected to have the C2 IP in it. It was however deleted long before we were able to reach it.
As for the first call, the second sends two parameters. The first is the file’s name and the other is the victim ID. In return, the server responds with two keys:
X – Unique key after encryption which will be appended to the file’s header.
Y – New filename which will be stored instead of the previous one.