上一篇博文介绍了自主访问模型,本篇介绍强制访问模型。SRM的强制访问过程是通过特权检查来实施的,sePrivilegeCheck调位于base\ntos\se\privileg.c文件中的SepPrivilegeCheck函数执行实际的特权检查。其参数Token代表了当前线程的安全环境,如同SepAccessCheck函数也需要代表调用线程的令牌一样;参数RequirePrivileges和RequiredPrivilegeCount合起来指明了调用线程要请求一组特权;参数PrivilegeSetControl定义了如何请求这些特权,即,请求所指定的特权或其中之一。
在windows中,特权是由LUID对象来标识的,LUID代表一个本地唯一标识符(Locally Unique Identify),由两个LONG成员构成,因此在32位系统中LUID是一个64位的整数。每一个特权都附带一些属性,两者结合起来构成了LUID_AND_ATTRIBUTES数据结构,而属性是一个无符号长整型类型,以下是关于LUID_AND_ATTRIBUTES属性的定义:
typedef struct _LUID_AND_ATTRIBUTES {
LUID Luid;
ULONG Attributes;
}LUID_AND_ATTRIBUTES,*PLUID_AND_ATTRIBUTES;
#define SE_PRIVILEGE_ENABLE_BY_DEFAULT (0x0000 00001L)
#define SE_PRIVILEGE_ENABLE (0x0000 00002L)
#define SE_PRIVILEGE_REMOVED (0x0000 00004L)
#define SE_PRIVILEGE_USED_FOR_ACCESS (0x8000 00000L)
在令牌Token数据结构的定义中,有一个Privilege成员,代表该令牌账户的所有特权,因此,SepPrivilegeCheck函数的实现是,用一个二重循环,对调用线程所请求检查的每一个特权,遍历Token参数中的Privilege数组成员是否能匹配。最后检查总共匹配了多少特权,是否所有的请求特权都匹配上,或者至少匹配上一个。检查流程如下:
windows内核中定义了一组特权,即类型位LUID的Se<xxx>全局变量,其定义位于base\ntos\se\seglobal.c文件中,它们的初始化由该文件中的SepVariableInitialization函数来完成,这组全局特权为:
LUID SeCreateTokenPrivilege; //创建令牌对象
LUID SeAssignPrimaryTokenPrivilege; //替换一个进程的令牌
LUID SeLockMemoryPrivilege; //锁住内存页面
LUID SeIncreaseQuotaPrivilege; //增加一个进程的内存配额
LUID SeUnsolicitedInputPrivilege; //已经过时不再使用
LUID SeTcbPrivilege; //操作系统使用
LUID SeSecurityPrivilege; //管理和审计安全日志
LUID SeTakeOwnershipPrivilege; //接管文件和其他对象的所有权
LUID SeLoadDriverPrivilege; //加载和卸载设备驱动程序
LUID SeCreatePageFilePrivilege; //创建页面文件
LUID SeIncreaseBasePriorityPrivilege; //增加调度优先级
LUID SeSystemProfilePrivilege; //系统性能分析
LUID SeSystemtimePrivilege; //改变系统时间
LUID SeProfileSingleProcessPrivilege; //对单个进程进行性能分析
LUID SeCreatePermanentPrivilege; //创建永久对象
LUID SeBackupPrivilege; //备份文件和目录
LUID SeRestorePrivilege; //恢复文件和目录
LUID SeShutdownPrivilege; //系统停机
LUID SeDebugPrivilege; //调试程序
LUID SeAuditPrivilege; //安全审计
LUID SeSystemEnvironmentPrivilege; //修改系统的固件环境变量
LUID SeChangeNotifyPrivilege; //(名字空间中)变化通知,也用于绕过穿越检查
LUID SeRemoteShutdownPrivilege; //远程停机
LUID SeUndockPrivilege; //从插接站(docking station)移除计算机
LUID SeSyncAgentPrivilege; //同步代理(目录服务)
LUID SeEnableDelegationPrivilege; //允许计算机账户或用户账户被用于委托
LUID SeManageVolumePrivilege; //管理卷
LUID SeImpersonatePrivilege; //模仿
LUID SeCreateGlobalPrivilege; //创建全局对象
对于每一种特权,当相关联的操作(即需要特权才能进行操作)在内核中被适当的组件激发时,应通过SePrivilegeCheck或它的包装函数SeSinglePrivilegeCheck,来检查调用线程是否具有相应的特权。如SepCreateToken函数把令牌对象插入倒进程的句柄之前调用SeSinglePrivilegeCheck函数来检查当前线程是否具有SeCreateTokenPrivilege特权;IopParseDevice函数在打开一个设备对象时通过IopCheckBackupRestorePrivilege函数(它调用SePrivilegeCheck)来检查调用线程是否具有SeBackupPrivilege或SeRestorePrivilege特权)。
另外SeMakeSystemToken函数,在系统初始化阶段被调用,它创建了System的进程令牌,以本地系统账号为SID和组SID,而在另两个函数SeMakeAnonymousLogonToken和SeMakeAnonymousLogonTokenNoEveryone中,则可以看到它们创建的令牌没有任何特权。
windows安全管理是一个复杂的课题,其安全模型涉及系统的方方面面,而且模型中的许多组件和扩展结构是在用户模式下运行的,这里只讨论了安全管理的核心部分,即内核中的安全引用监视器以及它的对象访问和特权操作的检查过程。在windows内核中,与安全相关的函数以“Se“作为前缀,有一些安全函数还存在对应的系统服务,这些系统服务函数的名称以”Nt"作为前缀,后面部分与“Se“ 相同,在WRK的public\sdk\inc\ntseapi.h文件中包含了与安全有关的系统服务函数的原型声明,而base\ntos\inc\se.h文件包含了内核安全函数的原型声明,包括众多以”Se”为前缀的函数。以Sep为前缀的函数是SRM的内部函数。