Metadata
Import PowerShell module
YAML
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
payload | string | no | — | Name of the payload in build.payloads to inject. |
technique: import_powermoduleShips a .ps1 tool as a managed PowerShell binary module whose Import-Module injects its functions into the operator’s session — the script is never on disk in the clear.
What it does
Section titled “What it does”An Execution technique for a build.output.kind: psmodule build. The
artifact is a managed .NET assembly (not the native Rust stub): Import-Module .\x.dll loads it and its IModuleAssemblyInitializer.OnImport runs in the
caller’s powershell.exe, decrypts the embedded .ps1 in memory and injects
it into the importing runspace, so the tool’s functions appear in the operator’s
session (Get-DomainUser, Invoke-AllChecks, …). It accepts only
format: powershell payloads.
How it works
Section titled “How it works”flowchart TD
A["build: read each .ps1"] --> B["encrypt (XChaCha20-Poly1305)"]
B --> C["render a C# IModuleAssemblyInitializer"]
C --> D["csc -> managed module (x.dll)"]
D --> E["operator: Import-Module .\\x.dll"]
E --> F["OnImport runs in powershell.exe"]
G{"patch_amsi step?"} --> H["patch AMSI in the host (native + managed)"]
F --> G
H --> I["decrypt each script in memory"]
I --> J["PowerShell.Create(CurrentRunspace)"]
J --> K["AddScript(text).Invoke()"]
K --> L["functions now in the caller's session"]
1. Build time
Section titled “1. Build time”- Each
import_powermodulepayload (a.ps1) is read, encrypted with XChaCha20-Poly1305 and its ciphertext/key/nonce baked into a generated C# module as hex constants. - The module implements
IModuleAssemblyInitializer; if the runtime declarespatch_amsi, itsOnImportfirst patches the host’s AMSI: the nativeAmsiScanBufferplus the managedAmsiUtils.amsiInitFailedflag (all the names are XOR-scrambled in the assembly). csccompiles the module to a managed.dll, which is signed and shipped.
2. Run time
Section titled “2. Run time”Import-Moduleloads the module and PowerShell callsOnImport().- It decrypts each script (the packer’s XChaCha20, ported to C# — .NET has no
XChaCha20) and injects it with
PowerShell.Create(RunspaceMode.CurrentRunspace).AddScript(text).Invoke(), so the functions land in the importing runspace. - The plaintext buffers are cleared after each injection.
Integration
Section titled “Integration”A kind: psmodule build’s runtime is restricted: only patch_amsi (the
host’s AMSI patch) and import_powermodule (one per script) are accepted;
everything else is rejected, because a module runs inside the operator’s
powershell.exe, not a loader process of its own. The order in runtime: is the
injection order (Plan C: several tools in one import).
What it evades
Section titled “What it evades”- No script on disk in the clear. The
.ps1is ciphertext inside the module; the dll is on disk, but the script is not. - Application whitelisting: only
powershell.exeruns; the tool arrives as a module it imports, and the plaintext signatures of the tool are never on disk.
What detects it
Section titled “What detects it”- The module file itself. Unlike
powershell_hosting, the.dllis on disk, so its metadata (the AMSI strings are scrambled, but the .NET type names andSystem.Management.Automationare necessarily present) is scannable. A lab signature over the module is the first report. Import-Moduleof an unsigned/unknown module:output.sign(an unsigned module beside a signed host is loud) is the mitigation.- AMSI / script block logging in the host: the
patch_amsistep is what keeps the injected script from being flagged. It patches the native scan exports and sets the managedAmsiUtils.amsiInitFailedflag; with invocation logging on, ETW still sees the blocks.
Measurements
Section titled “Measurements”- See
docs/measurements.md.
Implementation notes
Section titled “Implementation notes”- Built by
engine::managed::build_module; it reuses the packer’s XChaCha20 (ported to C#) and thecsc+System.Management.Automationdiscovery offormat: powershell. - The Rust
stub_fragment.rsis a placeholder: this technique is rendered in C#, and the fragment exists only so the registry entry and the compile guardrail are well-formed.plan::validaterejects the technique outside a psmodule build. - A
patch_amsistep emits two AMSI bypasses into the module: the nativeAmsiScanBufferpatch and the managedAmsiUtils.amsiInitFailedflag (both reflection/lookup names are scrambled, andamsi.dll/AmsiScanBufferwith them). resources(icon/VERSIONINFO/manifest) are not embedded into a managed module yet.
References
Section titled “References”IModuleAssemblyInitializerand the PowerShell module API — Microsoft Learn.RunspaceMode.CurrentRunspace— injecting into the importing runspace.