This document consolidates industry best practices, academic research, and practical recommendations for reliable, semantic-preserving obfuscation. Follow these guidelines for optimal results across all script types.
- Always validate — Use
-validatefor critical scripts; treat original as ground truth. - Use context-aware — Enable
-context-awarewhen using IEX, Add-Type, ScriptBlock::Create. - Use module-aware — Enable
-module-awarefor.psm1modules with Export-ModuleMember. - Pin your seed — Use
-seed Nfor reproducible builds and regression testing. - Profile by use case —
safefor compatibility,heavy/redteamfor labs; never skip testing.
| Feature | Flag(s) | Best-practice use |
|---|---|---|
| Validation | -validate, -validate-args, -validate-stderr ignore, -validate-timeout N |
Compare outputs. Use -validate-stderr ignore when PowerShell profile pollutes stderr. |
| Context-aware | -context-aware |
Skips strenc/stringdict for strings in IEX, Add-Type, ScriptBlock::Create. Essential for scripts with dynamic code. |
| Module-aware | -module-aware |
Protects functions in Export-ModuleMember. Mandatory for modules. |
| AST | -use-ast |
Uses native PowerShell AST for context/module parsing (pwsh required; fallback to regex). Best with -context-aware or -module-aware. |
| Profiles | -profile safe|heavy|stealth|paranoid|redteam|blueteam|size|dev |
safe = max compatibility; heavy/stealth/paranoid/redteam for Red Team; size for minimal output; dev for quick iteration. |
| Reproducibility | -seed N |
Deterministic output for regression testing and CI. |
| Report | -report |
Emits obfuscation report (techniques, complexity, sizes). Use to audit transforms. |
| Dry-run | -dry-run |
Analyzes input without obfuscating. Validate script before full run. |
| Docs | -docs, -examples |
Prints golden rules, recommended commands (copy-paste ready), and doc links. |
| Anti-reverse | -anti-reverse |
Injects anti-debug checks. For lab/Red Team only; increases detection surface. |
| Layers | -layers AST,Flow,Encoding,Runtime |
Override pipeline: AST=iden, Flow=cf/dead, Encoding=strenc/stringdict/numenc/fmt, Runtime=frag+level5. |
| Logging | -log <file> |
RFC3339 logs: input, level, seed, output, errors. No impact on output. |
| Levels | -level 1..5 |
1=char-join, 2–3=Base64, 4=GZip, 5=GZip+XOR+fragmentation+noise. |
| Fragmentation | -minfrag, -maxfrag, -frag profile=tight|medium|loose|pro |
Control fragment sizes; pro = stronger level 5. |
| FlowSafeMode | Default on | Skips cf/dead on try-catch/trap. Disable with -flow-unsafe (redteam/paranoid). |
| No-integrity | -no-integrity (default true) |
Disables level 5 hash check; avoids empty output if file is re-saved. |
| Fuzz | -fuzz N |
Generate N variants with different seeds. For detection testing. |
Semantic Preserving Transformations alter syntactic structure while preserving functionality. Core categories:
| Category | Description | ObfusPS equivalent |
|---|---|---|
| Layout | Formatting, whitespace, newlines | -fmt jitter |
| Data | Storage patterns, string encoding, number masking | strenc (xor/rc4), stringdict, numenc |
| Control flow | Logical path restructuring | cf-opaque, cf-shuffle, dead |
| Identifier renaming | Replace names with arbitrary strings | -iden obf (iden transform) |
Critical rule: Incorrect transformations can silently alter functionality. Always validate.
-pipeline iden,strenc,stringdict,numenc,fmt,cf,dead,frag
-strenc xor|rc4 -strkey <hex>
-stringdict 0..100 -numenc
-cf-opaque -cf-shuffle -deadcode 0..100Treat the original script as ground truth. Run both with identical inputs; compare outputs. ObfusPS implements this via -validate.
obfusps -i script.ps1 -o out.ps1 -profile safe -validate
obfusps -i script.ps1 -o out.ps1 -profile safe -validate -validate-args "-Name foo -Count 5"Use -seed N for deterministic output. Re-run after obfuscator changes; diff outputs to detect regressions.
obfusps -i script.ps1 -o out.ps1 -seed 42 -profile safe
# After engine changes:
obfusps -i script.ps1 -o out2.ps1 -seed 42 -profile safe
diff out.ps1 out2.ps1 # or fc on WindowsApply semantics-preserving changes to the original (e.g. add comments, reformat); obfuscate both variants; ensure behavior remains identical. Extends test coverage beyond a single script. (OBsmith, Auto-SPT)
obfusps -i deploy.ps1 -o deploy-obf.ps1 -profile safe -seed 12345 -validate -q
# Exit code 0 = pass; non-zero = fail- PowerPeeler (2024): Dynamic deobfuscation using AST nodes achieves ~95% correctness; AST correlation is key.
- Invoke-Deobfuscation: AST-based, semantics-preserving deobfuscation.
- System.Management.Automation.Language.Parser: Native PowerShell parser; gold standard for semantic accuracy.
ObfusPS supports -use-ast: native PowerShell AST for IEX, Add-Type, ScriptBlock::Create, and Export-ModuleMember. Requires pwsh (or powershell) and scripts/ast-parse.ps1; falls back to regex if unavailable. See ROADMAP.md.
Recommendation: Use -use-ast with -context-aware or -module-aware for maximum accuracy on executable strings and module exports.
Implication: Test complex scripts thoroughly; prefer -profile safe or -context-aware when in doubt.
Never encrypt or tokenize strings that are executed as code. Distinguish:
- Data strings → safe to transform (strenc, stringdict)
- Code strings (IEX, Add-Type, ScriptBlock::Create, reflection) → skip strenc/stringdict
ObfusPS: use -context-aware to protect:
Invoke-Expression,iex[ScriptBlock]::CreateAdd-Type -TypeDefinition,Add-Type -MemberDefinition
[Reflection.Assembly]::Load*Add-Type -AssemblyName- Some
Invoke-Command -ScriptBlockcases - Embedded
powershell -Command "..."
Protect exported API and module boundaries. Renaming exported functions breaks Import-Module consumers.
ObfusPS: use -module-aware to protect Export-ModuleMember -Function A,B,C names.
- Only
Export-ModuleMember -Functionis detected. Import-Module, dot-sourcing, module manifest not yet handled. Plan scripts accordingly.
- Automatic variables:
$args,$_,$MyInvocation,$PSScriptRoot,$PSCommandPath,$null,$true,$false, etc. - Attributes:
[CmdletBinding()],[Parameter()],[ValidateSet()] - Reflection and dynamic types: names used by
GetType(),Add-Type, etc. - Prefix
$__— always excluded from renaming (use for internal state).
| Use case | Profile | Rationale |
|---|---|---|
| Maximum compatibility, identical result | safe |
Encoding only; no renaming; identical behavior to original |
| CI/CD, regression | safe + -validate |
Guaranteed behavioral match |
| Red Team / detection testing | heavy, stealth, paranoid, redteam |
Higher obfuscation; test before deployment; anti-reverse on redteam/paranoid |
| Blue Team / defensive testing | blueteam |
Level 5, no anti-reverse, deterministic |
| Size optimization | size |
Minimal pipeline; level 4 |
| Quick iteration | dev |
Level 2, iden only |
| Light obfuscation | light |
iden, stringdict, numenc, frag; level 1–5 |
Obfuscated scripts run in a scriptblock; $MyInvocation.MyCommand.Path and $PSScriptRoot may be empty. Provide a fallback:
$script:RootPath = try {
$p = $MyInvocation.MyCommand.Path
if ($p) { Split-Path -Parent $p } else { (Get-Location).Path }
} catch { (Get-Location).Path }Use $script:variable instead of local variables for shared state; closures behave correctly in scriptblock context.
- Input: UTF-8 (with or without BOM)
- Output: UTF-8 with BOM + leading newline (preserves characters; newline avoids BOM breaking first token in Code Runner / some hosts)
- Do not re-save obfuscated files in ANSI/UTF-16; Base64 and embedded strings can corrupt
-no-integrity(default true): level 5 skips integrity hash; avoids empty output if file is re-saved in another editor
- Script runs correctly on target PowerShell (5.1 / 7.x)
- Path fallback for
$PSScriptRoot/$MyInvocation.MyCommand.Pathif needed - Closures use
$script:for shared state - No hardcoded paths or environment-specific assumptions (or document them)
- If module:
Export-ModuleMember -Functionpresent; use-module-aware - If IEX/Add-Type/ScriptBlock::Create: use
-context-aware
- Run
-validate(or manual comparison) with same args - Test on PowerShell 5.1 and 7.x if using Unicode/ANSI
- Verify
$PSScriptRoot/ path-dependent logic - Do not re-save in another encoding; keep UTF-8 BOM
| Pitfall | Mitigation |
|---|---|
| strenc breaks IEX/Add-Type strings | Use -context-aware |
| Renamed exported functions break Import-Module | Use -module-aware |
| Empty $PSScriptRoot | Provide path fallback (§8) |
| Closures fail in scriptblock | Use $script:variable |
| Different output after obfuscation | Use -profile safe; validate with -validate |
| Re-saved file produces empty output (level 5) | Keep -no-integrity true; do not re-save |
| Variable interpolation in strings | strenc/stringdict can break "Result:$x"; use -profile safe or avoid strenc on those strings |
- Combine context + module awareness:
-context-aware -module-awarefor complex scripts - Audit before obfuscating:
obfusps -i script.ps1 -dry-run -report - Fuzz variants for detection testing:
obfusps -i payload.ps1 -o out -fuzz 5(generates 5 variants) - Layers for fine control:
-layers Encoding,Runtimefor encoding + level 5 without flow/iden - Log for debugging:
-log obfusps.logto trace seed, level, errors
- Obfuscate with
-profile safefor maximum compatibility - Validate with
-validateand-validate-argsif the script accepts parameters - Pin seed with
-seed Nfor reproducible builds - Test on PowerShell 5.1 and 7.x if using Unicode or ANSI
- Document any known limitations for your script (modules, classes, etc.)
obfusps -i deploy.ps1 -o deploy-obf.ps1 -profile safe -seed 12345 -validateobfusps -i MyModule.psm1 -o MyModule-obf.psm1 -profile balanced -module-awareobfusps -i tool.ps1 -o tool-obf.ps1 -profile heavy -context-aware -use-astobfusps -i Tool.ps1 -o Tool-obf.ps1 -profile heavy -context-aware -module-aware -use-ast -validateobfusps -i payload.ps1 -o payload-obf.ps1 -level 5 -profile redteam -anti-reverse -reportobfusps -i script.ps1 -dry-run -reportobfusps -i script.ps1 -o out.ps1 -profile size -level 4obfusps -i payload.ps1 -o variant -fuzz 5
# Produces variant-0.ps1 .. variant-4.ps1- PowerPeeler: Precise and General Dynamic Deobfuscation for PowerShell (2024)
- Invoke-Deobfuscation: AST-Based Semantics-Preserving Deobfuscation (IEEE)
- OBsmith: LLM-Powered Obfuscator Testing (differential and metamorphic validation)
- Auto-SPT: Semantic Preserving Transformations for Code
- System.Management.Automation.Language.Parser (Microsoft)
- DOCUMENTATION.md — techniques, reserved vars, FlowSafeMode
- ROADMAP.md — AST, module-aware, context-aware roadmap