Brazilian CaminhoLoader uses steganography and UAC bypass to deliver Remcos RAT

05/03/2026 · ~12 min read
RATremcosloaderpowershelljavascriptc#dnspymalware analysissteganographyobfuscateduac bypassprocess hollowingrunpe
Sponsored By Hudson Rock

Hudson Rock offers a free cybercrime (including infostealers) intelligence. Thanks to their support I can spend more time creating valuable content.

Stage overview

In this analysis we will be going over several stages of obfuscating, loading and deploying the primary payload. The attack chain for today is:

  1. Initial delivery - Via spear-phishing emails containing archived JavaScript/VBScript files (the file name here was Productos listados.js, in english Listed products)
  2. Stage 1 - Obfuscated JavaScript file copies itself to startup and loads a Base64 encoded PowerShell command via WMI
  3. Stage 2 - Obfuscated PowerShell downloads an image from remote URL, extracts the payload from the steganographic image and the first DLL (CaminhoLoader) is executed in memory with several arguments including the second image URL and the hollowed process name
  4. Stage 3 - Obfuscated C# CaminhoLoader performs anti-analysis checks, disables UAC via cmstp.exe UAC bypass, abuses an open-source embedded Task Scheduler library for persistence, ultimately extracts the payload from a second steganographic image, where the URL was passed as an argument and injects final stage payload into appidtel.exe via Process Hollowing
  5. Stage 4 - Remcos RAT running purely in memory

We will be extracting each payload, explaining the deobfuscation process, it's functionality and purpose.

Stage 1 - Obfuscated JavaScript

JavaScript

Here we have the first stage of obfuscated JavaScript. This string:

this.hydrocinnamide += "ଢᾳᕇ≣⁦␇Οᛂѣ⒂⏷⧋ฃန♂";

repeats itself all over again with no specific meaning. Let's replace it with empty line:

JavaScript

And we can't forget to remove the empty lines:

JavaScript

Now we can finally see the real script.

JavaScript JavaScript

I noticed that these symbols in thehydrocinnamide variable do not serve any special purpose, so I removed them:

JavaScript

We can now see a PowerShell command! It decodes from napperty and executes via WMI.

JavaScript

I decided to comment out the actual WMI execution part and instead, I threw in a:

WScript.Echo(hydrocinnamide);

to see the full executed PowerShell command.

JavaScript

And once executed in command line:

JavaScript

Let's copy the string in a new NP++ tab and extract the Base64 string from that:

JavaScript

Stage 2 - Obfuscated PowerShell

I got this PowerShell script:

PowerShell

Let's add new lines to it by replacing ; with \n everywhere with the extended search mode:

PowerShell

And then replace the '+' with nothing:

PowerShell

This looks way better.

PowerShell

Now, look at jr1. The string Q2zC:jr1Usersjr1Publicjr1Downloadsjr1Q2z suggests that the jr1 could be the \ character. With this, we can also figure out that the Q2z should be ' character as it is used in PowerShell as a quote.

I went through the rest of the deobfuscation which consisted of renaming the functions, glueing them together and understanding them.

PowerShell

The step by step functionality of this PowerShell script is:

  1. Downloads optimized_MSI.png from C2
  2. Converts raw bytes to UTF-8 string
  3. Locates embedded payload between markers IN- and -in1
  4. Extracts substring between markers
  5. Replaces # with A, reverses the string character by character
  6. Base64 decodes the result into raw bytes
  7. Reflectively loads the bytes as a .NET assembly (completely fileless, no disk write)
  8. Resolves ClassLibrary1.Class1.Main() via Reflection
  9. Invokes Main() with several arguments, including the URL to GeneratedPayload.png - the second steganographic image, hollow process name and output path

It's time now to download the optimized_MSI.png from the first URL and extract it manually. You can, of course, patch the PowerShell script and instead of reflectively loading it via .NET assembly you can save it to disk, which will have the same effect but for the sake of showing the steganography in this analysis we will do it through NP++ and CyberChef.

