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
SetThreadTokenis wrong and incomplete.here only said that token must have
TOKEN_IMPERSONATEaccess rights. nothing said about Thread handle access rights - it must haveTHREAD_SET_THREAD_TOKENbut main:
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
SeImpersonatePrivilegeprivilege or have the same logon session id as impersonation token, otherwise .. no, function not fail, and return succeeds, but it silently replaceSECURITY_IMPERSONATION_LEVELmember in token toSecurityIdentification(look in WRK-v1.2\base\ntos\ps\security.cPsImpersonateClientfunction - begin fromSeTokenCanImpersonate(implemented in WRK-v1.2\base\ntos\se\token.c - here and checkedTOKEN_HAS_IMPERSONATE_PRIVILEGEand LogonSessionId) and if fail (STATUS_PRIVILEGE_NOT_HELD) returned bySeTokenCanImpersonate- thePsImpersonateClientfunction setImpersonationLevel = SecurityIdentification ;so even if you call
SetThreadTokenfrom 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 (withTHREAD_SET_THREAD_TOKENaccess rights) to restricted process, which have not impersonation privilege - he can success callSetThreadTokenfor your thread - impersonation level will be not reset toSecurityIdentificationin your case, because child process have no
SeImpersonatePrivilege(usually it exist only in elevated processes, but if user enter to system withLOGON32_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) - afterSetThreadTokenyour thread haveSecurityIdentificationimpersonation level. as result any system call, where security checked (say open file or registry key) will fail with errorERROR_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
WTSQueryUserTokenand if it isTokenElevationTypeLimited- we need get linked token byGetTokenInformationcall withTokenLinkedToken.this is complete undocumented, but which token returned in
TOKEN_LINKED_TOKENstructure depend from are calling thread (or process) haveSE_TCB_PRIVILEGE- if yes -TokenPrimaryis returned. otherwiseTokenImpersonationis returned withSECURITY_IMPERSONATION_LEVELset toSecurityIdentification(so this token can be used only for query). because service running under Local system account haveSE_TCB_PRIVILEGE- you got the primary token, which you need use inCreateProcessAsUsercall as is. so you need next function:and use next code for start child
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
SetThreadtokenwill be full ok, because child process will be have impersonate privileges