Mapped every skill to NIST CSF 2.0 subcategory IDs (GV/ID/PR/DE/RS/RC functions)
based on subdomain and content analysis. Restores 11 skills corrupted during
prior rebase, re-enriching with ATLAS, D3FEND, NIST AI RMF, and CSF 2.0 fields.
All 754 skills now carry structured mappings for all 5 security frameworks:
- MITRE ATT&CK (in tags)
- MITRE ATLAS v5.5 (atlas_techniques)
- MITRE D3FEND v1.3 (d3fend_techniques)
- NIST AI RMF 1.0 (nist_ai_rmf)
- NIST CSF 2.0 (nist_csf)
Systematically deobfuscate multi-layer PowerShell malware using AST analysis, dynamic tracing, and tools like PSDecode and PowerDecode to reveal hidden payloads and C2 infrastructure.
cybersecurity
malware-analysis
powershell
deobfuscation
malware-analysis
scripting
obfuscation
ast-analysis
incident-response
T1059.001
T1027
T1140
1.0
mahipal
Apache-2.0
Executable Denylisting
Execution Isolation
File Metadata Consistency Validation
Content Format Conversion
File Content Analysis
DE.AE-02
RS.AN-03
ID.RA-01
DE.CM-01
Deobfuscating PowerShell Obfuscated Malware
Overview
PowerShell is heavily abused by malware authors due to its deep Windows integration and powerful scripting capabilities. Obfuscation techniques include string concatenation, Base64 encoding, character substitution, Invoke-Expression layering, SecureString abuse, environment variable manipulation, and tick-mark insertion. Modern malware uses multiple obfuscation layers requiring iterative deobfuscation. Tools like PSDecode, PowerDecode, and PowerPeeler automate much of this process, while manual AST (Abstract Syntax Tree) analysis handles custom obfuscation. PowerPeeler achieves a 95% deobfuscation correctness rate using instruction-level dynamic analysis of expression-related AST nodes.
When to Use
When performing authorized security testing that involves deobfuscating powershell obfuscated malware
When analyzing malware samples or attack artifacts in a controlled environment
When conducting red team exercises or penetration testing engagements
When building detection capabilities based on offensive technique understanding
Understanding of PowerShell AST and Invoke-Expression patterns
Key Concepts
Common Obfuscation Techniques
PowerShell malware employs layered obfuscation to evade static detection. String concatenation splits commands across variables ($a='In'+'voke'). Base64 encoding wraps entire scripts in -EncodedCommand parameters. Character code arrays use [char] casting ([char[]](73,69,88)|%{$r+=$_}). Environment variable abuse reads substrings from $env: paths. Tick-mark insertion adds backticks between characters that PowerShell ignores (Invoke-Expression`). SecureString conversion encrypts strings using ConvertTo-SecureString with embedded keys.
AST-Based Deobfuscation
PowerShell's Abstract Syntax Tree exposes the parsed structure of scripts regardless of surface-level obfuscation. By walking the AST and evaluating expression nodes, analysts can resolve concatenated strings, decode encoded values, and reconstruct the original commands. PowerPeeler uses this approach at the instruction level, monitoring the execution process to correlate AST nodes with their evaluated results.
Dynamic Execution Tracing
By replacing Invoke-Expression (IEX) with Write-Output, analysts can safely capture the deobfuscated script content that would normally be executed. This technique works across multiple layers by iteratively replacing IEX calls until the final payload is revealed.
Workflow
Step 1: Identify Obfuscation Layers
#!/usr/bin/env python3"""Identify and classify PowerShell obfuscation techniques."""importreimportbase64importsysdefanalyze_obfuscation(script_content):"""Identify obfuscation techniques used in PowerShell script."""techniques=[]# Check for Base64 encoded commandb64_pattern=re.compile(r'-[Ee](?:nc(?:odedcommand)?)\s+([A-Za-z0-9+/=]{20,})',re.IGNORECASE)ifb64_pattern.search(script_content):techniques.append("Base64 EncodedCommand")# Check for FromBase64Stringifre.search(r'\[Convert\]::FromBase64String',script_content,re.IGNORECASE):techniques.append("Base64 FromBase64String")# Check for string concatenationconcat_count=script_content.count("'+'")+script_content.count('"+"')ifconcat_count>3:techniques.append(f"String Concatenation ({concat_count} joins)")# Check for char array constructionifre.search(r'\[char\]\s*\d+',script_content,re.IGNORECASE):techniques.append("Character Code Array")# Check for Invoke-Expression variantsiex_patterns=[r'Invoke-Expression',r'\bIEX\b',r'\.\s*\(\s*\$',r'&\s*\(\s*\$',r'\|\s*IEX',r'\|\s*Invoke-Expression',]forpatterniniex_patterns:ifre.search(pattern,script_content,re.IGNORECASE):techniques.append(f"Invoke-Expression variant: {pattern}")# Check for tick-mark obfuscationtick_count=script_content.count('`')iftick_count>5:techniques.append(f"Tick-mark Insertion ({tick_count} backticks)")# Check for environment variable abuseifre.search(r'\$env:',script_content,re.IGNORECASE):env_refs=re.findall(r'\$env:\w+',script_content,re.IGNORECASE)iflen(env_refs)>2:techniques.append(f"Environment Variable Abuse ({len(env_refs)} refs)")# Check for SecureStringifre.search(r'ConvertTo-SecureString',script_content,re.IGNORECASE):techniques.append("SecureString Encryption")# Check for compressionifre.search(r'IO\.Compression|DeflateStream|GZipStream',script_content,re.IGNORECASE):techniques.append("Compression (Deflate/GZip)")# Check for XOR encodingifre.search(r'-bxor\s+\d+',script_content,re.IGNORECASE):techniques.append("XOR Encoding")# Check for Replace chainreplace_count=len(re.findall(r'\.Replace\(',script_content))ifreplace_count>2:techniques.append(f"Replace Chain ({replace_count} replacements)")returntechniquesdefdecode_base64_command(script_content):"""Extract and decode Base64 encoded commands."""b64_match=re.search(r'-[Ee](?:nc(?:odedcommand)?)\s+([A-Za-z0-9+/=]{20,})',script_content,re.IGNORECASE)ifb64_match:encoded=b64_match.group(1)try:decoded=base64.b64decode(encoded).decode('utf-16-le')returndecodedexceptException:returnNonereturnNonedefremove_tick_marks(script_content):"""Remove PowerShell tick-mark obfuscation."""# Remove backticks that are not escape sequencesescape_chars={'`n','`r','`t','`a','`b','`f','`v','`0','``'}result=[]i=0whilei<len(script_content):ifscript_content[i]=='`'andi+1<len(script_content):pair=script_content[i:i+2]ifpairinescape_chars:result.append(pair)i+=2else:# Skip the backtick, keep the next charresult.append(script_content[i+1])i+=2else:result.append(script_content[i])i+=1return''.join(result)defresolve_string_concat(script_content):"""Resolve simple string concatenation patterns."""# Pattern: 'str1' + 'str2'pattern=re.compile(r"'([^']*)'\s*\+\s*'([^']*)'")whilepattern.search(script_content):script_content=pattern.sub(lambdam:f"'{m.group(1)}{m.group(2)}'",script_content)# Pattern: "str1" + "str2"pattern=re.compile(r'"([^"]*)"\s*\+\s*"([^"]*)"')whilepattern.search(script_content):script_content=pattern.sub(lambdam:f'"{m.group(1)}{m.group(2)}"',script_content)returnscript_contentif__name__=="__main__":iflen(sys.argv)<2:print(f"Usage: {sys.argv[0]} <powershell_script>")sys.exit(1)withopen(sys.argv[1],'r',errors='replace')asf:content=f.read()print("[+] Obfuscation Analysis")print("="*60)techniques=analyze_obfuscation(content)fortintechniques:print(f" - {t}")# Attempt automatic deobfuscationprint("\n[+] Attempting Deobfuscation")print("="*60)# Layer 1: Remove tick marksdeobfuscated=remove_tick_marks(content)# Layer 2: Resolve string concatenationdeobfuscated=resolve_string_concat(deobfuscated)# Layer 3: Decode Base64b64_decoded=decode_base64_command(deobfuscated)ifb64_decoded:print("[+] Base64 decoded content:")print(b64_decoded[:2000])deobfuscated=b64_decodedprint(f"\n[+] Deobfuscated script length: {len(deobfuscated)} chars")output_file=sys.argv[1]+".deobfuscated.ps1"withopen(output_file,'w')asf:f.write(deobfuscated)print(f"[+] Saved to {output_file}")
Step 2: Multi-Layer IEX Replacement
importsubprocessimporttempfileimportosdefiex_replacement_deobfuscate(script_content,max_layers=10):"""Iteratively replace IEX with Write-Output to unwrap layers."""# IEX replacement patternsreplacements=[(r'\bInvoke-Expression\b','Write-Output'),(r'\bIEX\b','Write-Output'),(r'\|\s*IEX\b','| Write-Output'),]current=script_contentlayers=[]forlayer_numinrange(max_layers):# Apply IEX replacementsmodified=currentforpattern,replacementinreplacements:modified=re.sub(pattern,replacement,modified,flags=re.IGNORECASE)ifmodified==currentandlayer_num>0:print(f" [+] No more IEX layers found at layer {layer_num}")break# Write to temp file and execute in constrained PowerShellwithtempfile.NamedTemporaryFile(mode='w',suffix='.ps1',delete=False)astmp:tmp.write(modified)tmp_path=tmp.nametry:result=subprocess.run(['powershell','-NoProfile','-ExecutionPolicy','Bypass','-File',tmp_path],capture_output=True,text=True,timeout=30)output=result.stdout.strip()ifoutputandoutput!=current:print(f" [+] Layer {layer_num+1}: Unwrapped "f"{len(output)} chars")layers.append({"layer":layer_num+1,"technique":"IEX replacement","content_length":len(output),})current=outputelse:breakexceptsubprocess.TimeoutExpired:print(f" [!] Layer {layer_num+1}: Execution timeout")breakfinally:os.unlink(tmp_path)returncurrent,layers
Step 3: Extract IOCs from Deobfuscated Script
defextract_iocs_from_script(deobfuscated_content):"""Extract indicators of compromise from deobfuscated PowerShell."""iocs={"urls":[],"ips":[],"domains":[],"file_paths":[],"registry_keys":[],"commands":[],"base64_blobs":[],}# URLsurl_pattern=re.compile(r'https?://[^\s\'"<>)\]]+',re.IGNORECASE)iocs["urls"]=list(set(url_pattern.findall(deobfuscated_content)))# IP addressesip_pattern=re.compile(r'\b(?:\d{1,3}\.){3}\d{1,3}\b')iocs["ips"]=list(set(ip_pattern.findall(deobfuscated_content)))# File pathspath_pattern=re.compile(r'[A-Za-z]:\\[^\s\'"<>|]+|'r'\\\\[^\s\'"<>|]+|'r'%(?:APPDATA|TEMP|USERPROFILE|PROGRAMFILES)%[^\s\'"<>|]*',re.IGNORECASE)iocs["file_paths"]=list(set(path_pattern.findall(deobfuscated_content)))# Registry keysreg_pattern=re.compile(r'(?:HKLM|HKCU|HKCR|HKU|HKCC)(?:\\[^\s\'"<>|]+)+',re.IGNORECASE)iocs["registry_keys"]=list(set(reg_pattern.findall(deobfuscated_content)))# Suspicious commandssuspicious_cmds=['New-Object Net.WebClient','DownloadString','DownloadFile','DownloadData','Start-Process','Invoke-WebRequest','New-Object IO.MemoryStream','Reflection.Assembly','Add-MpPreference -ExclusionPath','Set-MpPreference -DisableRealtimeMonitoring','New-ScheduledTask','Register-ScheduledTask',]forcmdinsuspicious_cmds:ifcmd.lower()indeobfuscated_content.lower():iocs["commands"].append(cmd)returniocs
Validation Criteria
All obfuscation layers identified and classified correctly
Base64 encoded commands decoded to readable PowerShell
Tick-mark and string concatenation obfuscation resolved
IEX replacement reveals next-stage payloads
URLs, IPs, and file paths extracted from final deobfuscated stage
Deobfuscated script matches observed malware behavior in sandbox