I would just like to note that 2 AV's already identified the encoded PE in the image which seems pretty interesting to me.

VT1

Steganography

I searched for the IN- string and here you can see that it is starting with ==, which is pretty common for reversed Base64. I also searched the -in1 string:

Steganography

So the goal here is to copy everything in between the IN- and -in1 strings:

Steganography

Just like the PowerShell script did, we started with:

  1. Reverse
  2. Replace # with A
  3. From Base64

and we get the MZ header and the infamous This program cannot be run in DOS mode. that tells us that we are dealing with an executable file.

Stage 3 - CaminhoLoader DLL

Let's download it and inspect it in DetectItEasy!

DIE

We have a .NET Reactor protected DLL. Good thing for us, we can use .NET Reactor Slayer to get rid of it:

.NET Reactor Slayer

Now we have an unprotected version created.

Let's open it in dnSpy!

dnSpy

The HackForums.gigajew class is a reference to user gigajew on HackForums that created the RunPE/Process Hollowing method that the HackForums.gigajew class contains.

The user is quite active on HackForums and makes various HackTools for malware purposes.

dnSpy

Here we have the Main function in Class1. This is the method that is responsible for handling the data passed from the PowerShell script:

try {
    $type = $LoadedAssembly.GetType('ClassLibrary1.Class1')
    $Class1Main = $type.GetMethod('Main')
    $Class1Main.Invoke($null, [object[]]$GeneratedPayloadData)
} catch { }

dnSpy

The DLL also uses a legitimate, open-source .NET wrapper for Task Scheduler available at https://github.com/dahall/TaskScheduler.

You may ask why? Using schtasks.exe is way easier but it is also way noisier for AV software.

dnSpy

Here we have the VM check function. Note that the EXE wasn't fully deobfuscated; it still contains partial obfuscation but for our purposes this is fine.

The 52:54:00:4A:04:AF is a popular mac address for QEMU VM.

dnSpy

Here we have the creation of a scheduled task. We can see brazilian PuTTY description Baixar e executar o PuTTY a cada 1 minuto indefinidamente, which in English means Download and run PuTTY every minute indefinitely.

It uses an interesting method - conhost.exe with the --headless argument. Attackers use this to hide the initial console.

The process here is that the CaminhoLoader starts a hidden (headless) conhost.exe to execute arbitrary command-line commands, which in this case is another hidden PowerShell command that downloads & starts a file.

dnSpy

Here we have some messages it returns when it detects a virtual machine.

dnSpy

Here is the RunPE/Process hollowing function itself. Process hollowing is a malware injection technique where the malicious code is executed within a legitimate process, which in this case is appidtel.exe which was passed previously as an argument by the PowerShell script.

namespace  HackForums.gigajew  
{  
// Token: 0x02000022 RID: 34  
public  static  class  x64  
{  
// Token: 0x060000B1 RID: 177  
[DllImport("kernel32.dll")]  
private  static  extern  bool  CreateProcess(string  string_0,  string  string_1,  IntPtr  intptr_0,  IntPtr  intptr_1,  bool  bool_0,  uint  uint_0,  IntPtr  intptr_2,  string  string_2,  byte[]  byte_0,  byte[]  byte_1);

// Token: 0x060000B2 RID: 178  
[DllImport("kernel32.dll")]  
private  static  extern  long  VirtualAllocEx(long  long_0,  long  long_1,  long  long_2,  uint  uint_0,  uint  uint_1);

// Token: 0x060000B3 RID: 179  
[DllImport("kernel32.dll")]  
private  static  extern  long  WriteProcessMemory(long  long_0,  long  long_1,  byte[]  byte_0,  int  int_0,  long  long_2);

// Token: 0x060000B4 RID: 180  
[DllImport("ntdll.dll")]  
private  static  extern  uint  ZwUnmapViewOfSection(long  long_0,  long  long_1);

// Token: 0x060000B5 RID: 181  
[DllImport("kernel32.dll")]  
private  static  extern  bool  SetThreadContext(long  long_0,  IntPtr  intptr_0);

// Token: 0x060000B6 RID: 182  
[DllImport("kernel32.dll")]  
private  static  extern  bool  GetThreadContext(long  long_0,  IntPtr  intptr_0);

// Token: 0x060000B7 RID: 183  
[DllImport("kernel32.dll")]  
private  static  extern  uint  ResumeThread(long  long_0);

// Token: 0x060000B8 RID: 184  
[DllImport("kernel32.dll")]  
private  static  extern  bool  CloseHandle(long  long_0);

On the start of the function, we can see the declaration of all the API's that are needed to perform RunPE. Alone the usage of these API's is highly suspicious and very typical for RunPE or other process injection techniques.

In this case, the process is:

  1. Create appidtel.exe in a suspended state
  2. Unmap it's memory
  3. Replace it with Remcos payload

An important thing to note is that this is a process injection, not file injection. All this happens only in the current processes memory and once the process exits, the malicious code is gone unless the malware re-injects it once again.

Unlike file infectors, this technique only patches the process memory - the legitimate appidtel.exe binary on disk remains unmodified. If you ran a VirusTotal scan on the appidtel.exe, it would show you an undetected, signed, legitimate Windows file, which is correct.

dnSpy

We are now encountering the most interesting part of this payload (at least for me). The UAC bypass using cmstp.exe.

cmstp.exe (Connection Manager Profile Installer) is a legitimate Windows binary located at C:\Windows\System32\cmstp.exe. It is present on all Windows versions and is *auto-elevate whitelisted* - meaning Windows will automatically elevate it to high integrity without showing a UAC prompt, because Microsoft trusts it as a system binary.

cmstp.exe accepts .inf configuration files as input. These files can define commands to be executed during installation via the RunPreSetupCommands section and once executed, it will prompt for a message box that the user needs to confirm.

I put the PowerShell script in NP++, let's see it:

UAC bypass

The ExecutionArguments contains the command that will be executed, which in this case is a way to disable all UAC prompts via registry.

UAC bypass

This is the C# part that is responsible for creating and modifying the initial template it defined. See this part:

[PreSetupCommandsSection]
LINE
taskkill /IM cmstp.exe /F

The LINE will be initially replaced here:

updatedInf.Replace(\"LINE\", command);

The command contains determination whether PowerShell or CMD was used in the TargetExecutable defined on line 4 and depending on that it creates the full command consisting of the interpreter (in this case PowerShell) and the command, which is ExecutionArguments on line 7.

So in this case, the full command will be:

powershell -c "reg.exe ADD HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System /v EnableLUA /t REG_DWORD /d 0 /f"

The LaunchElevation function is responsible for automatically confirming the window that pops up once you trigger the cmstp.exe.

For this, keep in mind these 2 lines in the INF file:

[Strings]
ServiceName=\"CorpVPN\"
ShortSvcName=\"CorpVPN\"

Let's take a look at the confirming functionality, here we define values for enter and alt:

    const int WM_SYSKEYDOWN = 0x0100;
    const int VK_ENTER = 0x0D;

This constant naming isn't entirely correct. The WM_SYSKEYDOWN isn't supposed to be WM_SYSKEYDOWN but actually WM_KEYDOWN, so it is a wrongly called constant. Correct WM_SYSKEYDOWN is supposed to be 0x0104, not 0x0100.

Here we generate and set the path of the INF file using the template:

string infPath = GenerateInfFile(command, infContent);

ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = CmstpExecutablePath;
psi.Arguments = \"/au \\\"\" + infPath + \"\\\"\";
psi.UseShellExecute = true;
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.CreateNoWindow = true;

try
{
    Process.Start(psi);
}
catch (Exception ex)
{
    Console.WriteLine(\"Erro ao iniciar cmstp: \" + ex.Message);
    return false;
}

Here we have the process starting part for cmstp.exe. It uses the /au switch, which installs it for all users and auto-elevates it, therefore it is crucial for this UAC bypass.

If the /au switch wasn't used, it would fail due to not having sufficient permissions.

Thread.Sleep(3000);
IntPtr windowHandle = FindWindow(null, \"CorpVPN\");
PostMessage(windowHandle, WM_SYSKEYDOWN, VK_ENTER, 0);

Thread.Sleep(5000);
try { File.Delete(infPath); } catch {}

return true;

The FindWindow part searches for the CorpVPN window title, and if found, it sends the Enter key to the window, therefore automatically confirming it.

This is just the error handling part and invoking the whole program.

UAC bypass

On an infected device, this is how the full .inf file looks like:

UAC bypass

And if you try to execute the cmstp.exe to load the malicious .inf, it shows you this window:

UAC bypass

But as we just discovered, the PowerShell script is able to automatically confirm the popup by looking for the window and sending the Enter key. That being said, this is a fully automatic UAC bypass.

I recorded a short video of how using a similarly crafted malicious .INF file I was able to escalate my privileges from a non-elevated command line to an elevated one:

https://rifteyy.org/images/remcosmultistage/cmstp_uac_bypass.mp4


Let's get back to the CaminhoLoaders functions! Here we have the function to find the encrypted code in the image. Similarly like with the previous image, this one uses the INICIO and FIM strings and once again selects everything between them.

dnSpy

But instead of replacing the # with A , it replaces it ### with A as seen here:

dnSpy

And finally, here is the execution method:

dnSpy

We determine the architecture of the decrypted PE, so in this case we've had Remcos that is 32-bit so it is using the folder %windir%\SysWOW64. If the malware it is supposed to load 64-bit, it would use the %windir%\System32 version.

VT2

The second image also has 1 detection for encoded PE. I went ahead and decoded the payload from the second image:

CyberChef

See that the only difference between the first decryption method is that we are replacing ### with A, not just # with A.

Stage 4 - Remcos RAT

I downloaded the file and opened it in DiE:

CyberChef

Seems like a relatively low file size and no protection.

I uploaded the file to VirusTotal and it has been already identified as Remcos RAT:

VT1

Remcos RAT is the final stage of this malware.

We can confirm this by AnyRun identifying it's Mutex & configuration:

Anyrun1

and by inspecting it's defined strings in Ghidra:

Ghidra1

IoC

Files:

Filename SHA256 Hash Description VirusTotal Link
Productos listados.js 377b8acd9b7402d77002d021962bf8ab0ccb668a423df97212bad3d717b0193e Stage 1, initial delivered obfuscated JS VT Link
[memory-based payload] 797c5e01328a5f1ee028835dcf71df4c6f13429e9dafff76030287399a87aa3f Stage 2, PowerShell steganographic loader of CaminhoLoader VT Link
optimized_MSI.png 40bd37eba7f9a56516c96092d5c6d50937fc4df00baf79155ada9d1673389830 Steganographic image with encrypted CaminhoLoader VT Link
[memory-based payload] 96d4e77c0d433b14c2030be194ad12e159b5292f33da3a7d4d2749475845c253 Stage 3 CaminhoLoader VT Link
GeneratedPayload.png b751ca693dcf93e299989d1020b1761c37229fb660eb26e544c14fa75cb31eb5 Steganographic image with encrypted Remcos RAT VT Link
[memory-based payload] a72fd420e246298d35068ceea173475a86ccc1b7fbca15e270fc4178cb1a4d37 Stage 4 Remcos RAT VT Link

URL's:

URL Description
jerrymac2008.duckdns.org C2
https://bafybeihamvbzrm2tsifa4s7xruhfnsgnkzgtk2jqwj6cwgmdxj4wqe5lm4.ipfs.dweb.link/?filename=optimized_MSI.png CaminhoLoader hidden in steganographic image
https://bafybeiedkdwsp77zcvi6477lovtfde7rwsjdz7654kdnrgmciqg5mfhwh4.ipfs.dweb.link/?filename=GeneratedPayload.png Remcos hidden in steganographic image

Thanks for reading my analysis!

Want to learn more?