C++ 窗口本地系统模拟在子进程中失败



试图解决它,但到目前为止所有的努力都是徒劳的。工作流程如下

作为本地系统运行的 Windows 服务使用带有当前登录用户的令牌CreateProcessAsUser(...)创建子项。

const auto session = WTSGetActiveConsoleSessionId();
auto result = WTSQueryUserToken(session, &token);
HANDLE primary;
result = DuplicateTokenEx(token,
TOKEN_QUERY_SOURCE | TOKEN_ALL_ACCESS | TOKEN_IMPERSONATE |
TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ADJUST_PRIVILEGES,
nullptr, SecurityImpersonation, TokenPrimary, &primary);
const auto args = std::to_string(reinterpret_cast<long>(access));
CreateProcessAsUser(primary,
const_cast<LPSTR>(command.c_str()), // module name
const_cast<LPSTR>(args.c_str()),    // Command line
nullptr, // Process handle not inheritable
nullptr, // Thread handle not inheritable
TRUE,    // Set handle inheritance to TRUE
0,       // No creation flags
nullptr, // Use parent's environment block
nullptr, // Use parent's starting directory
&si,     // Pointer to STARTUPINFO structure
&pi);    // Pointer to PROCESS_INFORMATION structure

子进程在用户工作站\桌面中启动,主线程捕获用户 I/O 事件。子进程模拟如下所示

