InfoSec Write-ups

A collection of write-ups from the best hackers in the world on topics ranging from bug bounties…

Follow publication

Backdooring ClickOnce .NET Apps for Initial Access: A Practical Example

This blogpost is about demonstrating the awesome ClickOnce .NET backdooring technique by Nick Powers (@zyn3rgy) and Steven F (@0xthirteen) presented at Defcon30. Here I tried to recreate the technique based on the written version of the presentation.

About

First of all, without unnecessarily repeating the original presentation and blogpost, ClickOnce is an easy deployment and execute technique (even for regular non-admin users) for applications what adversaries can still successfully use for initial access.

The benefit of the backdooring technique developed by the SpecterOps guys is that we do not need to buy an expensive EV code signing certificate anymore; the signature of the backdoored application remains untouched and valid (we’ll only modify the dependencies), and we also maintain Microsoft SmartScreen reputation; if the original app bypasses it, it won’t be triggered after backdooring. The valid signature may decrease the likelihood of EDR alerting also.

Let it be clear: this blogpost does not contain (almost) anything new, here I just recreate the technique by showing a specific example. The original blogpost also includes practical instructions, but here I would like to go into more details.

Searching For a Target App to Backdoor

First, search for a suitable ClickOnce .NET application to backdoor. Using the following Bing query we may find a lot of potential targets:

inbody:"ClickOnce and .NET Framework Resources" AND filetype:html
Bing search for potential victim ClickOnce apps to backdoor

Chose a random one. I am not going to name it because it is not important which one it is (could be anything else, the methodology should work for others also), and it could bring up copyright licensing issues. Note, that the target should fulfill some prerequisites, it is detailed in the original research.

Downloading the ClickOnce Application

Press the “Run” button using MS Edge (if brave enough, I mean let’s hope it is not a backdoored version yet 🙂). The full application should be deployed into a subfolder of this folder:

%LOCALAPPDATA%\Apps\2.0\<random>\<random>\

Also download and inspect the .application file (the file linked to the “Run” button directly). It contains reference to the entry .NET assembly, what we are going to analyze.

Alternatively, here is a custom .application downloader tool from Oddvar Moe (@Oddvarmoe), inspired by the original research.

Finding a Suitable Backdoor Location

Let’s open the entry .NET assembly with the de facto .NET reverse engineering tool, dnSpy (actually dnSpyEx).

Go to the entrypoint of the assembly and let’s try to find a suitable backdooring location. It should be close to the beginning (preferably before opening any windows) and it should be in a non-strongly versioned DLL reference (PublicKeyToken=null) what is also included in the deployment (meaning not a system DLL).

Here I found a reference to this class method get operand (implemented in a non-strongly versioned dependent DLL) near the beginning of the app, this is a good place to inject the backdoor.

Suitable backdooring location

Injecting the Backdoor

Because I had a working C# .NET stager for loading and executing (unmanaged) DLL files in-memory already, and it is performing well against some EDR products currently, decided to reuse it here. (Actually it is using DInvoke PE manual mapper, merged with ILMerge and obfuscated with ConfuserEx 2, but I am not going to cover it here in this blogpost, source code is here in this Tweet.)

Fortunately adding a .NET assembly (the stager) and executing it inside another .NET assembly (the backdoor location) can be done in an elegant way. Simply add the custom .NET assembly as a (private) file resource to the resource list of the DLL (using dnSpy). Make sure to use the same bitness version assembly (x86 targeted stager for x86 backdoor location DLL, x64 targeted stager for x64 backdoor location DLL).

In-memory executing the stager assembly from the resources can be done easy using System.Reflection features. Here is a complete backdooring solution example:

Backdooring code has been injected

The “TotallyLegitResource.res” is the stager EXE assembly added as a file resource. The backdoor creates a stream reference to it, reads it, picks up the entrypoint and executes it. Right before invoking it, a common failure message pops up to the user for avoiding any suspicion (because the backdoor silently runs in the background and the application does nothing visible, let’s tell the user it has been failed 🙂).

After editing the source with dnSpy, it is possible to compile the modified method to CIL bytecode and save the full assembly as the new backdoored DLL module (let’s simply overwrite it in its location). Now it can be tested by executing the application from the disk (click on the main EXE). If everything works as expected, it is ready to build the deployment package.

Building the Backdoored Deployment Package

Place the .application file (the Application Manifest; downloaded eariler from the “Run” link) in the new root application folder (what will be webserved). The .application file should contain the location of the main exe manifest file in the “codebase” attribute of the “dependentAssembly” tag. Create this folder in the application root (usually something like “Application Files/AppName_1_0_0_1234”) and copy all of the files from the deployment folder (used above on the Windows box) to here (preferably a Linux server; for webserving the backdoored ClickOnce application).

Now in the “Application Files/AppName*” folder add .deploy suffix to every file except .manifest and .cdf-ms files:

find -type f -not -name "*.manifest" -and -not -name "*.cdf-ms" \
-exec mv {} {}.deploy \;

Now it is needed to fix some validation digest hashes in order to restore the trust chain provided by the hash values in the manifest files, and it is also needed to remove (the invalid) signatures from the modified manifest files (what is not an operational issue, because unsigned manifests display a very similar warning to signed if downloaded from the internet).

