获取访问令牌DEBUG权限
getCurrentProcess 函数 (processthreadsapi.h)
官方文档:https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentprocess
检索当前进程的伪句柄。伪句柄是一个特殊常量,此句柄具有对进程对象的PROCESS_ALL_ACCESS访问权限。
HANDLE GetCurrentProcess();
OpenProcessToken 函数 (processthreadsapi.h)
官方文档:https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocesstoken
OpenProcessToken 函数打开与进程关联的访问令牌。如果正在检查的进程是由其他帐户启动的,则检查过程需要启用SE_DEBUG_NAME特权(必须提供此常量才能调试和调整另一帐户拥有的进程的内存)。
BOOL OpenProcessToken(
[in] HANDLE ProcessHandle,
[in] DWORD DesiredAccess,
[out] PHANDLE TokenHandle
);
参数 | 作用 |
---|---|
[in] ProcessHandle | 打开访问令牌的进程句柄。进程必须具有PROCESS_QUERY_LIMITED_INFORMATION访问权限。 |
[in] DesiredAccess | 指定访问掩码,该掩码指定访问令牌的请求访问类型。 |
[out] TokenHandle | 指向句柄的指针,该句柄标识函数返回时新打开的访问令牌。 |
lookupPrivilegeValueA 函数 (winbase.h)
官方文档:https://learn.microsoft.com/zh-cn/windows/win32/api/winbase/nf-winbase-lookupprivilegevaluea
LookupPrivilegeValue 函数检索本地唯一标识符 (LUID) 指定系统上用于本地表示指定特权名称。
BOOL LookupPrivilegeValueA(
[in, optional] LPCSTR lpSystemName,
[in] LPCSTR lpName,
[out] PLUID lpLuid
);
参数 | 作用 |
---|---|
[in, optional] lpSystemName | 指向以 null 结尾的字符串的指针,该字符串指定检索特权名称的系统的名称。如果指定了空字符串,则该函数将尝试在本地系统上查找特权名称。 |
[in] lpName | 指向以 null 结尾的字符串的指针,该字符串指定权限的名称。 |
[out] lpLuid | 指向一个变量的指针,该变量接收 LUID,lpSystemName 参数指定的系统上的权限是已知的。 |
TOKEN_PRIVILEGES结构 (winnt.h)
官方文档:https://learn.microsoft.com/zh-cn/windows/win32/api/winnt/ns-winnt-token_privileges
TOKEN_PRIVILEGES 结构包含有关访问令牌的一组特权的信息。
typedef struct _TOKEN_PRIVILEGES {
DWORD PrivilegeCount;
LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY];
} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;
参数 | 作用 |
---|---|
PrivilegeCount | 必须将其设置为 Privileges 数组中的条目数。 |
Privileges | 指定 LUID_AND_ATTRIBUTES 结构的数组。每个结构都包含特权的 LUID 和属性。若要获取与 LUID 关联的特权的名称,请调用 LookupPrivilegeName 函数,并将 LUID 的地址作为 lpLuid 参数的值传递。 |
Privileges特权的属性可以是以下值的组合:
特权值 | 含义 |
---|---|
SE_PRIVILEGE_ENABLED | 权限已启用。 |
SE_PRIVILEGE_ENABLED_BY_DEFAULT | 默认情况下,权限处于启用状态。 |
SE_PRIVILEGE_REMOVED | 用于删除特权。 |
SE_PRIVILEGE_USED_FOR_ACCESS | 用于获取对对象或服务的访问权限。 |
LUID_AND_ATTRIBUTES 结构(winnt.h)
官方文档:https://learn.microsoft.com/zh-cn/windows/win32/api/winnt/ns-winnt-luid_and_attributes
LUID_AND_ATTRIBUTES 结构表示 LUID(本地唯一标识符) 及其属性。
typedef struct _LUID_AND_ATTRIBUTES {
LUID Luid;
DWORD Attributes;
} LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES;
参数 | 作用 |
---|---|
Luid | 指定 LUID 值。 |
Attributes | 指定 LUID 的属性。此值最多包含 32 个一位标志。其含义取决于 LUID 的定义和使用。 |
adjustTokenPrivileges 函数 (securitybaseapi.h)
官方文档:https://learn.microsoft.com/zh-cn/windows/win32/api/securitybaseapi/nf-securitybaseapi-adjusttokenprivileges
AdjustTokenPrivileges 函数启用或禁用指定访问令牌中的特权。启用或禁用访问令牌中的特权需要TOKEN_ADJUST_PRIVILEGES访问权限。
BOOL AdjustTokenPrivileges(
[in] HANDLE TokenHandle,
[in] BOOL DisableAllPrivileges,
[in, optional] PTOKEN_PRIVILEGES NewState,
[in] DWORD BufferLength,
[out, optional] PTOKEN_PRIVILEGES PreviousState,
[out, optional] PDWORD ReturnLength
);
参数 | 作用 |
---|---|
[in] TokenHandle | 包含要修改的权限的访问令牌的句柄。句柄必须具有TOKEN_ADJUST_PRIVILEGES令牌的访问权限。如果 PreviousState 参数不为 NULL,则句柄还必须具有TOKEN_QUERY访问权限。 |
[in] DisableAllPrivileges | 指定函数是否禁用令牌的所有特权。如果此值为 TRUE,则函数将禁用所有特权并忽略 NewState 参数。如果为 FALSE,则函数根据 NewState 参数指向的信息修改权限。 |
[in, optional] NewState | 指向 TOKEN_PRIVILEGES 结构的指针,该结构指定特权及其属性的数组。如果 DisableAllPrivileges 参数为 FALSE, 则 AdjustTokenPrivileges 函数将启用、禁用或删除令牌的这些特权。 |
[in] BufferLength | 指定 PreviousState 参数指向的缓冲区的大小(以字节为单位)。如果 PreviousState 参数为 NULL,则此参数可以为 零。 |
[out, optional] PreviousState | 指向缓冲区的指针,该缓冲区由 函数填充TOKEN_PRIVILEGES 结构,该结构包含函数修改的任何特权的先前状态。 如果 TOKEN_PRIVILEGES 的 PrivilegeCount 成员为零,则此函数未更改任何特权。此参数可以为 NULL。 |
[out, optional] ReturnLength | 指向变量的指针,该变量接收 PreviousState 参数指向的缓冲区的所需大小(以字节为单位)。如果 PreviousState 为 NULL,此参数可以为 NULL。 |
源码分析
要对一个任意进程(包括系统安全进程和服务进程)进行指定了写相关的访问权的OpenProcess操作,只要当前进程具有SeDeDebug权限就可以了。
-
首先要通过OpenProcessToken获取到当前进程GetCurrentProcess的访问令牌;
-
再用LookupPrivilegeValue获取SE_DEBUG_NAME对应的luid数值;
-
设置访问令牌的特权属性SE_PRIVILEGE_ENABLED;
-
通过AdjustTokenPrivileges启用luid对应的权限。
BOOL CurrentProcessAdjustToken(void)
{
HANDLE hToken;
TOKEN_PRIVILEGES sTP;
// 1. 首先要通过OpenProcessToken获取到当前进程GetCurrentProcess的访问令牌
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
// 2. 再用LookupPrivilegeValue获取SE_DEBUG_NAME对应的luid数值
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &sTP.Privileges[0].Luid))
{
CloseHandle(hToken);
return FALSE;
}
// 3. 设置访问令牌的特权属性SE_PRIVILEGE_ENABLED,其用luid对应的权限
sTP.PrivilegeCount = 1;
sTP.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// 4. 通过AdjustTokenPrivileges启用luid对应的权限
if (!AdjustTokenPrivileges(hToken, 0, &sTP, sizeof(sTP), NULL, NULL))
{
CloseHandle(hToken);
return FALSE;
}
CloseHandle(hToken);
return TRUE;
}
return FALSE;
}
参考
[1]https://blog.didierstevens.com/2009/11/22/quickpost-selectmyparent-or-playing-with-the-windows-process-tree/
原文始发于微信公众号(蟹堡安全团队):父进程欺骗--DidierStevens(二)
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论