从 SID 创建用户令牌,在用户上下文中展开环境变量
Posted
技术标签:
【中文标题】从 SID 创建用户令牌,在用户上下文中展开环境变量【英文标题】:Create a user token from SID, expand environment variables in user context 【发布时间】:2017-11-21 11:57:26 【问题描述】:我有一个服务正在运行,并且想要访问常见的用户文件夹,如启动。为此,我想为系统上的每个用户(包括注销)扩展环境变量,如 %APPDATA%
。我可以获取登录用户的会话 ID 并从中创建一个令牌,然后调用 ExpandEnvironmentStringsForUser()
。但是注销的用户呢?他们不会有会话。我唯一能为他们得到的是帐户名(使用NetUserEnum()
或NetQueryDisplayInformation()
)和注册表中的SID(HKLM\software\Microst\Windows NT\current Version\Profile List
)
我可以从 SID 获取用户令牌或使用 SID 模拟用户,或者有什么方法可以使用 SID 扩展环境变量。
编辑:
我需要从所有用户的启动位置删除一些文件。为此,我需要在每个用户的上下文中展开 %APPDATA%
和 %USERPROFILE%
,无论是否登录。
编辑 2:
问题归结为为不同的用户扩展环境变量,如%APPDATA%
,而没有该用户的令牌。
【问题讨论】:
如果你能得到用户名,你不能访问来自c:\Users\%username%\Appdata\Roaming\Microsoft\StartMenu\Programs\Startup
的目录吗?
如果用户已更改%APPDATA%
你的最终目标是什么? - 可能有更好的方法,例如使用 \所有用户 启动或通过登录脚本运行任务来执行操作。
可能相关:1) ***.com/q/45458728/298054; 2)***.com/q/37630726/298054;
【参考方案1】:
从任何给定的 SID 创建令牌是可能的,但不是简单的。存在用于创建令牌的未记录系统 api:
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
);
这里 AuthenticationId 必须是一些有效的登录会话 id,否则我们会得到 STATUS_NO_SUCH_LOGON_SESSION
错误。例如,我们可以从当前进程令牌中获取此值。所有其他参数,通常可以是任何有效的感知数据。所以可以通过下一种方式创建令牌:
NTSTATUS CreateUserToken(PHANDLE phToken, PSID Sid)
HANDLE hToken;
NTSTATUS status = NtOpenProcessToken(NtCurrentProcess(), TOKEN_QUERY, &hToken);
if (0 <= status)
TOKEN_STATISTICS ts;
status = NtQueryInformationToken(hToken, TokenStatistics, &ts, sizeof(ts), &ts.DynamicCharged);
NtClose(hToken);
if (0 <= status)
TOKEN_PRIMARY_GROUP tpg = Sid ;
TOKEN_USER User = Sid ;
static TOKEN_SOURCE Source = "User32 " ;
static TOKEN_DEFAULT_DACL tdd;
static _SID EveryOne = SID_REVISION, 1, SECURITY_WORLD_SID_AUTHORITY, SECURITY_WORLD_RID ;
static TOKEN_GROUPS Groups = 1, &EveryOne, SE_GROUP_ENABLED|SE_GROUP_MANDATORY ;
struct TOKEN_PRIVILEGES_3
ULONG PrivilegeCount;
LUID_AND_ATTRIBUTES Privileges[3];
Privileges =
3,
SE_BACKUP_PRIVILEGE , SE_PRIVILEGE_ENABLED|SE_PRIVILEGE_ENABLED_BY_DEFAULT ,
SE_RESTORE_PRIVILEGE , SE_PRIVILEGE_ENABLED|SE_PRIVILEGE_ENABLED_BY_DEFAULT ,
SE_CHANGE_NOTIFY_PRIVILEGE , SE_PRIVILEGE_ENABLED|SE_PRIVILEGE_ENABLED_BY_DEFAULT
;
static SECURITY_QUALITY_OF_SERVICE sqos =
sizeof sqos, SecurityImpersonation, SECURITY_DYNAMIC_TRACKING
;
static OBJECT_ATTRIBUTES oa =
sizeof oa, 0, 0, 0, 0, &sqos
;
status = NtCreateToken(phToken, TOKEN_ALL_ACCESS, &oa, TokenImpersonation,
&ts.AuthenticationId, &ts.ExpirationTime, &User, &Groups, (PTOKEN_PRIVILEGES)&Privileges, 0,
&tpg, &tdd, &Source);
return status;
这个令牌将被赋予 SID 作为令牌用户 sid,3 个特权(SE_BACKUP_PRIVILEGE
、SE_RESTORE_PRIVILEGE
- 这需要调用 LoadUserProfile
api 和 SE_CHANGE_NOTIFY_PRIVILEGE
以获得遍历特权)和一组 - 每个人 (s-1-1-0)。
但是对于调用NtCreateToken
,我们必须拥有SE_CREATE_TOKEN_PRIVILEGE
特权,否则我们会收到错误STATUS_PRIVILEGE_NOT_HELD
。大多数系统进程都没有它。只有少数(如 lsass.exe)。说 services.exe 和所有服务 - 没有这个特权。所以一开始我们必须得到它。这可以通过枚举进程、查看具有此特权的进程、从该进程获取令牌并模拟它来完成:
BOOL g_IsXP;// true if we on winXP, false otherwise
static volatile UCHAR guz;
OBJECT_ATTRIBUTES zoa = sizeof zoa ;
NTSTATUS ImpersonateIfConformToken(HANDLE hToken)
ULONG cb = 0, rcb = 0x200;
PVOID stack = alloca(guz);zoa;
union
PVOID buf;
PTOKEN_PRIVILEGES ptp;
;
NTSTATUS status;
do
if (cb < rcb)
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
if (0 <= (status = NtQueryInformationToken(hToken, TokenPrivileges, buf, cb, &rcb)))
if (ULONG PrivilegeCount = ptp->PrivilegeCount)
ULONG n = 1;
BOOL bNeedAdjust = FALSE;
PLUID_AND_ATTRIBUTES Privileges = ptp->Privileges;
do
if (!Privileges->Luid.HighPart)
switch (Privileges->Luid.LowPart)
case SE_CREATE_TOKEN_PRIVILEGE:
if (!(Privileges->Attributes & SE_PRIVILEGE_ENABLED))
Privileges->Attributes |= SE_PRIVILEGE_ENABLED;
bNeedAdjust = TRUE;
if (!--n)
static SECURITY_QUALITY_OF_SERVICE sqos =
sizeof sqos, SecurityImpersonation, SECURITY_STATIC_TRACKING, FALSE
;
static OBJECT_ATTRIBUTES soa = sizeof(soa), 0, 0, 0, 0, &sqos ;
if (0 <= (status = NtDuplicateToken(hToken, TOKEN_ADJUST_PRIVILEGES|TOKEN_IMPERSONATE, &soa, FALSE, TokenImpersonation, &hToken)))
if (bNeedAdjust)
status = NtAdjustPrivilegesToken(hToken, FALSE, ptp, 0, 0, 0);
if (status == STATUS_SUCCESS)
status = NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, &hToken, sizeof(HANDLE));
NtClose(hToken);
return status;
break;
while (Privileges++, --PrivilegeCount);
return STATUS_PRIVILEGE_NOT_HELD;
while (status == STATUS_BUFFER_TOO_SMALL);
return status;
NTSTATUS GetCreateTokenPrivilege()
BOOLEAN b;
NTSTATUS status = RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, TRUE, FALSE, &b);
ULONG cb = 0x10000;
do
status = STATUS_INSUFF_SERVER_RESOURCES;
if (PVOID buf = LocalAlloc(0, cb))
if (0 <= (status = NtQuerySystemInformation(SystemProcessInformation, buf, cb, &cb)))
status = STATUS_UNSUCCESSFUL;
ULONG NextEntryOffset = 0;
union
PVOID pv;
PBYTE pb;
PSYSTEM_PROCESS_INFORMATION pspi;
;
pv = buf;
do
pb += NextEntryOffset;
HANDLE hProcess, hToken;
if (pspi->UniqueProcessId && pspi->NumberOfThreads)
NTSTATUS s = NtOpenProcess(&hProcess,
g_xp ? PROCESS_QUERY_INFORMATION : PROCESS_QUERY_LIMITED_INFORMATION,
&zoa, &pspi->TH->ClientId);
if (0 <= s)
s = NtOpenProcessToken(hProcess, TOKEN_DUPLICATE|TOKEN_QUERY, &hToken);
NtClose(hProcess);
if (0 <= s)
s = ImpersonateIfConformToken(hToken);
NtClose(hToken);
if (0 <= s)
status = STATUS_SUCCESS;
break;
while (NextEntryOffset = pspi->NextEntryOffset);
LocalFree(buf);
while (status == STATUS_INFO_LENGTH_MISMATCH);
return status;
获得SE_CREATE_TOKEN_PRIVILEGE
权限后,我们可以通过这种方式获得一些已知的文件夹路径:
HRESULT GetGetKnownFolderPathBySid(REFKNOWNFOLDERID rfid, PSID Sid, PWSTR *ppszPath)
PROFILEINFO pi = sizeof(pi), PI_NOUI ;
pi.lpUserName = L"*";
HANDLE hToken;
NTSTATUS status = CreateUserToken(&hToken, Sid);
if (0 <= status)
if (LoadUserProfile(hToken, &pi))
status = SHGetKnownFolderPath(rfid, 0, hToken, ppszPath);
UnloadUserProfile(hToken, pi.hProfile);
else
status = HRESULT_FROM_WIN32(GetLastError());
CloseHandle(hToken);
else
status = HRESULT_FROM_NT(status);
return status;
例如获取 %AppData%
void PrintAppDataBySid(PSID Sid)
PWSTR path, szSid;
if (S_OK == GetGetKnownFolderPathBySid(FOLDERID_RoamingAppData, Sid, &path))
if (ConvertSidToStringSidW(Sid, &szSid))
DbgPrint("%S %S\n", szSid, path);
LocalFree(szSid);
CoTaskMemFree(path);
最后我们可以枚举本地用户配置文件,并为每个找到的 sid 获取它的 appdata 路径:
void EnumProf()
STATIC_OBJECT_ATTRIBUTES(soa, "\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList");
UNICODE_STRING ObjectName;
OBJECT_ATTRIBUTES oa = sizeof(oa), 0, &ObjectName, OBJ_CASE_INSENSITIVE ;
if (0 <= ZwOpenKey(&oa.RootDirectory, KEY_READ, &soa))
PVOID stack = alloca(sizeof(WCHAR));
union
PVOID buf;
PKEY_BASIC_INFORMATION pkbi;
PKEY_VALUE_PARTIAL_INFORMATION pkvpi;
;
DWORD cb = 0, rcb = 16;
NTSTATUS status;
ULONG Index = 0;
do
do
if (cb < rcb)
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
if (0 <= (status = ZwEnumerateKey(oa.RootDirectory, Index, KeyBasicInformation, buf, cb, &rcb)))
*(PWSTR)RtlOffsetToPointer(pkbi->Name, pkbi->NameLength) = 0;
PSID _Sid, Sid = 0;
BOOL fOk = ConvertStringSidToSidW(pkbi->Name, &_Sid);
if (fOk)
Sid = _Sid;
ObjectName.Buffer = pkbi->Name;
ObjectName.Length = (USHORT)pkbi->NameLength;
HANDLE hKey;
if (0 <= ZwOpenKey(&hKey, KEY_READ, &oa))
rcb = 64;
NTSTATUS s;
do
if (cb < rcb)
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
STATIC_UNICODE_STRING(usSid, "Sid");
if (0 <= (s = ZwQueryValueKey(hKey, &usSid, KeyValuePartialInformation, buf, cb, &rcb)))
if (pkvpi->DataLength >= sizeof(_SID) &&
IsValidSid(pkvpi->Data) &&
GetLengthSid(pkvpi->Data) == pkvpi->DataLength)
Sid = pkvpi->Data;
while (s == STATUS_BUFFER_OVERFLOW);
NtClose(hKey);
if (Sid)
PrintAppDataBySid(Sid);
if (fOk)
LocalFree(_Sid);
while (status == STATUS_BUFFER_OVERFLOW);
Index++;
while (0 <= status);
NtClose(oa.RootDirectory);
例如我得到下一个结果:
S-1-5-18 C:\Windows\system32\config\systemprofile\AppData\Roaming
S-1-5-19 C:\Windows\ServiceProfiles\LocalService\AppData\Roaming
S-1-5-20 C:\Windows\ServiceProfiles\NetworkService\AppData\Roaming
S-1-5-21-*-1000 C:\Users\defaultuser0\AppData\Roaming
S-1-5-21-*-1001 C:\Users\<user>\AppData\Roaming
【讨论】:
这段代码是否应该从驱动程序模式运行?我很难编译它。 @user3819404 - 当然不是。这是用户模式代码。您在编译时遇到问题 - 毫无疑问,因为您现在包含 ntifs.h (就像我这样做)。所以您可能需要自己直接声明一些 Nt api 声明,或者将其替换为 win32 模拟 - 比如说NtQueryInformationToken
到 GetTokenInformation
等等。但是未记录且仅在 Windows 头文件中未声明 NtCreateToken
注意TOKEN_DEFAULT_DACL tdd;
没有初始化,我收到了 ACCESS_DENIED(在调试模式下编译,它被设置为像 0xCC 这样的保护值),直到我做了TOKEN_DEFAULT_DACL tdd = ;
@Sirotnikov - 在我的代码中它声明为 static TOKEN_DEFAULT_DACL tdd;
- 所以它在我的代码中初始化为 0。你肯定错过了 static
关键字
你是对的。我只是想提到它,以防有人犯了我的错误,并花一个小时调试:)【参考方案2】:
如果您有 SID,我相信您可以从以下位置检索 AppData
值
HKEY_USERS\<SID>\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
.
不确定是否每个 Windows 版本都相同。
【讨论】:
用户未使用“HKU\ProfileImagePath
,启用SeRestorePrivilege,然后调用RegLoadKey
来加载用户的NTUSER.DAT 配置单元。以上是关于从 SID 创建用户令牌,在用户上下文中展开环境变量的主要内容,如果未能解决你的问题,请参考以下文章