这篇文章主要是分析我在Background Intelligent Transfer Service中发现的任意文件移动漏洞,这是Windows 10中进行特权文件操作的一个漏洞示例,虽然没有什么新的技术,但这个漏洞本身却很有趣,因为它是被隐藏在未记录的函数中的。我将分析是如何挖到这个漏洞的,并且还将分享一些有关我为了确定漏洞而经历的逆向的一些见解。
0x01 漏洞描述
如果你不知道Windows的此功能,请参考Microsoft文档。
(https://docs.microsoft.com/en-us/windows/win32/bits/background-intelligent-transfer-service-portal)
程序员和系统管理员使用后台智能传输服务(BITS)从HTTP Web服务器和SMB文件共享下载文件或将文件上传到HTTP Web服务器和SMB文件共享。BITS将考虑传输成本以及网络使用情况,从而使用户的工作影响尽可能的小。即使重新启动后,BITS也可以处理网络中断,暂停并自动恢复传输。
该服务公开了几个COM对象,这些对象是“ 控件类 ”,并且还有一个“ 旧版控件类”,后者可用于获取指向旧版 IBackgroundCopyGroup接口的指针,该旧版接口有两个未记录的方法:QueryNewJobInterface()和SetNotificationPointer()。
如果用户调用接口CreateJob()的方法IBackgroundCopyGroup(即旧方法),则将获得指向旧接口IBackgroundCopyJob1的指针。另一方面,如果他调用QueryNewJobInterface()同一接口的方法,则将获得指向新接口IBackgroundCopyJob的指针。
问题是此调用是由服务处理的。这意味着用户可以在IBackgroundCopyJob的上下文中获得指向接口的指针NT AUTHORITY\SYSTEM,尽管可以通过其他方法实现模拟,但仍存在一些副作用。
创建作业并将文件添加到队列后,将创建一个临时文件,服务完成写入文件后,由于调用了,因此将其重命名为用户指定的文件名MoveFileEx(),问题在于,使用返回的接口指针QueryNewJobInterface()时,最后的操作是在没有模拟的情况下完成的。
因此,普通用户可以利用此行为使用操作锁和符号链接将任意文件移动到受限制的位置。
0x02 BITS COM类介绍
后台智能传输服务公开了几个COM对象,可以使用OleViewDotNet列出这些对象。
在这里,我将重点介绍后台智能传输(BIT)控件类1.0和旧版BIT控件类及其主要接口,分别为IBackgroundCopyManager和IBackgroundCopyMgr。
新BIT控件类
BIT 控件类1.0的工作方式如下:
1. 你必须创建BIT控件类(CLSID:)的实例,并使用来4991D34B-80A1-4291-83B6-3328366B9097请求指向该IBackgroundCopyManager接口CoCreateInstance()的指针。
2. 然后,你可以创建一个带有调用IBackgroundCopyManager::CreateJob()以获取指向该IBackgroundCopyJob接口的指针的“作业”。
3. 然后,你可以通过调用来将文件添加到作业中IBackgroundCopyJob::AddFile(),这需要两个参数:URL和本地文件路径,该URL也可以是UNC路径。
4. 最后,由于作业中创建一个SUSPENDED状态,你必须调用IBackgroundCopyJob::Resume(),并且IBackgroundCopyJob::Complete()要在TRANSFERRED工作的状态。
CoCreateInstance(CLSID_4991D34B-80A1-4291-83B6-3328366B9097) -> IBackgroundCopyManager* |__ IBackgroundCopyManager::CreateJob() -> IBackgroundCopyJob* |__ IBackgroundCopyJob::AddFile(URL, LOCAL_FILE) |__ IBackgroundCopyJob::Resume() |__ IBackgroundCopyJob::Complete()
尽管BIT服务的运行方式为NT AUTHORITY\SYSTEM,但所有这些操作都是在模拟RPC客户端时执行的,因此此处无法进行特权提升。
传统控件类
传统控件类的工作方式有些不同,在该过程开始前需要一个额外的步骤。
1. 你必须创建旧版BIT控件类(CLSID:)的实例,并使用69AD4AEE-51BE-439B-A92C-86AE490E8B30来请求指向该IBackgroundCopyQMgr接口CoCreateInstance()的指针。
2. 然后,你可以创建一个带有调用IBackgroundCopyQMgr::CreateGroup()以获取指向该IBackgroundCopyGroup接口的指针的“组”。
3. 然后,你可以创建一个带有调用IBackgroundCopyGroup::CreateJob()以获取指向该IBackgroundCopyJob1接口的指针的“作业”。
4. 然后,你可以通过调用来将文件添加到“作业”中,该调用IBackgroundCopyJob1::AddFiles()将FILESETINFO结构作为参数。
5. 最后,由于作业中创建一个SUSPENDED状态,你必须调用IBackgroundCopyJob1::Resume(),并且IBackgroundCopyJob1::Complete()要在工作TRANSFERRED的状态。
CoCreateInstance(CLSID_69AD4AEE-51BE-439B-A92C-86AE490E8B30) -> IBackgroundCopyQMgr* |__ IBackgroundCopyQMgr::CreateGroup() -> IBackgroundCopyGroup* |__ IBackgroundCopyGroup::CreateJob() -> IBackgroundCopyJob1* |__ IBackgroundCopyJob1::AddFiles(FILESETINFO) |__ IBackgroundCopyJob1::Resume() |__ IBackgroundCopyJob1::Complete()
尽管BIT服务运行为NT AUTHORITY\SYSTEM,但所有这些操作都是在模拟RPC客户端的同时执行的,因此在这里也不可能进行特权提升。
这两个COM类及其接口的用法在MSDN的此处和此处都有详细记录,但是,在尝试了解IBackgroundCopyGroup接口如何工作时,我注意到MSDN上列出的方法与其实际的Proxy定义之间存在一些差异。
该IBackgroundCopyGroup接口的文档可在此处获得,根据此资料,它有13种方法。但是,当使用OleViewDotNet查看此接口的代理定义时,我可以看到它实际上有15个方法。
Proc3以Proc15在文档中列出了,但方法Proc16和Proc17不在文档中。
相应的头文件是Qmgr.h,如果打开此文件,我应该获得此接口上可用的所有方法的准确列表。
实际上,我可以看到两个未记录的方法:QueryNewJobInterface()和SetNotificationPointer()。
0x03 未公开的函数学习
多亏了OleViewDotNet,我知道该IBackgroundCopyQMgr接口是在其中实现的,qmgr.dll可以在IDA中打开进行逆向,看看是否可以找到有关该IBackgroundCopyGroup接口以及我提到的两个未记录的方法的更多信息。
该QueryNewJobInterface()方法需要1个参数:接口标识符(REFIID iid),并返回指向接口(IUnknown **pUnk)的指针,该函数的原型如下:
virtual HRESULT QueryNewJobInterface(REFIID iid, IUnknown **pUnk);
首先,将输入GUID(接口ID)与硬编码值:37668d37-507e-4160-9316-26306d150b12进行比较,如果不匹配,则该函数返回错误代码0x80004001 ,否则,GetJobExternal()将从CJobClass 调用函数。
硬编码的GUID值(37668d37-507e-4160-9316-26306d150b12)很有趣,这是IID_IBackgroundCopyJob的值,我可以在Bits.h头文件中找到它。
0x04 漏洞分析
在进一步进行逆向过程之前,我可以根据收集到的少量信息做出有根据的猜测。
· 未记录方法的名称为QueryNewJobInterface()。
· 传统 BIT控件类的IBackgroundCopyGroup接口公开了这个方法。
· 使用了新GUID的IBackgroundCopyJob接口。
因此,我可以假定此函数的目的是从Legacy Control Class获取指向新 IBackgroundCopyJob接口的接口指针。
为了验证此假设,我创建了一个执行以下操作的应用程序:
1. 会创建传统控件类的实例,并获取指向旧IBackgroundCopyQMgr界面的指针。
2. 创建一个新的组,并带有调用IBackgroundCopyQMgr::CreateGroup()以获取指向该IBackgroundCopyGroup接口的指针。
3. 创建一个新作业,并带有调用IBackgroundCopyGroup::CreateJob()以获取指向该IBackgroundCopyJob1接口的指针。
4. 通过调用来向作业添加文件IBackgroundCopyJob1::AddFiles()。
5. 这是关键部分,它调用IBackgroundCopyGroup::QueryNewJobInterface()方法并获得指向未知接口的指针,但我将假定它是一个IBackgroundCopyJob接口。
6. 最后,它通过在接口上调用Resume()``Complete()``IBackgroundCopyJob``IBackgroundCopyJob1来恢复并完成作业。
在此应用程序中,目标URL为\\127.0.0.1\C$\Windows\System32\drivers\etc\hosts我不想使用网络访问,本地文件为C:\Temp\test.txt。
然后,我使用Procmon分析了BIT服务的行为。
首先,可以看到该服务在目标目录中创建了一个TMP文件,并在模拟当前用户的同时尝试打开作为参数给出的本地文件。
然后,一旦调用Resume()函数,该服务将开始读取目标文件\\127.0.0.1\C$\Windows\System32\drivers\etc\hosts并将其内容写入TMP文件C:\Temp\BITF046.tmp,同时仍在按预期方式模拟当前用户。
最后,将TMP文件重命名为test.txt,并且调用MoveFileEx(),这就是漏洞所在!这样做时,不会再模拟当前用户,这意味着文件移动操作是在NT AUTHORITY\SYSTEM的上下文中完成的。
以下截图确认了该SetRenameInformationFile调用源自Win32 MoveFileEx()函数。
此任意文件移动将导致“ SYSTEM本地特权升级”,通过将特制的DLL移到该System32文件夹中,普通用户可以在上下文中执行任意代码。
0x05 漏洞挖掘
在尝试发现QueryNewJobInterface()函数本身的缺陷之前,我首先尝试了解标准CreateJob()方法的工作方式。
接口方法CreateJob()在服务器端IBackgroundCopyGroup的COldGroupInterface类中实现。
由于CFG在这里的保护并不明显,但是CreateJobInternal()函数将调用同一类的方法。
该函数调用类的ValidateAccess()方法,调用CLockedJobWritePointer类的CheckClientAccess()方法。
该CheckClientAccess()方法会检查用户令牌的位置,并将其应用于当前线程以进行模拟。
最终,执行流程返回到CreateJobInternal()方法,该方法调用该类的GetOldJobExternal()方法,并返回指向IBackgroundCopyJob1客户端接口的指针。
这些调用可以总结如下:
(CLIENT) IBackgroundCopyGroup::CreateJob() | V (SERVER) COldGroupInterface::CreateJob() |__ COldGroupInterface::CreateJobInternal() |__ CLockedJobWritePointer::ValidateAccess() | |__ CJob::CheckClientAccess() // Client impersonation |__ CJob::GetOldJobExternal() // IBackgroundCopyJob1* returned
现在知道了该CreateJob()方法的整体工作原理,现在可以回去对QueryNewJobInterface()方法进行逆向。
如果提供的GUID匹配IID_IBackgroundCopyJob,则执行以下代码。
在此查询新的接口指针,并立即调用,将其返回给客户端CJob::GetExternalJob(),因此,可以简单地总结如下:
(CLIENT) IBackgroundCopyGroup::QueryNewJobInterface() | V (SERVER) COldGroupInterface::QueryNewJobInterface() |__ CJob::GetJobExternal() // IBackgroundCopyJob* returned
看起来,当通过调用方法来请求指向新IBackgroundCopyJob接口的指针时,不会模拟客户端,这意味着客户端会获得指向存在于IBackgroundCopyGroup``QueryNewJobInterface()``NT AUTHORITY\SYSTEM上下文中的接口的指针(如果有任何意义)。
实际上,我注意到文件移动操作发生在调用IBackgroundCopyJob::Resume()之后和IBackgroundCopyJob::Complete()之前。
调用时的简化call 回溯跟踪:
(CLIENT) IBackgroundCopyJob::Resume() | V (SERVER) CJobExternal::Resume() |__ CJobExternal::ResumeInternal() |__ ... |__ CJob::CheckClientAccess() // Client impersonation |__ CJob::Resume() |__ ...
调用IBackgroundCopyJob::Complete()时的简化call跟踪:
(CLIENT) IBackgroundCopyJob::Complete() | V (SERVER) CJobExternal::Complete() |__ CJobExternal::CompleteInternal() |__ ... |__ CJob::CheckClientAccess() // Client impersonation |__ CJob::Complete() |__ ...
在这两种情况下,客户端都是假冒的,这意味着客户没有完成这项工作,它是由服务本身完成的,可能是因为队列中没有其他文件。
因此,当IBackgroundCopyJob从到IBackgroundCopyGroup::QueryNewJobInterface()的调用中接收到接口指针并且服务由服务而不是RPC客户端完成作业时,最后的CFile::MoveTempFile()调用将在没有模拟的情况下完成。我无法发现逻辑缺陷的确切位置,但是我猜测添加CJob::CheckClientAccess()检查COldGroupInterface::QueryNewJobInterface()可能会出现问题。
这是一个简化的图,显示了MoveFileEx()在CJob对象上下文中导致调用的函数。
0x06 漏洞利用
漏洞利用非常简单,就是为服务提供一个文件夹的路径,该路径最初将用作与另一个物理目录的连接,我使用本地文件创建一个新作业以下载,并在TMP文件上设置Oplock。恢复该作业后,该服务将在模拟RPC客户端的同时开始写入TMP文件,并命中Oplock。
然后,我要做的就是将挂载点切换到对象目录并创建两个符号链接,TMP文件将指向我拥有的任何文件,本地文件将指向文件夹System32中的新DLL文件。最后,在释放Oplock之后,该服务将继续写入原始TMP文件,但是它将通过我的两个符号链接执行最后的移动操作。
1)创建目录
想法是创建具有以下结构的目录:
C:\workspace |__ bait |__ mountpoint |__ FakeDll.dll
mountpoint目录的目的是从bait联结到目录,切换联结到RPC Control对象目录,FakeDll.dll是我要移动到受限位置的文件,例如C:\Windows\System32\。
2)创建一个挂载点
我要创建一个从C:\workspace\mountpoint到C:\workspace\bait的挂载点。
3)建立新作业
我将使用旧版控件类提供的接口来创建具有以下参数的新作业。
Target URL: \\127.0.0.1\C$\Windows\System32\drivers\etc\hosts Local file: C:\workspace\mountpoint\test.txt
由于先前已创建联结,因此本地文件的实际路径将为C:\workspace\bait\test.txt。
4)找到TMP文件并设置一个Oplock
将文件添加到作业队列时,服务会立即创建TMP文件。由于它具有“随机”名称,因此我必须列出目录的bait内容才能找到它。在这里,可以找到一个类似BIT1337.tmp的名称,现在就可以在文件上设置Oplock。
5)恢复作业并等待Oplock
如前所述,一旦恢复作业,该服务将打开TMP文件进行写入并触发Oplock,。
6)切换挂载点
在此步骤之前:
TMP file = C:\workspace\mountpoint\BIT1337.tmp -> C:\workspace\bait\BIT1337.tmp Local file = C:\workspace\mountpoint\test.txt -> C:\workspace\bait\test.txt
我切换挂载点并创建符号链接:
C:\workspace\mountpoint -> \RPC Control Symlink #1: \RPC Control\BIT1337.tmp -> C:\workspace\FakeDll.dll Symlink #2: \RPC Control\test.txt -> C:\Windows\System32\FakeDll.dll
完成此步骤后:
TMP file = C:\workspace\mountpoint\BIT1337.tmp -> C:\workspace\FakeDll.dll Local file = C:\workspace\mountpoint\test.txt -> C:\Windows\System32\FakeDll.dll
7)释放Oplock并完成作业
释放Oplock后,CreateFile将返回对原始TMP文件的操作,并且该服务将开始写入C:\workspace\bait\BIT1337.tmp。之后,MoveFileEx()由于符号链接,最终call 将被重定向,因此,我的DLL将被移至System32文件夹。
因为这是移动操作,所以保留了文件的属性,这意味着该文件仍归当前用户所有,因此即使它位于受限制的位置,也可以在以后对其进行修改。
8)执行利用代码
为了使代码执行为System,我使用了任意文件移动漏洞在WindowsCoreDeviceInfo.dll文件夹System32中创建文件,然后,我利用Update Session Orchestrator服务将DLL加载为System。
0x07 漏洞演示
19
0x08 相关资源
· MSRC - CVE-2020-0787 | Windows Background Intelligent Transfer Service Elevation of Privilege Vulnerability https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-0787
· OleViewDotNet - James Forshaw https://github.com/tyranid/oleviewdotnet
· Symbolic Link Testing Tools - James Forshaw https://github.com/googleprojectzero/symboliclink-testing-tools
· UsoDllLoader https://github.com/itm4n/UsoDllLoader
· MSDN - IBackgroundCopyManager interface https://docs.microsoft.com/en-us/windows/win32/api/bits/nn-bits-ibackgroundcopymanager
· MSDN - IBackgroundCopyQMgr interface https://docs.microsoft.com/en-us/windows/win32/api/qmgr/nn-qmgr-ibackgroundcopyqmgr
本文翻译自:https://itm4n.github.io/cve-2020-0787-windows-bits-eop/如若转载,请注明原文地址: