以下的函数显示其实行的方法:
BOOL CopyACL(PACL pACLDestination, PACL pACLSource ){
BOOL fReturn = FALSE;
try{{
// 取得原始ACL中ACEs的数量
ACL_SIZE_INFORMATION aclSize;
if (!GetAclInformation(pACLSource, &aclSize,
sizeof(aclSize), AclSizeInformation)){
goto leave;
}
// 使用GetAce及AddAce复制ACEs
for(ULONG lIndex=0;lIndex < aclSize.AceCount;lIndex++){
ACE_HEADER* pACE;
if(!GetAce(pACLSource, lIndex, (PVOID*)&pACE))
goto leave;
if(!AddAce(pACLDestination, ACL_REVISION, MAXDWORD,
(PVOID*)pACE, pACE->AceSize))
goto leave;
}
fReturn = TRUE;
}leave:;
}catch(...){
}
return (fReturn);
}CopyACL函数相当简单,它只不过重复着通过现存DACL之ACEs并复制每一个到新DACL的动作而已。CopyACL使用了AddAce系统函数,其定义如下:
BOOL AddAce(
PACL pACL,
DWORD dwACERevision,
DWORD dwStartingACEIndex,
PVOID pACEList,
DWORD dwACEListLength);
请注意,AddAce与AddAccessAllowedAce及AddAccessDeniedAce不同。首先您必须提供ACE;系统不会为您收集ACE并放置到DACL里,这意味着您应要定义ACEs的类型,所以AddAce可以把任意类型的ACE加至DACL中;第二,经由使用以0开始的索引值,AddAce可让您决定想要插入新ACE到DACL的位置;第叁,AddAce可让您增加更多的ACE,经由传递连续的ACEs清单给pACEList参数以及清单大小给dwACEListLength参数(不要被清单中的ACEs数量混淆)。
说明
经由允许AddAce复制一个个别函数以呼叫之中所有的原始ACEs,CopyACL范例函数可以更有效地被实作。然而,如此做而取得的ACL结构资讯,应该被视为不透明的。
在您复制旧DACL到更宽敞的新DACL之后,可以使用AddAce开始加入ACEs。请注意,必须将您的ACEs插入适当的新DACL索引中,以便维护适当的ACE顺序(关于ACE顺序,请参阅 表10-6 )。您可以使用以下的函数为ACL中的新ACE决定适当的索引值:
ULONG GetACEInsertionIndex(PACL pDACL, PACE_UNION pACENew){
ULONG lIndex = (ULONG)-1;
try{{
// ACL顺序的ACE类型
ULONG lFilterType[] = { ACCESS_DENIED_ACE_TYPE,
ACCESS_DENIED_OBJECT_ACE_TYPE,
ACCESS_ALLOWED_ACE_TYPE,
ACCESS_ALLOWED_OBJECT_ACE_TYPE};
// 决定新的ACE应该隶属的群组
ULONG lNewAceGroup;
for(lNewAceGroup = 0; lNewAceGroup<4 ; lNewAceGroup++){
if(pACENew->aceHeader.AceType == lFilterType[lNewAceGroup])
break;
}
// 假如群组 == 4,则这个ACE类型不好
if(lNewAceGroup==4)
goto leave;
// 假如新的ACE是个继承的ACE,那么它会追求其他的ACEs
if((pACENew->aceHeader.AceFlags & INHERITED_ACE) != 0)
lNewAceGroup+=4;
// 取得ACE总数
ACL_SIZE_INFORMATION aclSize;
if (!GetAclInformation(pDACL, &aclSize,
sizeof(aclSize), AclSizeInformation)){
goto leave;
}
// 重复通过ACEs
lIndex = 0;
for(lIndex = 0;lIndex < aclSize.AceCount;lIndex++){
ACE_HEADER* pACE;
if(!GetAce(pDACL, lIndex, (PVOID*)&pACE))
goto leave;
// 取得ACL之ACE的群组
ULONG lAceGroup;
for(lAceGroup = 0; lAceGroup<4 ; lAceGroup++){
if(pACE->AceType == lFilterType[lAceGroup])
break;
}
// 测试不好的ACE
if(lAceGroup==4){
lIndex = (ULONG)-1;
goto leave;
}
// 调整继承
if((pACE->AceFlags & INHERITED_ACE) != 0)
lAceGroup+=4;
// 假如是相同的群组,那么找出插入点
if(lAceGroup>=lNewAceGroup)
break;
}
}leave:;
}catch(...){
}
return (lIndex);
}GetACEInsertionIndex函数会找出您插入新ACE的索引值,并在过程中考虑对象ACEs及ACE继承的部份。在您知道索引值之后,可以呼叫AddAce,以增加新的ACE到ACL中。
每个新的ACE都会如此做,然后再使用适当的函数设定新的DACL到安全对象。不要忘了在您使用后清理并释放已经分配的任何内存。
在本章稍早,我曾答应要展示一个在DACL中安排ACEs顺序的范例函数。假如在您彻底完成加入ACEs到DACL前,一点也不想要担心ACE顺序的问题,那么此函数对您可能会很有帮助。以下的OrderDACL函数相当简单,它建立在GetACEInsertionIndex范例函数上:
BOOL OrderDACL(PACL pDACL ){
BOOL fReturn = FALSE;
try{{
// 取得ACL大小及ACE总数
ACL_SIZE_INFORMATION aclSize;
if (!GetAclInformation(pDACL, &aclSize,
sizeof(aclSize), AclSizeInformation)){
goto leave;
}
// 为暂时的ACL取得内存
PACL pTempDACL = (PACL)_alloca(aclSize.AclBytesInUse);
if (pTempDACL==NULL)
goto leave;
// 初始暂时的ACL
if (!InitializeAcl(pTempDACL, aclSize.AclBytesInUse,
ACL_REVISION))
goto leave;
// 重复通过ACEs
for (ULONG lAceIndex = 0;
lAceIndex < aclSize.AceCount ; lAceIndex++){
// 取得ACE
PACE_UNION pACE;
if (!GetAce(pDACL, lAceIndex, (PVOID*)&pACE))
goto leave;
// 找出位置,并且把ACE加入temp DACL
ULONG lWhere = GetACEInsertionIndex(pTempDACL, pACE);
if (!AddAce(pTempDACL, ACL_REVISION,
lWhere, pACE, pACE->AceSize))
goto leave;
}
// 复制temp DACL到原始的
CopyMemory(pDACL, pTempDACL, aclSize.AclBytesInUse);
fReturn = TRUE;
}
leave:;
}catch(...){
}
return (fReturn);
}让我们看看真实世界实作讨论过的技巧范例。
修改DACL范例
我们的范例提供了一组有用的函数给服务开发人员,它显示如何修改一个现存DACL的方法。我使用了Windows中的window站台及桌面安全对象。
window站台 是Windows的安全对象,包含了剪贴簿、一组通用元素及桌面对象的集合。window站台可以是互动式的,意味着使用者可以看到它的「桌面」,一个互动式windows站台也包含键盘及滑鼠资讯。一个程序可以有一个与它相关联的个别windows站台。
桌面 是一个包含在window站台内的安全对象。一个桌面维护着一个逻辑的显示外观,并包含功能表、视窗及萤幕上的其他可视对象。
不与使用者互动的服务和互动式桌面没有关联。当使用者登入系统时,互动式window站台(称为WinSta0)的DACL及其预设桌面(称为Default)会被重新设定,并且把此对象的存取权给予使用者。最后,只有已登录的使用者及系统会被授予对此对象的存取权。
问题描述 服务有时必须在任一信任成员帐户下(使用下一章要讨论的CreateProcessAsUser函数)建立另一个程序,以在目前没有与系统互相作用的使用者环境下建立一个程序。假如此程序需要与使用者互动,而使用者帐户对互动式window站台及预设桌面没有存取权,则系统呼叫CreateProcessAsUser函数时会失败。
由于发现了这个问题,在建立程序之前,您必须检查这些对象之DACLs的使用者存取权利。假如没有找到这些权利,则它们必须被加入。首先检查权利是很重要的,因为盲目地增加ACE到使用者对象,最后可能会耗尽系统中的资源(通常您只能增加约80个ACEs到window站台)。
解决方案 现在让我们开始着手处理解决方案。我使用这一节所讨论的工具及观念(和一些范例函数)实作了两个函数:一个可让信任成员存取window站台,而另一个可让信任成员存取桌面。这些函数非常简单。尽管修改对象DACL的程序看起来有点令人怯步,但真实世界中的程序并没有我们所想像的复杂。以下的程序代码可让信任成员存取window站台:
BOOL AllowAccessToWinSta(PSID psidTrustee, HWINSTA hWinSta ){
BOOL fReturn = FALSE;
PSECURITY_DESCRIPTOR psdWinSta = NULL;
PACE_UNION pACENew = NULL;
try{{
// 取得window站台的DACL
PACL pDACLWinSta;
if(GetSecurityInfo(hWinSta, SE_WINDOW_OBJECT,
DACL_SECURITY_INFORMATION, NULL, NULL, &pDACLWinSta,
NULL, &psdWinSta) != ERROR_SUCCESS)
goto leave;
// 分派新的ACE
// 这个存取授予互动地登录的使用者
PACE_UNION pACENew = AllocateACE(ACCESS_ALLOWED_ACE_TYPE, 0,
DELETE|WRITE_OWNER|WRITE_DAC|READ_CONTROL|
WINSTA_ENUMDESKTOPS|WINSTA_READATTRIBUTES|
WINSTA_ACCESSCLIPBOARD|WINSTA_CREATEDESKTOP|
WINSTA_WRITEATTRIBUTES|WINSTA_ACCESSGLOBALATOMS|
WINSTA_EXITWINDOWS|WINSTA_ENUMERATE|WINSTA_READSCREEN,
psidTrustee);
// ACE是否已经在DACL中?
if (FindACEInACL(pDACLWinSta,pACENew) == -1){
// 假如没有,计算新的DACL大小
ULONG lNewACL = CalculateACLSize(pDACLWinSta, NULL, 0,
&pACENew, 1 );
// 分派内存给新的DACL
PACL pNewDACL = (PACL)_alloca(lNewACL);
if (pNewDACL == NULL)
goto leave;
// 初始ACL
if (!InitializeAcl(pNewDACL, lNewACL, ACL_REVISION))
goto leave;
// 复制ACL
if (!CopyACL(pNewDACL, pDACLWinSta))
goto leave;
// 取得新ACE的位置
ULONG lIndex = GetACEInsertionIndex(pNewDACL, pACENew);
// 增加新的ACE
if (!AddAce(pNewDACL, ACL_REVISION, lIndex,
pACENew, pACENew->aceHeader.AceSize))
goto leave;
// 设定DACL回到window站台
if (SetSecurityInfo(hWinSta, SE_WINDOW_OBJECT,
DACL_SECURITY_INFORMATION, NULL, NULL,
pNewDACL, NULL)!=ERROR_SUCCESS)
goto leave;
}
fReturn = TRUE;
}leave:;
}catch(...){
}
// 清除
if(pACENew != NULL)
LocalFree(pACENew);
if(psdWinSta != NULL)
LocalFree(psdWinSta);
return (fReturn);
}下一个范例函数可让信任成员存取桌面:
