如何"unelevate"流程

How can I "unelevate" a process

本文关键字:流程 unelevate 如何      更新时间:2023-10-16

我有一个提升的进程,它是在用户在UAC对话框中回答"是"后启动的。

这个过程开始得很好,一切都如预期。

现在,我需要在某个时候"取消提升"该进程,换句话说,该进程不应该像用户正常启动一样被提升。

示例场景

  • 用户A已登录
  • 用户A启动将通过UAC提升的进程P1
  • 提升进程P1启动进程P2和P2不应被提升,应在用户A下再次运行

有办法做到这一点吗?

提升的进程有链接的令牌-它指的是未提升的用户会话。我们可以通过两种方式使用这个链接的令牌:

第一种方式

  1. 将其作为TokenPrimary(为此,我们需要SE_TCB_PRIVILEGE当我们查询此令牌时(
  2. 使用此令牌调用CreateProcessAsUser。为此,我们还需要SE_ASSIGNPRIMARYTOKEN_PRIVILEGESE_INCREASE_QUOTA_PRIVILEGE
  3. 为了获得所有这些特权-枚举进程,查询it令牌,如果进程令牌具有所有这3个权限,则使用模拟在调用CreateProcessAsUser之前。因为提升的令牌具有SE_DEBUG_PRIVILEGE任务是可能的

第二种方式:

  1. 从链接的令牌中查询登录会话id(AuthenticationId来自TOKEN_STATISTICS(

  2. 在进程令牌中找到具有相同AuthenticationId的进程。

  3. 通过帮助将此进程用作父进程PROC_THREAD_ATTRIBUTE_PARENT_PROCESS

方式1:的实现

static volatile UCHAR guz;
ULONG RunNonElevated(HANDLE hToken, HANDLE hMyToken, PCWSTR lpApplicationName, PWSTR lpCommandLine)
{
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))
{
TOKEN_LINKED_TOKEN tlt;
if (GetTokenInformation(hMyToken, TokenLinkedToken, &tlt, sizeof(tlt), &rcb))
{
STARTUPINFO si = { sizeof (si) };
PROCESS_INFORMATION pi;
if (!CreateProcessAsUserW(tlt.LinkedToken, lpApplicationName, lpCommandLine, 
NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
{
err = GetLastError();
}
CloseHandle(tlt.LinkedToken);
if (err == NOERROR)
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
}
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(HANDLE hMyToken, PCWSTR lpApplicationName, PWSTR lpCommandLine)
{
static TOKEN_PRIVILEGES tp = {
1, { { { SE_DEBUG_PRIVILEGE } , SE_PRIVILEGE_ENABLED } }
};
AdjustTokenPrivileges(hMyToken, FALSE, &tp, sizeof(tp), NULL, NULL);
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(hToken, hMyToken, lpApplicationName, lpCommandLine);
CloseHandle(hToken);
}
else
{
err = GetLastError();
}
CloseHandle(hProcess);
}
else
{
err = GetLastError();
}
}
} while (err && Process32NextW(hSnapshot, &pe));
}
else
{
err = GetLastError();
}
CloseHandle(hSnapshot);
}
return err;
}
ULONG RunNonElevated(PCWSTR lpApplicationName, PWSTR lpCommandLine)
{
HANDLE hToken;
ULONG err = NOERROR;
if (OpenProcessToken(NtCurrentProcess(), TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES, &hToken))
{
TOKEN_ELEVATION_TYPE tet;
ULONG rcb;
if (GetTokenInformation(hToken, ::TokenElevationType, &tet, sizeof(tet), &rcb))
{
if (tet == TokenElevationTypeFull)
{
RunNonElevated(hToken, lpApplicationName, lpCommandLine);
}
else
{
err = ERROR_ALREADY_ASSIGNED;
}
}
else
{
err = GetLastError();
}
CloseHandle(hToken);
}
else
{
err = GetLastError();
}
return err;
}

方式2的实现:

