Trying to resolve it but all efforts are in vain so far. The workflow as follows
Windows service running as LocalSystem creates child using CreateProcessAsUser(...)
with token of current logged user.
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
The child process is launched in user workstation\\desktop and main thread captures user I/O events. The child process impersonation is as follows
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;
}
}
Though current user is Administrator and added "Impersonate a Client After authentication" it still reporting "Security Identification".
Q: Is there anything else required to raise it to Security impersonate? Thanks,
how I understand you do next - you duplicate LocalSystem token, from service, to child process (via inherit handle) and pass it handle value in command line. then you call SetThreadToken
.
but documentation of SetThreadToken
is wrong and incomplete.
here only said that token must have TOKEN_IMPERSONATE
access rights. nothing said about Thread handle access rights - it must have THREAD_SET_THREAD_TOKEN
but main:
When using the SetThreadToken function to impersonate, you must have the impersonate privileges and make sure that the SetThreadToken function succeeds
what is mean under you must have ? usually this mean that calling thread (or process to which calling thread belong in case thread have no token) must have impersonate privileges in token.
but this is wrong and not true. which privilege you ( calling thread ) have - does not matter. the process ( even if target thread have token ) to which target (not calling !) thread belong must have SeImpersonatePrivilege
privilege or have the same logon session id as impersonation token, otherwise .. no, function not fail, and return succeeds, but it silently replace SECURITY_IMPERSONATION_LEVEL
member in token to SecurityIdentification
(look in WRK-v1.2\\base\\ntos\\ps\\security.c PsImpersonateClient
function - begin from SeTokenCanImpersonate
(implemented in WRK-v1.2\\base\\ntos\\se\\token.c - here and checked TOKEN_HAS_IMPERSONATE_PRIVILEGE
and LogonSessionId) and if fail ( STATUS_PRIVILEGE_NOT_HELD
) returned by SeTokenCanImpersonate
- the PsImpersonateClient
function set ImpersonationLevel = SecurityIdentification ;
so even if you call SetThreadToken
from service (which have impersonation privilege) for child process thread - call is "fail" if child process have not impersonation privilege. and visa versa - if you say pass(duplicate) own thread handle (with THREAD_SET_THREAD_TOKEN
access rights) to restricted process, which have not impersonation privilege - he can success call SetThreadToken
for your thread - impersonation level will be not reset to SecurityIdentification
in your case, because child process have no SeImpersonatePrivilege
(usually it exist only in elevated processes, but if user enter to system with LOGON32_LOGON_INTERACTIVE
- even "admins" have really restricted token (so they not really true admins)) and have different session id (compare local system token session id) - after SetThreadToken
your thread have SecurityIdentification
impersonation level. as result any system call, where security checked (say open file or registry key) will fail with error ERROR_BAD_IMPERSONATION_LEVEL
.
how about solution ? if user have admin privileges - you need create elevated child process in user session (like "run as admin" ). for this you need query elevation type of token returned by WTSQueryUserToken
and if it is TokenElevationTypeLimited
- we need get linked token by GetTokenInformation
call with TokenLinkedToken
.
this is complete undocumented, but which token returned in TOKEN_LINKED_TOKEN
structure depend from are calling thread (or process) have SE_TCB_PRIVILEGE
- if yes - TokenPrimary
is returned. otherwise TokenImpersonation
is returned with SECURITY_IMPERSONATION_LEVEL
set to SecurityIdentification
(so this token can be used only for query). because service running under Local system account have SE_TCB_PRIVILEGE
- you got the primary token, which you need use in CreateProcessAsUser
call as is. so you need next function:
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;
}
and use next code for start child
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);
}
in this case you may be not need at all impersonate to LocalSystem in child process. however if still need LocalSystem - you can duplicate such token in child process and in this case SetThreadtoken
will be full ok, because child process will be have impersonate privileges
Forgive me for asking what should be obvious but it needs to be asked:
Are you checking the return values of these functions? Calling GetLastError when they fail? What error codes are you getting back?
If this is C++ are you setting an unhandled exception handler?
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.