【问题标题】:Do windows forms have fewer permissions than console applications?Windows 窗体的权限是否比控制台应用程序少?
【发布时间】:2018-10-20 03:29:29
【问题描述】:

我创建了一个库,可以使用多种方法将 DLL 文件注入进程。我正在使用 Windows 窗体使用 GUI 对其进行测试。

所有方法都按预期工作,但使用 QueueUserAPC 时除外。当我尝试使用此方法时,我将 DLL 注入的进程崩溃了。

我创建了一个基本的控制台应用程序来在 Windows 窗体之外测试此方法,它按预期工作而没有进程崩溃。此外,我的错误检查告诉我,当使用 Windows 窗体中的 QueueUserAPC 方法时,DLL 正在注入没有任何错误,但是,该进程仍然崩溃。

我感觉在使用 Windows 窗体时进程崩溃的原因与 QueueUserAPC 方法的代码以及更多关于 Windows 窗体的权限无关。但是,我可能是错的,所以我将包含以下方法的代码。

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 方法

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;
    }  
}

我如何在 Windows 窗体/控制台应用程序中使用它

var injector = new Injector();

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

我想我要问的是 Windows 窗体的权限是否比控制台应用程序少,如果是这样,我该如何配置我的 Windows 窗体,以便在尝试使用 QueueUserAPC 时不会遇到进程崩溃的问题。

如果您想测试这个库,我在 Github 上提供了如何使用它的说明。

【问题讨论】:

  • 不应该有,除非您的防病毒软件在沙盒中运行您的应用程序。您可以通过使用提升的权限运行应用程序来测试权限问题。最简单的方法是在管理员权限下重新启动 Visual Studio,或者您可以编译然后右键单击 .exe 并“以管理员身份运行”。
  • 在管理员下运行会产生同样的问题。我想权限不是这里的问题。您对为什么它可以在控制台应用程序而不是 Windows 窗体应用程序中工作有任何想法吗?
  • 进入QueueUserAPC 调用,它在哪一行中断?
  • 它不会中断任何东西。返回 True 并显示 MessageBox,但是,2 - 3 秒后(正在注入的)进程崩溃。我将完全相同的代码复制到控制台应用程序和新的 Windows 窗体应用程序中,它在控制台应用程序中运行良好,但在 Windows 窗体应用程序中运行良好。
  • 应用程序崩溃时报告的错误(在应用程序错误日志中)是什么?

标签: c# winforms winapi pinvoke


【解决方案1】:

进程正在崩溃,因为您调用VirtualFreeEx 获取内存,其中存储了dll 的名称(dllMemoryPointer)。当LoadLibrary 开始使用此内存时,它可能已经无效。 APC - 这是 异步 过程调用,所以您无法知道在您调用 VirtualFreeEx 的位置,LoadLibrary 是否已经执行或正在处理中,甚至没有开始。

关于权限 - 当然不是。如果您没有权限-您只是使打开的进程或进程中的线程失败。结果什么都不会发生。你在目标进程中崩溃了,反之亦然确认你有权限。

还需要了解,在向其注入 APC 后,并非所有进程中的线程都会处于警报状态。如此可能,尽管 APC 将成功排队 - 它永远不会被调用。将其注入进程中的所有线程,希望其中至少有一个以错误的方式处于警报状态。起初可能不是,其次 - 某些工作线程根本不能设计用于调用 LoadLibrary - 说线程可以没有激活上下文,不连接到 csrss,可能是其他线程。所有这些也可能产生崩溃或未定义的影响。最后,这根本没有效率。

通过QueueUserAPC 的注入在您创建进程本身(处于挂起状态)并在恢复之前将 apc 注入初始进程线程时很有用。这将是有效的(直到最少),因为进程中的新线程总是从LdrInitializeThunk(他初始化进程和/或调用加载dll代码的地方)开始在用户模式下执行,并且总是在跳转到真正的入口点之前(在所有现有的窗口中)版本)致电ZwTestAlert。正是在这一点上,您的 APC 调用将被执行。但严格来说这也不是 100% 正确的方法,因为我们使用 LoadLibrary[W/A] 作为 APC 入口点。但是如果 APC 执行得太早,kernel32.dll 尚未映射到进程或尚未初始化会怎样?显然那次崩溃。在clear windows中一定不能出现这种情况(进程完全初始化后会调用apc,加载所有静态dll,就在调用exe入口点之前)。但可能某些驱动程序会通过 APC 调用(例如映射 kernel32.dll 事件)注入自身代码来处理并强制 APC 在此执行观点。因此,这里只有 100% 可靠的方式使用 ZwQueueApcThread 到 shellcode,这将只调用 ntdll api,通过 LdrLoadDll 加载 dll,而 dll 本身仅从 ntdll.dll

进行静态导入>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-09
    • 1970-01-01
    相关资源
    最近更新 更多