9 管理员以标准用户权限运行时
Windows vista之前的windows版本采用一刀切的方式,所有资源管理器的子进程都会得到由资源管理器进程授予关联的令牌环,这样不安全。
Windows vista中,如果用户使用管理员这样的搞特权账户登录,除了与这个账户对应的的安全令牌之外,还有一个经过筛选的令牌(filtered token)。 以后从包括windows资源管理器在内的第一个进程开始,这个筛选后的令牌会与系统代表最终用户启动更多所有新进程关联, 权限受限的进程无法访问需要高权限的资源。
怎么提升权限呢,权限的提升只能在进程的边界上提升,边界也就是进程创建的时候,可以右键:【以管理员身份运行】, 然后永固可能会看到三种类型的对话框:
① 蓝色:应用程序是系统的一部分
② 灰色:应用程序进行了签名
③ 橙色:应用程序没有签名
如果用户当前是以一个标准用户的身份登录,系统会弹出一个要求输入管理员账户密码的登录框,这种机制称为over-the-shoulder,即管理员越过标准用户的肩膀输入管理员账户密码,标准用户干瞪眼。
一般需要管理员权限运行的程序图标上会有一个盾牌的图标。 在windows任务管理器上有一个【显示所有用户的进程】的按钮,这个按钮的功能就需要管理员权限(有个盾牌图标),单击以后发现当前taskmgr.exe进程的PID变了,说明此时已经不是刚才那个任务管理器了,再次说明进程权限的提升只能是在进程的边界上。
一个未提升权限的进程可以创建一个提升了权限的进程,后者将包含一个COM服务器,这个新进程将保持活动状态,这样一来,未提升权限的进程就可以向已经提升了权限的进程发出IPC调用,从而不必为了提升权限而启动一个新的实例。
9.1 自动提升进程权限
在清单文件中,添加<trustinfo>段,如下:
<trustinfo xmlns=”urn:schemas-microsoft-com:asm.v2”>
<security>
<requestedPrivileges>
<requestedExecutionLevel
Level=”requiredAdministrator” />
</requestedPrivileges>
</security>
</trustinfo>
在VS2010中,可以在项目属性中设置,
Level的取值可以有如下三个:
① requireAdministrator:必须以管理员权限启动,否则无法运行
② highestAvaliable:按当前可用的最高权限,如果用户使用管理员账户则会出现一个要求批准提升权限的对话框。 如果用户使用普通用户账户登录,则用这些标准权限来启动(不会提升用户权限)
③ asInvoker:应用程序使用与主调应用程序一样的权限来启动
9.2 手动提升权限
BOOL ShellExecuteEx( LPSHELLEXECUTEINFO pExecInfo);
1 Typedef struct _SHELLEXECUTEINFO{ 2
3 DWORD cbSize, //结构体大小
4
5 ULONG fMask; 6
7 HWND hwnd; 8
9 PCTSTR lpVerb; //必须为 _T(“runas”)
10
11 PCTSTR lpFile; //可执行文件名
12
13 PCTSTR lpParameters; 14
15 PCTSTR lpDirectory; 16
17 Int nShow; 18
19 HINSTANCE hInstApp; 20
21 PVOID lpIDList; 22
23 PCTSTR lpClass; 24
25 HKEY hKeyClass; 26
27 DWORD dwHotKey; 28
29 Union{ 30
31 HANDLE hIcon; 32
33 HANDLE hMonitor; 34
35 }DUMMYUNIONNAME; 36
37
38
39 HANDLE hProcess; 40
41 }SHELLEXECUTEINFO, *LPSHELLEXECUTEINFO; 42
43
44
45 //eg.
46
47 void CShellExecuteExDlg::OnBnClickedButton1() 48 { 49
50 // TODO: 在此添加控件通知处理程序代码;
51
52 PCTSTR lpFile = _T("C:\\Program Files (x86)\\EditPlus 3\\EditPlus.exe"); 53
54
55 SHELLEXECUTEINFO shellInfo = {sizeof(shellInfo)}; 56
57
58 shellInfo.lpVerb = _T("runas"); 59
60 shellInfo.lpFile = lpFile; 61
62
63
64 BOOL bRet = ShellExecuteEx(&shellInfo); 65
66 if (FALSE == bRet) 67 { 68
69 DWORD dwErrorCode = GetLastError(); 70
71 if (ERROR_CANCELLED == dwErrorCode) 72
73 { 74
75 AfxMessageBox(_T("ERROR_CANCELED")); 76
77 } 78
79 else if (ERROR_FILE_NOT_FOUND == dwErrorCode) 80
81 { 82
83 AfxMessageBox(_T("ERROR_FILE_NOT_FOUND")); 84
85 } 86
87 } 88
89 }
注意:当一个进程使用它提升后的权限启动时,它每次调用CreateProcess来生成另一个进程时,子进程都会获得和它的父进程一样的提升后的权限,在这种情况下不需要调用ShellExecuteEx。 假如一个应用程序是用一个筛选后的令牌来运行,那么一旦视图调用CreateProcess来创建一个要求提升权限的可执行文件时就会失败,GetLastError返回ERROR_ELEVATION_ERQUIRED。
如果希望被调试的进程继承什么权限,就以那种权限来启动VISUAL STUDIO,
9.3 当前权限上下文
BOOL OpenProcessToken(
__in HANDLE ProcessHandle, //要修改访问权限的进程句柄
__in DWORD DesiredAccess, //指定你要进行的操作类型
__out PHANDLE TokenHandle //返回的访问令牌指针
);
BOOL WINAPI GetTokenInformation(
_In_ HANDLE TokenHandle,
_In_ TOKEN_INFORMATION_CLASS TokenInformationClass,
_Out_opt_ LPVOID TokenInformation,
_In_ DWORD TokenInformationLength,
_Out_ PDWORD ReturnLength
);
BOOL WINAPI CreateWellKnownSid(
__in WELL_KNOWN_SID_TYPE WellKnownSidType, // WELL_KNOWN_SID_TYPE是枚举类型,它包含一系列的安全描述符类型
__in_opt PSID DomainSid, // DomainSid 指向创建了SID的域的指针,为NULL时表示使用本地计算机
__out_opt PSID pSid, // pSid 指向存储SID的地址
__inout DWORD *cbSid // cbSid 指向存储pSid的大小的地址
);
typedef enum _TOKEN_ELEVATION_TYPE {
TokenElevationTypeDefault = 1,//进程以默认用户运行,或者UAC被禁用
TokenElevationTypeFull,//进程权限被成功提升&&令牌没有被筛选过
TokenElevationTypeLimited,//进程以受限的权限运行,它对应于一个筛选过的令牌
} TOKEN_ELEVATION_TYPE, *PTOKEN_ELEVATION_TYPE;
1 //eg.
2
3 void CShellExecuteExDlg::OnBnClickedButton2() 4 { 5
6 // TODO: 在此添加控件通知处理程序代码;
7
8
9 TOKEN_ELEVATION_TYPE elevationType = TokenElevationTypeLimited; 10
11 BOOL bret = FALSE; 12
13
14 BOOL bResult = GetProcessElevation(&elevationType, &bret); 15
16
17 } 18
19
20
21 BOOL CShellExecuteExDlg::GetProcessElevation( TOKEN_ELEVATION_TYPE *pElevationType, BOOL *pIsAdmin ) 22
23 { 24
25 HANDLE hToken = NULL; 26
27 DWORD dwSize = 0; 28
29
30 if (! OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) 31 { 32
33 return FALSE; 34
35 } 36
37
38 BOOL bRetult = FALSE; 39
40 if (GetTokenInformation(hToken, TokenElevationType, 41
42 pElevationType, sizeof(TOKEN_ELEVATION_TYPE), &dwSize)) 43
44 { 45
46 BYTE adminSID[SECURITY_MAX_SID_SIZE]; 47
48 dwSize = sizeof(adminSID); 49
50 CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, &adminSID, &dwSize); 51
52 if (*pElevationType == TokenElevationTypeLimited) 53
54 { 55
56 HANDLE hUnfilteredToken = NULL; 57
58 GetTokenInformation(hToken, TokenLinkedToken, (VOID*)&hUnfilteredToken, sizeof(HANDLE), &dwSize); 59
60
61
62 if (CheckTokenMembership(hUnfilteredToken, &adminSID, pIsAdmin)) 63 { 64
65 bRetult = TRUE; 66
67 } 68
69
70 CloseHandle(hUnfilteredToken); 71
72 } 73
74 else
75 { 76
77 *pIsAdmin = IsUserAnAdmin(); 78
79 bRetult = TRUE; 80
81 } 82
83 } 84
85
86 CloseHandle(hToken); 87
88
89 return bRetult; 90
91 }
首先判断令牌是否被筛选过,如果令牌没有被筛选过,则要判断是否是以管理员身份在运行,IsUserAnAdmin()函数来判断是否以管理员身份运行。 在令牌已被筛选的情况下,需要把未筛选的令牌(把TokenLinkedToken传给GetTokenInformation),然后判断其中是否包含一个管理员SID(借助CreateWellKnownSid和CheckTokenMembership)。
关于盾牌图标:
① LRESULT Button_SetElevationRequiredState(
[in] HWND hwnd, //Button控件句柄
[in] BOOL fRequired //TRUE需要盾牌图标,FALSE不需要
);
② SHGetStockIconInfo传入SIID_SHIELD参数也可以获取盾牌图标。
10 枚举系统中正在运行的进程
Win95/win98:Process32First, Process32Next
Win NT: EnumProcesses
BOOL WINAPI EnumProcesses(
_Out_ DWORD * pProcessIds,//保存进程ID的数组,要分配足够大
_In_ DWORD CB, //数组的大小(字节)
_Out_ DWORD * pBytesReturned //返回的数组的字节数
);
1 void CEnumProcessDlg::OnBnClickedButton1() 2 { 3
4 // TODO: 在此添加控件通知处理程序代码;
5
6 DWORD dwArr[300] = {0}; 7
8 DWORD dwBytes = 0; 9
10
11 typedef BOOL (WINAPI *MYFUNC)(DWORD*, DWORD, DWORD*); 12
13
14 HINSTANCE hInst = LoadLibrary(_T("psapi.dll")); 15
16 if (NULL != hInst) 17 { 18
19 MYFUNC myFunc = (MYFUNC) GetProcAddress(hInst, "EnumProcesses"); 20
21 BOOL bRet = myFunc(dwArr, sizeof(dwArr), &dwBytes); 22
23 if (! AllocConsole()) //MFC程序输出到控制台
24 { 25
26 freopen("CONOUT$","w+t",stdout); 27
28 freopen("CONIN$","r+t",stdin); 29
30
31 int count = 0; 32
33 for (int i = 0; i < sizeof(dwArr) / sizeof(DWORD); ++ i) 34 { 35
36 if (dwArr[i] != (DWORD)(0)) 37
38 { 39
40 std::cout << dwArr[i] <<std::endl; 41
42 ++count; 43
44 } 45
46 } 47
48
49
50 std::cout<<std::endl<<"****************"<<count<<"*************"<<std::endl; 51
52
53 fclose(stdout); 54
55 fclose(stdin); 56
57 system("pause"); 58
59 FreeConsole(); 60
61 } 62
63
64 FreeLibrary(hInst); 65
66 } 67
68 }
//获得一个模块的首选基地址
PVOID GetModulePreferredBaseAddr(
DWORD dwProcessId, //进程ID
PVOID pvModuleRomete //进程内一个模块的地址
)
关于完整性级别(Integrity level):
除了众所周知的的安全描述符(SID)和访问控制列表(access control list, ACL),系统还通过在系统访问控制列表(SACL)中新增一个名为强制标签的访问控制项(access control entry, ACE)来为受保护的资源分配一个所谓的完整性级别(integrity level)。 凡是没有这个ACE的安全对象,操作系统将默认其拥有“中”(Medium)完整性级别。 另外每个进程都有一个基于其安全令牌的完整性级别,它与系统授予的一个信任级别是对应的,如下:
低----保护模式中的IE是以“低”的信任级别来运行的,目的在于拒绝从网上下载的代码修改其他应用程序和windows环境
中----默认情况下,应用程序都以“中”信任级别来启动&&使用一个筛选过的令牌来运行
高----如果程序以提升后的权限来启动,则以“高”信任级别来运行
系统---只有以local system 或 local service的身份来运行的进程才能获得这个信任级别。
使用process explorer工具可以查看进程的完整性级别。
GetTokenInformation传入TokenMandatoryPolicy和进程的安全令牌句柄,返回的是一个DWORD值,其中包含了一个位掩码(bitwise mask),详细描述了使用的策略。
POLICY_NO_WRITE_UP---在这个安全令牌下运行的代码不能向具有更高完整性级别的资源写入
POLICY_NEW_PROCESS_MIN---在这个安全令牌下运行的代码启动一个新的进程时,子进程将检查父进程和清单中描述的优先级,并从中选择最低的一个优先级。 如果没有清单就假定清单中的优先级为“中”。
用户界面特权隔离:
对于窗口,使用完整性级别来拒绝低完整性级别的【进程】访问/更新高完整性级别的【进程】的用户界面UI,这种机制称为【用户界面特权隔离】。
操作系统将阻止低完整性级别的【进程】通过PostMessage/SendMessage/HOOK向高完整性级别的【进程】发送windows消息,或HOOK完整性级别高的进程的windows消息。
//终于写完了 第四章 时间:2013年5月25日 23:59:17