windows权限管理
权限管理是通过账户权限实现的。用户一登录,就与os建立会话,分配权限。
权限在windows中被抽象为Token,它保存了
- 账户SID
- 组SID
- 权限
账户创建进程时,token会分配给进程,进程就有了访问资源的一定权限。
安全标识符SID,内有ACL和ACE(Entrance)。通过遍历ACL中的ACE知道找到有权限的账户或组。
// //
// Security Id (SID) //
// //
//
//
// Pictorially the structure of an SID is as follows:
//
// 1 1 1 1 1 1
// 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
// +---------------------------------------------------------------+
// | SubAuthorityCount |Reserved1 (SBZ)| Revision |
// +---------------------------------------------------------------+
// | IdentifierAuthority[0] |
// +---------------------------------------------------------------+
// | IdentifierAuthority[1] |
// +---------------------------------------------------------------+
// | IdentifierAuthority[2] |
// +---------------------------------------------------------------+
// | |
// +- - - - - - - - SubAuthority[] - - - - - - - - -+
// | |
// +---------------------------------------------------------------+
//
//
#ifndef SID_DEFINED
#define SID_DEFINED
typedef struct _SID {
BYTE Revision;
BYTE SubAuthorityCount;
SID_IDENTIFIER_AUTHORITY IdentifierAuthority;
#ifdef MIDL_PASS
[size_is(SubAuthorityCount)] DWORD SubAuthority[*];
#else // MIDL_PASS
DWORD SubAuthority[ANYSIZE_ARRAY];
#endif // MIDL_PASS
} SID, *PISID;
#endif
UAC
以前的windows,进程权限就是账户权限。但很多用户以管理员身份登录,这就让恶意程序有了可乘之机。
vista之后引入UAC机制,User Account Control,即使以管理员登录,创建进程时也会分配一个低权限令牌Filter Token,只有程序以管理员身份运行时,才弹出UAC盾牌对话框,确认后给予高权限。
关闭弹窗:
运行 - gpedit.msc - 组策略 - 计算机配置 - windows设置 - 安全设置 - 本地策略 - 安全选项 - 用户账户控制:管理员批准模式。。。(默认非二进制。。。)改为不提示
有时也会碰到某个按钮有UAC图标,对个别功能提权,提高用户体验。这种做法除了依赖权限控制,还有特殊的进程创建:
- 程序运行,判断进行权限;
- 低权限,则将按钮加上UAX盾牌标志;
- 高权限,则隐藏提权按钮;
- 点击提权按钮后,调用
ShellExecuteEx()
以管理员权限重新开启进程。
显示UAC按钮
流程:
- 获得本进程令牌;
- 获取提升类型
- 判断具体的权限状况
- 根据权限的不同控制按钮的显示
第二步又有这么几步:
获取令牌信息后,创建管理员组的对应SID --> 判断当前进程运行用户角色是否为管理员(获得FlilterToken后判断)。
大概流程:
BOOL MyDlg::QueryPrivileges()
{
// 1. 获得本进程的令牌
HANDLE hToken = NULL;
if (!OpenProcessToken())
{
return FALSE;
}
// 2. 获取提升类型
TOKEN_ELEVATION_TYPE elevationType = TokenElevationTypeDefault; // Enum type
BOOL bIsAdmin = FALSE;
DWORD dwSize = 0;
if (!GetTokenInformation())
{
return FALSE;
}
// 2.1 创建管理员组的对应SID
BYTE adminSID[SECURITY_MAX_SID_SIZE]; //管理员的SID标识
CreateWellKnownSid();
// 2.2 判断当前进程运行用户角色是否为管理员
// 当前权限是滤令牌(低权限令牌)时
if (elevationType == TokenElevationTypeLimited)
{
// a. 获取连接令牌的句柄
// b. 检查这个原始的令牌是否包含管理员的SID
}
else
{
// 如果令牌是最高权限-或者关闭UAC
// 那么直接获取当前用户是否是管理员
}
CloseHandle(hToken);
// 3. 判断具体的权限状况
BOOL bFullToken = FALSE;
switch(elevationType){}
// 4. 根据权限的不同控制按钮的显示
if(bFullToken){}
return FALSE;
}
完整代码:
BOOL CMFCTestDlg::OnInitDialog()
{
//...
QueryPrivileges();
//...
return TRUE;
}
// 查询进程权限
BOOL CMy01PrivilegesDlg::QueryPrivileges()
{
// 1. 获得本进程的令牌
HANDLE hToken = NULL;
if (!OpenProcessToken(
GetCurrentProcess(), // 当前进程句柄
TOKEN_QUERY, // 打开这个令牌作用
&hToken)) // 返回令牌句柄
return false;
// 2. 获取提升类型
TOKEN_ELEVATION_TYPE ElevationType = TokenElevationTypeDefault;
BOOL bIsAdmin = false; // 是否管理员
DWORD dwSize = 0; // 权限信息结构体大小
// 获取令牌信息
if (GetTokenInformation(
hToken, // 令牌句柄
TokenElevationType, // 获取令牌信息类型
&ElevationType, // 返回令牌信息类型对应的结构体
sizeof(TOKEN_ELEVATION_TYPE), // 结构体大小
&dwSize)) // 实际大小
{
// 2.1 创建管理员组的对应SID
BYTE adminSID[SECURITY_MAX_SID_SIZE]; //管理员的SID标识
dwSize = sizeof(adminSID); //管理员的SID标识的大小
// 获取管理员的SID
CreateWellKnownSid(
WinBuiltinAdministratorsSid, // 查询类型
NULL, // NULL
&adminSID, // 存放管理员的SID缓冲区
&dwSize); // 缓冲区大小
// 2.2 判断当前进程运行用户角色是否为管理员
// 当前权限是滤令牌(低权限令牌)
if (ElevationType == TokenElevationTypeLimited) {
// a. 获取连接令牌的句柄
HANDLE hUnfilteredToken = NULL;
GetTokenInformation(hToken, //令牌句柄
TokenLinkedToken, //链接令牌信息(账户令牌)
(PVOID)&hUnfilteredToken, //链接令牌的句柄
sizeof(HANDLE),
&dwSize);
// b. 检查这个原始的令牌是否包含管理员的SID
if (!CheckTokenMembership(
hUnfilteredToken, // 链接令牌句柄
&adminSID, // 管理员的SID
&bIsAdmin)) // 返回是否管理员
{
return false;
}
CloseHandle(hUnfilteredToken); //关闭句柄
}
else {
// 如果令牌是最高权限-或者关闭UAC
// 那么直接获取当前用户是否是管理员
bIsAdmin = IsUserAnAdmin();
}
CloseHandle(hToken);
}
// 3. 判断具体的权限状况
BOOL bFullToken = false;
// 判断提升类型
switch (ElevationType) {
case TokenElevationTypeDefault: /* 默认的用户或UAC被禁用 */
if (IsUserAnAdmin()) bFullToken = true; // 默认用户有管理员权限
else bFullToken = false;// 默认用户不是管理员组
break;
case TokenElevationTypeFull: /* 已经成功提高进程权限 */
if (IsUserAnAdmin()) bFullToken = true; //当前以管理员权限运行
else bFullToken = false;//当前未以管理员权限运行
break;
case TokenElevationTypeLimited: /* 进程在以有限的权限运行 */
if (bIsAdmin) bFullToken = false;//用户有管理员权限,但进程权限有限
else bFullToken = false;//用户不是管理员组,且进程权限有限
}
// 4. 根据权限的不同控制按钮的显示
if (!bFullToken)
// 给按钮添加一个盾牌
Button_SetElevationRequiredState(::GetDlgItem(m_hWnd, IDC_BUTTON1), !bFullToken);
else
// 隐藏按钮
::ShowWindow(::GetDlgItem(m_hWnd, IDC_BUTTON1), SW_HIDE);
return 0;
}
// 以管理员方式打开
void CMy01PrivilegesDlg::OnBnClickedButton1()
{
// 1. 隐藏当前窗口
//ShowWindow(hWnd, SW_HIDE);
this->ShowWindow(SW_HIDE);
// 2. 获取当前程序路径
WCHAR szApplication[MAX_PATH] = { 0 };
DWORD cchLength = _countof(szApplication);
QueryFullProcessImageName(GetCurrentProcess(), 0,
szApplication, &cchLength);
// 3. 以管理员权限重新打开进程
SHELLEXECUTEINFO sei = { sizeof(SHELLEXECUTEINFO) };
sei.lpVerb = L"runas"; // 请求提升权限
sei.lpFile = szApplication; // 可执行文件路径
sei.lpParameters = NULL; // 不需要参数
sei.nShow = SW_SHOWNORMAL; // 正常显示窗口
if (ShellExecuteEx(&sei))
exit(0);
else
::ShowWindow(m_hWnd, SW_SHOWNORMAL);
}
遍历权限
流程:
- 获取进程令牌句柄;
- 查询令牌中的权限;
- 获取数据大小,申请内存;
- GetTokenInformation();
- 用TOKEN_PRIVILEGES结构体解析。
void showPrivileges()
{
HANDLE hToken = NULL;
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken);
if (!hToken)
{
printf("OpenProcessToken() error\n");
return;
}
DWORD dwSize = 0;
GetTokenInformation(hToken, TokenPrivileges, NULL, NULL, &dwSize);
TOKEN_PRIVILEGES* pTokenPriv = (TOKEN_PRIVILEGES*)calloc(1, dwSize);
GetTokenInformation(hToken, TokenPrivileges, pTokenPriv, dwSize, &dwSize);
DWORD dwCount = pTokenPriv->PrivilegeCount;
LUID_AND_ATTRIBUTES* pPriv = pTokenPriv->Privileges;
char szPrivName[100] = { 0 };
DWORD dwNameLen = sizeof(szPrivName);
for (int i = 0; i < dwCount; ++i)
{
LookupPrivilegeNameA(0, &(pPriv[i].Luid), szPrivName, &dwNameLen);
printf("[%s] -- Attributes:[%d]\n", szPrivName, pPriv[i].Attributes);
}
if (pTokenPriv)
{
free(pTokenPriv);
pTokenPriv = NULL;
}
}
管理员运行可以输出更多。
重要的结构体
typedef struct _TOKEN_PRIVILEGES {
DWORD PrivilegeCount;
LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY];
} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;
typedef struct _LUID_AND_ATTRIBUTES {
LUID Luid;
DWORD Attributes;
} LUID_AND_ATTRIBUTES, * PLUID_AND_ATTRIBUTES;
typedef LUID_AND_ATTRIBUTES LUID_AND_ATTRIBUTES_ARRAY[ANYSIZE_ARRAY];
typedef LUID_AND_ATTRIBUTES_ARRAY *PLUID_AND_ATTRIBUTES_ARRAY;
顺便一提,这个结构体下面紧接着就是SID的定义。
//
// Locally Unique Identifier
//
typedef struct _LUID {
DWORD LowPart;
LONG HighPart;
} LUID, *PLUID;
和GUID的要求保证全局唯一不同,LUID只要保证局部唯一,就是指在系统的每一次运行期间保证是唯一的就可以了。每个值都对应一个权限。
和GUID相同的一点,LUID也是一个64位的值,LookupPrivilegevalue()
则是用来获得这个值。
提权
这段代码在写任务管理器时用到过。
注意,只能是管理员才能提权。
// 提升权限 - 只能是管理员才可以提升
BOOL EnableDebugPrivilege(BOOL fEnable) { //提升为调试权限
BOOL fOk = FALSE; HANDLE hToken;
// 以修改权限的方式,打开进程的令牌
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES,
&hToken)) {
// 令牌权限结构体
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
//获得LUID
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
tp.Privileges[0].Attributes = fEnable ? SE_PRIVILEGE_ENABLED : 0;
// 提升权限
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL); //修改权限
fOk = (GetLastError() == ERROR_SUCCESS);
CloseHandle(hToken);
}
return(fOk);
}