Related
I am trying to launch a new process as NT AUTHORITY\Network Service from a process that is running as NT AUTHORITY\System.
I have looked at other questions, such as the following, which does not provide a working example: CreateProcess running as user: "NT AUTHORITY/Network Service" without knowing the credentials?
And, I have come across some posts which talk about copying a token from a process that is already running as NT AUTHORITY\Network Service: Windows API and Impersonation Part 1 - How to get SYSTEM using Primary Tokens.
I wonder, is there a way to launch a process without having to depend on another process to copy a token from? Is there a way to hand-craft a token that can help launch a process as NT AUTHORITY\Network Service using CreateProcessAsUserW(), for example?
I came across a function LogonUser which can be used to create token for required user. The doc shows an example for creating token for NT AUTHORITY\LocalService like this:
LogonUser(L"LocalService", L"NT AUTHORITY", NULL, LOGON32_LOGON_SERVICE, LOGON32_PROVIDER_DEFAULT, &hToken)
I used the above in combination with CreateProcessAsUser function which starts child process as NT AUTHORITY\NetworkService where the parent is running as NT AUTHORITY\System
#include <Windows.h>
HANDLE token;
LogonUser(
L"NetworkService",
L"NT AUTHORITY",
nullptr,
LOGON32_LOGON_SERVICE,
LOGON32_PROVIDER_DEFAULT,
&token);
// Setup required variables to start the process
LPPROCESS_INFORMATION lpProcessInformation;
STARTUPINFOEX si;
PWCHAR path;
PCWSTR environmentBlockPtr = nullptr;
DWORD creationFlags;
WCHAR* commandStr;
CreateProcessAsUser(
token,
nullptr,
const_cast<WCHAR*>(commandStr),
nullptr,
nullptr,
FALSE,
creationFlags,
const_cast<WCHAR*>(environmentBlockPtr),
path,
&si.StartupInfo,
lpProcessInformation);
I suggest you do it via a scheduled task and then delete the task after it runs (or maybe there is a one-shot setting you could use). While System has the create token privilege the NtCreateToken function is not part of the documented API and using it would be an enormous pain. If not a scheduled task then as a service (again even if you are only going to run it once).
Is there a way to hand-craft a token that can help launch a process
as NT AUTHORITY\Network Service
yes. by call NtCreateToken. but for this need have SE_CREATE_TOKEN_PRIVILEGE. but services.exe and services, even running as 'NT AUTHORITY\System' have not it. so you can not just call NtCreateToken. first you need find token with this privilege and only after this.
for get token with required privileges set we can use next code:
extern const SECURITY_QUALITY_OF_SERVICE sqos = {
sizeof (sqos), SecurityImpersonation, SECURITY_DYNAMIC_TRACKING, FALSE
};
extern const OBJECT_ATTRIBUTES oa_sqos = { sizeof(oa_sqos), 0, 0, 0, 0, const_cast<SECURITY_QUALITY_OF_SERVICE*>(&sqos) };
NTSTATUS GetToken(_In_ PVOID buf, _In_ const TOKEN_PRIVILEGES* RequiredSet, _Out_ PHANDLE phToken)
{
NTSTATUS status;
union {
PVOID pv;
PBYTE pb;
PSYSTEM_PROCESS_INFORMATION pspi;
};
pv = buf;
ULONG NextEntryOffset = 0;
do
{
pb += NextEntryOffset;
HANDLE hProcess, hToken, hNewToken;
CLIENT_ID ClientId = { pspi->UniqueProcessId };
if (ClientId.UniqueProcess)
{
if (0 <= NtOpenProcess(&hProcess, PROCESS_QUERY_LIMITED_INFORMATION,
const_cast<POBJECT_ATTRIBUTES>(&oa_sqos), &ClientId))
{
status = NtOpenProcessToken(hProcess, TOKEN_DUPLICATE, &hToken);
NtClose(hProcess);
if (0 <= status)
{
status = NtDuplicateToken(hToken, TOKEN_ADJUST_PRIVILEGES|TOKEN_IMPERSONATE|TOKEN_QUERY,
const_cast<POBJECT_ATTRIBUTES>(&oa_sqos), FALSE, TokenImpersonation, &hNewToken);
NtClose(hToken);
if (0 <= status)
{
status = NtAdjustPrivilegesToken(hNewToken, FALSE, const_cast<PTOKEN_PRIVILEGES>(RequiredSet), 0, 0, 0);
if (STATUS_SUCCESS == status)
{
*phToken = hNewToken;
return STATUS_SUCCESS;
}
NtClose(hNewToken);
}
}
}
}
} while (NextEntryOffset = pspi->NextEntryOffset);
return STATUS_UNSUCCESSFUL;
}
NTSTATUS GetToken(_In_ const TOKEN_PRIVILEGES* RequiredSet, _Out_ PHANDLE phToken)
/*++
Routine Description:
try found process token with RequiredSet; duplicate and adjust privilege
Arguments:
RequiredSet - set of privileges which must be in token
phToken - Impersonation Token with all privileges from RequiredSet, all it is enabled (even if some is disabled in original token)
--*/
{
NTSTATUS status;
ULONG cb = 0x40000;
do
{
status = STATUS_INSUFFICIENT_RESOURCES;
if (PBYTE buf = new BYTE[cb += PAGE_SIZE])
{
if (0 <= (status = NtQuerySystemInformation(SystemProcessInformation, buf, cb, &cb)))
{
status = GetToken(buf, RequiredSet, phToken);
if (status == STATUS_INFO_LENGTH_MISMATCH)
{
status = STATUS_UNSUCCESSFUL;
}
}
delete [] buf;
}
} while(status == STATUS_INFO_LENGTH_MISMATCH);
return status;
}
with this we can do next:
#define BEGIN_PRIVILEGES(name, n) static const union { TOKEN_PRIVILEGES name;\
struct { ULONG PrivilegeCount; LUID_AND_ATTRIBUTES Privileges[n];} label(_) = { n, {
#define LAA(se) {{se}, SE_PRIVILEGE_ENABLED }
#define LAA_D(se) {{se} }
#define END_PRIVILEGES }};};
BEGIN_PRIVILEGES(tp_dbg, 2)
LAA(SE_DEBUG_PRIVILEGE), // need for open processes
LAA(SE_IMPERSONATE_PRIVILEGE), // need for impersonate token
END_PRIVILEGES
BEGIN_PRIVILEGES(tp_cai, 3)
LAA(SE_CREATE_TOKEN_PRIVILEGE),
LAA(SE_ASSIGNPRIMARYTOKEN_PRIVILEGE),
LAA(SE_INCREASE_QUOTA_PRIVILEGE),
END_PRIVILEGES
EXTERN_C NTSYSCALLAPI NTSTATUS NTAPI NtCreateToken(
_Out_ PHANDLE TokenHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,
_In_ TOKEN_TYPE TokenType,
_In_ PLUID AuthenticationId,
_In_ PLARGE_INTEGER ExpirationTime,
_In_ PTOKEN_USER User,
_In_ PTOKEN_GROUPS Groups,
_In_ PTOKEN_PRIVILEGES Privileges,
_In_opt_ PTOKEN_OWNER Owner,
_In_ PTOKEN_PRIMARY_GROUP PrimaryGroup,
_In_opt_ PTOKEN_DEFAULT_DACL DefaultDacl,
_In_ PTOKEN_SOURCE TokenSource
);
NTSTATUS CreateServiceToken(HANDLE hToken, PHANDLE phToken)
{
NTSTATUS status;
PVOID stack = alloca(guz);
PVOID buf = 0;
ULONG cb = 0, rcb;
struct {
PTOKEN_GROUPS ptg;
PTOKEN_STATISTICS pts;
PTOKEN_DEFAULT_DACL ptdd;
PTOKEN_PRIVILEGES ptp;
} s;
void** ppv = (void**)&s.ptp;
static const ULONG rcbV[] = {
sizeof(TOKEN_GROUPS)+0x80,
sizeof(TOKEN_STATISTICS),
sizeof(TOKEN_DEFAULT_DACL)+0x80,
sizeof(TOKEN_PRIVILEGES)+0x80,
};
static TOKEN_INFORMATION_CLASS TokenInformationClassV[] = {
TokenGroups,
TokenStatistics,
TokenDefaultDacl,
TokenPrivileges,
};
ULONG n = _countof(TokenInformationClassV);
do
{
TOKEN_INFORMATION_CLASS TokenInformationClas = TokenInformationClassV[--n];
rcb = rcbV[n], cb = 0;
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
}
status = NtQueryInformationToken(hToken, TokenInformationClas, buf, cb, &rcb);
} while (status == STATUS_BUFFER_TOO_SMALL);
if (0 > status)
{
return status;
}
*(ppv--) = buf, stack = buf;
} while (n);
static const SID NetworkService = { SID_REVISION, 1, SECURITY_NT_AUTHORITY, { SECURITY_NETWORK_SERVICE_RID } };
static const TOKEN_OWNER to = { const_cast<SID*>(&NetworkService) };
static const TOKEN_USER tu = { { const_cast<SID*>(&NetworkService) } };
static const TOKEN_SOURCE ts = { {"Advapi"}, SYSTEM_LUID};
return NtCreateToken(phToken, TOKEN_ALL_ACCESS, 0, TokenPrimary,
&s.pts->AuthenticationId, &s.pts->ExpirationTime,
const_cast<PTOKEN_USER>(&tu), s.ptg, s.ptp, const_cast<PTOKEN_OWNER>(&to),
(PTOKEN_PRIMARY_GROUP)&to, s.ptdd, const_cast<PTOKEN_SOURCE>(&ts));
}
NTSTATUS RunAsNetworkService()
{
HANDLE hMyToken, hToken;
NTSTATUS status = NtOpenProcessToken(NtCurrentProcess(), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &hMyToken);
if (0 <= status)
{
if (0 <= (status = NtAdjustPrivilegesToken(hMyToken, FALSE, const_cast<PTOKEN_PRIVILEGES>(&tp_dbg), 0, 0, 0)))
{
if (0 <= (status = GetToken(&tp_cai, &hToken)))
{
status = RtlSetCurrentThreadToken(hToken);
NtClose(hToken);
if (0 <= status)
{
if (0 <= (status = CreateServiceToken(hMyToken, &hToken)))
{
ULONG SessionId;
ProcessIdToSessionId(GetCurrentProcessId(), &SessionId);
if (0 <= (status = NtSetInformationToken(hToken, TokenSessionId, &SessionId, sizeof(SessionId))))
{
STARTUPINFO si = { sizeof(si) };
si.lpDesktop = const_cast<PWSTR>(L"WinSta0\\Default");
PROCESS_INFORMATION pi;
WCHAR cmdline[] = L"cmd /k whoami.exe /user";
if (CreateProcessAsUserW(hToken, 0, cmdline, 0, 0, 0, 0, 0, 0, &si, &pi))
{
NtClose(pi.hThread);
NtClose(pi.hProcess);
}
else
{
status = RtlGetLastNtStatus();
}
}
NtClose(hToken);
}
RtlSetCurrentThreadToken();
}
}
}
NtClose(hMyToken);
}
return status;
}
(code not use /RTCs )
I created the process through NtCreateUserProcess. This was successful and returned STATUS_SUCCESS.
This created process attempted to run through NtResumeThread in a suspended state.
But immediately the process was turned off.
In the same code, NtCreateUserProcess was replaced with CreateProcessW. NtResumeThread was used to create the same suspended process to normal process, which worked well.
How can I resume a process created with NtCreateUserProcess?
Edit: CreateProcess (Working) -> NtCreateUserProcess (Nop..) means process created with wrong code. Got it. I created process with following code.
HANDLE hProcess;
HANDLE hThread;
PS_CREATE_INFO procInfo;
PS_ATTRIBUTE_LIST attrList;
RTL_USER_PROCESS_PARAMETERS10 userParams;
RtlSecureZeroMemory(&userParams, sizeof(RTL_USER_PROCESS_PARAMETERS10));
RtlSecureZeroMemory(&attrList, sizeof(PS_ATTRIBUTE_LIST));
RtlSecureZeroMemory(&procInfo, sizeof(PS_CREATE_INFO));
userParams.Length = sizeof(RTL_USER_PROCESS_PARAMETERS10);
userParams.MaximumLength = sizeof(RTL_USER_PROCESS_PARAMETERS10);
attrList.TotalLength = sizeof(PS_ATTRIBUTE_LIST) - sizeof(PS_ATTRIBUTE);
procInfo.Size = sizeof(PS_CREATE_INFO);
userParams.Environment = (WCHAR *) data;
userParams.EnvironmentSize = sizeof(data);
userParams.EnvironmentVersion = 0;
userParams.Flags = 0x01;
userParams.ShowWindowFlags = SW_HIDE;
userParams.ImagePathName = filename2;
userParams.CommandLine = command;
attrList.Attributes[0].Attribute = PsAttributeValue(PsAttributeImageName, FALSE, TRUE, FALSE);
attrList.Attributes[0].Size = filename.Length;
attrList.Attributes[0].Value = (ULONG_PTR) filename.Buffer;
NTSTATUS status = INLINE_SYSCALL(NtCreateUserProcess)(&hProcess, &hThread, MAXIMUM_ALLOWED, MAXIMUM_ALLOWED,
NULL, NULL, 0, THREAD_CREATE_FLAGS_CREATE_SUSPENDED,
reinterpret_cast<_RTL_USER_PROCESS_PARAMETERS *>(&userParams), &procInfo, &attrList);
if (!NT_SUCCESS(status)) {
printf("%x\n", status);
return 1;
}
And using following struct.
typedef struct _RTL_USER_PROCESS_PARAMETERS10
{
ULONG MaximumLength; //6c0
ULONG Length;//6c0
ULONG Flags;//0
ULONG DebugFlags;//0
HANDLE ConsoleHandle;//NULL
ULONG ConsoleFlags;//0
HANDLE StandardInput;//NULL
HANDLE StandardOutput;//NULL
HANDLE StandardError;//NULL
CURDIR CurrentDirectory;
UNICODE_STRING DllPath;
UNICODE_STRING ImagePathName;
UNICODE_STRING CommandLine;
PWSTR Environment;
ULONG StartingX;
ULONG StartingY;
ULONG CountX;
ULONG CountY;
ULONG CountCharsX;
ULONG CountCharsY;
ULONG FillAttribute;
ULONG WindowFlags;
ULONG ShowWindowFlags;
UNICODE_STRING WindowTitle;
UNICODE_STRING DesktopInfo;
UNICODE_STRING ShellInfo;
UNICODE_STRING RuntimeData;
RTL_DRIVE_LETTER_CURDIR CurrentDirectories[32];
#if (NTDDI_VERSION >= NTDDI_LONGHORN)
SIZE_T EnvironmentSize;
#endif
#if (NTDDI_VERSION >= NTDDI_WIN7)
SIZE_T EnvironmentVersion;
#endif
} RTL_USER_PROCESS_PARAMETERS10, *PRTL_USER_PROCESS_PARAMETERS10;
I cant find any error in this process creation.
Sorry for bad english.. I'm not a native speaker so there can be some wrong grammer, graceless manner.
Recently I came across a Windows API called GetAppContainerNamedObjectPath. But I have no idea on how I can use it.
I found a msdn page for this api (https://learn.microsoft.com/en-us/windows/win32/api/securityappcontainer/nf-securityappcontainer-getappcontainernamedobjectpath). But it does not have a right example and remarks, parameters are written poorly.
I am getting ERROR_INVALID_PARAMETER(87) error at the end, which tells me something's wrong with the parameters that I put. Here's what I've tried.
#define TokenIsAppContainer 29
#define TokenAppContainerSid 31
#define TokenAppContainerNumber 32
typedef struct _TOKEN_APPCONTAINER_INFORMATION {
PSID TokenAppContainer;
} TOKEN_APPCONTAINER_INFORMATION, *PTOKEN_APPCONTAINER_INFORMATION;
void GetAppContainerProcessInfo(CString & procName)
{
DWORD dwSize = 0;
DWORD dwResult;
HANDLE hToken;
PTOKEN_APPCONTAINER_INFORMATION pAppCoInfo;
WCHAR wcsDebug[1024] = {0,};
WCHAR * pwSID = NULL;
typedef BOOL (WINAPI *_LPGETAPPCONTAINERNAMEOBJECTPATH)(HANDLE, PSID, ULONG, LPWSTR, PULONG);
static _LPGETAPPCONTAINERNAMEOBJECTPATH lpGetAppContainerNamedObjectPath = NULL;
if (0 == lpGetAppContainerNamedObjectPath)
{
HMODULE hKernel32 = LoadLibraryExW(L"kernel32.dll", NULL, 0);
if (hKernel32)
{
lpGetAppContainerNamedObjectPath = reinterpret_cast<_LPGETAPPCONTAINERNAMEOBJECTPATH>(GetProcAddress(hKernel32, "GetAppContainerNamedObjectPath"));
}
}
if (lpGetAppContainerNamedObjectPath)
{
DWORD processId = (DWORD)_ttoi((LPCTSTR)procName);
//HANDLE hProcess = GetProcessHandleByProcessName(procName);
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
if(!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))
{
dwResult = GetLastError();
swprintf_s( wcsDebug, _countof(wcsDebug), L"OpenProcessToken Error(%u) PID(%d)\n", dwResult, processId );
AfxMessageBox(wcsDebug);
return;
}
if (!GetTokenInformation(hToken, (TOKEN_INFORMATION_CLASS) TokenAppContainerSid, NULL, dwSize, &dwSize))
{
dwResult = GetLastError();
if( dwResult != ERROR_INSUFFICIENT_BUFFER )
{
swprintf_s( wcsDebug, _countof(wcsDebug), L"GetTokenInformation Error %u\n", dwResult );
AfxMessageBox(wcsDebug);
return;
}
}
pAppCoInfo = (PTOKEN_APPCONTAINER_INFORMATION) GlobalAlloc( GPTR, dwSize );
if (!GetTokenInformation(hToken, (TOKEN_INFORMATION_CLASS) TokenAppContainerSid, pAppCoInfo, dwSize, &dwSize))
{
dwResult = GetLastError();
swprintf_s( wcsDebug, _countof(wcsDebug), L"GetTokenInformation Error %u\n", dwResult );
AfxMessageBox(wcsDebug);
return;
}
WCHAR wcsNamedObjectPath[MAX_PATH];
ULONG ulRetlen = 0;
BOOL bRet = lpGetAppContainerNamedObjectPath(hToken, pAppCoInfo->TokenAppContainer, _countof(wcsNamedObjectPath), wcsNamedObjectPath, &ulRetlen );
if (bRet)
{
swprintf_s( wcsDebug, _countof(wcsDebug), L"GetAppContainerNamedObjectPath Path(%s)\n", wcsNamedObjectPath );
AfxMessageBox(wcsDebug);
}
else
{
dwResult = GetLastError();
swprintf_s( wcsDebug, _countof(wcsDebug), L"GetAppContainerNamedObjectPath Error %u\n", dwResult );
AfxMessageBox(wcsDebug);
}
if (pwSID)
LocalFree(pwSID);
CloseHandle(hToken)
CloseHandle(hProcess);
}
}
As a side-note, I have tried using wchar_t * and dynamically allocate the memory buffer by calling GetAppContainerNamedObjectPath twice. But still had no chance. Return length does not return a meaningful value.
if you call RtlGetLastNtStatus(); instead GetLastError(); after GetAppContainerNamedObjectPath you will got
STATUS_INVALID_PARAMETER_MIX - An invalid combination of parameters was specified.
this give you more info compare simply invalid parameter.
then look for function signature
BOOL
WINAPI
GetAppContainerNamedObjectPath(
_In_opt_ HANDLE Token,
_In_opt_ PSID AppContainerSid,
_In_ ULONG ObjectPathLength,
_Out_writes_opt_(ObjectPathLength) LPWSTR ObjectPath,
_Out_ PULONG ReturnLength
);
the Token and AppContainerSid declared with In_opt -- this mean that this parameters is optional, and you can pass 0 in place one of it. then ask your self - for what you query token for TokenAppContainerSid ? are system can not do this for you if you pass this token to api ? obvious can. so you not need do this yourself. really you need pass Token to api and in this case AppContainerSid must be 0. or you can pass AppContainerSid to api and in this case Token must be 0. when both AppContainerSid and Token not zero - you and got STATUS_INVALID_PARAMETER_MIX
also as side note - you not need open process with PROCESS_ALL_ACCESS if you need get it token. the PROCESS_QUERY_LIMITED_INFORMATION is enough
really api not do big magic. it return to you
AppContainerNamedObjects\<Sid>
path, where string form of app container sid.(some like S-1-15-2-...)
There is a launcher - a program in C++, which you need to run as administrator. The launcher launches another process as well on behalf of the administrator and because of this, third-party programs (AutoHotkey) running with the rights of a normal user can not access it. The second process does not require administrator rights, so I would like to implement the launch with the rights of a normal user. How to do it?
At the moment I'm running the process using boost::process::system.
Raymond Chen has a blog article on this very topic:
How can I launch an unelevated process from my elevated process and vice versa?
Going from an unelevated process to an elevated process is easy. You can run a process with elevation by passing the runas verb to ShellExecute or ShellExecuteEx.
Going the other way is trickier. For one thing, it's really hard to munge your token to remove the elevation nature properly. And for another thing, even if you could do it, it's not the right thing to do, because the unelevated user may be different from the elevated user.
...
The solution here is to go back to Explorer and ask Explorer to launch the program for you. Since Explorer is running as the original unelevated user, the program ... will run as [the user].
His article provides the following code to launch an unelevated process, and a detailed explanation of what the code is actually doing (the FindDesktopFolderView function mentioned below is defined in his Manipulating the positions of desktop icons blog article):
#define STRICT
#include <windows.h>
#include <shldisp.h>
#include <shlobj.h>
#include <exdisp.h>
#include <atlbase.h>
#include <stdlib.h>
// FindDesktopFolderView incorporated by reference
void GetDesktopAutomationObject(REFIID riid, void **ppv)
{
CComPtr<IShellView> spsv;
FindDesktopFolderView(IID_PPV_ARGS(&spsv));
CComPtr<IDispatch> spdispView;
spsv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&spdispView));
spdispView->QueryInterface(riid, ppv);
}
void ShellExecuteFromExplorer(
PCWSTR pszFile,
PCWSTR pszParameters = nullptr,
PCWSTR pszDirectory = nullptr,
PCWSTR pszOperation = nullptr,
int nShowCmd = SW_SHOWNORMAL)
{
CComPtr<IShellFolderViewDual> spFolderView;
GetDesktopAutomationObject(IID_PPV_ARGS(&spFolderView));
CComPtr<IDispatch> spdispShell;
spFolderView->get_Application(&spdispShell);
CComQIPtr<IShellDispatch2>(spdispShell)
->ShellExecute(CComBSTR(pszFile),
CComVariant(pszParameters ? pszParameters : L""),
CComVariant(pszDirectory ? pszDirectory : L""),
CComVariant(pszOperation ? pszOperation : L""),
CComVariant(nShowCmd));
}
int __cdecl wmain(int argc, wchar_t **argv)
{
if (argc < 2) return 0;
CCoInitialize init;
ShellExecuteFromExplorer(
argv[1],
argc >= 3 ? argv[2] : L"",
argc >= 4 ? argv[3] : L"",
argc >= 5 ? argv[4] : L"",
argc >= 6 ? _wtoi(argv[5]) : SW_SHOWNORMAL);
return 0;
}
if we run as admin (more concrete have S-1-5-32-544 'Administrators' group enabled in token) we can open local system process token (it grant all needed to as access for 'Administrators').
so we can do next:
get self terminal session id
enable debug privileges in token, for be able open any process with
PROCESS_QUERY_LIMITED_INFORMATION
enumerate running processes in system
open process with PROCESS_QUERY_LIMITED_INFORMATION
open process token with TOKEN_QUERY|TOKEN_DUPLICATE
query token privileges - are it have
SE_ASSIGNPRIMARYTOKEN_PRIVILEGE and SE_INCREASE_QUOTA_PRIVILEGE -
it need for call CreateProcessAsUser and SE_TCB_PRIVILEGE it
require for WTSQueryUserToken
if token have all this privileges (LocalSystem token have) -
duplicate this token to TokenImpersonation type.
if need enable some of this 3 privileges in token
impersonate with this token
and now we can
call WTSQueryUserToken with self session id - for get not elevated
user token
and finally CreateProcessAsUser with this token
reset impersonation
code:
static volatile UCHAR guz;
ULONG RunNonElevated(_In_ ULONG SessionId,
_In_ HANDLE hToken,
_In_opt_ LPCWSTR lpApplicationName,
_Inout_opt_ LPWSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags,
_In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCWSTR lpCurrentDirectory,
_In_ LPSTARTUPINFOW lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation)
{
ULONG err;
PVOID stack = alloca(guz);
ULONG cb = 0, rcb = FIELD_OFFSET(TOKEN_PRIVILEGES, Privileges[SE_MAX_WELL_KNOWN_PRIVILEGE]);
union {
PVOID buf;
::PTOKEN_PRIVILEGES ptp;
};
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
}
if (GetTokenInformation(hToken, ::TokenPrivileges, buf, cb, &rcb))
{
if (ULONG PrivilegeCount = ptp->PrivilegeCount)
{
int n = 3;
BOOL fAdjust = FALSE;
::PLUID_AND_ATTRIBUTES Privileges = ptp->Privileges;
do
{
switch (Privileges->Luid.LowPart)
{
case SE_ASSIGNPRIMARYTOKEN_PRIVILEGE:
case SE_INCREASE_QUOTA_PRIVILEGE:
case SE_TCB_PRIVILEGE:
if (!(Privileges->Attributes & SE_PRIVILEGE_ENABLED))
{
Privileges->Attributes |= SE_PRIVILEGE_ENABLED;
fAdjust = TRUE;
}
if (!--n)
{
err = NOERROR;
if (DuplicateTokenEx(hToken,
TOKEN_ADJUST_PRIVILEGES|TOKEN_IMPERSONATE,
0, ::SecurityImpersonation, ::TokenImpersonation,
&hToken))
{
if (fAdjust)
{
AdjustTokenPrivileges(hToken, FALSE, ptp, rcb, NULL, NULL);
err = GetLastError();
}
if (err == NOERROR)
{
if (SetThreadToken(0, hToken))
{
HANDLE hUserToken;
if (WTSQueryUserToken(SessionId, &hUserToken))
{
if (!CreateProcessAsUserW(hUserToken,
lpApplicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttributes,
bInheritHandles,
dwCreationFlags,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
lpProcessInformation))
{
err = GetLastError();
}
CloseHandle(hUserToken);
}
else
{
err = GetLastError();
}
SetThreadToken(0, 0);
}
else
{
err = GetLastError();
}
}
CloseHandle(hToken);
}
else
{
err = GetLastError();
}
return err;
}
}
} while (Privileges++, --PrivilegeCount);
}
return ERROR_NOT_FOUND;
}
} while ((err = GetLastError()) == ERROR_INSUFFICIENT_BUFFER);
return err;
}
ULONG RunNonElevated(_In_opt_ LPCWSTR lpApplicationName,
_Inout_opt_ LPWSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags,
_In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCWSTR lpCurrentDirectory,
_In_ LPSTARTUPINFOW lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation
)
{
ULONG SessionId;
if (!ProcessIdToSessionId(GetCurrentProcessId(), &SessionId))
{
return GetLastError();
}
BOOLEAN b;
RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, TRUE, FALSE, b);
ULONG err = NOERROR;
// much more effective of course use NtQuerySystemInformation(SystemProcessesAndThreadsInformation) here
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0), hToken;
if (hSnapshot != INVALID_HANDLE_VALUE)
{
PROCESSENTRY32W pe = { sizeof(pe) };
if (Process32FirstW(hSnapshot, &pe))
{
err = ERROR_NOT_FOUND;
do
{
if (pe.th32ProcessID && pe.th32ParentProcessID)
{
if (HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe.th32ProcessID))
{
if (OpenProcessToken(hProcess, TOKEN_QUERY|TOKEN_DUPLICATE, &hToken))
{
err = RunNonElevated(
SessionId,
hToken,
lpApplicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttributes,
bInheritHandles,
dwCreationFlags,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
lpProcessInformation);
CloseHandle(hToken);
}
else
{
err = GetLastError();
}
CloseHandle(hProcess);
}
else
{
err = GetLastError();
}
}
} while (err && Process32NextW(hSnapshot, &pe));
}
else
{
err = GetLastError();
}
CloseHandle(hSnapshot);
}
return err;
}
and test:
void test()
{
STARTUPINFO si = { sizeof(si)};
PROCESS_INFORMATION pi;
WCHAR ApplicationName[MAX_PATH];
if (GetEnvironmentVariableW(L"ComSpec", ApplicationName, RTL_NUMBER_OF(ApplicationName)))
{
if (!RunNonElevated(ApplicationName, L"cmd /k whoami.exe /priv /groups",0,0,0,0,0,0,&si, &pi))
{
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
}
}
with this we can not only run not elevated process, but ,if need, start it with duplicated handles
since most links to this particular issue on http://undocumented.ntinternals.net are apparently dead and the NtQueryInfoThread along with relevant THREADINFOCLASSes has vanished from the Winternl.h I am now sitting here struggling to find the TEB of a process that I know the handle of.
I tried loading the method from the ntdll.dll, which was another solution that seemed to work but sadly I still fail to get the desired address.
typedef NTSTATUS(*ThreadInfoProc)(HANDLE, THREADINFOCLASS, PVOID, ULONG, PULONG);
PVOID CProcessHelper::GetThreadStackTopAddress(HANDLE hThread)
{
HINSTANCE ntdllInstance;
ThreadInfoProc NtQueryInfoThread;
ntdllInstance = LoadLibrary("Ntdll.dll");
if (ntdllInstance != NULL)
{
NtQueryInfoThread = (ThreadInfoProc)GetProcAddress(ntdllInstance, "NtQueryInformationThread");
if (NtQueryInfoThread != NULL)
{
THREAD_BASIC_INFORMATION bi;
NT_TIB tib;
NTSTATUS ntstat = 0;
NTSTATUS ntstat = (NtQueryInfoThread)(hThread, (THREADINFOCLASS)0, &bi, sizeof(THREAD_BASIC_INFORMATION),NULL);
ReadProcessMemory(CurrentProcessHandle, bi.TebBaseAddress, &tib, sizeof(NT_TIB), 0);
PrintHex(tib.StackBase); // output: CCCCCCCCCC
}
}
return nullptr;
}
Is there any other way, perhaps using public api calls to get the TEB of a thread? (As MSDN states that this approach should not be used any longer.)
Best Regards,
Alex
Works fine :S The only other way to get the TEB of a thread is to read it using:
NT_TIB* tib = (NT_TIB*)__readfsdword(0x18);
and read the base address from that.
Your calls may be failing because you might not have the right permissions to read the memory. Try using VirtualProtect?
The below works but I've only tested it on the current process..
#include <iostream>
#include <windows.h>
typedef LONG NTSTATUS;
typedef DWORD KPRIORITY;
typedef WORD UWORD;
typedef struct _CLIENT_ID
{
PVOID UniqueProcess;
PVOID UniqueThread;
} CLIENT_ID, *PCLIENT_ID;
typedef struct _THREAD_BASIC_INFORMATION
{
NTSTATUS ExitStatus;
PVOID TebBaseAddress;
CLIENT_ID ClientId;
KAFFINITY AffinityMask;
KPRIORITY Priority;
KPRIORITY BasePriority;
} THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;
enum THREADINFOCLASS
{
ThreadBasicInformation,
};
void* GetThreadStackTopAddress(HANDLE hProcess, HANDLE hThread)
{
bool loadedManually = false;
HMODULE module = GetModuleHandle("ntdll.dll");
if (!module)
{
module = LoadLibrary("ntdll.dll");
loadedManually = true;
}
NTSTATUS (__stdcall *NtQueryInformationThread)(HANDLE ThreadHandle, THREADINFOCLASS ThreadInformationClass, PVOID ThreadInformation, ULONG ThreadInformationLength, PULONG ReturnLength);
NtQueryInformationThread = reinterpret_cast<decltype(NtQueryInformationThread)>(GetProcAddress(module, "NtQueryInformationThread"));
if (NtQueryInformationThread)
{
NT_TIB tib = {0};
THREAD_BASIC_INFORMATION tbi = {0};
NTSTATUS status = NtQueryInformationThread(hThread, ThreadBasicInformation, &tbi, sizeof(tbi), nullptr);
if (status >= 0)
{
ReadProcessMemory(hProcess, tbi.TebBaseAddress, &tib, sizeof(tbi), nullptr);
if (loadedManually)
{
FreeLibrary(module);
}
return tib.StackBase;
}
}
if (loadedManually)
{
FreeLibrary(module);
}
return nullptr;
}
void __stdcall Test()
{
for (int i = 0; i < 10; ++i)
{
printf("Hi. ");
Sleep(500);
}
}
int main()
{
std::cout<<GetThreadStackTopAddress(GetCurrentProcess(), GetCurrentThread())<<"\n";
DWORD threadID = 0;
HANDLE hThread = CreateThread(nullptr, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(Test), nullptr, 0, &threadID);
std::cout<<GetThreadStackTopAddress(GetCurrentProcess(), hThread)<<"\n\n";
CloseHandle(hThread);
Sleep(7000);
return 0;
}