void impersonate() {
const auto args = GetCommandLine();
const auto system_token = reinterpret_cast<HANDLE>(std::stol(args, nullptr));
if (SetThreadToken(nullptr, system_token) == TRUE) {
auto result = OpenThreadToken(GetCurrentThread(),
TOKEN_QUERY | TOKEN_QUERY_SOURCE, TRUE, &token);
if (result == TRUE)
{
DWORD dwSize = 0;
if (!GetTokenInformation(token, TokenStatistics, NULL, 0, &dwSize)) {
const auto dwResult = GetLastError();
if (dwResult != ERROR_INSUFFICIENT_BUFFER) {
cout << "GetTokenInformation Error: " << dwResult;
} else {
// Allocate the buffer.
PTOKEN_STATISTICS statistics =
(PTOKEN_STATISTICS)GlobalAlloc(GPTR, dwSize);
// Call GetTokenInformation again to get the group information.
if (!GetTokenInformation(token, TokenStatistics, statistics, dwSize,
&dwSize)) {
cout << "GetTokenInformation Error: " << error;
} else {
const auto level = statistics->ImpersonationLevel;
std::string str;
switch (level) {
case SecurityAnonymous:
str = R"(anonymous)";
break;
case SecurityIdentification:
str = R"(identification)";
break;
case SecurityImpersonation:
str = R"(impersonation)";
break;
case SecurityDelegation:
str = R"(delegation)";
break;
}
// This outputs identification.
cout << "impersonation level : " << str;  
}
}
}
}
void thread_main() 
{
impersonate();
// if impersonation is successful, file opening fails otherwise not.
const auto file = CreateFile(R"(C:foo.txt)",                // name of the write
GENERIC_WRITE,          // open for writing
0,                      // do not share
NULL,                   // default security
CREATE_NEW,             // create new file only
FILE_ATTRIBUTE_NORMAL,  // normal file
NULL);                  // no attr. template
if (file == INVALID_HANDLE_VALUE) {
} else {
// Rest of code;
}
} 

尽管当前用户是管理员并添加了"身份验证后模拟客户端",但它仍然报告"安全标识"。

问:是否还需要将其提升为安全模拟? 谢谢

我如何理解你接下来要做 - 您将LocalSystem令牌从服务复制到子进程(通过继承句柄(并在命令行中传递它句柄值。 然后你调用SetThreadToken.

但是SetThreadToken的文档是错误和不完整的。

这里只说令牌必须具有TOKEN_IMPERSONATE访问权限。 没有说线程句柄访问权限 - 它必须具有THREAD_SET_THREAD_TOKEN

但主要:

使用SetThreadToken函数进行模拟时,必须 具有模拟权限,并确保SetThreadToken函数成功

通常这意味着调用线程(或调用线程所属的进程,如果线程没有令牌(必须在令牌中具有模拟权限。

但这是错误的,不是真的。 您(调用线程(拥有哪个权限 - 无关紧要。 目标(不调用!(线程所属的进程(即使目标线程有令牌(必须具有SeImpersonatePrivilege特权或具有与模拟令牌相同的登录会话ID,否则.. 不,函数不会失败,并且返回成功,但它静默地将令牌中的SECURITY_IMPERSONATION_LEVEL成员替换为SecurityIdentification(查看WRK-v1.2\basetos\ps\security.cPsImpersonateClient函数 - 从SeTokenCanImpersonate开始(在WRK-v1.2\basetos\se\token.c中实现 - 这里并检查TOKEN_HAS_IMPERSONATE_PRIVILEGE和 LogonSessionId(,如果失败(STATUS_PRIVILEGE_NOT_HELD(由SeTokenCanImpersonate返回 -PsImpersonateClient函数集ImpersonationLevel = SecurityIdentification ;

因此,即使您从服务(具有模拟权限(为子进程线程调用SetThreadToken- 如果子进程没有模拟权限,则调用为"失败"。 反之亦然 - 如果您说将(复制(自己的线程句柄(具有THREAD_SET_THREAD_TOKEN访问权限(传递给没有模拟权限的受限进程 - 他可以成功调用您的线程SetThreadToken- 模拟级别将不会重置为SecurityIdentification

在您的情况下,因为子进程没有SeImpersonatePrivilege(通常它只存在于提升的进程中,但如果用户以LOGON32_LOGON_INTERACTIVE进入系统 - 即使是"管理员"也确实限制了令牌(所以他们不是真正的管理员((并且具有不同的会话 ID(比较本地系统令牌会话 ID( -SetThreadToken之后,您的线程具有SecurityIdentification模拟级别。 结果任何系统调用, 其中安全检查(例如打开的文件或注册表项(将失败并显示错误ERROR_BAD_IMPERSONATION_LEVEL

解决方案怎么样?如果用户具有管理员权限- 您需要在用户会话中创建提升的子进程(如"以管理员身份运行"(。为此,您需要查询WTSQueryUserToken返回的令牌的提升类型,如果它是TokenElevationTypeLimited- 我们需要通过GetTokenInformation调用TokenLinkedToken来获取链接令牌。

这是完全未记录的,但是TOKEN_LINKED_TOKEN结构所依赖的返回令牌是调用线程(或进程(具有SE_TCB_PRIVILEGE- 如果是 - 返回TokenPrimary。 否则返回TokenImpersonationSECURITY_IMPERSONATION_LEVEL设置为SecurityIdentification(因此此令牌只能用于查询(。 因为在本地系统帐户下运行的服务具有SE_TCB_PRIVILEGE- 你得到了主令牌, 您需要按原样在调用中使用CreateProcessAsUser。所以你需要下一个功能:

ULONG GetElevatedUserToken(PHANDLE phToken)
{
union {
ULONG SessionId;
TOKEN_ELEVATION_TYPE tet;
TOKEN_LINKED_TOKEN tlt;
TOKEN_TYPE tt;
};
SessionId = WTSGetActiveConsoleSessionId();
if (SessionId == MAXDWORD)
{
return ERROR_NO_SUCH_LOGON_SESSION;
}
HANDLE hToken;
if (!WTSQueryUserToken(SessionId, &hToken))
{
return GetLastError();
}
ULONG len;
ULONG dwError = NOERROR;
if (GetTokenInformation(hToken, TokenElevationType, &tet, sizeof(tet), &len))
{
if (tet == TokenElevationTypeLimited)
{
if (GetTokenInformation(hToken, TokenLinkedToken, &tlt, sizeof(tlt), &len))
{
CloseHandle(hToken);
hToken = tlt.LinkedToken;
}
else
{
dwError = GetLastError();
}
}
}
else
{
dwError = GetLastError();
}
if (dwError == NOERROR)
{
if (GetTokenInformation(hToken, TokenType, &tt, sizeof(tt), &len))
{
if (tt != TokenPrimary)
{
dwError = ERROR_INVALID_HANDLE;
}
}
else
{
dwError = GetLastError();
}
if (dwError == NOERROR)
{
*phToken = hToken;
return NOERROR;
}
CloseHandle(hToken);
}
return dwError;
}

并使用下一个代码启动子项

HANDLE hToken;
ULONG dwError = GetElevatedUserToken(&hToken);
if (dwError == NOERROR)
{
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
//***
if (CreateProcessAsUser(hToken, ***, &si, &pi))
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
CloseHandle(hToken);
}

在这种情况下,您可能根本不需要在子进程中模拟本地系统。 但是,如果仍然需要LocalSystem- 您可以在子进程中复制此类令牌,在这种情况下SetThreadtoken将完全正常,因为子进程将具有模拟权限

原谅我问什么应该是显而易见的,但需要问:

您是否正在检查这些函数的返回值? 当他们失败时调用 GetLastError? 你得到什么错误代码?

如果这是C++是否设置了未经处理的异常处理程序?

相关内容

  • 没有找到相关文章

最新更新