CreateProcessAsUser()
的用法。
我遵循了我在网上可以找到的一些代码示例。我还完成了一个使用
CreateProcess()
的类似示例应用程序(或多或少遵循 这个示例),并且效果很好。
LogonUser()
,然后将该令牌传递给 CreateProcessAsUser()
。我还使用 CreateEnvironmentBlock()
来获取令牌的环境。尽管我不确定这是否必要,但我看到的示例包括该步骤。我打印了CreateEnvironmentBlock()
的结果,它们正如我所期望的那样,包括显示当前用户是我正在尝试登录的用户。
我的问题:
CreateProcessAsUser()
执行没有错误,但子进程whoami.exe
立即创建一个弹出窗口说:
应用程序无法正确启动(0xc0000142)。单击“确定”关闭应用程序。
我从管理员命令提示符启动我的应用程序,并通过本地安全策略,我已向管理员和我的特定帐户授予用户权限
SeAssignPrimaryTokenPrivilege
和 SeIncreaseQuotaPrivilege
,我读到需要调用 CreateProcessAsUser()
。我有点不确定是否需要做更多事情来确保我的调用进程具有这些权限。我是否也需要以编程方式启用这些权限?
下面的登录账户
username
是普通账户。它具有访问和执行whoami.exe
以及交互登录的权限。我不相信它需要其他权限(?)。
这是我的代码片段:
int main()
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
WCHAR cmd_line[] = L"C:\\Windows\\System32\\whoami.exe";
HANDLE token = INVALID_HANDLE_VALUE;
LPVOID proc_env = NULL;
char username[MAX_PATH] = "username";
BOOL success = LogonUserA(username, "machine_domain", "password",
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token);
if (!success)
{
std::cout << "LogonUser() GLE " << GetLastError() << std::endl;
}
if (token == NULL || token == INVALID_HANDLE_VALUE)
{
std::cout << "token is bad" << std::endl;
}
// Use token to get environment.
success = CreateEnvironmentBlock(&proc_env, token, FALSE);
if (!success)
{
std::cout << "CreateEnvironmentBlock() GLE " << GetLastError() << std::endl;
CloseHandle(token);
return 1;
}
if (proc_env == NULL)
{
std::cout << "procenv is null" << std::endl;
}
success = CreateProcessAsUser(
token,
NULL, // app name
cmd_line, // includes app name.
NULL, // proc security attrs
NULL, // thread security attrs
FALSE, // inherit handles
CREATE_UNICODE_ENVIRONMENT, // creation flags
proc_env, // env
L"C:\\", // current dir
&si, // startupinfo
&pi);
if (!success)
{
std::cout << "CreateProcessAsUser() GLE " << GetLastError() << std::endl;
return 1;
}
// Wait until child process exits.
WaitForSingleObject(pi.hProcess, INFINITE);
// Close process and thread handles.
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return 0;
}
我在
CreationFlags
中尝试了许多其他选项(例如 CREATE_UNICODE_ENVIRONMENT
),并且我也尝试将 STARTUPINFO
的 lpDesktop
设置为 "winsta0\\default"
,如一些帖子中所建议的。我尝试的所有方法都有相同的结果,但也许我错过了正确的选项。
我做了一些编辑,以确保
STARTUPINFO
的标准句柄正确配置了管道,其中包括在指定 lbInherit
时根据需要将 TRUE
更改为 STARTF_USESTDHANDLES
。下面的代码适用于我如果目标用户是管理员。管道按预期为目标用户输出 whoami
结果! (我尝试了 NULL
L""
和 L"winsta0\\default"
作为 lpDesktop,并且全部成功。)但是,当目标用户不是管理员时,我遇到了与以前相同的失败。
我发现这些文章表明启动的进程需要访问桌面的权限winsta0\default
。是否可以将此权限授予非管理员帐户?我还看到了尝试CreateProcessWithLogon()
的建议,我会尝试。
int main()
{
WCHAR cmd_line[] = L"C:\\Windows\\System32\\whoami.exe";
HANDLE token = INVALID_HANDLE_VALUE;
LPVOID proc_env = NULL;
char username[MAX_PATH] = "target_user";
BOOL success = LogonUserA(username, "domain_local", "password",
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token);
if (!success)
{
std::cout << "LogonUser() GLE " << GetLastError() << std::endl;
}
if (token == NULL || token == INVALID_HANDLE_VALUE)
{
std::cout << "token is bad" << std::endl;
}
// Use token to get environment.
success = CreateEnvironmentBlock(&proc_env, token, FALSE);
if (!success)
{
std::cout << "CreateEnvironmentBlock() GLE " << GetLastError() << std::endl;
CloseHandle(token);
return 1;
}
if (proc_env == NULL)
{
std::cout << "procenv is null" << std::endl;
}
HANDLE child_write_pipe = NULL; // child's STDOUT
HANDLE parent_read_pipe = NULL;
SECURITY_ATTRIBUTES sa = { 0 };
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
sa.nLength = sizeof(sa);
// Create pipe to communicate with child.
success = CreatePipe(&parent_read_pipe, &child_write_pipe, &sa, 0);
if (!success)
{
std::cout << "CreatePipe fail" << std::endl;
return 1;
}
success = SetHandleInformation(parent_read_pipe, HANDLE_FLAG_INHERIT, 0);
if (!success)
{
std::cout << "SetHandleInformation fail" << std::endl;
return 1;
}
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.hStdOutput = child_write_pipe;
si.hStdError = child_write_pipe;
si.hStdInput = NULL;
//// Prevent cmd window pop up.
si.wShowWindow = SW_HIDE;
WCHAR lpd[] = L"";
si.lpDesktop = lpd;
ZeroMemory(&pi, sizeof(pi));
// Token must have TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY.
success = CreateProcessAsUser(
token,
NULL, // app name
cmd_line, // includes app name.
NULL, // proc security attrs
NULL, // thread security attrs
TRUE, // inherit handles
CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, // creation flags
proc_env, // env
L"C:\\", // current dir
&si, // startupinfo
&pi);
if (!success)
{
std::cout << "CreateProcessAsUser() GLE " << GetLastError() << std::endl;
return 1;
}
// Read the results from child (POLL / BLOCK).
char* result_buf = NULL;
_result_read(pi.hProcess, parent_read_pipe, &result_buf);
if (result_buf)
{
std::cout << "result: " << result_buf << std::endl;
// Copy into result object.
free(result_buf);
}
// get exit code.
DWORD ec = 0;
success = GetExitCodeProcess(pi.hProcess, &ec);
std::cout << "exit code: " << ec << std::endl;
// Close process and thread handles.
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return 0;
}
您在第二次编辑中就明白了全部要点。您的非管理员用户没有与 windows station 和 destkop 对象交互的权限。
默认 ACL 仅限于
NT AUTHORITY\LogonSessionId_X_Y
组(以及 admin、system 等)。 SID 是 S-1-5-5-X-Y
,其中值看起来是随机的。当调用 LogonUser
时,返回的访问令牌不属于该组,重点是用户拥有 NT AUTHORITY\LogonSessionId_X_Y
,但 X 和 Y 与 windows 对象上的一个位置不同。当调用 UserLogon
时,它会使用自己的 ID 创建另一个登录会话。这就是为什么管理员用户可以但普通用户不行。您可以使用谷歌项目零中的TokenViewer来查看您的令牌组以帮助您调试。
要解决此问题,您可以使用
OpenWindowStationW
、OpenDesktopW
和 SetSecurityInfo
编辑 ACL 来为您的用户定义一些 ACL。否则我们可以尝试找到一种方法来获取相同的会话 ID,但我暂时没有找到方法。
这里是在 Station 对象上设置一些 非常危险 ACL 的代码,允许每个人用它做任何事情 :
void AddFullAccessForEveryoneStation(LPCWSTR lpWinstaName) {
HWINSTA hWinsta = OpenWindowStationW(lpWinstaName, FALSE, READ_CONTROL | WRITE_DAC);
if (hWinsta == NULL) {
std::cerr << "OpenWindowStationW Error: " << GetLastError() << std::endl;
return;
}
PSECURITY_DESCRIPTOR pSD = NULL;
PACL pDacl = NULL;
DWORD dwRes = GetSecurityInfo(
hWinsta, // handle to the object
SE_WINDOW_OBJECT, // type of the object
DACL_SECURITY_INFORMATION, // type of security information
NULL, // owner SID
NULL, // primary group SID
&pDacl, // DACL
NULL, // SACL
&pSD); // security descriptor
if (dwRes != ERROR_SUCCESS) {
std::cerr << "GetSecurityInfo Error: " << dwRes << std::endl;
if (pSD) {
LocalFree(pSD);
}
CloseWindowStation(hWinsta);
return;
}
EXPLICIT_ACCESS ea;
ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
ea.grfAccessPermissions = GENERIC_ALL |
WINSTA_ACCESSCLIPBOARD |
WINSTA_ACCESSGLOBALATOMS |
WINSTA_CREATEDESKTOP |
WINSTA_ENUMDESKTOPS |
WINSTA_ENUMERATE |
WINSTA_EXITWINDOWS |
WINSTA_READATTRIBUTES |
WINSTA_READSCREEN |
WINSTA_WRITEATTRIBUTES;
ea.grfAccessMode = GRANT_ACCESS;
ea.grfInheritance = NO_INHERITANCE;
PSID pEveryoneSID = NULL;
SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
if (!AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &pEveryoneSID)) {
std::cerr << "AllocateAndInitializeSid Error: " << GetLastError() << std::endl;
if (pSD) {
LocalFree(pSD);
}
CloseWindowStation(hWinsta);
return;
}
ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
ea.Trustee.ptstrName = (LPTSTR)pEveryoneSID;
PACL pNewDacl = NULL;
dwRes = SetEntriesInAcl(1, &ea, pDacl, &pNewDacl);
if (dwRes != ERROR_SUCCESS) {
std::cerr << "SetEntriesInAcl Error: " << dwRes << std::endl;
if (pEveryoneSID) {
FreeSid(pEveryoneSID);
}
if (pSD) {
LocalFree(pSD);
}
CloseWindowStation(hWinsta);
return;
}
dwRes = SetSecurityInfo(
hWinsta, // handle to the object
SE_WINDOW_OBJECT, // type of the object
DACL_SECURITY_INFORMATION, // type of security information
NULL, // owner SID
NULL, // primary group SID
pNewDacl, // DACL
NULL); // SACL
if (dwRes != ERROR_SUCCESS) {
std::cerr << "SetSecurityInfo Error: " << dwRes << std::endl;
}
else {
std::cout << "Successfully added full access for Everyone." << std::endl;
}
if (pEveryoneSID) {
FreeSid(pEveryoneSID);
}
if (pNewDacl) {
LocalFree(pNewDacl);
}
if (pSD) {
LocalFree(pSD);
}
CloseWindowStation(hWinsta);
}
Then do the same for the Desktop. But please, keep in mind that this code is dangerous as it allow everyone. Modify it for your user only.
// call it with `AddFullAccessForEveryoneStation(L"winsta0")`