C++ windows 子进程中的 LocalSystem 模拟失败
C++ windows LocalSystem impersonation in child process failing
正在尝试解决,但目前所有的努力都是徒劳的。工作流程如下
Windows 服务 运行 as LocalSystem 使用 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
子进程在用户 workstation\desktop 中启动,主线程捕获用户 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;
}
}
虽然当前用户是管理员并添加了 "Impersonate a Client After authentication",但它仍然报告 "Security Identification"。
问: 将其提升为安全模拟还需要其他什么吗?
谢谢,
原谅我问了一些应该很明显但又需要问的问题:
您是否正在检查这些函数的 return 值?失败时调用 GetLastError?您返回的是什么错误代码?
如果这是 C++,您是否正在设置未处理的异常处理程序?
我的理解是你接下来要做什么 - 你复制 LocalSystem 令牌,从服务到子进程(通过继承句柄)并在命令行中传递句柄值。然后你打电话给 SetThreadToken
.
但是 SetThreadToken
的文档是错误且不完整的。
这里只说token必须有TOKEN_IMPERSONATE
访问权限。
没有提到线程句柄访问权限 - 它必须具有 THREAD_SET_THREAD_TOKEN
但主要是:
When using the SetThreadToken function to impersonate, you must
have the impersonate privileges and make sure that the
SetThreadToken function succeeds
you must have 是什么意思?通常这意味着调用线程(或调用线程所属的进程,如果线程没有令牌)必须在令牌中具有模拟权限。
但这是错误的,不是真的。您(调用线程)拥有哪种特权并不重要。目标(不调用!)线程所属的进程(即使目标线程具有令牌)必须具有SeImpersonatePrivilege
特权或具有与模拟令牌相同的登录会话ID,否则.. 不,函数没有失败,并且 return 成功,但它默默地将令牌中的 SECURITY_IMPERSONATION_LEVEL
成员替换为 SecurityIdentification
(查看 WRK-v1.[=109= .c PsImpersonateClient
函数 - 从 SeTokenCanImpersonate
开始(在 WRK-v1.2\base\ntos\se\token.c 中实现 - 在这里检查TOKEN_HAS_IMPERSONATE_PRIVILEGE
和 LogonSessionId) 如果失败 (STATUS_PRIVILEGE_NOT_HELD
) return 由 SeTokenCanImpersonate
- PsImpersonateClient
函数集 ImpersonationLevel = SecurityIdentification ;
因此,即使您从服务(具有模拟权限)为子进程线程调用 SetThreadToken
- 如果子进程没有模拟权限,调用也是 "fail"。反之亦然 - 如果你说将自己的线程句柄(具有 THREAD_SET_THREAD_TOKEN
访问权限)传递(复制)到受限进程,该进程没有模拟权限 - 他可以成功调用 SetThreadToken
为 你的 线程 - 模拟级别不会重置为 SecurityIdentification
在你的情况下,因为子进程没有 SeImpersonatePrivilege
(通常它只存在于提升的进程中,但是如果用户使用 LOGON32_LOGON_INTERACTIVE
进入系统 - 即使 "admins" 也确实受到限制令牌(因此他们不是真正的管理员))并具有不同的会话 ID(比较本地系统令牌会话 ID)- 在 SetThreadToken
之后,您的线程具有 SecurityIdentification
模拟级别。结果,任何系统调用,其中安全检查(比如打开文件或注册表项)将失败并出现错误 ERROR_BAD_IMPERSONATION_LEVEL
。
解决方案如何? 如果用户拥有管理员权限 - 您需要在用户会话中创建 提升的 子进程(如 "run as admin" )。为此,您需要查询由 WTSQueryUserToken
编辑的令牌 return 的提升类型,如果它是 TokenElevationTypeLimited
- we need get linked token by GetTokenInformation
call with TokenLinkedToken
.
这是完全没有记录的,但是return在TOKEN_LINKED_TOKEN
结构中所依赖的标记是调用线程(或进程)有 SE_TCB_PRIVILEGE
- 如果是 - TokenPrimary
是 returned。否则 TokenImpersonation
是 returned,SECURITY_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。但是,如果仍然需要 LocalSystem - 您可以在子进程中复制此类令牌,在这种情况下 SetThreadtoken
将完全正常,因为子进程将具有模拟权限
正在尝试解决,但目前所有的努力都是徒劳的。工作流程如下
Windows 服务 运行 as LocalSystem 使用 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
子进程在用户 workstation\desktop 中启动,主线程捕获用户 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;
}
}
虽然当前用户是管理员并添加了 "Impersonate a Client After authentication",但它仍然报告 "Security Identification"。
问: 将其提升为安全模拟还需要其他什么吗? 谢谢,
原谅我问了一些应该很明显但又需要问的问题:
您是否正在检查这些函数的 return 值?失败时调用 GetLastError?您返回的是什么错误代码?
如果这是 C++,您是否正在设置未处理的异常处理程序?
我的理解是你接下来要做什么 - 你复制 LocalSystem 令牌,从服务到子进程(通过继承句柄)并在命令行中传递句柄值。然后你打电话给 SetThreadToken
.
但是 SetThreadToken
的文档是错误且不完整的。
这里只说token必须有TOKEN_IMPERSONATE
访问权限。
没有提到线程句柄访问权限 - 它必须具有 THREAD_SET_THREAD_TOKEN
但主要是:
When using the SetThreadToken function to impersonate, you must have the impersonate privileges and make sure that the SetThreadToken function succeeds
you must have 是什么意思?通常这意味着调用线程(或调用线程所属的进程,如果线程没有令牌)必须在令牌中具有模拟权限。
但这是错误的,不是真的。您(调用线程)拥有哪种特权并不重要。目标(不调用!)线程所属的进程(即使目标线程具有令牌)必须具有SeImpersonatePrivilege
特权或具有与模拟令牌相同的登录会话ID,否则.. 不,函数没有失败,并且 return 成功,但它默默地将令牌中的 SECURITY_IMPERSONATION_LEVEL
成员替换为 SecurityIdentification
(查看 WRK-v1.[=109= .c PsImpersonateClient
函数 - 从 SeTokenCanImpersonate
开始(在 WRK-v1.2\base\ntos\se\token.c 中实现 - 在这里检查TOKEN_HAS_IMPERSONATE_PRIVILEGE
和 LogonSessionId) 如果失败 (STATUS_PRIVILEGE_NOT_HELD
) return 由 SeTokenCanImpersonate
- PsImpersonateClient
函数集 ImpersonationLevel = SecurityIdentification ;
因此,即使您从服务(具有模拟权限)为子进程线程调用 SetThreadToken
- 如果子进程没有模拟权限,调用也是 "fail"。反之亦然 - 如果你说将自己的线程句柄(具有 THREAD_SET_THREAD_TOKEN
访问权限)传递(复制)到受限进程,该进程没有模拟权限 - 他可以成功调用 SetThreadToken
为 你的 线程 - 模拟级别不会重置为 SecurityIdentification
在你的情况下,因为子进程没有 SeImpersonatePrivilege
(通常它只存在于提升的进程中,但是如果用户使用 LOGON32_LOGON_INTERACTIVE
进入系统 - 即使 "admins" 也确实受到限制令牌(因此他们不是真正的管理员))并具有不同的会话 ID(比较本地系统令牌会话 ID)- 在 SetThreadToken
之后,您的线程具有 SecurityIdentification
模拟级别。结果,任何系统调用,其中安全检查(比如打开文件或注册表项)将失败并出现错误 ERROR_BAD_IMPERSONATION_LEVEL
。
解决方案如何? 如果用户拥有管理员权限 - 您需要在用户会话中创建 提升的 子进程(如 "run as admin" )。为此,您需要查询由 WTSQueryUserToken
编辑的令牌 return 的提升类型,如果它是 TokenElevationTypeLimited
- we need get linked token by GetTokenInformation
call with TokenLinkedToken
.
这是完全没有记录的,但是return在TOKEN_LINKED_TOKEN
结构中所依赖的标记是调用线程(或进程)有 SE_TCB_PRIVILEGE
- 如果是 - TokenPrimary
是 returned。否则 TokenImpersonation
是 returned,SECURITY_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。但是,如果仍然需要 LocalSystem - 您可以在子进程中复制此类令牌,在这种情况下 SetThreadtoken
将完全正常,因为子进程将具有模拟权限