user10454073
user10454073

Reputation:

Do windows forms have fewer permissions than console applications?

I have created a library to inject DLL files into processes using a variety of methods. I am testing it out with a GUI using Windows Forms.

All the methods work as intended except when using QueueUserAPC. When I attempt to use this method, the process that I am injecting a DLL into crashes.

I created a basic console application to test this method outside of a Windows Form and it worked as intended without the process crashing. Furthermore, my error checking tells me that the DLL is injecting without any errors when using the QueueUserAPC method from a Windows Form, however, the process still crashes.

I have a feeling that the reason the process is crashing when using a Windows Form isn't to do with the code of the QueueUserAPC method and more about the permissions of a Windows Form. However, I could be wrong so I will include the code for the method below.

pinvoke

[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenProcess(ProcessPrivileges dwDesiredAccess, bool bInheritHandle, int dwProcessId);

[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);

[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

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

[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, int lpNumberOfBytesWritten);

[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool QueueUserAPC(IntPtr pfnAPC, IntPtr hThread, IntPtr dwData);

[DllImport("kernel32.dll", SetLastError = true)]
public static extern void CloseHandle(IntPtr handle);

[DllImport("kernel32.dll", SetLastError=true)]
public static extern void VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, MemoryAllocation dwFreeType);

public enum MemoryAllocation
{
    Commit = 0x1000,
    Reserve = 0x2000,
    Release = 0x8000,
    AllAccess = Commit | Reserve
}

public enum MemoryProtection
{
    PageReadWrite = 0x04,
    PageExecuteReadWrite = 0x40
}

public enum ThreadAccess
{
    SuspendResume = 0x02,
    GetContext = 0x08,
    SetContext = 0x010,
    AllAccess = SuspendResume | GetContext | SetContext
}

QueueUserAPC Method

public static class MQueueUserAPC
{
    public static bool Inject(string dllPath, string processName)
    {
        // Get the pointer to load library

        var loadLibraryPointer = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");

        if (loadLibraryPointer == IntPtr.Zero)
        {
            return false;
        }

        // Get the handle of the specified process

        var processId = Process.GetProcessesByName(processName)[0].Id;

        var processHandle = OpenProcess(ProcessPrivileges.AllAccess, false, processId);

        if (processHandle == IntPtr.Zero)
        {
            return false;
        }

        // Allocate memory for the dll name

        var dllNameSize = dllPath.Length + 1;

        var dllMemoryPointer = VirtualAllocEx(processHandle, IntPtr.Zero, (uint) dllNameSize, MemoryAllocation.AllAccess, MemoryProtection.PageReadWrite);

        if (dllMemoryPointer == IntPtr.Zero)
        {
            return false;
        }

        // Write the dll name into memory

        var dllBytes = Encoding.Default.GetBytes(dllPath);

        if (!WriteProcessMemory(processHandle, dllMemoryPointer, dllBytes, (uint) dllNameSize, 0))
        {
            return false;
        }

        // Call QueueUserAPC on each thread

        foreach (var thread in Process.GetProcessesByName(processName)[0].Threads.Cast<ProcessThread>())
        {
            var threadId = thread.Id;

            // Get the threads handle

            var threadHandle = OpenThread(ThreadAccess.SetContext, false, (uint) threadId);

            // Add a user-mode APC to the APC queue of the thread

            QueueUserAPC(loadLibraryPointer, threadHandle, dllMemoryPointer);

            // Close the handle to the thread

            CloseHandle(threadHandle);
        }

        // Close the previously opened handle

        CloseHandle(processHandle);

        // Free the previously allocated memory

        VirtualFreeEx(processHandle, dllMemoryPointer, dllNameSize, MemoryAllocation.Release);

        return true;
    }  
}

How I am using it in my Windows Form / Console Application

var injector = new Injector();

if(Injector.QueueUserAPC(dllPath, processName))
{
    MessageBox.Show("No error was raised");
}

I guess what I am asking is do Windows Forms have fewer permissions than Console Applications and if so, how could I configure my Windows Form so that I don't run into the problem of the process crashing when trying the use QueueUserAPC.

If you would like to test the library I have it on Github with instructions on how to use it.

Upvotes: 0

Views: 114

Answers (1)

RbMm
RbMm

Reputation: 33744

process is crashing because you call VirtualFreeEx for memory, where you store name of dll (dllMemoryPointer). when LoadLibrary begin use this memory it can be already not valid. APC - this is asynchronous procedure call, so you can not know ,at point where you call VirtualFreeEx, are LoadLibrary already executed or in process, or even not begin.

about the permissions - of course no. if you have no permissions - you simply fail open process or threads in process. as result nothing will happens. that you got crash in target process, visa versa confirmed that you have permissions.

also need understand that not all threads in process will be in alertable state, after you inject APC to it. so possible, despite that APC will be successfully queued - it will be never called. inject it to all threads in process, hoping that at least one of them will be in alertable state wrong way. at first may be not, at second - some working threads can be not design for call LoadLibrary at all - say thread can have no activation context, not connect to csrss, may be some else. all this also can produce crash or undefined effects. and finally this is simply not efficient.

the injection via QueueUserAPC useful in case you create process itself (in suspended state) and inject apc to the initial process thread before resume it. this will be work (how minimum until) because new thread in process begin execute in user mode always from LdrInitializeThunk (where he initialize process and/or call load dlls code) and before jump to real entry point always (in all existing windows versions) call ZwTestAlert. exactly at this point your APC call will be executed. but strictly speaking this is also not 100% correct way, because we use LoadLibrary[W/A] as APC entry point. but what will be if APC exceuted too early, when kernel32.dll yet not mapped to process or yet not initialized ? obviously that crash. in clear windows must not be this situation (apc will be called after process fully initialized, all static dll loaded, just before call exe entry point). but possible that some drivers will inject self code to process via APC call (say on mapping kernel32.dll event ) and force APC executing at this point. as result only 100% reliable way here use ZwQueueApcThread to shellcode, which will be call only ntdll api, load dll via LdrLoadDll and dll itself have static import only from ntdll.dll

Upvotes: 1

Related Questions