ACL即访问控制表,由一个ACL头和零到多个ACE(Access_control entry 访问控制实例)构成。
ACL的应用平台是WindowsNT/2000/XP/2003,实际上WindowsNT3.1之后的使用NT内核的操作系统都支持这个结构。ACL标志了第三方对某一个对象的访问权限,这个对象可以是任何类的实例,当然也包括了进程(Process)对象。
1 概述
每一个ACE包含一个授权对象(Trustee)和一组权限,一个有效的SecurityDescriptor(安全标志)包含两个ACL,即DACL和SACL。
在WindowsNT下,使用OpenProcess打开进程的时候,系统会根据相应进程的DACL结构确定对象的访问权限,假如DACL不存在,那么将开放所有权限;假如DACL存在但是为空,那么将拒绝一切形式的访问。
实际上,在不同的操作系统下,对DACL为空的处理是不尽相同的,有可能不拒绝系统管理员权限进程的任意形式访问。
SACL的作用是允许系统管理员纪录所有对安全对象的访问情况,SACL中ACE即是需要被记录的项目。
DACL的工作原理如下图:
(图略)
2 DACL and CreateProcess
2.1 SECURITY_ATTRIBUTES
SECURITY_ATTRIBUTES结构用于在创建进程时指定访问安全级别。
typedef struct _SECURITY_ATTRIBUTES
{
DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES;
其中lpSecurityDescriptor指向一个SECURITY_DESCRIPTOR结构。
2.2 SECURITY_DESCRIPTOR
SECURITY_DESCRIPTOR结构不能直接修改,只能通过调用API函数来修改,它的内容包括进程的所有者(SID Owner)、所属工作组(SID PrimaryGroup)、DACL(一个)、SACL(一个)、对前面项目的修改等级。
主要的API函数包括:
(1) InitializeSecurityDescriptor
初始化一个SECURITY_DESCRIPTOR结构,除SECURITY_DESCRIPTOR_REVISION是默认的修改等级,Owner等项目全部为空或者为NULL值。
(2) SetSecurityDescriptorDacl
本函数设置SECURITY_DESCRIPTOR结构中的DACL,参数中包含了指向ACL结构的指针。
(3) SetSecurityDescriptorGroup
本函数设置SECURITY_DESCRIPTOR结构中的主工作组(primary group)信息,原先如果存在主工作组信息,执行本函数时原信息将被覆盖。
(4) SetSecurityDescriptorOwner
本函数设置SECURITY_DESCRIPTOR结构中的所有者(owner)信息,并覆盖原有信息。
(5) SetSecurityDescriptorRMControl
本函数设置SECURITY_DESCRIPTOR中对应资源管理程序的64位信息,资源管理程序通过访问这8个字节的信息,获取进程对象的相关资料。
(6) SetSecurityDescriptorSacl
本函数设置SECURITY_DESCRIPTOR结构中的SACL,参数中包含了指向ACL结构的指针。
通过以上六个函数,我们可以完成对SECURITY_DESCRIPTOR结构的访问。
2.3 CreateProcess
函数声明:
BOOL CreateProcess(
LPCTSTR lpApplicationName, // name of executable module
LPTSTR lpCommandLine, // command line string
LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD
LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD
BOOL bInheritHandles, // handle inheritance option
DWORD dwCreationFlags, // creation flags
LPVOID lpEnvironment, // new environment block
LPCTSTR lpCurrentDirectory, // current directory name
LPSTARTUPINFO lpStartupInfo, // startup information
LPPROCESS_INFORMATION lpProcessInformation // process information
);
部分参数说明:
lpCommandLine 指向字符串的指针,是新进程的命令行。命令行字符串以空字符结束,同样的'\0'也代表字符的结束。当lpApplicationName为NULL的时候,本字符串将被看成是新进程的可执行文件路径和文件名,这时要注意空格的处理;LpProcessAttributes,lpThreadAttributes 这两个指针指向的内容代表了对新进程和其线程的可继承性的设定。特别的,在NT内核情况下,指向一个SECURITY_ATTRIBUTES结构。如前所述,它包含了对象安全级别的信息;DwCreationFlags 这个DWORD值用于设置进程的创建模式和优先权,大多flag值是可以组合的。
3 ACL
3.1 ACL头定义
typedef struct _ACL {
BYTE AclRevision;
BYTE Sbz1;
WORD AclSize;
WORD AceCount;
WORD Sbz2;
} ACL,*PACL;
参数说明:
AclRevision 内容修正等级,一般是ACL_REVISION,在NT内核下,当结构中存在指定对象ACE时,应该设置为ACL_REVISION_DS;
Sbz1, Sbz2 填料;
AceCount 结构中包含ACE的数量;
AclSize ACL头和所有ACE的总字节数。
结构说明:
3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+----------------------------------+----------------+---------------+
| AclSize | Sbz1 | AclRevision |
+----------------------------------+----------------+---------------+
| Sbz2 | AceCount |
+----------------------------------+---------------------------------+
ACL是由ACL头(即ACL结构)和不定数量的ACE构成的,ACL中每个ACE从0到n-1编号,因此,n就是ACL中ACE的数量,即n = AceCount。编辑ACL的时候,应用程序通过ACE编号来访问每个单独的ACE。
如前所述,ACL有两种,即DACL和SACL。
DACL由对象所有者(Owner)进程和拥有该对象WRITE_DAC权限的任何进程维护。这种机制类似于某一个文件的主人或者管理员有权决定能让谁看到这份文件。
一个对象同样拥有系统级的安全设定,这就是SACL。SACL由系统管理员(administrator)维护,它决定了对于某个对象,系统管理员能够纪录并审核哪些访问。
3.2 ACE for DACL
ACE是ACL中标志访问权限的单元,以下是已经定义的能够在DACL中使用的ACE类型:
ACCESS_ALLOWED_ACE
这个结构允许指定的用户或群组访问某一个对象;
ACCESS_ALLOWED_OBJECT_ACE
这个结构允许指定的用户或群组访问某一个特定的对象,应用在Windows2000/XP。当ACL中包含这个类型的ACE时,AclRevision应设置为ACL_REVISION_DS;
ACCESS_DENIED_ACE
这个结构拒绝指定的用户或群组访问某一个对象;
ACCESS_DENIED_OBJECT_ACE
这个结构拒绝指定的用户或群组访问某一个特定的对象,应用在Windows2000/XP。当ACL中包含这个类型的ACE时,AclRevision应设置为ACL_REVISION_DS。
为了进一步说明ACE,以下介绍ACE的数据结构。
typedef struct _ACE_HEADER {
BYTE AceType;
BYTE AceFlags;
WORD AceSize;
} ACE_HEADER;
typedef ACE_HEADER *PACE_HEADER;
参数说明:
AceType 指定ACE类型;
AceFlags ACE的控制标志,可以是几个标志的组合;
AceSize ACE的字节长度。
每个ACE都以ACE_HEADER开头,并包含了其全长(按字节)。
3.3 如何应用ACL
本节将介绍如何往一个DACL中添加ACCESS_DENIED_ ACE元素,并实现对一个进程的访问保护。
3.3.1 AddAccessDeniedAce函数
BOOL AddAccessDeniedAce(
PACL pAcl, // access control list
DWORD dwAceRevision, // ACL revision level
DWORD AccessMask, // access mask
PSID pSid // security identifier
);
部分参数说明:
dwAceRevision 如果ACL中已经包含了一个以上ACCESS_ALLOWED_OBJECT_ACE或者ACCESS_DENIED_OBJECT_ACE类型的ACE对象,应设置为ACL_REVISION_DS,否则应设置为ACL_REVISION;
AccessMask 访问类型掩码,它可以标志一种访问类型或者多种访问类型的组合,本函数中这些被标志的访问类型将被禁用;
pSid Trutee的ID信息。这个参数指向一个SID结构,它指定了那些用户将被剥夺访问权限,这些用户可以是普通用户、群组以及共享用户。
另外,在ACL中,应该按照先设ACCESS_DENIED_ACE再设ACCESS_ALLOWED_ACE的顺序来添加ACE,否则将会导致错误。这一点十分重要,也是初学者容易犯的错误。在这里添加的只是ACCESS_DENIED_ACE,不需要考虑顺序问题。
3.3.2 ACCESS_MASK and SID
typedef DWORD ACCESS_MASK;
这个类型组合标志了一个或者多个访问类型。
SID(The security identifier)
这是一个变长的数据结构,用于唯一的标志用户或者群组。这个结构不能被直接编辑,需要用系统API函数来创建、修改,具体应用如下:
前一阵子,有网友向我抱怨。他说自己在本机建立了一个共享内存,然后用B/S架构的浏览器端来访问这块内存,结果总是访问成功,返回错误是没有权限。
的确,B/S程序,其插件运行在浏览器中,账户是guest,当然没有权限访问身为内核对象的本机共享内存。不过,如果你在创建内核对象时重新设置了它的访问控制列表(DACL),那么就能让它被你所允许的任何账户访问。很神奇,不是吗?
下面我通过创建一个在Windows2000下不会被杀死的计事本程序,向各位展示这个技术:
void Test()
{
printf("本程序將创建一个不可被杀死的记事本!\n");
PSID pEveryoneSID = NULL; // everyone群组SID
PSID pAdminSID = NULL; // 本机系统管理员群组SID
EXPLICIT_ACCESS ea[3]; // ACE內容
PACL pProcessACL = NULL; // 进程的DACL
PSECURITY_DESCRIPTOR pSD = NULL; // 进程的SD
SECURITY_ATTRIBUTES saProcess; // 进程SA
DWORD dwRet;
// 以下創建SID
// S-1-1-0
SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;
if (!AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID,
0, 0, 0, 0, 0, 0, 0, &pEveryoneSID))
{
printf("AllocateAndInitializeSid failed! EveryoneSID\n");
goto Err;
}
// S-1-5-32-0x220
if (!AllocateAndInitializeSid(&SIDAuthNT, 1,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdminSID))
{
printf("AllocateAndInitializeSid failed! AdminSID\n");
goto Err;
}
// S-1-5-32-0x1F4
PSID pAdminuUserSID;
if (!AllocateAndInitializeSid(&SIDAuthNT, 1,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_USER_RID_ADMIN, 0, 0, 0, 0, 0, 0, &pAdminuUserSID))
{
printf("AllocateAndInitializeSid failed! pAdminuUserSID\n");
goto Err;
}
// 填充外部访问组
ZeroMemory(&ea, 2 * sizeof(EXPLICIT_ACCESS));
// S-1-1-0,禁止关闭进程和修改参数
ea[0].grfAccessPermissions =
PROCESS_TERMINATE|PROCESS_SET_INFORMATION;
ea[0].grfAccessMode = GRANT_ACCESS;
ea[0].grfInheritance= NO_INHERITANCE;
ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
ea[0].Trustee.ptstrName? = (LPTSTR) pEveryoneSID;
// S-1-5-32-0x220,禁止关闭进程和修改参数
ea[1].grfAccessPermissions =
PROCESS_TERMINATE|PROCESS_SET_INFORMATION;
ea[1].grfAccessMode = GRANT_ACCESS;
ea[1].grfInheritance= NO_INHERITANCE;
ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
ea[1].Trustee.ptstrName? = (LPTSTR) pAdminSID;
// S-1-5-32-0x1F4,禁止关闭进程和修改参数
ea[2].grfAccessPermissions =
PROCESS_TERMINATE|PROCESS_SET_INFORMATION;
ea[2].grfAccessMode = GRANT_ACCESS;
ea[2].grfInheritance= NO_INHERITANCE;
ea[2].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[2].Trustee.TrusteeType = TRUSTEE_IS_USER;
ea[2].Trustee.ptstrName? = (LPTSTR) pAdminuUserSID;
// 创建并填充ACL
dwRet = SetEntriesInAcl(3, ea, NULL, &pProcessACL);
if (dwRet != ERROR_SUCCESS)
{
printf("SetEntriesInAcl failed!\nCode = %d", dwRet);
goto CleanUp;
}
// 创建并初始化SD
pSD = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR,
SECURITY_DESCRIPTOR_MIN_LENGTH);
if (pSD == NULL)
{
printf( "LocalAlloc failed!\n");
goto Err;
}
if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION))
{
printf( "InitializeSecurityDescriptor failed!\n");
goto Err;
}
// 添加ACL到SD中去
if (!SetSecurityDescriptorDacl(pSD,
TRUE, // fDaclPresent flag
pProcessACL,
FALSE)) // not a default DACL
{
printf( "SetSecurityDescriptorDacl failed!");
goto Err;
}
// 设置SA
saProcess.nLength = sizeof(SECURITY_ATTRIBUTES);
saProcess.lpSecurityDescriptor = pSD;
saProcess.bInheritHandle = FALSE;
// 运行进程并等待其正常结束
PROCESS_INFORMATION ProcessInfo;
STARTUPINFO StartupInfo;
ZeroMemory(&StartupInfo, sizeof(StartupInfo));
StartupInfo.cb = sizeof(StartupInfo);
if (CreateProcess("c:\\winnt\\notepad.exe", NULL,
&saProcess, NULL, FALSE, 0, NULL,
NULL, &StartupInfo, &ProcessInfo))
{
WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
CloseHandle(ProcessInfo.hThread);
CloseHandle(ProcessInfo.hProcess);
}
else
{
printf("CreateProcess failed!\n");
goto Err;
}
// Clean up
CleanUp:
printf("Exiting...\n");
if (pEveryoneSID != NULL)
{
FreeSid(pEveryoneSID);
}
if (pAdminSID != NULL)
{
FreeSid(pAdminSID);
}
if (pProcessACL != NULL)
{
LocalFree(pProcessACL);
}
if (pSD != NULL)
{
LocalFree(pSD);
}
printf("Success!\n");
return;
// Error process
Err:
DWORD dwErr;
dwErr = GetLastError();
printf("Code = %d \n", dwErr);
goto CleanUp;
}