在 Windows API 上调试 ERROR_ACCESS_DENIED (CreateProcessAsUser)

Debugging ERROR_ACCESS_DENIED on Windows API (CreateProcessAsUser)

我正在使用 JNA 调用 CreateProcessAsUserW from the Windows API. I'm passing a simple command to CreateProcessAsUserW:

C:\Windows\System32\cmd.exe /c C:\Windows\SysWOW64\whoami.exe > whoami.txt

基本上,它应该将当前用户(通过 whoami.exe 获得)打印到名为 whoami.txt 的文件中。 它成功了。 执行我的 Java 程序后,我看到一个包含我的用户名的新文件 whoami.txt

但是,我打印 GetLastError and see that the error code goes from 0 to 5 immediately after calling CreateProcessAsUserW. The System Error Codes 的 return 值表示 5ERROR_ACCESS_DENIED 错误。

我 运行 以管理员身份从命令提示符 运行 Java 进程。值得注意的是,我在调用 CreateProcessAsUserW.

时没有切换用户

我的直觉是,此函数的众多参数之一导致了表面权限错误,该错误得到了妥善处理。但是,也可能是我缺少一些用户权限。

我是 Windows 的新手,不熟悉调试此类错误的更好方法。你们会推荐哪些工具来获取有关拒绝访问错误的更多信息?在 Linux 上,journalctl 在这里会有很大帮助。

这是我的代码:

// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfow
final STARTUPINFOW.ByReference startupInfoW =
    new STARTUPINFOW.ByReference();
startupInfoW.cb = startupInfoW.size();
startupInfoW.lpReserved = Pointer.NULL;
startupInfoW.lpDesktop = Pointer.NULL;
startupInfoW.lpTitle = Pointer.NULL;
startupInfoW.dwFlags
    = startupInfoW.dwX = startupInfoW.dwY
    = startupInfoW.dwXSize = startupInfoW.dwYSize
    = startupInfoW.dwXCountChars = startupInfoW.dwYCountChars
    = startupInfoW.dwFillAttribute
    = startupInfoW.wShowWindow
    = 0;
startupInfoW.cbReserved2  = 0;
startupInfoW.lpReserved2 = Pointer.NULL;
startupInfoW.hStdInput = startupInfoW.hStdOutput
    = startupInfoW.hStdError
    = Pointer.NULL;
// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-process_information
final PROCESS_INFORMATION.ByReference processInformation =
    new PROCESS_INFORMATION.ByReference();
processInformation.hProcess = processInformation.hThread
    = Pointer.NULL;
processInformation.dwProcessId = processInformation.dwThreadId
    = 0;
// Converts string to char array with 0 as last element.
final char[] whoamiCmd = toCString(
    "C:\Windows\System32\cmd.exe /c C:\Windows\SysWOW64\whoami.exe > whoami.txt"
);
System.out.printf(
    "last err code = %d\n",
    ErrHandlingApi.INSTANCE.GetLastError()
);
final boolean createProcessOk = MyProcessThreadsApi.INSTANCE
    .CreateProcessAsUserW(
        userPrimaryToken.getValue(),
        Pointer.NULL,
        whoamiCmd,
        Pointer.NULL,
        Pointer.NULL,
        false,
        WinBase.CREATE_UNICODE_ENVIRONMENT,
        new PointerByReference(),
        Pointer.NULL,
        startupInfoW,
        processInformation
    );
System.out.printf(
    "last err code = %d\n",
    ErrHandlingApi.INSTANCE.GetLastError()
);
System.out.printf("ok = %b\n", createProcessOk);
System.out.printf(
    "dwProcessId = %d\n", processInformation.dwProcessId
);
public static char[] toCString(final String str) {
    final char[] cString = new char[str.length() + 1];
    for (int i = 0; i < str.length(); i++) {
        cString[i] = str.charAt(i);
    }
    // c-strings end in 0 for lack of bounds checking
    cString[cString.length-1] = 0;
    return cString;
}
C:\Users\zjoseal\Desktop>whoami
ant\zjoseal

C:\Users\zjoseal\Desktop>java -cp windows-credentials-poc-1.0-SNAPSHOT.jar Main
...
last err code = 0
last err code = 5
ok = true
dwProcessId = 6628

C:\Users\zjoseal\Desktop>type whoami.txt
ant\zjoseal

对代码 1 的编辑

有些人问 toCString 方法是否会导致错误代码更改。它只是将字符串的内容复制到 char[] 并附加一个 0 以使其成为一个以 null 结尾的 c 字符串。我把它移到第一个 print 语句之前 gua运行tee 它不对错误代码负责。

有人还指出 print 语句可能会弄乱错误代码,因此我确保 GetLastError 调用在 CreateProcessAsUserW 调用之后立即发生。

在您的输出中,您已指示变量 createProcessOk,即 CreateProcessAsUserW() 的 return 值,是 true(非零)。该函数的 WinAPI documentation 声明:

If the function succeeds, the return value is nonzero.

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

既然函数成功了,这就是你需要知道的。你不应该检查 GetLastError 除非函数失败。

GetLastError 在每个线程的基础上工作。结果 is documented:

Most functions that set the thread's last-error code set it when they fail. However, some functions also set the last-error code when they succeed. If the function is not documented to set the last-error code, the value returned by this function is simply the most recent last-error code to have been set; some functions set the last-error code to 0 on success and others do not.

由于函数成功,“未记录设置最后一个错误代码”。您看到的“5”结果“只是最近设置的最后一个错误代码”。正如您在问题中指出的那样,可能有一些内部 WinAPI 调用作为导致错误的内部实现的一部分,但有一个优雅的回退。

正如 @PaulMcKenzie 在评论中指出的那样,API 需要一个可修改的 Unicode(宽)字符串 (LPWSTR) 作为第三个参数。您的代码有一个神秘的 toCString() 调用,您尚未发布其源代码。如果该方法没有生成可修改的 UTF-16 字符数组,它可能是错误的来源,并且 Windows 无论如何都会帮助处理结果。