freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

从mimikatz学习Windows安全之访问控制模型(二)
2021-08-17 10:36:54

0x00 前言

上次的文章分析了mimikatz的token模块,并简单介绍了windows访问控制模型的概念。在本篇文章中,主要介绍sid相关的概念,并介绍mimikatz的sid模块,着重分析sid::patch功能的原理

0x01 SID简介

1. 安全标识符(SID)

在Windows操作系统中,系统使用安全标识符来唯一标识系统中执行各种动作的实体,每个用户有SID,计算机、用户组和服务同样也有SID,并且这些SID互不相同,这样才能保证所标识实体的唯一性

SID一般由以下组成:

“S”表示SID,SID始终以S开头

**“1”**表示版本,该值始终为1

**“5”**表示Windows安全权威机构

**“21-1463437245-1224812800-863842198”**是子机构值,通常用来表示并区分域

**“1128”**为相对标识符(RID),如域管理员组的RID为512

Windows也定义了一些内置的本地SID和域SID来表示一些常见的组或身份

SIDName
S-1-1-0World
S-1-3-0Creator Owner
S-1-5-18Local SYSTEM
S-1-5-11Authenticated Users
S-1-5-7Anonymous

2. AD域中的SID

在AD域中,SID同样用来唯一标识一个对象,在LDAP中对应的属性名称为objectSid

重点需要了解的是LDAP上的sIDHistory属性

(1) SIDHistory

SIDHistory是一个为支持域迁移方案而设置的属性,当一个对象从一个域迁移到另一个域时,会在新域创建一个新的SID作为该对象的objectSid,在之前域中的SID会添加到该对象的sIDHistory属性中,此时该对象将保留在原来域的SID对应的访问权限

比如此时域A有一个用户User1,其LDAP上的属性如下:

cnobjectSidsIDHistory
User1S-1-5-21-3464518600-3836984554-627238718-2103null

此时我们将用户User1从域A迁移到域B,那么他的LDAP属性将变为:

cnobjectSidsIDHistory
User1S-1-5-21-549713754-3312163066-842615589-2235S-1-5-21-3464518600-3836984554-627238718-2103

此时当User1访问域A中的资源时,系统会将目标资源的DACL与User1的sIDHistory进行匹配,也就是说User1仍具有原SID在域A的访问权限

值得注意的是,该属性不仅在两个域之间起作用,它同样也可以用于单个域中,比如实战中我们将一个用户A的sIDHistory属性设置为域管的objectSid,那么该用户就具有域管的权限

另一个实战中常用的利用,是在金票中添加Enterprise Admins组的SID作为sIDHistory,从而实现同一域林下的跨域操作,这个将在后面关于金票的文章中阐述

(2) SID Filtering

SID Filtering简单的说就是跨林访问时目标域返回给你的服务票据中,会过滤掉非目标林中的SID,即使你添加了sIDHistory属性。SID Filtering林信任中默认开启,在单林中默认关闭

具体可以参考微软的文档和@dirkjanm的文章:

https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/55fc19f2-55ba-4251-8a6a-103dd7c66280?redirectedfrom=MSDN

https://dirkjanm.io/active-directory-forest-trusts-part-one-how-does-sid-filtering-work/

0x02 mimikatz的sid模块

1. sid::lookup

该功能实现SID与对象名之间的相互转换,有三个参数:

/name:指定对象名,将其转换为SID

/sid:指定SID,将其转换为对象名

/system:指定查询的目标计算机

其原理是调用LookupAccountName()LookupAccountSid()来实现对象名和SID之间的相互转化,这类API底层是调用MS-LSAT协议(RPC),比如将对象名转换为SID,底层调用的是LsarLookupNames4()

2. sid::query

该功能支持通过SID或对象名来查询对象的信息,同样有三个参数,使用时指定**/sam/sid**,/system可选

/sam:指定要查询对象的sAMAccountName

/sid:指定要查询对象的objectSid

/system:指定查询的目标域控(LDAP)

这个功能其原理就是直接使用LDAP查询,通过sAMAccountName查询对应的objectSid,或者通过objectSid查询对应的sAMAccountName

其核心是调用Windows一系列的LDAP操作API,主要是ldap_search_s()

3. sid::modify

该功能用于修改一个域对象的SID,可以使用的参数有三个:

/sam:通过sAMAccountName指定要修改SID的对象

/sid:通过objectSid指定要修改SID的对象

/new:要修改对象的新SID

使用该功能是需要先使用sid::patch功能对xxxx进行patch(自然也需要先开启debug特权),需要在域控上执行

修改时的操作就很简单了,调用LDAP操作的API对域对象的objectSid进行修改,主要使用的是ldap_modify_s()

4. sid::add

该功能用来向一个域对象添加sIDHistoy属性,有两个参数:

/sam:通过sAMAccountName指定要修改的对象

/sid:通过objectSid指定要修改的对象

/new:要修改sIDHistory为哪个对象的SID,该参数可指定目标的sAMAccountNameobjectSid,当指定名称时会先调用LookupAccountSid将其转换为SID

使用该功能也要先执行sid::patch,修改时同样是操作LDAP通过ldap_modify_s()修改,不再赘述

5. sid::clear

该功能用来清空一个对象的sIDHistory属性

/sam:要清空sIDHistory的对象的sAMAccountName

/sid:要清空sIDHistory的对象的objectSid

原理就是使用ldap_modify_s()将目标对象sIDHistory属性修改为空

