MSIX and AppX as malware delivery
MSIX was sold as the modern, signed, sandboxed successor to MSI. From an offensive perspective it is a gift: a signed installer format with a documented script hook, a protocol handler that historically bypassed Mark-of-the-Web, and a user-mode install path that AppLocker administrators frequently forget to constrain. The format is not broken, but the trust assumptions around it are.
Why MSIX shows up in payloads
Three properties make MSIX attractive for delivery:
- The package is an Authenticode-signed
.msix/.appx(really a ZIP withAppxManifest.xml,AppxBlockMap.xml, and a signature). Signed installers earn SmartScreen reputation faster than raw PE droppers. ms-appinstaller:is a registered protocol handler. A single click on anhttps://link can hand the URI toAppInstaller.exe, which fetches and installs the package without the user ever seeing the file in Downloads. MotW never lands on disk because the appinstaller flow does not treat the package as a downloaded executable.- Per-user install means no admin prompt. AppLocker default rules allow signed packaged apps for everyone, and Publisher rules are commonly wide open (
*in publisher field).
See modern-phishing-payload-formats-post-vba for where this slots into the broader post-macro landscape, and applocker-bypass-techniques for the policy gaps that make packaged apps a soft target.
The CVE-2021-43890 era and what changed
The original Emotet/BazarLoader campaigns abused ms-appinstaller to install spoofed packages whose DisplayName and publisher rendered as Adobe, Zoom, or TeamViewer. Microsoft patched the spoofing in the App Installer UI and eventually disabled the protocol handler by default in 2023 after the Storm-0569 / Storm-1113 abuse wave. It came back, partially, when admins re-enabled it via EnableMSAppInstallerProtocol for LOB deployments. Treat the protocol handler as “off by default, on in the enterprises you actually care about.”
Post-patch tradecraft moved to:
- Hosting
.msixdirectly behind an HTTPS link and relying on the user to double-click. SmartScreen still triggers, but a freshly signed EV cert with a few weeks of reputation usually clears it. ms-appinstalleragainst.appinstallerXML files that point at an external.msix, used where the registry value is still enabled.- Signed packages from compromised software vendors, where reputation is inherited.
Install-time code execution
MSIX does not give you arbitrary RunOnce-style scripts, but it gives you enough. The PackagedCOM and windows.startupTask extensions register code that runs on first launch. The psf (Package Support Framework) wrapper, which Microsoft itself recommends for legacy apps, lets you inject a DLL into the packaged process. That DLL is your loader.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<Package xmlns="https://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:rescap="https://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:uap="https://schemas.microsoft.com/appx/manifest/uap/windows10">
<Identity Name="Contoso.HelpdeskAgent"
Publisher="CN=Contoso IT, O=Contoso, C=US"
Version="1.0.4.0" ProcessorArchitecture="x64"/>
<Properties>
<DisplayName>Contoso Helpdesk Agent</DisplayName>
<PublisherDisplayName>Contoso IT</PublisherDisplayName>
<Logo>Assets\logo.png</Logo>
</Properties>
<Applications>
<Application Id="App" Executable="PsfLauncher32.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements DisplayName="Helpdesk" Description="IT" BackgroundColor="transparent"
Square150x150Logo="Assets\150.png" Square44x44Logo="Assets\44.png"/>
<Extensions>
<uap:Extension Category="windows.startupTask">
<uap:StartupTask TaskId="HelpdeskBoot" Enabled="true" DisplayName="Helpdesk"/>
</uap:Extension>
</Extensions>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust"/>
</Capabilities>
</Package>
runFullTrust plus PsfLauncher32.exe means the packaged process is not sandboxed in any meaningful way. config.json next to PsfLauncher controls which DLL gets injected before the real EXE runs.
Packaging transcript
1
2
3
4
5
6
7
8
9
10
PS> makeappx pack /d .\src /p Helpdesk.msix /nv
MakeAppx : packing 38 files...
PS> signtool sign /fd SHA256 /a /n "Contoso IT" /tr https://timestamp.digicert.com Helpdesk.msix
Done Adding Additional Store
Successfully signed: Helpdesk.msix
PS> Add-AppxPackage .\Helpdesk.msix
PS> Get-AppxPackage Contoso.HelpdeskAgent | select PackageFamilyName
PackageFamilyName
-----------------
Contoso.HelpdeskAgent_8wekyb3d8bbwe
The Package Family Name suffix is derived from the publisher hash. Reusing a familiar DisplayName while owning the signing key is the package-family-name impersonation trick: the user sees “Microsoft Teams” in the install prompt, but the PFN is Contoso.HelpdeskAgent_<hash>.
Content URI rules and the SmartScreen story
uap:ApplicationContentUriRules whitelist domains the packaged app can navigate WebView to without losing trust. Operators abuse this to point an embedded WebView at attacker infrastructure that inherits the package identity for cookie and storage access. Combined with aitm-evilginx-modern-phishing this gets you session tokens with no extra prompts.
SmartScreen Application Reputation evaluates the signing cert and the package hash, not the manifest. A fresh OV cert is reputation zero; an EV cert plus a few hundred successful installs across distinct machines flips it. Burn rate on certs is the real cost of this technique.
Detection and defender posture
Useful telemetry: Microsoft-Windows-AppXDeploymentServer/Operational, AppXDeployment-Server 854/855 events, and AppInstaller ETW. Hunt for Add-AppxPackage from non-IT contexts, ms-appinstaller URIs in browser history, and PsfLauncher parents spawning rundll32 or powershell. For control, set EnableMSAppInstallerProtocol=0 via policy and tighten AppLocker Packaged App rules to specific publishers, not *.
Related reading: living-off-the-land, dll-side-loading, client-side-attacks-primer, phishing-infrastructure-design.