Reputation:
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
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