Access is denied from WinAPI OpenProcess

0

So I decided I want to inject a DLL into a notepad process for educational purposes.

IntPtr hProcess = OpenProcess(ProcessAccessFlags.VirtualMemoryWrite | ProcessAccessFlags.VirtualMemoryWrite | ProcessAccessFlags.VirtualMemoryOperation, false, process.Id);           
_log.Info({0}, new Win32Exception(Marshal.GetLastWin32Error()).Message);

This seems to fail with "Access is denied". The strange thing is that hProcess is not IntPtr.Zero and it looks like a handle. Therefore I 'm not 100% sure whether it really failed or not.

Things I tried are listed as follows.

  • starting Visual Studio 2017 as Administrator
  • using Process.EnterDebugMode();
  • other access flags, including 0x001F0FFF

The whole class is down below (not on paste bin anymore).

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Threading.Tasks;

namespace Blade.Injector
{
class Injector
{
    private readonly NLog.Logger _log = NLog.LogManager.GetCurrentClassLogger();

    [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
    static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);

    [Flags]
    public enum AllocationType
    {
        Commit = 0x1000,
        Reserve = 0x2000,
        Decommit = 0x4000,
        Release = 0x8000,
        Reset = 0x80000,
        Physical = 0x400000,
        TopDown = 0x100000,
        WriteWatch = 0x200000,
        LargePages = 0x20000000
    }

    [Flags]
    public enum MemoryProtection
    {
        Execute = 0x10,
        ExecuteRead = 0x20,
        ExecuteReadWrite = 0x40,
        ExecuteWriteCopy = 0x80,
        NoAccess = 0x01,
        ReadOnly = 0x02,
        ReadWrite = 0x04,
        WriteCopy = 0x08,
        GuardModifierflag = 0x100,
        NoCacheModifierflag = 0x200,
        WriteCombineModifierflag = 0x400
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
    [SuppressUnmanagedCodeSecurity]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CloseHandle(IntPtr hObject);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, int processId);

    [Flags]
    public enum ProcessAccessFlags : uint
    {
        All = 0x001F0FFF,
        Terminate = 0x00000001,
        CreateThread = 0x00000002,
        VirtualMemoryOperation = 0x00000008,
        VirtualMemoryRead = 0x00000010,
        VirtualMemoryWrite = 0x00000020,
        DuplicateHandle = 0x00000040,
        CreateProcess = 0x000000080,
        SetQuota = 0x00000100,
        SetInformation = 0x00000200,
        QueryInformation = 0x00000400,
        QueryLimitedInformation = 0x00001000,
        Synchronize = 0x00100000
    }        

    private const string DllFile = @"C:\Poison.dll";

    public void List()
    {
        var processes = Process.GetProcesses().Where(p => !string.IsNullOrEmpty(p.MainWindowTitle)).ToList();

        foreach (var process in processes)
        {
            _log.Info("{0} -> {1}", process.MainWindowTitle, process.Id);
        }
    }

    public bool Inject(int pid)
    {                                       
        Process.EnterDebugMode();

        Process process = Process.GetProcessById(pid);                        

        foreach (ProcessModule module in process.Modules)
        {
            if (module.FileName.Equals(DllFile))
            {
                _log.Info("{0} already inside process.", module.FileName);
                return false;
            }
        }

        _log.Info("Opening process. Last Win32 error is now '{0}'.", new Win32Exception(Marshal.GetLastWin32Error()).Message);
        IntPtr hProcess = OpenProcess(ProcessAccessFlags.VirtualMemoryWrite | ProcessAccessFlags.VirtualMemoryWrite | ProcessAccessFlags.VirtualMemoryOperation, false, process.Id);           
        _log.Info("Opening process resulted in message '{0}'.", new Win32Exception(Marshal.GetLastWin32Error()).Message);

        _log.Info("Allocating memory for dll file.");            
        IntPtr allocAddress = VirtualAllocEx(hProcess, IntPtr.Zero, (uint)Encoding.Unicode.GetByteCount(DllFile.ToCharArray()), AllocationType.Commit, MemoryProtection.ReadWrite);
        _log.Info("Allocating memory for '{0}' resulted in '{1}'.", DllFile, new Win32Exception(Marshal.GetLastWin32Error()).Message);

        CloseHandle(hProcess);

        foreach (ProcessModule module in process.Modules)
        {
            if (module.FileName.Equals(DllFile))
            {
                _log.Info("Success! {0} inside process.", module.FileName);
            }
        }

        return true;
    }

}
}
  • How do I make sure that the message in new Win32Exception(Marshal.GetLastWin32Error()).Message) has something to do with my last call? Maybe something else failed?
  • How do I open the process without having access denied messages?
  • How do I know whether the OpenProcess() method worked in C#? Is checking hProcess for not being IntPtr.Zero OK?
c#
winapi
pinvoke
asked on Stack Overflow Aug 13, 2018 by Tony Stark • edited Aug 14, 2018 by Tony Stark

1 Answer

2

The first place to find an answer to

How do I know whether (any function) worked?

is going to be in the documentation page for that function. For OpenProcess(), the documentation page is https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-openprocess

Among other things, that page contains the following statements:

If the function succeeds, the return value is an open handle to the specified process.

If the function fails, the return value is NULL. To get extended error information, call GetLastError.

Applying basic logic to the second statement, we conclude that

  • If the return value is not NULL, the function did not fail.

Therefore, comparing the return value to IntPtr.Zero is OK and actually the best thing to do.

Also note that "To get extended error information, call GetLastError." is subordinate to "If the function fails". If the function succeeded, there's no detailed error information, so you have no reason to call GetLastError and no way to interpret its result if you do call it.

answered on Stack Overflow Aug 13, 2018 by Ben Voigt • edited Jun 20, 2020 by Community

User contributions licensed under CC BY-SA 3.0