So we have backdoored a DLL. The main EXE references to the DLL, this means the manifest file of the main EXE needs to be updated by replacing the SHA256 digest value of the backdoored DLL (by looking inside the manifest file, we see that SHA256 digests are used).

Here is how to calculate the new digest (of the backdoored XYCommon.dll):

openssl dgst -binary -sha256 \
Application\ Files/AppName_1_0_0_1234/XYCommon.dll.deploy | \
openssl enc -base64

Replace the digest in the appropriate “dsig:DigestValue” element. Also update the “size” attribute in the “dependentAssembly” tag.

If the manifest file was signed, it’ll be invalid because we have made some changes, so the signature needs to be removed. First, delete the “publisherIdentity” and the “Signature” elements at the end. And also zero out the “publicKeyToken” attribute in the “asmv1:assemblyIdentity” tag at the top (where the “name” attribute matches the assembly name the manifest belongs to). Zeroing out means 16 zeroes:

publicKeyToken="0000000000000000"

This ensures the signature won’t be checked and it won’t cause an issue if it is missing.

Now we have changed the main EXE manifest what is referenced by the .application manifest (and contains digest values for the main EXE manifest). This means the .application manifest also needs to be updated.

Recalculate the main EXE manifest SHA256 digest by the same method as above:

openssl dgst -binary -sha256 \
Application\ Files/AppName_1_0_0_1234/AppName.exe.manifest | \
openssl enc -base64

Replace the element “dsig:DigestValue” by this value and also update the EXE manifest file size (“dependentAssembly” tag, “size” attribute).

Because the .application manifest file has been changed, the signature will be invalid (as for the .EXE manifest), so it needs to be removed. Delete the “publisherIdentity” and the “Signature” elements at the end and zero out the “publicKeyToken” attribute for the .application file (“assemblyIdentity” tag at the top level), and also zero out the “publicKeyToken” attribute for the “assemblyIdentity” tag for the “dependentAssembly”.

If you want, rename the .application manifest and change the “assemblyIdentity” tag “name” attribute to the new name, and feel free to update the “description” tag attributes (publisher, suitename, product).

Now the backdoored ClickOnce application is ready to be deployed.

Hosting the Backdoored Application

The hosting and delivery method depends on the scenario, but creating an authentic hosting site for the backdoored application obviously increases the success rate of a compromise.

Have never managed to use ChatGPT for anything useful before, but maybe this is the task it was created for. If you kindly ask it (I mean don’t tell it that it’ll be used as a malicious phishing page), it’ll help.

ChatGPT building the pishing page

Started as in the screenshot, after some rounds of clarification about what I was asking for, here is the result (as a single static HTML page):

To make it more attracting, AI generated images could be added also.

Gameplay

Here is what happens when the victim user clicks the “Launch the Game Now!” link in an MS Edge browser.

Gameplay: Clicking the“Launch” button on the webpage
  1. After clicking the “Launch the Game Now!” button on the webpage in MS Edge, the browser downloads the ClickOnce application manifest and pops up a harmless “Do you want to open the file?” question.
  2. If accepting “Open”, the system downloads the main EXE manifest file, and a warning message pops up about running files downloaded from the internet and also mentioning unverifiable publisher (because we removed signature from the manifest file). There are two options: run or abort.
Gameplay: Warning popup about files downloaded from the internet and unverified publisher

3. Now if the victim user clicks on “Run”, it is game over. 🎉🎉🎉

The full package (including our backdoor payload) gets downloaded and launched. The application EXE launched is signed by the original publisher, it bypasses SmartScreen and may be less suspicious for EDRs.

Our backdoor payload included is the C# .NET stager (see above), which downloads and executes an unmanaged DLL. The DLL served is a Cobalt Strike Beacon built with a custom evasive Artifact Kit (lesson learnt: it needs to have the same bitness as everything else, costed me hours of troubleshooting :) ).

Gameplay Finale: Coblat Strike Beacon session on the target

At the end, the attacker has full control over the victim user session on the target computer through the Cobalt Strike Beacon. The victim has the dumb ERROR popup (it does nothing), just for confirming why the app is not doing anything at all.

Defender real-time protection with cloud was enabled during the full payload detonation process.

Finally, here is the gameplay in a demo video (demonstrating the victim user experience and the attacker control):

Full Movie: Running the Backdoored ClickOnce .NET Application

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Published in InfoSec Write-ups

A collection of write-ups from the best hackers in the world on topics ranging from bug bounties and CTFs to vulnhub machines, hardware challenges and real life encounters. Subscribe to our weekly newsletter for the coolest infosec updates: https://weekly.infosecwriteups.com/

Written by István Tóth

IT Security Expert, Penetration Testing, Red Teaming | OSCP | CRT(E|O) | @RingZer0_CTF 1st (for 2yrs), RCEH | HackTheBox Top10 | RPISEC MBE | Flare-On completer

Responses (2)

Write a response

Could you share the files used please? the chronicles of eldoria

--

OMG. Been a while since I saw a Windows instance in Hungarian. 🙂

--