Join the discord

Reverse engineering "Win Speedup 2018"

04 Oct, 2018 12:04
First of all, let's see what a Scareware is:
Wikipedia.orgScareware is a form of malware which uses social engineering to cause shock, anxiety, or the perception of a threat in order to manipulate users into buying unwanted software.

My journey with this one began with the following popup:

*The masked in red parts are where my IP was printed*

This, combined by two beeps much like the PC speaker beeps on bootup and a scary message is used to grab the victim's attention.
Although the site is mentioning Microsoft and Windows and copies Microsoft's developer network website, this link has nothing to do with Microsoft. The header part for example is one whole image with drawn links and texts.

The website is served by the Amazon's cloud services through this link.
Serving URL
&ip=<user's IP>
&cep=<tracking data>
&sub=<tracking data>
&clickid=<tracking data>

I've downloaded the page to see the client-side source:
HTML code<html>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <base href=".">
  <meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <title>Virus Found - Microsoft</title>
    function getUrlParameter(t) {
      var e, n, o = decodeURIComponent(,
        a = o.split("&");
      for (n = 0; n < a.length; n++)
        if (e = a[n].split("="), e[0] === t) return void 0 === e[1] ? !0 : e[1]
  <meta name="robots" content="noindex, nofollow, noarchive">
  <script src="./files/jquery-3.2.1.min.js"></script>
  <script src="./files/language-set.js"></script>
  <script src="./files/language-version-1.js"></script>
  <script charset="UTF-8" src="./files/moment-with-locales.min.js"></script>
  <link rel="stylesheet" type="text/css" href="./files/main.css">
<!-- User interface trimmed for clarity //-->
<div class="outer outer2 zpzpzp" style="display:none">
<!-- User interface trimmed for clarity //-->
    function outclick() {'', '_blank');
<script charset="UTF-8" src="./files/mackeeper-script.js"></script>
<script type="text/javascript" src="./files/main.js"></script>
<script type="text/javascript">
  function noExit() {
    alert("has detected that your Microsoft Windows system is currently out of date and corrupted.\n\n"+
	"This causes automatic deletion of your system files.\n\n"+
	"Follow the instructions immediately to resolve this problem and make sure your system stays up to date.");
  (function () {document.addEventListener('mouseleave', function () {noExit();});})();
Includes like jquery-3.2.1.min.js, moment-with-locales.min.js and main.css are not interesting.

From the localisation file language-set.js I can pull a bit of metadata about the user groups targeted - English (international), French, Arabic, Crotian, Czech, German, Hindi, Hungarian, Indonesian, Italian, Japanese, Polish, Portuguese, Romanian, Spanish, Tagalog, Vietnamese, Greek, Kurdish, Mandarin, Thai, Turkish, Korean, Dutch, Danish, Finnish, Norwegian and Swedish speaking users.
That pretty much covers Western and Central Europe, South Asia and the Americas.

The file mackeeper-script.js handles the countdown counter and the progress bar for the "scanning" process, triggered after the user clicks on the "SCAN NOW >>" button.
Of course this scanning process is a hoax, but we'll get to that later.

The final included file is main.js where the "beep" code is.

The URL components are parsed by getUrlParameter(), so the parameters from the link can be used by the JS code later.

According to the code at the end, every time the user moves the mouse pointer out of the screen area another scary message gets printed.
This one never got triggered on Firefox and only worked on Chrome.

After the user clicks the scan button, a fraudulent scanning process is triggered leading to the payload:

Of course none of this is true - the virus found and all the rest is just hardcoded strings inside the localisation file language-set.js:
* Language Format variable should be {x}Set .
* Where x-> language abbrevation w.r.t
/*------------------LANGUAGE OBJECT VARIABLE----------------------*/
var enSet = {
   saf_overlay_P_5: 'Please click on the download symbol',
   overlay_P_5: 'Click <br> here <br> and install.',
   saf_install: ' and install.',
   loading : 'Loading…',
   centeredtext : 'LOADING... PLEASE WAIT.',
   id_HEADER1 : 'Your system is infected with 3 viruses',
   id_E1STPAR_HEAD : 'Your PC is infected with',
   id_E1STPAR_VIRUS : 'viruses. Our security check found traces of',
   id_E1STPAR_MALWARE : 'malware and ',
   id_E1STPAR : 'phishing/spyware. System damage: 28.1% - Immediate removal required!',
   id_2NDPAR1 : 'The immediate removal of the viruses is required to prevent further system damage, loss of Apps, Photos or other files.',
   id_E2NDPAR2_TRACES : 'Traces of',
   id_E2NDPAR2 : 'phishing/spyware were found on your PC with',
   id_E2NDPAR3 : 'Personal and banking information is at risk.',
   id_E3RDPAR : 'To avoid more damage click on \'Scan Now\'immediately. Our deep scan will provide help immediately!',
   id_E32RDPARTIMER_MINUTE: 'minute and  ',
   id_E32RDPARTIMER : 'seconds remaining before damage is permanent.',
   id_BUTTON: 'Scan Now',
   id_HEAVILYDAMAGED : 'Your PC is heavily damaged! (33.2%)',
   id_DOWNLOADMACKEEPER : 'Please download the',
   id_DOWNLOADMACKEEPER_VIRUS : 'application to remove 3 Viruses from your PC.',
   id_VIRNAME : 'Virus Name:  Ransomware 2.0; Trojan.Win32.SendIP.15',
   id_VIRRISK: 'Risk:  HIGH',
   id_INFECTEDDATA: 'Infected Files:  '+
                    'C:/WINDOWS/System32/migration/ADJF9009de.@*fg/windows.exe; '+
   id_APPLICATION : 'Application: ',
   id_RATING : 'Rating:  9.9/10',
   id_PRICE : 'Price:  Free',
   alertwindow: "IMMEDIATE ACTION REQUIRED\n\nWe have detected a trojan virus (e.tre456_worm_Windows) on your PC.\n\n"+
                "Press OK to begin the repair process.",
   loadingtext1: "Loading",
   loadingtext2 : "Scanning... Folders",
   loadingtext3 : "Scanning... Documents",
   loadingtext4 : "Scanning... System Files",
   loadingtext5 : "Scanning... Registry",
   loadingtext6 : "Scan Complete",
   virfoundtext1 : "Virus Found: Ransomware 2.0; Trojan.Win32.SendIP.15",
   virfoundtext2 : "Searching for removal options..."

Ransomwares got popular in the past few years, and here it's used as a bogeyman word, to scare the user and make him believe that his system is infected.
The goal at this stage is to simply lure the user into downloading the attackers "snake oil" of a software, and here's where things might get sketchy.

There are usually two options here.
Straight forward is the illegal one of course, where the cleaning software is a trojan or a botnet client disguised as a scanning program.
The second one would be that the scanning software is a legit scanner that scans for low risk threats (eg. tracking cookies), report them as high risk and further force the user to buy the full version of the app, that will clean these threats.

Overall, the lure page is crafted as a kit, that can be easily modified to serve another application.
For example, the name of this one "Win Speedup 2018" is passed as a URL argument, so they can easily change it for the next bait.

The final step leads the user to the official download page of Win Speedup 2018, where the user can download and install wspsetup.exe and where, for some reason, the application is called "PC Repair Tool".

The page again mimics the well known design of Microsoft's support pages, but there's no longer any Microsoft logos at all. There's however banners of Norton and McAfee, ironically with the "click to verify" text, that unironically is a static, unclickable image.
According to their metrics, they already have over 60 million downloads which is ridiculous claim.

So, let's see the homepage of "Win Speedup 2018" -

All the "Download" links are direct requests for
Direct download links are generally a bad practise and no one serious enough to sell commercial product actually does that.

Now, let's see what the actual app does.

Reverse engineering Win Speedup 2018 (wspsetup.exe)

The installer used is Inno Setup, with encrypted payload.
I didn't wanted to blindly install the app, so I want to use the innounp (which is an excellent tool by the way) I'll have to provide the decryption key.

To get the key however, I need to dig into the install script, and that's where a tool named InnoExtractor turn out to be really handy.
The tool was able to extract the code section of the install script, and after extracting the strings from the code, I was able to find the decryption password - fx!@#$%sp.

Cool, now I have the extracted files, so let's put them in a nice table:
{app}\bpp.exeMain app executable
{app}\bpp.exe.configMain app config file
{app}\gtcmg,1.dllResource bank of GIF, PNG and JPG images
{app}\gtcmg,2.dllcopy of the file above
{app}\Microsoft.Win32.TaskScheduler.dll.NET wrapper for the Windows Task Scheduler, created by David Hall
{app}\NAudio.dll.NET Audio Toolkit, created by Mark Heath
{app}\TAFactory.IconPack.dllIconPack by NCEEE, created by Systweak Software
{app}\Interop.IWshRuntimeLibrary.dllWindows Scripting Host runtime library (iWshRuntimeLibrary)
{app}\application.icoMain app icon
{app}\System.Data.SQLite.DLLSystem.Data.SQLite core assembly by
{app}\x64\SQLite.Interop.dllSystem.Data.SQLite x64 interop assembly by
{app}\x86\SQLite.Interop.dllSystem.Data.SQLite x86 interop assembly by
{app}\HtmlRenderer.dllHTML Renderer, created by ArthurHub
{app}\HtmlRenderer.WinForms.dllHTML Renderer (WinForms), created by ArthurHub
{app}\langs.dbLocalisation for the main app
{app}\english_iss.iniDownloader localisation in English
{app}\finish_iss.iniDownloader localisation in Finish
{app}\French_iss.iniDownloader localisation in French
{app}\german_iss.iniDownloader localisation in German
{app}\italian_iss.iniDownloader localisation in Italian
{app}\japanese_iss.iniDownloader localisation in Japanese
{app}\norwegian_iss.iniDownloader localisation in Norwegian
{app}\portuguese_iss.iniDownloader localisation in Portuguese
{app}\russian_iss.iniDownloader localisation in Russian
{app}\spanish_iss.iniDownloader localisation in Spanish
{app}\swedish_iss.iniDownloader localisation in Swedish
{app}\danish_iss.iniDownloader localisation in Danish
{app}\Dutch_iss.iniDownloader localisation in Dutch
{app}\setup_en.bmpDownloader background for another tool named "Driver Updater"
{commonappdata}\Win ~Speed ~Up2018 for {computername}\mdb.dbMalware detection database
{commonappdata}\Win ~Speed ~Up2018 for {computername}\pcspstartrepair_en.mp3Sounds for the scanning process

The DLLs are taken from open source projects and if we also exclude the files that are part of the advertisement for Driver Updater, what's left is the main app, its config and the two .DB files.

Speaking of the databases, they are both SQLite3 database files that are currently encrypted with a password that I don't know... yet.

The app is written in C#, and its apparently obfuscated with Eazfuscator.NET:
app.exe general info// Entry point: \uE032.Main
// Timestamp: 5B7AA3B8 (8/20/2018 11:19:20 AM)

using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

[assembly: AssemblyVersion("")]
[assembly: Obfuscation(Feature = "Apply to type USSCare.UC.usSetting.exclusionDataCombined: renaming")]
[assembly: Obfuscation(Feature = "Apply to type JsonPost: renaming")]
[assembly: Obfuscation(Feature = "string encryption", Exclude = false)]
[assembly: Obfuscation(Feature = "Apply to type USSCEngine.cRegMainLight.ROOT_KEY: renaming")]
[assembly: Obfuscation(Feature = "Apply to type Params: renaming")]
[assembly: Obfuscation(Feature = "Apply to type *: apply to member Htmf* when public and method: renaming", Exclude = true)]
[assembly: Obfuscation(Feature = "encrypt symbol names with password A!&*^CC", Exclude = false)]
[assembly: Obfuscation(Feature = "code control flow obfuscation", Exclude = false)]
[assembly: AssemblyTitle("SpeedUp Tool")]
[assembly: AssemblyDescription("SpeedUp Tool")]
[assembly: Guid("647EA527-0C1A-4096-BBCF-E0CA56AA8B1B")]
[assembly: Obfuscation(Feature = "Apply to type USSCEngine.Settings.ExclusionItem: renaming")]
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Obfuscation(Feature = "encrypt resources", Exclude = false)]
[assembly: AssemblyFileVersion("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SpeedUp Tool")]
[assembly: AssemblyCopyright("Copyright ©  2018")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: SuppressIldasm]

Unfortunately de4dot wasn't able to strip the obfuscator. However, it did fixed (partially) the control flow obfuscation and set the naming convention to readable characters only.

So, next stop - defeating the obfuscation.

Defeating Eazfuscator

I must admit - I'm not familiar wit this obfuscator, so I started with visiting its official site to check out its features.
Here's what I should expect:
- Code control flow obfuscation: as expected, nothing new here
- Symbols renaming: messing up the naming convention is also a trivial obfuscation technique
- String encryption and compression: no plaintext strings
- Resource encryption and compression: no raw resources either
- Assemblies merging and embedding: piggybacked apps and libs
- Code and data virtualization: I didn't get that one, so I'll have to deal with it on the run

The control flow obfuscation and symbols renaming are pretty straightforward techniques in obfuscation as seen here:
Control flow and symbols obfuscationprivate \uE046() {
   this.\uE009 = new ReaderWriterLock();
   for (;;) {
      int num = \uE05A.\uE000(44);
      for (;;) {
         switch (num) {
            case 0:
               this.\uE00B = new \uE049();
               num = 3;
            case 1:
               this.\uE00A = new Dictionary<int, \uE042>();
               num = \uE05A.\uE000(48);
            case 2:
               num = 4;
            case 3:
               this.\uE00C = new \uE039();
               num = 2;
            case 4:
// ...
internal static int \uE000(int \uE5EB) {
   switch (\uE5EB - (-(~(122163458 ^ 396110977)) + -282343428 >> 5)) {
      case 0:
         return (~(344087681 - -292020289) ^ -667834690) - 35939714;
      case 1:
         return ~(499702595 - 499702787 >> 6);
      case 2:
      case 3:
      case 4:
         return (-1892683008 - -399197141 ^ 530801822) - -686893083 ^ -498501018;
      case 5:
         return (501330286 ^ 501330190) >> 3;
         switch (\uE5EB - ~(-637577808 + 637577751)) {
            case 0:
               return ~(-8);
            case 1:
               return ~(--331689596 + -331690365) >> 7;
   return ~119816532 ^ 119816532;

Both of these can be sometimes partially fixed by de4dot, so I did that and reloaded the app in dnSpy.

This helped a lot, so I moved to the static initialization code of the main class, that is executed right before the Main() procedure.
Here two calls are made - one to Class129.smethod_0() and another calling Class127.smethod_0()

The first one is creating a debugging protection thread and the second one is a checksum calculator used for code integrity check.

Because the thread that the first method creates delays its execution, I have to deal with the integrity check from the second method Class127.smethod_0() first:
Class127.smethod_0(): code integrity checkinternal unsafe static void smethod_0() {
   Module module = typeof(Class127).Module;
   // ptr = base of MZ
   byte* ptr = (byte*)((void*)Marshal.GetHINSTANCE(module));
   if (ptr == -1) {
   // the following code will always set flag to False, due to the fullyQualifiedName[0] == '<' comparison
   string fullyQualifiedName = module.FullyQualifiedName;
   bool flag = fullyQualifiedName.Length > 0 && fullyQualifiedName[0] == '<';
   byte* ptr2 = ptr + *(uint*)(ptr + 60);		// Offset to 'PE' signature
   ushort num = *(ushort*)(ptr2 + 6);			// Number of sections
   ushort num2 = *(ushort*)(ptr2 + 20);			// Size of optional headers
   uint* ptr3 = (uint*)(ptr2 + 24 + num2);		// Begin of section table, taken as array of DWORDS (unsigned int)
   uint num3 = 0x2E811FC9;				// / DELTA 1
   uint num4 = 0x2059B988;				// | DELTA 2
   uint num5 = 0x5C278B8E;				// | DELTA 3
   uint num6 = 0x1934D839;				// | DELTA 4
   uint num7 = 0x00000000;				// \ verification checksum buffer
   for (int i = 0; i < (int)num; i++) {			// iterate through section table
      // this basically multiplies the first and the second DWORD of the section name
      // so ".text" section will be 0x7865742E * 0x00000074 = 0x8DF8A4D8
      uint num8 = *(ptr3++) * *(ptr3++);
      // Skip everything except ".text" and ".rsrc" sections
      if (num8 != 0x8DF8A4D8 && num8 != 0x42A527CA) {
         ptr3 += 8;
      } else {
         uint num9 = *ptr3 >> 2;			// section VirtualSize >> 2 (divide by 4)
         // because the flag is False, ptr3[3] will be divided by 4, then added to the ptr
         // ptr is the beginning of the executable and ptr3[3] is the PointerToRawData
         // so in the end, ptr4 point to the beginning of the current PE section, as array of DWORDS
         uint* ptr4 = (uint*)(ptr + (UIntPtr)(flag ? ptr3[3] : ptr3[1]) / 4);
         if (num8 == 0x8DF8A4D8) {			// only for ".text" section
            ptr4 += 2;					// skip first 2 DWORDS
            num9 -= 29u;				// decrement the iterator limit by 29 DWORDS less
         // At this point, the for() loop will iterate through the whole section code as DWORDS
         // skipping the first 2 DWORDS and the last 27, if the current section is ".text"
         // the following loop is simple math and shifting the 
         for (uint num10 = 0u; num10 < num9; num10 += 1u) {
            uint num11 = *(ptr4++);
            uint num12 = (num3 ^ num11) + (num4 + num5) + num6;
            num3 = num4;
            num4 = num5;
            num5 = num6;
            num6 = num12;
         if (num8 == 0x8DF8A4D8) {			// only for ".text" section
            // the following while() loop takes the 27'th DWORD as verification checksum
            // in other words, this is the last DWORD value of the ".text" section
            // this DWORD is the _CorExeMain virtual address from the .NET's entry point stub - JMP _CorExeMain
            uint num13 = 0u;
            while ((ulong)num13 < 27UL) {
               num7 = *(ptr4++);
               num13 += 1u;
         ptr3 += 8;					// Move to next section
   // if num7 and num6 doesn't match, a call to Environment.FailFast(null) is executed
   // num7 is the virtual address to _CorExeMain
   // num6 is calculated 
   if (num7 != num6) {
I already used de4dot to partially deobfuscate parts of the code, and changing the code will mess up the integrity check validation.
To fix that, I can either adjust the starting DELTA values, so in the end num6 matches num7, or just patch the check / NOP the Class127.smethod_1() call.
The most simple solution is to patch it, and the integrity check is defeated!

Time to deal with the first debugging check, but before going further, I'll have to deal with the encrypted strings and resources as well.

There's quite some encrypted data in the Class129.smethod_0() method, like here:
Class129.smethod_0()private static void smethod_1(object object_0) {
   Thread thread = object_0 as Thread;
   if (
      Environment.GetEnvironmentVariable(Class130.smethod_0(Class124.smethod_0(112563), 57547)) != null ||
      Environment.GetEnvironmentVariable(Class130.smethod_0(Class124.smethod_0(112588), 60876)) != null)
      Class129.smethod_2(); // call to Environment.FailFast(null);
   // more code ...

The two nested calls that should produce a string passed to Environment.GetEnvironmentVariable(), so I started with the inner one Class124.smethod_0() that takes an integer as parameter.
This is a direct call to a quite bulky class Class124, but really not that hard for understanding:
class Class124internal sealed class Class124 {

   // Static initialization
   static Class124() {
      Assembly executingAssembly = Assembly.GetExecutingAssembly();
      // Class124.delegate5_0() is basically a reference to Class124.smethod_1()
      Class124.delegate5_0 = new Class124.Delegate5(Class124.smethod_1);
      // So here it basically does a Class124.smethod_1(0) call, that returns the resource name "FmZo"
      // The requested resource is passed to Class132.smethod_0() that parses its structure and decrypts it
      Stream stream_ = Class132.smethod_0(executingAssembly.GetManifestResourceStream(Class124.delegate5_0(0)));
      // The final call parses the decrypted resource and assigns it to a hashtable in the current AppDomain
      Class124.string_0 = new Class124.Class125().method_1(stream_);

   // Pull the requested string from a hashtable
   public static string smethod_0(int int_0) {
      return (string)((Hashtable)AppDomain.CurrentDomain.GetData(Class124.string_0))[int_0];

   // Simple key-XOR based decryption, that produces the string "FmZo"
   public static string smethod_1(int int_0) {
      char[] array = "%\u000e9\f".ToCharArray();
      int num = array.Length;
      while (--num >= 0) {
         array[num] = (char)((int)(array[num] ^ 'c') ^ int_0);
      return new string(array);
   // more code ...

Here's where the things get a bit complicated.
The code looks for resource named "FmZo", but such resource does not exist and the only resource in this executable is named "pGxH" that is getting parsed first.

I was able to partially reverse engineer the encrypted resource format:
encrypted resource formatstruct ENCRYPTED_RESOURCE_FORMAT {
   short header_length;			// 0x25 bytes
   byte	header[header_length];	{	// The header is key-XOR decrypted using the following header_key
      // encrypted: 55 B0 3C E5 6F EB 17 B7 68 EA 6C B0 5F DD 55 86 5E DC 54 8E 70 EC E4 2C FD 6A A8 CC 56 C3 33 DF 50 E2 8E 47 4D
      // decrypted: 0B 6C 69 63 31 37 42 31 36 36 39 36 01 01 00 00 00 00 01 08 2E 30 B1 AA A3 B6 FD 4A 08 1F 66 59 0E 3E DB C1 13
      byte	strA_len;		// / "lic17B16696"
      char	strA[strA_len];		// \ not used
      bool	flag_DES_encrypt;	// if TRUE, encrypted_data is DES encrypted in CBC mode
      bool	flag_compress;		// if TRUE, encrypted_data is compressed
      long	SHA1_crc_len;		// / Used for integrity check
      byte	SHA1_crc[SHA1_crc_len];	// \ in the current app, this feature is not used
      bool	flag_public_key;	// / If set to FALSE, it takes the encryption key from the app's public key
                                        // \ In the current case, this is set to TRUE, so the key is taken from this structure
      byte	key_len;		// / 0x08
      byte	key[key_len];		// \ 2E 30 B1 AA A3 B6 FD 4A
      byte	iv_len;			// / 0x08
      byte	iv[iv_len];		// \ 1F 66 59 0E 3E DB C1 13
   byte	header_key[4];			// key for the header decryption
   byte	encrypted_data[];		// gets decrypted using the information from the header

This app uses both compression and encryption of its resources, and even thought finding the DES encryption was easy, determining the correct compression method wasn't.
I did saw artefacts from a inflate decompression, but I think the authors used some custom Huffman encoding.
In the end, I dumped the decompressed buffer from the memory.

This buffer has its own structure:
structure of the pGxH resource, after decryption and decompressionstruct RAW_RES {
   long	num_entries;		// Number of strings in the string table, XORed with 0x0x1DEAF202
   string[num_entries] {
      byte	str_len;
      string	str;		// unicode string, XOR encrypted with 0xF51
   long		payload_size;	// size of the upcoming payload, XORed with 0x4254FD09
   byte[]	payload;	// MZ-PE executable

There's a piggybacked DLL in there, named KDelTUprDdOa, that is just a resource storage, meaning there's no code inside.
However its resources are the interesting part. The strings that got decrypted from "pGxH" are references to the resource names inside this executable.
Among its resources there's one named "FmZo" - just what the main app is requesting.
It is obfuscated in the same way and all it contains are strings. Lots of strings.

The rest of the resources from the main "pGxH" are HTML, CSS, JS, PNG, ICO, BMP, JPG and GIF files that are part of the main application's user interface.

The Hashtable builder code has additional obfuscations applied, starting with a Base64+XOR:
public MethodBuilder method_0(TypeBuilder typeBuilder_0): Hashtable builder
public MethodBuilder method_0(TypeBuilder typeBuilder_0) {
   byte[] array = Convert.FromBase64String("WT9FY3IKPC9JDEszF9wCAUl0Pg9zSXTuJVQ0Kz1rbhxlfG9dBhVoNc1cMzcLTRTDQE47R2V0VHlwZUZf"\
   array[0] = (array[0] ^ 30);
   array[1] = (array[1] ^ 90);
   array[2] = (array[2] ^ 49);
   array[3] = (array[3] ^ 37);
   array[4] = (array[4] ^ 0);
   array[5] = (array[5] ^ 107);
   array[6] = (array[6] ^ 81);
   array[7] = (array[7] ^ 74);
   array[8] = (array[8] ^ 114);
   array[9] = (array[9] ^ 75);
   array[10] = (array[10] ^ 46);
   array[11] = (array[11] ^ 71);
   array[12] = (array[12] ^ 90);
   array[13] = (array[13] ^ 185);
   array[14] = (array[14] ^ 118);
   array[15] = (array[15] ^ 105);
   array[16] = (array[16] ^ 38);
   array[17] = (array[17] ^ 16);
   array[18] = (array[18] ^ 5);
   array[19] = (array[19] ^ 104);
   array[20] = (array[20] ^ 22);
   array[21] = (array[21] ^ 61);
   array[22] = (array[22] ^ 43);
   array[23] = (array[23] ^ 170);
   array[24] = (array[24] ^ 64);
   array[25] = (array[25] ^ 55);
   array[26] = (array[26] ^ 88);
   array[27] = (array[27] ^ 74);
   array[28] = (array[28] ^ 79);
   array[29] = (array[29] ^ 2);
   array[59] = (array[59] ^ 45);
   array[31] = (array[31] ^ 123);
   array[32] = (array[32] ^ 49);
   array[33] = (array[33] ^ 5);
   array[34] = (array[34] ^ 31);
   array[35] = (array[35] ^ 56);
   array[36] = (array[36] ^ 61);
   array[37] = (array[37] ^ 46);
   array[38] = (array[38] ^ 15);
   array[39] = (array[39] ^ 80);
   array[40] = (array[40] ^ 185);
   array[41] = (array[41] ^ 3);
   array[42] = (array[42] ^ 117);
   array[43] = (array[43] ^ 66);
   array[44] = (array[44] ^ 103);
   array[45] = (array[45] ^ 33);
   array[46] = (array[46] ^ 90);
   array[47] = (array[47] ^ 162);
   array[48] = (array[48] ^ 45);
   array[49] = (array[49] ^ 43);
   array[61] = (array[61] ^ 102);
   string[] array2 = Encoding.UTF8.GetString(array).Split(new char[] { ';' });
   // More code...

This produces an array of strings used as method names and strings in the following CIL code:
Dynamic procedure builderilgenerator.Emit(OpCodes.Newobj, constructor);			// /
ilgenerator.Emit(OpCodes.Stloc_0);				// \ loc0 = new StackTrace()
ilgenerator.Emit(OpCodes.Ldc_I4_0);				// /
ilgenerator.Emit(OpCodes.Stloc_S, 5);				// \ loc5 = 0
ilgenerator.Emit(OpCodes.Br, label);
ilgenerator.Emit(OpCodes.Ldloc_0);				// /
ilgenerator.Emit(OpCodes.Ldloc_S, 5);				// |
ilgenerator.Emit(OpCodes.Callvirt, method);			// | loc0.GetFrame(loc5)
ilgenerator.Emit(OpCodes.Callvirt, method2);			// | System.Diagnostics.StackFrame.GetMethod()
ilgenerator.Emit(OpCodes.Callvirt, method3);			// | get_DeclaringType
ilgenerator.Emit(OpCodes.Stloc_S, 6);				// \ loc6 = loc0.GetFrame(loc5).GetMethod().DeclaringType
ilgenerator.Emit(OpCodes.Ldloc_S, 6);				// /
ilgenerator.Emit(OpCodes.Brfalse, label2);			// \ if loc6 == False : break loop
ilgenerator.Emit(OpCodes.Ldloc_S, 6);				// /
ilgenerator.Emit(OpCodes.Callvirt, method4);			// | get_FullName
ilgenerator.Emit(OpCodes.Stloc_S, 7);				// \ loc7 = loc6.FullName
ilgenerator.Emit(OpCodes.Ldloc_S, 7);				// /
ilgenerator.Emit(OpCodes.Ldstr, "de4dot");			// | "de4dot"
ilgenerator.Emit(OpCodes.Ldc_I4_5);				// | 5
ilgenerator.Emit(OpCodes.Callvirt, method5);			// | IndexOf
ilgenerator.Emit(OpCodes.Ldc_I4_M1);				// | -1
ilgenerator.Emit(OpCodes.Bne_Un, label3);			// \ if loc7.IndexOf("de4dot", 5) != -1 : terminate
ilgenerator.Emit(OpCodes.Ldloc_S, 7);				// /
ilgenerator.Emit(OpCodes.Ldstr, "SimpleAssemblyExplorer");	// | "SimpleAssemblyExplorer"
ilgenerator.Emit(OpCodes.Ldc_I4_5);				// | 5
ilgenerator.Emit(OpCodes.Callvirt, method5);			// | IndexOf
ilgenerator.Emit(OpCodes.Ldc_I4_M1);				// | -1
ilgenerator.Emit(OpCodes.Bne_Un, label3);			// \ if loc7.IndexOf("SimpleAssemblyExplorer", 5) != -1 : terminate
ilgenerator.Emit(OpCodes.Ldloc_S, 7);				// /
ilgenerator.Emit(OpCodes.Ldstr, "babelvm");			// |
ilgenerator.Emit(OpCodes.Ldc_I4_5);				// |
ilgenerator.Emit(OpCodes.Callvirt, method5);			// | IndexOf
ilgenerator.Emit(OpCodes.Ldc_I4_M1);				// |
ilgenerator.Emit(OpCodes.Bne_Un, label3);			// \ if loc7.IndexOf("babelvm", 5) != -1 : terminate
ilgenerator.Emit(OpCodes.Ldloc_S, 7);				// /
ilgenerator.Emit(OpCodes.Ldstr, "smoketest");			// |
ilgenerator.Emit(OpCodes.Ldc_I4_5);				// |
ilgenerator.Emit(OpCodes.Callvirt, method5);			// | IndexOf
ilgenerator.Emit(OpCodes.Ldc_I4_M1);				// |
ilgenerator.Emit(OpCodes.Beq, label4);				// \ if loc7.IndexOf("smoketest", 5) != -1 : terminate
ilgenerator.Emit(OpCodes.Ldc_I4_0);				// /
ilgenerator.Emit(OpCodes.Call, method6);			// \ Exit(0)
ilgenerator.Emit(OpCodes.Ldloc_S, 5);				// /
ilgenerator.Emit(OpCodes.Ldc_I4_1);				// |
ilgenerator.Emit(OpCodes.Add);					// |
ilgenerator.Emit(OpCodes.Stloc_S, 5);				// \ loc5 += 1
ilgenerator.Emit(OpCodes.Ldloc_S, 5);				// /
ilgenerator.Emit(OpCodes.Ldloc_0);				// |
ilgenerator.Emit(OpCodes.Callvirt, method7);			// | get_FrameCount
ilgenerator.Emit(OpCodes.Blt, label5);				// \ if loc0.FrameCount == loc5 : break loop
ilgenerator.Emit(OpCodes.Ldarg_0);				// /
ilgenerator.Emit(OpCodes.Callvirt, method8);			// | get_Length
ilgenerator.Emit(OpCodes.Stloc_1);				// \ loc1 = arg0.Length
ilgenerator.Emit(OpCodes.Ldarg_0);				// /
ilgenerator.Emit(OpCodes.Newobj, constructor2);			// |
ilgenerator.Emit(OpCodes.Stloc_2);				// \ loc2 = new BinaryReader(arg0)
ilgenerator.Emit(OpCodes.Newobj, constructor3);			// /
ilgenerator.Emit(OpCodes.Stloc_3);				// \ loc3 = new Hashtable()
ilgenerator.Emit(OpCodes.Ldloc_2);				// /
ilgenerator.Emit(OpCodes.Callvirt, method9);			// | ReadString
ilgenerator.Emit(OpCodes.Stloc_S, 4);				// \ loc4 = loc2.ReadString()
ilgenerator.Emit(OpCodes.Ldloc_3);				// /
ilgenerator.Emit(OpCodes.Ldc_I4_M1);				// | -1
ilgenerator.Emit(OpCodes.Box, typeof(int));			// |
ilgenerator.Emit(OpCodes.Ldloc_S, 4);				// |
ilgenerator.Emit(OpCodes.Callvirt, method10);			// \ loc3.Add(-1, loc4)
ilgenerator.Emit(OpCodes.Br, label6);
ilgenerator.Emit(OpCodes.Ldloc_3);				// /
ilgenerator.Emit(OpCodes.Ldarg_0);				// |
ilgenerator.Emit(OpCodes.Callvirt, method11);			// | get_Position
ilgenerator.Emit(OpCodes.Conv_I4);				// |
ilgenerator.Emit(OpCodes.Ldc_I4, 37);				// | 37
ilgenerator.Emit(OpCodes.Add);					// |
ilgenerator.Emit(OpCodes.Ldc_I4, arg);				// | int arg = int.Parse("15518");
ilgenerator.Emit(OpCodes.Xor);					// |
ilgenerator.Emit(OpCodes.Box, typeof(int));			// | stack1 = (arg0.Position + 37) ^ 15518
ilgenerator.Emit(OpCodes.Ldloc_2);				// |
ilgenerator.Emit(OpCodes.Callvirt, method9);			// | stack0 = loc2.ReadString()
ilgenerator.Emit(OpCodes.Callvirt, method10);			// \ loc3.Add(stack1, stack0)
ilgenerator.Emit(OpCodes.Ldarg_0);				// /
ilgenerator.Emit(OpCodes.Callvirt, method11);			// | get_Position
ilgenerator.Emit(OpCodes.Ldloc_1);				// |
ilgenerator.Emit(OpCodes.Blt, label7);				// \ if arg0.Position < loc1 : continue loop 
ilgenerator.Emit(OpCodes.Call, method12);			// / get_CurrentDomain
ilgenerator.Emit(OpCodes.Ldloc_S, 4);				// |
ilgenerator.Emit(OpCodes.Ldloc_3);				// |
ilgenerator.Emit(OpCodes.Callvirt, method13);			// \ AppDomain.SetData(AppDomain.CurrentDomain, loc4)
ilgenerator.Emit(OpCodes.Ldloc_S, 4);				// /
ilgenerator.Emit(OpCodes.Ret);					// \ return loc4
return methodBuilder;
The whole dynamically built method is working as debugger/deobfuscator protection and Hashtable builder with specific key scheme.

So, the inner procedure Class124.smethod_0(Int) I started with, returns the requested string from the final string Hashtable.
And the outer procedure Class130.smethod_0(String, Int) is a simple XOR decryptor:
Class130.smethod_0(String, Int): String decryptorinternal sealed class Class130 {

   // Original entry point
   public static string smethod_0(string string_0, int int_0) {
      return Class130.Class131.class131_0.method_0(string_0, int_0);

   private sealed class Class131 {

      private Class131() { }

      // actual deXOR procedure
      public string method_0(string string_0, int int_0) {
         int num = string_0.Length;
         char[] array = string_0.ToCharArray();
         while (--num >= 0) {
            array[num] = (char)((int)array[num] ^ int_0);
         return new string(array);

      public static readonly Class130.Class131 class131_0 = new Class130.Class131();

That entirely covers the string and resource encryption, added by Eazfuscator, at least for this case.

Analysis of bpp.exe

Aside from the stuff Eazfuscator put inside, it's time to get some info about the application itself.

Probably as a part of the Eazfuscator, there are three basic anti debugging protections applied in the static initialization before the Main() procedure.

The first one is process profiling protection, that checks if COR_PROFILER or COR_ENABLE_PROFILING environment variables are present.
Second is a standard CheckRemoteDebuggerPresent() and the third one is checking the System.Diagnostics.Debugger.IsAttached flag.

All these checks are executed under a recursive thread, working as a real time debugging protection of the application.
Pretty basic stuff.

The application is forcefully executed under Administrator's account:
Privilege checkprivate static void Main(string[] args) {
   if (!Class75.smethod_0()) {
      ProcessStartInfo processStartInfo = new ProcessStartInfo(Assembly.GetExecutingAssembly().CodeBase);
      if (args != null) {
         processStartInfo.Arguments = string.Join(" ", args);
      processStartInfo.UseShellExecute = true;
      processStartInfo.Verb = "runas";
      try {
      } catch (Exception) { }
   // continue execution

// Check if current role is in Administrator's group
internal static bool smethod_0() {
   WindowsIdentity current = WindowsIdentity.GetCurrent();
   WindowsPrincipal windowsPrincipal = new WindowsPrincipal(current);
   return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator);
That's not necessarily a bad thing.
Most of the system security applications like anti virus software, require administrative privileges to work correctly.

There's, of course, a copy protection, and aside from the obfuscations applied on the who application is... quite odd.
First, the licensing scheme is based on a serial number that is verified over live server and stored in the registry.
However, once written in the registry, the license data is not verified against this live service, so it can get bypassed easily by a crafted registry key.

A easy to defeat licensing is one thing, but having a hardcoded serial number is another.
Yes, there's one presented here, and using it will activate the application for a month, that with some windows registry-fu, can be extended to infinity.

But, wait there's more!
There's a option where you can obtain a "pre activated" installation, that is a matter of changing a single digit in the configuration XML file.
Yes, you read it right.

This is not a cracking tutorial and although I doubt someone would unironically install and use this "tool" on his system, I'm not going to give more details how to bypass the licensing.

The threat detection system is based on fingerprinting registry keys and file names stored inside the database file mdb.db
This file is a standard System.Data.SQLite database locked with a username and password.
Once I defeated the string encryption, finding the credentials turn out to be pretty easy:
Database connection stubstring connectionString = string.Format(
   "Data Source={0};Version=3;New={1};Compress={2};synchronous=ON; UseUTF16Encoding=True;username={3};password={4}",
   new object[] {
      Path.Combine(path2, path),

The database holds hardcoded strings for various treats like Google Chrome and Firefox extensions, Internet Explorer home URLs, file and folder names, services, installed applications and so on.
However, some of them are quite questionable, as for example blocking folder names like "002", which can generate a lot of false positive alarms.
Another thing is that the database is really outdated. According to the "version" table inside it, the last update date was 11 August, 2015.
And although I let the application running for quite some time (probably a month or something), it didn't update its database for that whole period.
Go figure.

Overall, here's the database's statistics:
tablecountdata description
chrmext1367Google Chrome extensions
chrmfiles10Google Chrome files
ffxext945FireFox extensions
ffxfiles259FireFox files
files717File names
folders2706Folder names
ieurls265IE URLs
ifeo47Image File Execution Options
productids686Product IDs
regkeyvalues2892Windows registry keys
services573Windows services
softwares2492Installed programs
tasks493Scheduled tasks
whitelist39White listed applications
Total entries:20257

It's not much, having in mind it's fingerprinting based on exact matches.

There's a backup system built in that turned out to be based on reg.exe, for registry backup and system restore points for the filesystem.
It's better than nothing, I guess.

There's a large portion of built in functionalities dedicated to downloading additional tools from the same developers.
But more on that you can find in the end of this article.

So, let's do some tests.

I installed and run the application on a fresh system and still got detections:

Check the detail on those shows this:

It's a bit ironic that it finds its own shortcut as something unwanted.
The rest are just leftover folders from windows updater.

The key thing here however is that on the first screen, they are flagged as low severity, while on the detail page they magically became medium.
In reality, their presence is irrelevant to the system's performance.

In case you decide to repair them, and you haven't activated Win Speedup 2018 yet, you'll get redirected to the purchase page, where another well known brand is used to lure the user into buying the program:

Oh yeah, and even Firefox flagged that website as harmful/unwanted...

Appendix A: notable finds

There's few hidden things in the configuration file bpp.exe.config:
bpp.exe.config encrypted data<add key="appname" value="cZORLMNPlRBuyUcjCoQIhJaGwD6UFtJF" />
<add key="support_url" value="5/DIJYaOjB6IuPk8HnbKJIWC/BPOb85PY/RMQeYXvz7iLwkMKiq2ug==" />
<add key="web_url" value="5/DIJYaOjB6IuPk8HnbKJIWC/BPOb85PSpqwU/ZHPJY=" />
<add key="price_url" value="5/DIJYaOjB7R+OQukaLRyvP9SI3x0WnSYUGGH8flJOI+auwzrrpIGMxuGwL1H8fz" />
<add key="renewal_url" value="5/DIJYaOjB7R+OQukaLRyvP9SI3x0WnSYUGGH8flJOLF7L+pxdd5i15yjxTqd/lB" />
<add key="web_eula" value="5/DIJYaOjB6IuPk8HnbKJIWC/BPOb85PQq6Wj36M0DCWGHNbPkMLnw==" />
<add key="web_privacypolicy" value="5/DIJYaOjB6IuPk8HnbKJIWC/BPOb85POwJ8EPl4HXSuAydRo4yO8yLiGUzc8aSV" />
<add key="web_guideline" value="5/DIJYaOjB6IuPk8HnbKJIWC/BPOb85PyMuZWxE8QKem1GkjYHwTWBaG9dDw8jBtHrlG75AL+xI=" />
<add key="web_cancelpolicy" value="5/DIJYaOjB6IuPk8HnbKJIWC/BPOb85PS6hk19iMZnQ2Flu4BSLPvkaoG0YFkeE9" />

Those are all Base64 decoded then DES in CBC mode decrypted, using "ADV!@#SC" as both key and IV:
bpp.exe.config decrypted dataWin ~Speed ~Up2018
Why are those encrypted in the first place, remains a mystery.

Out of curiosity I ran the scanner on the virtual machine I'm working on.
During the reverse engineering process I've installed and used a lot of additional dev tools, so I was curious what's going to happen if I scan a system that was actually used and abused a lot:

It turned out that some of the Visual Studio's registry keys are considered as unwanted, and down the list there was also few registry keys that the Java runtime uses.
So, it seems like using this program might actually be harmful for your system after all.

Something I mentioned in the beginning was the direct download link of the application.
Loading the base address prints the whole bucket of this Amazon based website:<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="">
   <!-- trimmed //-->
Kind of a "whoops" moment, isn't it?
Using the Amazon's API I got the full content of their bucket, which has the whooping number of ~5500 files in it.
These are part of their other products including installers and images related to their advertisements.

In that bucket I also found the installers for "Auto Mechanic 2018", "Auto PC Booster 2018", "Boost PC Pro 2018", "Dr. Clean Pro 2018", "My PC Repair 2018", "Quick Speedup 2018", "Smart PC Care", "Speedy PC Pro 2018", "Win Boost Pro 2018" and "Win PC Repair 2018", that are all just rebranded versions of "Win Speedup 2018".
Along with them was the "Driver Updater" - the application they all shill about.

Something that I find funny was that after installing one by one those apps, every next scan was increasing the threats found counter, flagging files from the previous app.

Additionally, there's reference to two more tools, that seems to be from the same developers - "Advanced Password Manager" and "Malware Crusher".

The detection database, as mentioned before, holds the data that is considered as threat, but there's also a list of exclusion CLSID values that is hardcoded inside the application's resource section.
The list contains about 1200 items, and obviously I can't verify them all, but having them in a hardcoded list is quite odd.

Appendix B: checksums

productfilenameMD5 checksum
Auto Mechanic 2018bpp.exeED8ADDFFE63F7018F148921B4C0C4DC4
Auto Mechanic 2018mdb.db857B2A82A3651F017BF9BE18A7E508F9
Auto PC Booster 2018mysysm.exe7194049A7956BE12FA5C5A857D1790CB
Auto PC Booster 2018mdb.db857B2A82A3651F017BF9BE18A7E508F9
Boost PC Pro 2018bpp.exe02C2CE728E5BEE47E303A6463CF81457
Boost PC Pro 2018mdb.db857B2A82A3651F017BF9BE18A7E508F9
Dr. Clean Pro 2018mysysm.exe121ED304D17981B505503CBEC7640584
Dr. Clean Pro 2018mdb.db857B2A82A3651F017BF9BE18A7E508F9
My PC Repair 2018mpr.exeFF7DBC98BF0F4730A3E40DDD00FD7D9A
My PC Repair 2018mdb.db857B2A82A3651F017BF9BE18A7E508F9
Quick Speedup 2018rclr.exeBA08B80E63880CB61B4CCE712524040D
Quick Speedup 2018mdb.db857B2A82A3651F017BF9BE18A7E508F9
Smart PC Carempr.exe5DF0EF6CC5538A352FD6B24F18BDC6DD
Smart PC Caremdb.db857B2A82A3651F017BF9BE18A7E508F9
Speedy PC Pro 2018bpp.exeB97925FB6C77A0226FCFB3083DF6419F
Speedy PC Pro 2018mdb.db857B2A82A3651F017BF9BE18A7E508F9
Win Boost Pro 2018bpp.exe798558A128D16C326B29C26ABE39F53F
Win Boost Pro 2018mdb.db857B2A82A3651F017BF9BE18A7E508F9
Win PC Repair 2018iytr.exe1A83F31B7EC2343CC94186DF98576B70
Win PC Repair 2018mdb.db857B2A82A3651F017BF9BE18A7E508F9
Win Speedup 2018bpp.exeE044E2BCE03A14AF0429E8A895B7D66B
Win Speedup 2018mdb.db857B2A82A3651F017BF9BE18A7E508F9
Driver Updateraptdu.exeEA8B4EF4500F30AD13FA3E7BDAA172FE


* You have an opinion? Let us all hear it!

Guest 29 Apr, 2024 16:12
20 Things You Should Know About Best Accident Attorneys accident attorney boca (Dario)
Guest 08 Oct, 2018 14:29
I'm seeing this page regularly. It would not bother me more than the other AD pages if it would not beep like a hell.
Thanks for your good work!
Guest 04 Oct, 2018 02:24
great job brother
Allah's blessings to your family
© 2011-2024 | legal | terms & rules | contacts