6. sid::patch

对域控LDAP修改过程中的验证函数进行patch,需要在域控上执行,该功能没有参数

patch共分为两个步骤,如果仅第一步patch成功的话,那么可以使用sid::add功能,两步都patch成功的话才可以使用sid::modify功能

0x03 sid::patch分析

sid::patch在系统版本 < Vista时,patch的是samss服务中ntdsa.dll的内存,更高版本patch的是ntds服务中ntdsai.dll的内存

整个patch过程分为两步:

第一步patch的是SampModifyLoopbackCheck()的内存

第二步patch的是ModSetAttsHelperPreProcess()的内存

我们以Windows Server 2012 R2环境为例来分析,首先我们需要找到NTDS服务所对应的进程,我们打开任务管理器选中NTDS服务,单击右键,选择“转到详细信息”就会跳转到对应进程,这里NTDS服务对应的进程是lsass.exe

1. 域控对LDAP请求的处理

大致分析一下域控对本地LDAP修改请求的过滤与处理流程,当我们修改objectSidsIDHistory时,SampModifyLoopbackCheck()会过滤我们的请求,即使绕过该函数修改objectSid时,仍会受到SysModReservedAtt()的限制

侵入式切换到lsass进程并重新加载用户态符号表:

给两个检查函数打断点

此时我们修改一个用户的描述来触发LDAP修改请求

命中断点后的调用栈如下:

SampModifyLoopbackCheck()函数中存在大量Check函数,通过动态调试发现修改sIDHistoy的请求经过该函数后便会进入返回错误代码的流程

继续调试到下一个断点

SysModReservedAtt()执行结束后,正常的修改请求不会在jne处跳转,而当修改objectSid时会在jne处跳转,进入返回错误的流程

2. Patch 1/2

当我们想要进行内存patch时,通常会寻找目标内存地址附近的一块内存的值作为标记,编写程序时首先在内存中搜索该标记并拿到标记的首地址,然后再根据偏移找到要patch的内存地址,然后再进行相应的修改操作

mimikatz正是使用这种方法,其在内存中搜索的标记在代码中有明确的体现:

我们将域控的ntdsai.dll拿回本地分析,在其中搜索标记41 be 01 00 00 00 45 89 34 24 83

这一部分内容是在函数SampModifyLoopbackCheck()函数的流程中,我们可以使用windbg本地调试对比一下patch前后的函数内容

首先我们找到lsass.exe的基址并切换到该进程上下文:

使用lm列出模块,可以看到lsass进程中加载了ntdsai.dll,表明此时我们可以访问ntdsai.dll对应的内存了

我们直接查看SampModifyLoopbackCheck()函数在内存中的反汇编

为了对比patch前后的区别,我们使用mimikatz执行sid::patch,然后再查看函数的反汇编。如下图所示,箭头所指处原本是74也就是je,而patch后直接改为ebjmp,使流程直接跳转到0x7ffc403b2660

0x7ffc403b2660处的代码之后基本没有条件检查的函数了,恢复堆栈和寄存器后就直接返回了,这样就达到了绕过检查逻辑的目的

3. Patch 2/2

同理,按照mimikatz代码中的标记搜索第二次patch的位置0f b7 8c 24 b8 00 00 00

查看ModSetAttsHelperPreProcess()处要patch的内存,patch前如下图所示

patch完成后内存如下图,其实本质是让SysModReservedAtt()函数失效,在内存中寻找到标记后偏移-6个字节,然后将验证后的跳转逻辑nop

4. 解决patch失败的问题

由于mimikatz中内存搜索的标记覆盖的windows版本不全,所以经常会出现patch失败的问题。例如在我的Windows Server 2016上,第二步patch就会失败,这种情况多半是因为mimikatz中没有该系统版本对应的内存patch标记

此时我们只需要将目标的ntdsai.dll拿下来找到目标地址

然后修改为正确的内存标记和对应的偏移地址即可,如果新增的话记得定义好版本号等信息

此时重新编译后就可以正常patch了

0x04 渗透测试中的应用

在渗透测试中的利用,一个是使用SIDHistory属性来留后门,另一个是修改域对象的SID来实现域内的“影子账户”或者跨域等操作

1. SIDHistoy后门

拿下域控后,我们将普通域用户test1的sIDHistory属性设置为域管的SID:

此时test1将具有域管权限,我们可以利用这个特性来留后门

2. 域内“影子账户”

假设我们此时拿到了域控,然后设置一个普通域用户的SID为域管的SID

此时我们这个用户仍然只是Domain Users组中的普通域成员

但该用户此时已经具有了域管的权限,例如dcsync:

并且此时也可以用该用户的账号和密码登录域控,登录成功后是administrator的session。但该操作很有可能造成域内一些访问冲突(猜测,未考证),建议在生产环境中慎用

3. 跨域

通常我们拿到一个域林下的一个子域,会通过黄金票据+SIDHistory的方式获取企业管理员权限,控制整个域林

除了这种方法,我们也可以直接修改当前子域对象的sIDHistory属性,假设我们现在拿到一个子域域控,通过信任关系发现存在一个父域,此时我们无法访问父域域控的CIFS

但我们给子域域管的sIDHistory属性设置为父域域管的SID

此时就可以访问父域域控的CIFS了:

0x05 参考

https://docs.microsoft.com/

https://github.com/gentilkiwi/mimikatz

作者:Loong716@Amulab

# 系统安全 # 安全模型 # 网络安全技术
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录