ULONG CreateProcessEx(HANDLE hProcess,
PCWSTR lpApplicationName,
PWSTR lpCommandLine)
{
SIZE_T Size = 0;
STARTUPINFOEX si = { sizeof(si) };
PROCESS_INFORMATION pi;
InitializeProcThreadAttributeList(0, 1, 0, &Size);
ULONG err = GetLastError();
if (err = ERROR_INSUFFICIENT_BUFFER)
{
si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)alloca(Size);
if (InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &Size))
{
if (UpdateProcThreadAttribute(si.lpAttributeList, 0, 
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hProcess, sizeof(hProcess), 0, 0) &&
CreateProcessW(lpApplicationName, lpCommandLine, 0, 0, 0, 
EXTENDED_STARTUPINFO_PRESENT, 0, 0, &si.StartupInfo, &pi))
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
else
{
err = GetLastError();
}
DeleteProcThreadAttributeList(si.lpAttributeList);
}
else
{
err = GetLastError();
}
}
else
{
err = GetLastError();
}
return err;
}
ULONG CreateProcessEx(LUID AuthenticationId,
PCWSTR lpApplicationName,
PWSTR lpCommandLine)
{
ULONG err = ERROR_NOT_FOUND;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot != INVALID_HANDLE_VALUE)
{
PROCESSENTRY32W pe = { sizeof(pe) };
ULONG rcb;
if (Process32First(hSnapshot, &pe))
{
err = ERROR_NOT_FOUND;
BOOL found = FALSE;
do 
{
if (pe.th32ProcessID && pe.th32ParentProcessID)
{
if (HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION|PROCESS_CREATE_PROCESS, FALSE, pe.th32ProcessID))
{
HANDLE hToken;                  
if (OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))
{
TOKEN_STATISTICS ts;
if (GetTokenInformation(hToken, TokenStatistics, &ts, sizeof(ts), &rcb))
{
if (ts.AuthenticationId.LowPart == AuthenticationId.LowPart && 
ts.AuthenticationId.HighPart == AuthenticationId.HighPart)
{
found = TRUE;
err = CreateProcessEx(hProcess,
lpApplicationName,
lpCommandLine);
}
}
CloseHandle(hToken);
}
CloseHandle(hProcess);
}
}
} while (!found && Process32Next(hSnapshot, &pe));
}
else
{
err = GetLastError();
}
CloseHandle(hSnapshot);
}
else
{
err = GetLastError();
}
return err;
}
ULONG CreateProcessEx(PCWSTR lpApplicationName,
PWSTR lpCommandLine)
{
HANDLE hToken;
ULONG err = NOERROR;
if (OpenProcessToken(NtCurrentProcess(), TOKEN_QUERY, &hToken))
{
union {
TOKEN_ELEVATION_TYPE tet;
TOKEN_LINKED_TOKEN tlt;
};
ULONG rcb;
if (GetTokenInformation(hToken, TokenElevationType, &tet, sizeof(tet), &rcb))
{
if (tet == TokenElevationTypeFull)
{
if (GetTokenInformation(hToken, TokenLinkedToken, &tlt, sizeof(tlt), &rcb))
{
TOKEN_STATISTICS ts;
BOOL fOk = GetTokenInformation(tlt.LinkedToken, TokenStatistics, &ts, sizeof(ts), &rcb);
CloseHandle(tlt.LinkedToken);
if (fOk)
{
err = CreateProcessEx(ts.AuthenticationId,
lpApplicationName,
lpCommandLine);
}
else
{
err = GetLastError();
}
}
else
{
err = GetLastError();
}
}
else
{
err = ERROR_ALREADY_ASSIGNED;
}
}
else
{
err = GetLastError();
}
CloseHandle(hToken);
}
else
{
err = GetLastError();
}
return err;
}

和测试:

WCHAR ApplicationName[MAX_PATH];
if (GetEnvironmentVariableW(L"ComSpec", ApplicationName, RTL_NUMBER_OF(ApplicationName)))
{
WCHAR cmdline[] = L"cmd.exe /k whoami /priv /groupsrn";
CreateProcessEx(ApplicationName, cmdline);
RunNonElevated(ApplicationName, cmdline);
}

对于方法#2,理论上我们找不到与链接令牌中具有相同登录id(AuthenticationId(的进程。但是方式#1总是必须起作用的。总是存在具有SeTcbPrivilege(用于获取链接令牌的主要形式(+SeAssignPrimaryTokenPrivilege(用于CreateProcessAsUser(的系统进程(SeIncreaseQuotaPrivilege在msdn中侦听,这是CreateProcessAsUser的典型要求,但在我的测试中,即使没有启用此特权,也能工作(。然而,所有系统进程(以LocalSystem运行(在令牌中都具有这3个特权(从smss.exe开始(,并且一些系统进程总是在系统中运行。

所以方式#1必须永远不会失败并且是首选的。例如,我们还可以使用从流程中继承的句柄来与子流程进行交互。这是不可能的方式#2。它显示的是图片的完整性


开始时,我们检查TOKEN_ELEVATION_TYPE并执行作业,仅当它是TokenElevationTypeFull时。在TokenElevationTypeLimited的情况下,我们没有提升进程,所以没有什么要做的。如果TokenElevationTypeDefault是指UAC,如果关闭(禁用LUA(,或者我们以内置管理员的身份运行,并且LUA不过滤该帐户的令牌(因此所有进程都是"提升的",或者更确切地说,它没有通过CreateRestrictedToken(..LUA_TOKEN..)过滤令牌(-在这种情况下,也没有意义,尝试在该用户下运行"未提升的"进程