windows权限管理

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图标,对个别功能提权,提高用户体验。这种做法除了依赖权限控制,还有特殊的进程创建:

  1. 程序运行,判断进行权限;
  2. 低权限,则将按钮加上UAX盾牌标志;
  3. 高权限,则隐藏提权按钮;
  4. 点击提权按钮后,调用ShellExecuteEx()以管理员权限重新开启进程。

显示UAC按钮

流程:

  1. 获得本进程令牌;
  2. 获取提升类型
  3. 判断具体的权限状况
  4. 根据权限的不同控制按钮的显示

第二步又有这么几步:

获取令牌信息后,创建管理员组的对应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);
}

遍历权限

流程:

  1. 获取进程令牌句柄;
  2. 查询令牌中的权限;
  3. 获取数据大小,申请内存;
  4. GetTokenInformation();
  5. 用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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值