会话 0 隔离(动手实验)
概览
服务是整合在Microsoft Windows操作系统中的结构。服务与用户的应用程序不同,因为你可以对他们进行配置,不需要一个激活的用户登录,就可以使这些服务在系统启动的时候运行,直到系统关闭。Windows中的服务,负责所有种类的后台活动,但不包括从远程过程调用(RPC)服务到网络位置服务的用户。
一些服务可能会试图显示一些用户界面对话框,或者与用户的应用程序进行通信。这些服务都将面临与Windows 7 的兼容性问题。如果不去讨论与你的应用程序进行通信的必要的安全准备,那么,你的服务将不能在Windows 7上工作。
目标
在本实验中,你将学会如何:
• 重新设计和修复一个试图显示UI界面的服务
• 对服务和应用程序间共享的kernel对象,设置适当的安全和访问级别
系统需求
完成本实验,你必须包含以下组件:
• Microsoft Visual Studio 2008
• Windows 7
• Windows Sysinternals进程浏览器
练习 #1: 简洁的服务UI 界面
在这个练习中,你将安装并且运行一个直接向用户展示UI用户界面的服务。你将看到对用户体验十分有效的Windows内置的自动简洁窗口(交互的服务侦测对话框),还有可以修改服务,使其不会直接的展示UI用户界面。
你还可以修改服务,使其在当前的活动用户下,在一个独立的进程中,使用简洁的UI用户界面。
任务 1 –安装和运行服务
作为这个任务的一部分,你需要通过使用sc命令行安装服务,并且首先运行它。这个服务将试图展示一个会触发简洁UI用户界面服务的用户对话框。
1. 使用Visual Studio,打开Session0_Starter解决方案。
2. 生成当前解决方案(请注意你使用的生成配置- Debug/Release,x86/x64)
3. 打开管理员命令窗口:
4. 点击 Start.
5. 指向所有程序。
6. 指向 Accessories.
7. 右击 Command Prompt.
8. 点击以管理员身份运行。
9. 使用cd命令,导向包含应用程序的输出目录。例如,如果输出目录是C:\Session0_Starter\Debug,那么就是用下面的目录,导向到相应目录:
CMD
C:
cd C:\Session0_Starter\Debug
10. 使用下面的命令,创建TimeService服务
CMD
sc create TimeService binPath= C:\Session0_Starter\Debug\TimeService.exe
帮助
确认你已经将Step 9中的目录替换,并且确认在“binPath=”后,复制了空格。
11. 使用组合键+R,并且在Run对话框中输入services.msc来打开MMC服务管理单元。
12. 定位到TimeService服务,右键点击,并且点击Start。
13. 随后,你将看到一个类似下图的对话框。
14. 这个就是交互服务侦测对话框,它将检测试图展示UI用户界面的服务,并且显示这个简洁的对话框。
15. 点击Remind me in a few minutes解除这个消息,或者点击Show me the message切换到安全Session 0桌面去查看服务UI用户界面(一个消息框)。
16. 返回MMC服务管理单元,定位到TimeService服务,右键点击,然后点击Stop来停止服务。
使用WTSSendMessage (快速定位)来修改服务
作为此任务的一部分,你将使用WTSSendMessage方法向用户展示一个消息对话框。它将给用户提供一个交互服务侦测对话框快速定位和替换。
1. 如果你还没有做这项操作,请根据任务1中的Step 1-5安装TimeService服务。
2. 如果你完成了任务1后还没有做这个,请确认你已经停止了TimeService服务(参考任务1中的Step 10)
3. 使用Visual Studio,打开Session0_Starter解决方案。
4. 在UI\Native解决方案文件夹中找到TimeService项目,然后打开TimeService.cpp文件。
5. 在文件中,找到第一个//TODO注释。找到MessageBox方法调用,并使用下面的代码进行替换:
C++
LPWSTR lpszTitle = L"Time Change";
LPWSTR lpszText = L"Notification: 5 seconds have elapsed.\r\nWould you like to see more details?";
DWORD dwSession = WTSGetActiveConsoleSessionId();
WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, dwSession, lpszTitle,
static_cast<DWORD>((wcslen(lpszTitle) + 1) * sizeof(wchar_t)),
lpszText, static_cast<DWORD>((wcslen(lpszText) + 1) * sizeof(wchar_t)),
MB_YESNO|MB_ICONINFORMATION, 0 /*wait indefinitely*/, &dwResponse, TRUE);
6. 生成该解决方案。
7. 重复任务1中的Step 6-7 。你应该能看到在你的主桌面,会出现一个对话框询问你,而不是交互服务侦测对话框。
8. 点击No解除消息。
9. 停止服务(参看任务1中Step 10)。
任务 3 –使用不同的用户凭证浏览UI 用户界面
作为该任务的一部分,你需要修改服务,以便于让其在当前的活动用户下运行一个新的交互UI用户界面进程,来显示代表服务的用户交互界面。
1. 重复任务2中的Step 1-4操作。
2. 在TimeService.cpp文件中找到第二个//TODO注释。
3. 以检索到的活动的Session ID和与其相关的用户权证(参看http://en.wikipedia.org/wiki/Token_(Windows_NT_architecture) 用户权证背景)来使用WTSGetActiveConsoleSessionId 和 WTSQueryUserToken方法。这个是用来创建交互UI用户界面进程的权证。插入下面的代码:
C++
BOOL bSuccess = FALSE;
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
si.cb = sizeof(si);
DWORD dwSessionID = WTSGetActiveConsoleSessionId();
HANDLE hToken = NULL;
if (WTSQueryUserToken(dwSessionID, &hToken) == FALSE)
{
goto Cleanup;
}
4. 使用DuplicateTokenEx方法来复制权证,以便于它能够创建进程。插入下面的代码:
C++
HANDLE hDuplicatedToken = NULL;
if (DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hDuplicatedToken) == FALSE)
{
goto Cleanup;
}
5. 使用CreateEnvironmentBlock方法为交互进程创建一个环境块。插入下面的代码:
C++
LPVOID lpEnvironment = NULL;
if (CreateEnvironmentBlock(&lpEnvironment, hDuplicatedToken, FALSE) == FALSE)
{
goto Cleanup;
}
6. 通过检索服务执行的完整路径,来获取客户端应用程序的完整路径(使用GetModuleFileName),除去文件名称(使用PathRemoveFileSpec),然后连结客户端应用程序名称。插入下面的代码:
C++
WCHAR lpszClientPath[MAX_PATH];
if (GetModuleFileName(NULL, lpszClientPath, MAX_PATH) == 0)
{
goto Cleanup;
}
PathRemoveFileSpec(lpszClientPath);
wcscat_s(lpszClientPath, sizeof(lpszClientPath)/sizeof(WCHAR), L"\\TimeServiceClient.exe");
7. 使用CreateProcessAsUser方法,在目标用户中创建一个进程:
C++
if (CreateProcessAsUser(hDuplicatedToken, lpszClientPath, NULL, NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
lpEnvironment, NULL, &si, &pi) == FALSE)
{
goto Cleanup;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
bSuccess = TRUE;
8. 请确认在这项工作期间,你有代码去分配空余资源。插入下面的代码:
C++
Cleanup:
if (!bSuccess)
{
ShowMessage(L"An error occurred while creating fancy client UI", L"Error");
}
if (hToken != NULL)
CloseHandle(hToken);
if (hDuplicatedToken != NULL)
CloseHandle(hDuplicatedToken);
if (lpEnvironment != NULL)
DestroyEnvironmentBlock(lpEnvironment);
9. 生成解决方案。
10. 重复任务1中的Step 6-7操作。除了交互服务侦测对话框,你应该看到在你的主桌面将出现一个对话框询问你问题。点击Yes,客户端应用程序将启动,并且及时展现给你。
11. 关闭客户端应用程序并且停止进程(参考任务1中的Step 10)。
注意
为了达到这个练习的目的,我们简化了示例代码,并且在我们设计和完成这个项目的时候,没有遵照所有的安全代码标准。请在另外用户下创建进程并且使用该进程与后台服务通信之前,仔细考虑可能引起的安全问题。
练习 #2: 对共享对象进行安全验证
在这个练习中,你需要安装和运行一个服务来创建一个标准应用程序的共享kernel对象(事件)。你将会看到,标准应用程序是不能访问这个事件的,因为它没有驻存在同一个Session的命名空间中,并且没有设置相应的访问控制权限。
任务 1 –安装和运行服务
作为该任务的一部分,你将使用sc命令行安装一个服务,然后首先运行它。你将看到,当它去试图使用服务所创建的事件时,服务的客户端将收到“Access Denied”的错误信息。
1. 激活用户账户控制(UAC)。在开始菜单,点击查询,然后输入“User Account Control”。并在查询结果中选择“Change User Account Control settings”。然后确认滑块不是设置在Never notify上的。
2. 使用Visual Studio,打开Session0_Starter解决方案。
3. 生成当前的解决方案(确认你使用的生成配置- Debug/Release, x86/x64)。
4. 打开一个管理员命令行窗口,点击Start,指向 All Programs,然后指向 Accessories,再右键点击 Command Prompt。点击 Run as administrator。
5. 使用cd命令,定位到包含应用程序的输出目录。例如,如果输出目录是C:\Session0_Starter\Debug,那么就是用下面的命令指向相应的目录:
CMD
C:
cd C:\Session0_Starter\Debug
6. 使用下面的命令,创建AlertService服务注意:确认你已经使用Step 4 中的路径,替换了服务的路径,并且请确认在“binPath=”后,你复制了空格。
CMD
sc create AlertService binPath= C:\Session0_Starter\Debug\AlertService.exe
7. 使用快捷键+R,并且在运行对话框中输入services.msc进入MMC服务管理单元。
8. 定位到AlertService服务,右键点击,并且点击Start。
9. 打开一个标准的命令行窗口。从开始菜单中,指向All Programs,点击Accessories,然后点击Command Prompt(注意:不要使用管理员方式运行命令行窗口)。
10. 在标准命令行窗口中重复Step 5 。
11. 使用下面的命令运行AlertService客户端应用程序,这个服务将尝试去打开服务创建的事件,并且用它同步(WaitForSingleObject)。
CMD
AlertServiceClient
12. 注意到客户端在打开事件时失败,错误为2,此错误表示没有找到相应的事件。
13. 返回到MMC服务管理单元,定位到AlertService服务,右键点击,然后点击Stop来停止该服务。
任务 2 –修改服务,以便能够在全局命名空间中创建对象
作为该任务的一部分,你需要将对象的名称进行更改,使其包含全局命名空间的前缀。
1. 如果你还不能做这项操作,请根据任务1中的Step 1-5,安装AlertService服务。
2. 如果你在完成了任务1的前提下,还不能做这项操作,请确认AlertService服务已经停止(查看任务1中的Step 10)。
3. 使用 Visual Studio, 打开 Session0_Starter 解决方案。
4. 在Security\Native解决方案的文件夹中,定位到AlertService项目,然后打开AlertService.cpp文件。
5. 在该文件中,找到标记为“STEP 1”的//TODO注释,然后使用下面的代码将CreateEvent的调用替换:
C++
g_hAlertEvent = CreateEvent(NULL, FALSE, FALSE, L"Global\\AlertServiceEvent");
6. 在Security\Native解决方案的文件夹中,定位到AlertServiceClient项目,然后打开AlertServiceClient.cpp文件。
7. 在该文件中,找到标记为“STEP 1”的//TODO注释,然后使用下面的代码将OpenEvent的调用替换:
C++
HANDLE hEvent = OpenEvent(SYNCHRONIZE, FALSE, L"Global\\AlertServiceEvent");
8. 生成该解决方案。
9. 重复任务1中的Step 7-13 。注意现在客户端还是不能访问事件(这次是因为安全设置,而不是因为命名空间的问题)。
10. 从Windows Sysinternals中运行进程浏览器(如果你没有,请从http://technet.microsoft.com/en-us/sysinternals/0e18b180-9b7a-4c49-8120-c47c5a693683.aspx 下载工具)。
11. 在进程列表中选择AlertService服务(如果这个服务没有出现在出现列表中,从File菜单中,点击Show processes from all users使用管理员权限重启进程浏览器)。
12. 确认对话框下方的窗口是可见的,并且能够显示进程句柄(使用快捷键CTRL+H,或者从View菜单中打开该窗口)。
13. 在句柄列表中,找到\BaseNamedObjects\AlertServiceEvent事件,右击该事件,并且点击Properties 。
14. 在Properties对话框中,选择Security标签。请注意能够访问该事件的安全的组,只有SYSTEM 和Administrators组。
任务 3 –修改服务,以便于为对象提供安全属性(DACL 和SACL )
作为该任务的其中一部分,你将修改服务,以便于能够为事件对象设置适当的访问控制权限(DACL和SACL),使其客户端能对其访问。
1. 重复任务2中的Step 1-4操作。
2. 在文件中,找到标记为“STEP 2”的//TODO注释。
3. 使用WTSGetActiveConsoleSessionId和WTSQueryUserToken方法,找到当前活动的Session ID和与其相关联的用户权证。插入下面的代码:
C++
DWORD dwSessionID = WTSGetActiveConsoleSessionId();
HANDLE hToken = NULL;
if (WTSQueryUserToken(dwSessionID, &hToken) == FALSE)
{
goto Cleanup;
}
4. 使用GetTokenInformation方法检索用户账号的SID(安全标识)。请注意,此时需要两个通行证–一个用于判断TOKEN_USER结构,另外一个则实际的填充它。插入下面的代码:
C++
DWORD dwLength;
TOKEN_USER* account = NULL;
if (GetTokenInformation(hToken, TokenUser, NULL, 0, &dwLength) == FALSE &&
GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
goto Cleanup;
}
account = (TOKEN_USER*)new BYTE[dwLength];
if (GetTokenInformation(hToken, TokenUser, (LPVOID)account, dwLength, &dwLength) == FALSE)
{
goto Cleanup;
}
5. 使用ConvertSidToStringSid方法,将用户账号的SID转换成字符串形式,然后根据它再去构建一个SDDL字符串,用来表示服务以后创建的事件的安全说明。插入下面的代码:
C++
LPWSTR lpszSid = NULL;
if (ConvertSidToStringSid(account->User.Sid, &lpszSid) == FALSE)
{
goto Cleanup;
}
WCHAR sddl[1000];
wsprintf(sddl, L"O:SYG:BAD:(A;;GA;;;SY)(A;;GA;;;%s)S:(ML;;NW;;;ME)", lpszSid);
6. 使用ConvertStringSecurityDescriptorToSecurityDescriptor方法,将SDDL安全说明字符串,转换成安全说明形式。插入下面的代码:
C++
PSECURITY_DESCRIPTOR sd = NULL;
if (ConvertStringSecurityDescriptorToSecurityDescriptor(sddl, SDDL_REVISION_1, &sd, NULL) == FALSE)
{
goto Cleanup;
}
7. 将step 6 中创建的安全说明,序列化为一个SECURITY_ATTRIBUTES结构,并且使用下面的代码创建事件:
C++
SECURITY_ATTRIBUTES sa;
sa.bInheritHandle = FALSE;
sa.lpSecurityDescriptor = sd;
sa.nLength = sizeof(sa);
g_hAlertEvent = CreateEvent(&sa, FALSE, FALSE, L"Global\\AlertServiceEvent");
if (g_hAlertEvent == NULL)
{
goto Cleanup;
}
8. 定位到标记为“STEP 3”的//TODO注释,并且插入下面的代码,来释放序列化事件所需要的资源:
C++
Cleanup:
if (hToken != NULL)
CloseHandle(hToken);
if (account != NULL)
delete[] account;
if (lpszSid != NULL)
LocalFree(lpszSid);
if (sd != NULL)
LocalFree(sd);
if (g_hAlertEvent == NULL)
CloseHandle(g_hAlertEvent);
9. 生成该解决方案。
10. 重复任务1中的Step 7-13 。请注意目前客户端已经可以成功打开事件,并且可以接收到从AlertService服务发出的消息了。
11. 重复任务2中的Step 9-13 。请注意,现在事件是允许当前的活动用户进行访问的,这也就是为什么AlertService客户端即使没有当前系统的管理员权限,也是能够打开事件的原因所在。
当心
示例中所使用的安全说明字符串(SDDL),并不是最好的表现形式。在你的应用程序中,确认你已经为服务和应用程序共享的资源设置了最高的安全级别,并且进行了威胁分析和模型来确保你不是创建了一个安全漏洞。
练习 #3: 为文件对象进行加密
在这个练习中,你需要安装和运行一个服务来创建一个用户可以访问的日志文件。但是,用户是不能对这个文件进行编写或者删除的,除非服务对其进行了安全属性的设置,包括允许用户访问文件的整合级别。
任务 1 – 安装和运行服务
作为当前任务的其中一部分,你需要使用installutil命令安装一个服务,然后首先运行这个服务。当你试图去删除服务所创建的文件时,将看到用户会收到一个“Access Denied”的错误。
1. 使用 Visual Studio,打开 Session0_Starter 解决方案。
2. 生成当前的解决方案(请注意你所使用的生成配置 - Debug/Release, x86/x64)。
3. 要打开管理员命令窗口,点击Start,指向All Programs,找到Accessories,然后右键点击Command Prompt,选中Run as administrator。
4. 使用cd命令导航到部署应用程序的输出目录。例如,如果输出目录是C:\Session0_Starter\Debug,那么就是用下面的命令,导航到指定的目录中:
CMD
C:
cd C:\Session0_Starter\Debug
5. 使用下面的命令创建一个LoggingService服务(确认你已经使用step 4 中的路径,替换了服务的路径,并且在“binPath=”之后,复制了空格)。
CMD
installutil LogService.exe
6. 使用快捷键+R,在运行对话框中输入services.msc打开MMC服务管理单元。
7. 定位到LoggingService服务,右键点击,然后单击Start。
8. 如果打开一个标准命令行窗口,点击Start,指向All Programs,选择Accessories,点击Command Prompt(注意:不要使用管理员权限运行命令行窗口)。
9. 返回MMC服务管理单元,定位到LoggingService服务,右键点击,然后选择Stop来停止服务。
10. 打开一个Windows Explorer窗口(从我的电脑进入,或者直接打开),然后指向到C:\ root目录。找到LogService.txt文件。
11. 尝试使用Windows Explorer去删除文件(右键点击文件,然后选择Delete,或者点击Del键)。那么这个尝试将由于拒绝访问错误而失败,因为我们没有授权用户去写或者删除该文件。
任务 2 –修改日志文件的整合级别
作为该任务的其中一部分,你需要修改服务所创建的日志文件的整合级别。这样,用户就能够对日志文件进行写操作,甚至可以删除它。
1. 如果你还是不能做这项操作,那么请参照任务1中的step 1-5安装LoggingService服务。
2. 如果你已经完成了任务1还是不能做这项操作,请确认LoggingService服务已经停止(参看任务1中的step 10)。
3. 使用 Visual Studio,打开 Session0_Starter 解决方案。
4. 在Security\Managed解决方案文件夹中,找到LogService项目,然后打开LoggingService.cs文件。
5. 在文件中,找到标记为?的//TODO注释,然后添加下面的代码:
C++
IntegrityLevelHelper.SetFileIntegrityLevel(@"C:\LogService.txt", IntegrityLevel.Medium);
6. 生成解决方案。
7. 这次,由于日志文件已经不再受系统整合级别的保护,所以用户可以删除该日志文件了。
摘要
在这个实验中,你已经诊断了由Session 0隔离机制所造成的两个问题,为这些问题设计了一些修复应用程序,并且实现了这些修复。你也已经使用了快速修复策略,比如使用WTSSendMessage方法在服务中向交互用户发送消息,与具有良好设计的解决方案一样,比如为共享的kernel对象或者文件配置访问控制,并且在当前的活动用户下,在服务中执行进程的UI用户界面。
会话 0 隔离 - 白皮书
Windows 服务的Session 0 隔离机制
服务是整合在Microsoft Windows操作系统中的结构。服务与用户的应用程序不同,因为你可以对他们进行配置,不需要一个激活的用户登录,就可以使这些服务在系统启动的时候运行,直到系统关闭。Windows中的服务,负责所有种类的后台活动,但不包括从远程过程调用(RPC)服务到网络位置服务的用户。
一些服务可能会试图显示一些用户界面对话框,或者与用户的应用程序进行通信。那么这些服务将会遇到与Windows 7 的兼容性问题。可能你有一个服务,试图显示一个对话框,但是,可能仅仅是在任务栏中出现一个闪烁的图标。无独有偶,你的服务可能会多多少少遇到下面的一些现象。这些服务:
• 正在运行,但是没有做他们本希望去做的事情
• 正在运行,但是其他的一些进程却不能和这个服务进行通信
• 试图通过窗体消息,与用户的应用程序进行通信,但是窗体消息并没有被目标所接收到
• 在任务栏中,显示一个闪烁的图标,说明此服务希望与桌面信息进行交互
这些是Windows 7的服务的Session 0隔离机制的一些症状。它们可以大致分为两大类:
• 服务显示用户界面失败,或者仅仅显示了一个简化的用户界面
当一个服务试图去展示任何有关用户界面的元素(即使这个服务被允许与桌面信息进行交互),一个叫做Interactive services dialog detection的简单对话框也会弹出,用于提示用户。用户可以进入Session 0的安全桌面中查看服务的用户界面,但是,工作流中的干扰,使得这个变成了一个严重的应用程序兼容性问题。
• 很难实现服务和应用程序共享对象,并且这个对象将不可见
当一个对象由服务创建出来,并且允许标准应用程序访问(以标准用户权限运行),那么这个对象将不能在全局命名空间中找到(它将被Session 0 中私有)。此外,安全变更,也将保证即使对象是可见的,但它也是不能被访问的。这些将有可能影响其他的与你的服务进行交互的进程(比如普通用户的应用程序)。
真正的问题是Windows 7 服务的Session 0 隔离机制
在Windows XP, Windows Server 2003或者更早期的Windows操作系统中,所有的服务和应用程序都是运行在与第一个登录到控制台的用户得Session中。这个Session叫做Session 0。在Session 0 中一起运行服务和用户应用程序,由于服务是以高权限运行的,所以会造成一些安全风险。这些因素使得一些恶意代理利用这点,来寻找提升他们自身权限的结构。
在Windows Vista中,服务在一个叫做Session 0 的特殊Session中承载。由于应用程序运行在用户登录到系统后所创建的Session 0 之后的Session中,所以应用程序和服务也就隔离开来:第一个登录的用户在Session 1中,第二个在Session 2中,以此类推。事实上运行在不同的Session中,如果没有特别将其放入全局命名空间(并且设置了相应的访问控制配置),是不能互相传递窗体消息,共享UI元素或者共享kernel对象。下面的图例中,将进行图解:
了解更多关于Session 0 隔离机制,阅读Windows Vista 中的驱动和服务的Session 0 隔离机制的影响:http://www.microsoft.com/whdc/system/vista/services.mspx
解决方案
• 如果一个服务,需要向用户传递一条消息,那么可以使用WTSSendMessage方法。这个方法和MessageBox基本相同。它将可以给不需要复杂的UI界面的服务,一个简单而有效的解决方案,同时,由于所显示的消息对话框不能用于控制底层的服务,所以,这个解决方案也是安全的。
• 如果需要使用一个复杂的UI界面,那么可以使用CreateProcessAsUser方法,在提出请求的用户桌面中创建一个进程。
• 如果这两种交互方式都需要,那么可以使用诸如Windows Communication Foundation (WCF),.NET远程处理,命名管道或者其他的进程间通信(IPC)结构(除窗体消息之外)来跨Session通信。
• 安全通信和其他共享对象(比如,命名管道,文件地图),可以使用任意访问控制列表(DACL)来严格控制用户的权限设置。使用系统访问控制列表(SACL),来确保中低权限的进程依然可以访问由系统或高权限服务所创建的结构。
• 确保跨Session访问的kernel对象是以Global\字符串为首字母进行命名,这意味着他们是属于全局Session命名空间中的。
解决方案步骤
目前,我们已经遇到了所有的Windows服务的Session 0 隔离机制的情况,解释了什么是服务隔离,它是如何影响你的服务和应用程序的,并且提出了一些解决方案。下面的一些测试和一些操作,可以帮助你找到问题,并且解决它。
试验 #1
1. 打开进程浏览器。
a. 下载或了解更多关于进程浏览器,请在Microsoft TechNet网站参看Process Explorer Web site 。
2. 确保进程浏览器显示了所有的进程:
a. 点击 File.
b. 选择 Show processes from all users.
3. 定位到可疑的服务,并且查看它的属性:
a. 右键点击进程。
b. 选择 Properties 。
c. 导航到 Security 标签。
d. 请注意服务运行在哪个Session中(通常在Session 0),并且它的全部级别。
下面是两个进程的例子-其中一个以中级权限运行(在Session 1),另外一个则以系统权限运行(在Session 1):
如果你的服务是以高权限运行在Session 0中,那么它将不能直接显示UI。不过即使这样,当你与服务进行共享kernel对象或者文件的时候,你可能还会遇到一些问题。
试验 #2
1. 打开进程浏览器。
2. 确保进程浏览器显示了所有的进程:
a. 点击 File.
b. 选择 Show processes from all users.
3. 定位到可疑的服务。
4. 如果服务包含了你已知的与用户应用程序共享的对象,请在下面的Handles窗口中检查他们的句柄(使用CTRL+H查看,或者从View菜单中进入)。
a. 右键点击每一个可以的句柄,选择Properties。
b. 切换到Security标签页,查看当前用户和组是否允许使用当前句柄来访问对象。
下面的图中,展示了一个不管是否是在Session 0 中运行的系统服务中,所有人都可以访问的共享对象(“同步”权限):
下面的图中,展示了一个只允许管理员和系统组才能访问的共享对象:
5. 如果服务需要创建一个用户应用程序可以访问的文件,请依照以下步骤:
a. 打开命令行窗口:
i. 点击 Start
ii. 指向 Programs
iii. 点击 Accessories
iv. 点击 Command Prompt
b. 运行icacls工具查看文件或者目录的集合权限和DACL信息,并且修改它们。
(请在Microsoft TechNet网站查看关于icacls的文档,以获取更多信息。)
c. 运行icacls MyFile来显示叫MyFile的文件的访问控制列表。
d. 运行icacls MyFile /setintegritylevel Medium来将MyFile的集合权限级别修改为中级(这将使它可以访问大部分用户应用程序。)
工具
• 进程浏览器 –Windows进程的监视工具,可以显示进程的集合级别和对象的安全信息。
o 更多信息: http://technet.microsoft.com/en-us/sysinternals/bb896645.aspx
o 下载: http://download.sysinternals.com/Files/ProcessExplorer.zip
• icacls –Windows实用工具中的一个,用于更改文件系统对象的ACL和整合级别。
o 更多信息: http://technet.microsoft.com/en-us/library/cc753525.aspx
代码示例
下面包含了一些示例代码,用来演示如何:
• 使用WTSSendMessage从服务端向当前活动用户的桌面发送消息
• 当需要显示一个相对复杂的UI界面时,如何在当前活动用户的桌面创建一个进程
• 创建一个属于全局命名空间的事件,并且包含安全配置,以便于它可以被当前活动用户所访问。
使用WTSSendMessage 从服务端向当前活动用户的桌面发送消息:
void ShowMessage(LPWSTR lpszMessage, LPWSTR lpszTitle)
{
DWORD dwSession = WTSGetActiveConsoleSessionId();
DWORD dwResponse = 0;
WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, dwSession,
lpszTitle,
static_cast<DWORD>((wcslen(lpszTitle) + 1) * sizeof(wchar_t)),
lpszMessage,
static_cast<DWORD>((wcslen(lpszMessage) + 1) * sizeof(wchar_t)),
0, 0, &dwResponse, FALSE);
}
当需要显示一个相对复杂的UI 界面时,如何在当前活动用户的桌面创建一个进程:
BOOL bSuccess = FALSE;
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
si.cb = sizeof(si);
DWORD dwSessionID = WTSGetActiveConsoleSessionId();
HANDLE hToken = NULL;
if (WTSQueryUserToken(dwSessionID, &hToken) == FALSE)
{
goto Cleanup;
}
HANDLE hDuplicatedToken = NULL;
if (DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hDuplicatedToken) == FALSE)
{
goto Cleanup;
}
LPVOID lpEnvironment = NULL;
if (CreateEnvironmentBlock(&lpEnvironment, hDuplicatedToken, FALSE) == FALSE)
{
goto Cleanup;
}
WCHAR lpszClientPath[MAX_PATH];
if (GetModuleFileName(NULL, lpszClientPath, MAX_PATH) == 0)
{
goto Cleanup;
}
PathRemoveFileSpec(lpszClientPath);
wcscat_s(lpszClientPath, sizeof(lpszClientPath)/sizeof(WCHAR), L"\\TimeServiceClient.exe");
if (CreateProcessAsUser(hDuplicatedToken, lpszClientPath, NULL, NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
lpEnvironment, NULL, &si, &pi) == FALSE)
{
goto Cleanup;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
bSuccess = TRUE;
Cleanup:
if (!bSuccess)
{
ShowMessage(L"An error occurred while creating fancy client UI", L"Error");
}
if (hToken != NULL)
CloseHandle(hToken);
if (hDuplicatedToken != NULL)
CloseHandle(hDuplicatedToken);
if (lpEnvironment != NULL)
DestroyEnvironmentBlock(lpEnvironment);
创建一个属于全局命名空间的事件,并且包含安全配置,以便于它可以被当前活动用户所访问(从DACL 和SACL ):
DWORD dwSessionID = WTSGetActiveConsoleSessionId();
HANDLE hToken = NULL;
if (WTSQueryUserToken(dwSessionID, &hToken) == FALSE)
{
goto Cleanup;
}
DWORD dwLength;
TOKEN_USER* account = NULL;
if (GetTokenInformation(hToken, TokenUser, NULL, 0, &dwLength) == FALSE &&
GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
goto Cleanup;
}
account = (TOKEN_USER*)new BYTE[dwLength];
if (GetTokenInformation(hToken, TokenUser, (LPVOID)account, dwLength, &dwLength) == FALSE)
{
goto Cleanup;
}
LPWSTR lpszSid = NULL;
if (ConvertSidToStringSid(account->User.Sid, &lpszSid) == FALSE)
{
goto Cleanup;
}
WCHAR sddl[1000];
wsprintf(sddl, L"O:SYG:BAD:(A;;GA;;;SY)(A;;GA;;;%s)S:(ML;;NW;;;ME)", lpszSid);
PSECURITY_DESCRIPTOR sd = NULL;
if (ConvertStringSecurityDescriptorToSecurityDescriptor(sddl, SDDL_REVISION_1, &sd, NULL) == FALSE)
{
goto Cleanup;
}
SECURITY_ATTRIBUTES sa;
sa.bInheritHandle = FALSE;
sa.lpSecurityDescriptor = sd;
sa.nLength = sizeof(sa);
g_hAlertEvent = CreateEvent(&sa, FALSE, FALSE, L"Global\\AlertServiceEvent");
if (g_hAlertEvent == NULL)
{
goto Cleanup;
}
while (!g_Stop)
{
Sleep(5000);
SetEvent(g_hAlertEvent);
}
Cleanup:
if (hToken != NULL)
CloseHandle(hToken);
if (account != NULL)
delete[] account;
if (lpszSid != NULL)
LocalFree(lpszSid);
if (sd != NULL)
LocalFree(sd);
if (g_hAlertEvent == NULL)
CloseHandle(g_hAlertEvent);
其他资源
• 应用程序兼容性手册: http://msdn.microsoft.com/en-us/library/bb963893.aspx
• 了解和工作在Internet Explorer 保护模式下:http://msdn.microsoft.com/en-us/library/bb250462.aspx
• 修改Windows Vista中的保护对象的强制完整性等级:http://blogs.msdn.com/cjacks/archive/2006/10/24/modifying-the-mandatory-integrity-level-for-a-securable-object-in-windows-vista.aspx
• Windows完整性机制资源:http://msdn.microsoft.com/en-us/library/bb625959.aspx
• Session 0 隔离机制对Windows Vista中服务和驱动的影响:http://www.microsoft.com/whdc/system/vista/services.mspx