最近,我不得不通过内核驱动程序对FILE_OBJECT进行一些访问检查。我不能只是将数据传递给usermode组件,因为它会在I/O路径中引入延迟,特别是对于网络文件,并且我并没有针对当前用户本身进行操作。
缩略语
DACL(自由访问控制列表):包含允许/拒绝访问安全对象的ACE;
ACE(访问控制条目):包含一组访问权限和一个SID,用于标识受权、拒绝或审核其权限的受托人;
SID(安全性标识符):可变长度的唯一值,用于标识受托人(用户、组、计算机帐户等)
访问检查的介绍
FILE_OBJECT访问检查的概述可以在下图中看到,其中的逻辑来自于《Windows内核》(一本针对Win 2k的驱动程序开发书籍)和《Nt调试》。
访问检查算法
强制性完整性检查
该检查是避免完全访问检查的第一种有效方法,它基于主体(源)和安全对象(目标)的完整性确定对资源的访问。主体可以是一个进程(完整性存储在进程令牌中),而对象可以是一个文件(完整性存储在SACL中)。具有低完整性级别的主体无法写入具有中等完整性级别的对象,即使该对象的DACL允许对该主体进行写访问。缺少完整性标签的对象被操作系统视为攻击媒介;这可以防止低完整性代码修改未标记的对象。
Process HI:可以对 H/M/L对象对象进行读写;
Process MI:可以对M和L对象对象进行读写;
Process LI:可以对LI和R M对象进行读写;
遍历DACL中的所有ACE
你可以在有效的ACL上使用RtlGetAce()并遍历每个ACE索引,由于所有ACE变体(ACCESS_ALLOWED_ACE,ACCESS_DENIED_ACE,SYSTEM_MANDATORY_LABEL_ACE等)都具有通用标头,因此你可以将其转换为任意标头并检查ACE_HEADER.AceType以确定其类型。
typedef struct _ACCESS_ALLOWED_ACE { ACE_HEADER Header; ACCESS_MASK Mask; ULONG SidStart; } ACCESS_ALLOWED_ACE;
ACE中的SID检查
用于提取用户/组成员身份的SID和使用RtlEqualSid()的自定义内容,由于没有内核API可以获取随机用户的组成员身份SID,因此IR/E对Windows进行了访问检查,并找到了“\Registry\Machine\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\
SID ACCESS_MASK检查
位掩码操作,文件/目录/管道对于不同的标志具有不同的含义,但是基本概念是相同的,即:
· 0x0001 = FILE_READ_DATA (file and pipe) = FILE_LIST_DIRECTORY (dir)
· 0x0002 = FILE_WRITE_DATA (file and pipe) = FILE_ADD_FILE (dir)
· 0x0004 = FILE_APPEND_DATA (file) = FILE_CREATE_PIPE_INSTANCE (pipe) = FILE_ADD_SUBDIRECTORY(dir)
检查ACE允许/拒绝类型
其中ALLOWED *和DENIED *表示变体,例如ACCESS_ALLOWED_ACE_TYPE,ACCESS_ALLOWED_CALLBACK_ACE_TYPE,ACCESS_ALLOWED_COMPOUND_ACE_TYPE等。因此,即使你是10个组中的一员,并且9个组有直接的正面ACE,即使1个组有直接的负面ACE,你也会被完全拒绝(其中直接表示成员SID实际上明确地处于ACE中,而不是从父类继承的)。ACE的优先级如下:
Direct negative ace Direct positive ace Inherited negative ace Inherited positive ace
检查所需的剩余访问权限
检查时,有3种访问状态:
1. DesiredAccess:用户的请求,即R|W|X;
2. 授予访问权限:ACE根据组(即R|X|DEL)给予他的访问权限;
3. 剩余的访问权:什么是剩余的,即W。
如图1所示,如果在检查所有ace后仍然保留任何访问,则无论如何都将被拒绝,因为不存在部分特权的概念。而且,如果你被授予了所有的访问权限,还有ACE要处理,你就可以提前退出并被接受。
请注意此处可能存在的安全问题,如果某人(配置错误)在一组ACE结束时被明确拒绝,但用户在拒绝之前获得了所有访问权限,则将授予该对象访问权限。因此,任何拒绝ACE的操作都应在ACL的开头执行,但请注意,大多数ACL API只是附加到结尾。
ACCESS_MASK
ACCESS_MASK 32位包含通用、标准和特定权限的组合:
1.GENERIC_READ为文件授予所有读的特定权限:READ_CONTROL,SYNCHRONIZE,FILE_READ_DATA,FILE_READ_ATTRIBUTES、FILE_READ_EA。
2.注册表项的GENERIC_READ授予所有读权限:READ_CONTROL,KEY_QUERY_VALUE,KEY_ENUMERATE_SUB_KEYS,KEY_NOTIFY。
SID
SID格式为S-R-I-S1-S2-S3-Sn,其中R为修订版,I为IdentityAuthority,S1至Sn为SubAuthority。
IdentityAuthority可以采用十进制或十六进制格式,即“S-1-5-15”和“S-1-0x0100080000ff-15”。其中,format{0,0,0,0,0,5}中的IdentifierAuthority == SECURITY_NT_AUTHORITY,和0x0100080000ff是一个自定义IdentifierAuthority。
SubAuthority(通常称为RID)是发布SID的权限,并且是SID内部的数组。在上面的例子中,RID为15,还有SID S-1-5-15-544。
· Revision为1;
· IdentityAuthority为5;
· RID [0]为15(SECURITY_THIS_ORGANIZATION_RID);
· RID [1]为544(DOMAIN_ALIAS_RID_ADMINS)。
安全描述符
SECURITY_DESCRIPTOR(SD)包含有关对象的安全性信息,它包含对象的所有者和DACL。通过研究发现,SD有两种口味,分别是指向ACL数据的指针或连续出现在SD本身之后的指针。虽然没有很好地记录第二个变量,但是你使用Win32 API进行的大多数SD操作通常都使用指针变量,因为这通常是在内存中的布局方式。但是,如果你处于FileSystem I/O路径中,则在发生Pre/Post Create/Access事件之前,来自磁盘本身的数据将采用这种相对格式,这就是ACL在SD之后连续存储在磁盘上的方式。在创建此自定义结构的过程中,我最终在ntifs.h中遇到了PISECURITY_DESCRIPTOR_RELATIVE,这正是我们想要的。虽然没有记录,但是可以通过Ida/WinDbg找到,可以通过检查SD-> Control中的公共标头并使用0x8000位来检查SECURITY_DESCRIPTOR的类型(指针还是相对),我最终在ntifs.h中找到了SE_SELF_RELATIVE (0x8000)。
获取继承权限
1. 要在目录中创建文件时通过ACL继承向用户授予FILE_EXECUTE(0x20),请将OBJECT_INHERIT(0x1)应用于父目录。
1.1 但这只会继承给子FILE对象类型,而不继承给DIRECTORY对象,否则子目录将得到FILE_TRAVERSE(0x20)
1.2 但是,应用0x20会导致父目录本身具有FILE_TRAVERSE(0x20),以使用INHERIT_ONLY_ACE(0x8)来解决此OR标志,因此:将FILE_EXECUTE 0x20授予用户,并具有继承权限(0x1 | 0X8),这意味着只有子对象才能获得0x20,因为它只能继承。
2. 应用于对象的显式权限将覆盖继承。
2.1 例如我们要拒绝Bob在所有文件夹/子文件夹中的所有DELETE权限,但允许Bob删除一个特定的深层子文件夹。因此,在根文件夹上设置INHERIT DELETE DENY Bob,但显式地允许Bob删除特定的子文件夹。
WinDbg
有用的WinDbg命令如下所示:
· !error @@(status)
· !acl @@(pAcl)
· !sid @@(pSid)
· !sd @@(piSD)
· !list
注意:不要使用“?”进行位掩码操作,它会搞乱无符号/有符号的位操作,使用“ ??”并将结果强制转换为(无符号int)以查看十六进制。
当I/O出现IRQL DISPATCH_LEVEL时,准备好BSOD。
本文翻译自:https://astralvx.com/index.php/2019/07/30/access-checks-from-the-kernel/如若转载,请注明原文地址: