翻译:9f3K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4L8$3!0Y4L8r3g2H3M7X3!0B7k6h3y4@1P5X3g2J5L8#2)9J5k6h3u0D9L8$3N6K6M7r3!0@1i4K6u0W2j5$3!0E0i4K6u0r3x3U0l9I4z5q4)9J5c8U0l9@1i4K6u0r3N6$3W2F1k6r3!0%4M7#2)9J5k6r3g2^5M7r3I4G2K9i4c8S2N6r3W2G2L8W2)9J5k6s2c8J5K9h3y4C8M7#2)9J5k6r3g2^5M7r3I4G2K9i4c8A6L8X3N6Q4x3X3g2Z5N6r3#2D9
之前我发表了一个技术(e3dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4L8$3!0Y4L8r3g2H3M7X3!0B7k6h3y4@1P5X3g2J5L8#2)9J5k6h3u0D9L8$3N6K6M7r3!0@1i4K6u0W2j5$3!0E0i4K6u0r3x3U0l9I4y4#2)9J5c8U0l9^5i4K6u0r3N6$3W2F1k6r3!0%4M7#2)9J5k6r3g2^5M7r3I4G2K9i4c8S2N6r3W2G2L8W2)9J5k6s2c8J5K9h3y4C8M7#2)9J5k6r3q4J5j5X3W2@1M7X3q4J5P5g2)9J5k6h3S2@1L8h3I4Q4c8f1k6Q4b7V1y4Q4z5o6W2Q4c8e0g2Q4z5o6S2Q4b7e0W2Q4c8e0N6Q4z5e0c8Q4b7e0S2i4K9h3&6V1L8%4N6K6i4@1f1@1i4@1t1^5i4K6S2m8i4@1f1%4i4K6W2m8i4K6R3@1i4@1f1@1i4@1u0n7i4@1u0n7i4@1f1$3i4K6R3@1i4K6S2r3i4@1f1%4i4K6W2n7i4@1q4q4i4@1f1#2i4@1u0p5i4K6V1#2i4@1f1#2i4K6R3^5i4K6W2n7i4@1f1#2i4@1u0n7i4@1u0m8i4@1f1^5i4K6R3@1i4K6R3$3i4@1f1#2i4@1u0o6i4@1t1I4i4@1f1$3i4K6R3H3i4@1p5%4i4@1f1%4i4@1u0n7i4K6V1&6i4@1f1@1i4@1u0p5i4@1p5H3i4@1f1@1i4@1u0n7i4@1u0n7i4@1f1$3i4K6R3@1i4K6S2r3i4@1f1$3i4K6V1$3i4K6R3%4i4@1f1@1i4@1u0n7i4@1t1$3i4@1f1^5i4@1q4r3i4@1u0n7i4@1f1$3i4K6W2p5i4K6R3K6i4@1f1&6i4K6V1&6i4K6V1H3i4@1f1K6i4K6R3H3i4K6R3J5i4@1f1#2i4K6W2o6i4@1p5^5i4@1f1#2i4K6S2p5i4@1t1K6i4@1f1#2i4@1t1H3i4K6R3$3i4@1f1#2i4K6R3^5i4@1t1H3i4@1f1$3i4K6W2p5i4@1p5#2i4@1f1%4i4K6W2m8i4K6R3@1i4@1f1$3i4K6V1^5i4@1p5#2i4@1f1#2i4@1p5@1i4@1p5&6i4@1g2r3i4@1u0o6i4K6S2o6i4@1f1#2i4K6W2o6i4@1p5^5i4@1f1$3i4K6V1$3i4@1t1H3i4@1f1%4i4K6W2m8i4K6R3@1i4@1f1$3i4K6W2n7i4@1t1@1i4@1f1$3i4K6V1$3i4@1t1H3i4@1f1@1i4@1t1^5i4@1q4p5i4@1f1#2i4@1t1%4i4@1t1J5i4@1f1%4i4@1u0n7i4K6S2r3i4@1f1#2i4@1t1H3i4K6R3$3i4@1f1@1i4@1t1&6i4K6S2n7i4@1f1#2i4K6R3&6i4K6S2p5i4@1f1#2i4K6S2p5i4K6W2m8i4@1f1#2i4@1q4q4i4@1p5J5i4@1f1@1i4@1t1^5i4@1q4p5i4@1f1$3i4K6S2r3i4K6V1H3i4@1f1#2i4K6R3^5i4@1t1H3i4@1f1%4i4K6W2m8i4K6R3@1i4@1f1$3i4@1u0n7i4@1p5#2i4@1f1%4i4K6V1@1i4@1p5^5i4@1f1$3i4K6S2o6i4K6R3J5i4@1f1^5i4@1u0p5i4@1u0p5i4@1f1%4i4K6R3J5i4@1t1&6i4@1f1$3i4K6W2p5i4@1p5#2i4@1f1&6i4K6V1K6i4@1u0q4i4@1f1$3i4K6S2q4i4@1p5#2i4@1f1#2i4K6R3^5i4@1t1H3i4@1f1$3i4K6V1$3i4K6R3%4i4@1f1@1i4@1u0n7i4@1t1$3i4@1f1%4i4K6W2m8i4K6R3@1i4@1f1$3i4K6V1$3i4@1t1&6i4@1f1$3i4@1t1K6i4K6V1#2i4@1f1@1i4@1u0r3i4@1q4q4i4@1f1^5i4@1p5I4i4@1p5#2i4@1f1@1i4@1u0m8i4K6R3$3i4@1f1K6i4K6R3H3i4K6R3J5
和之前的博客保持一样的精神,我将一个新的技术在Win10下进行更常用的任意文件写功能。或许微软也会加固操作系统使得利用这类脆弱性更困难。我将详细讲述ProjectZero最近报告给微软的问题。
任意文件写脆弱性使用户可以创建或修改它无法访问目录的文件。这可能是因为一个特权服务不正确地审查用户传给它的信息,或者因为符号链接移植攻击(用户写一个链接到一个被特权服务使用的位置)。理想的脆弱性是攻击者不仅可以控制被写文件的路径,还可以控制完整内容。这个博客讲的就是这样一个脆弱性。
常见的任意文件写利用方法是进行DLL劫持。当一个Windows可执行程序执行到最初的加载阶段,NTDLL将尝试寻找所有导入的DLL。检测导入DLL的位置顺序很复杂,可以概括为:
DLL劫持的目的是找到一个在高特权运行的可执行程序,它会从从一个脆弱性准许写的目录加载一个DLL,并且在搜索顺序排在前面的位置没有找到这个DLL。
有两个问题会使DLL劫持变得恼人:
第二个问题意味着DLL搜索位置2和3也是在system32中。在重写DLL不是问题的前提下(如果DLL已经被加载到一个进程,将无法重写),这个问题使得找到合适的劫持DLL非常困难。这个问题的通常解决办法是,找到一个不在system32的特权程序,并可以被容易地启动(如通过加载COM服务或运行任务调度)。
就算你找到了一个合适的目标exe,进行DLL劫持也很困难。有时你需要实现原DLL的全部导出函数stub,不然加载DLL将会失败。此外,运行自己代码的最好位置是DLLMain,这又会遇见其它问题(如运行代码内部的加载锁239K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6E0M7$3c8F1i4K6u0W2L8h3W2U0M7X3!0K6L8$3k6@1i4K6u0W2j5$3!0E0i4K6u0r3k6h3&6Q4x3X3c8#2M7#2)9J5c8X3I4A6j5Y4u0S2M7Y4W2Q4x3V1k6%4K9h3&6V1L8%4N6K6i4K6u0r3k6r3g2K6K9%4c8G2M7q4)9J5c8X3c8F1y4U0x3K6z5e0M7I4i4K6t1#2x3U0S2$3i4K6y4p5N6Y4y4Q4x3X3f1^5y4g2)9J5y4e0t1&6i4K6u0W2j5i4y4H3P5q4!0q4c8W2!0n7b7#2)9^5z5g2!0q4x3#2)9^5x3q4)9^5x3W2!0q4y4W2)9&6b7#2)9^5x3q4!0q4y4g2!0m8y4g2!0n7c8q4!0q4y4#2)9&6b7g2)9^5y4q4!0q4y4W2)9^5x3#2)9^5y4g2!0q4y4g2)9^5y4W2!0n7y4g2!0q4y4W2)9&6z5q4!0m8c8W2!0q4c8W2!0n7b7#2)9^5b7#2!0q4y4q4!0n7z5q4)9^5x3q4!0q4y4q4!0n7z5q4!0m8b7g2!0q4y4#2)9^5z5g2!0n7z5g2!0q4y4W2)9&6c8q4)9^5x3#2!0q4y4W2)9&6b7#2)9^5c8q4!0q4y4g2)9^5b7g2!0m8x3g2!0q4y4g2)9^5c8W2!0m8b7g2!0q4y4g2)9^5b7g2!0m8x3q4!0q4z5q4!0n7c8q4!0n7c8q4!0q4y4q4!0n7z5q4)9^5x3q4!0q4y4q4!0n7z5q4!0m8b7f1c8x3e0q4!0q4c8W2!0n7b7#2)9^5b7#2!0q4y4W2)9&6y4#2!0m8x3q4!0q4z5g2)9&6b7#2)9^5x3q4!0q4z5q4!0m8c8g2!0m8z5g2!0q4y4#2)9^5z5g2!0n7z5g2!0q4y4W2)9&6c8q4)9^5x3#2!0q4z5q4!0n7c8W2)9&6b7W2!0q4y4#2!0m8z5q4)9^5b7W2!0q4y4W2!0m8c8q4!0m8x3#2!0q4y4g2!0n7z5q4!0n7z5q4!0q4z5q4!0n7c8W2)9&6x3q4!0q4z5q4!0m8x3g2)9^5b7#2!0q4z5q4!0n7y4g2!0n7y4#2!0q4y4W2)9&6c8q4!0m8y4g2!0q4x3#2)9^5x3q4)9^5x3W2!0q4z5g2)9&6y4#2!0m8c8g2!0q4z5g2!0m8x3W2)9&6z5q4!0q4y4W2)9&6z5q4!0m8c8W2!0q4c8W2!0n7b7#2)9&6b7g2!0q4y4g2)9&6x3#2!0m8b7g2!0q4z5g2)9^5y4#2)9^5b7#2!0q4y4g2)9^5c8g2!0n7b7W2!0q4y4W2)9^5z5g2!0n7c8g2!0q4z5q4!0n7c8W2)9&6z5g2!0q4y4W2!0m8x3q4!0n7y4#2!0q4y4#2)9&6b7g2)9^5y4q4!0q4y4W2)9&6b7#2)9^5c8q4!0q4y4g2)9^5b7g2!0m8x3g2!0q4c8W2!0n7b7#2)9&6c8R3`.`.
某人回答有,并且这个服务在之前至少被利用过两次。一次是Lokihardt的沙箱逃逸,一个是我(James Forshaw)的user to system Eop(bf1K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1N6h3N6K6i4K6u0W2j5$3S2J5L8$3#2A6N6h3#2Q4x3X3g2G2M7X3N6Q4x3V1k6H3i4K6u0r3M7s2u0G2K9X3g2U0N6q4)9J5k6s2A6W2M7X3!0Q4x3V1k6A6M7%4y4#2k6i4y4Q4x3V1k6V1k6i4c8S2K9h3I4Q4x3@1k6A6k6q4)9K6c8o6R3^5y4#2!0q4c8W2!0n7b7#2)9^5z5g2!0q4x3#2)9^5x3q4)9^5x3W2!0q4z5q4!0n7c8W2)9&6z5g2!0q4y4q4!0n7z5q4!0m8b7g2!0q4y4W2)9&6b7#2)9^5c8q4!0q4y4g2)9^5b7g2!0m8x3g2!0q4y4g2)9&6x3q4)9^5c8q4!0q4y4g2!0m8c8q4)9&6y4#2!0q4y4W2)9&6z5q4!0m8c8V1#2A6j5%4u0G2M7$3!0X3N6l9`.`. (R) Diagnostics Hub Standard Collector Service,在后面我们称它DiagHub。
DiagHub服务在Win10引进系统,在Win7/8.1有一个执行类似功能的服务叫IE ETW Collector。服务的任务是使用ETW搜集沙箱应用的诊断信息,特别是Edge和IE。它有个有趣的特征,可以配置来加载一个system32目录下的任意DLL,这就是Lokihardt和我用来进行提权的特征。这个服务的所有功能都通过一个注册的DCOM对象暴露出,所以为了加载我们的DLL,我们需要找到调用DCOM对象的方法。如果不关注寻找过程,只关注利用方法,可以直接跳到最后。
我们一起来走一遍整个步骤,寻找未知DCOM对象支持哪些接口并且找到接口的实现,进而取逆向某个接口。通常有两个思路:直接在IDA逆向;先做些系统内的检查定位我们关注的区域再逆向。这里我们选择第二种思路。
我们需要一些工具:OleViewDotNetv1.4+(OVDN)(f0cK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6@1P5i4u0S2L8X3W2V1i4K6u0r3L8$3I4W2N6X3W2W2N6$3c8G2N6r3&6W2N6q4)9J5c8Y4u0W2L8r3g2S2M7$3g2K6i4@1g2r3i4@1u0o6i4K6R3&6i4@1g2r3i4@1u0o6i4K6W2n7i4@1f1#2i4@1q4q4i4K6R3&6i4@1f1^5i4@1p5K6i4K6R3#2N6$3W2F1k6r3u0Y4i4@1f1K6i4K6R3H3i4K6R3J5
首先要找到DCOM对象的注册信息并发现什么接口可访问。我们知道DCOM对象居于服务中,所以一旦你加载OVDN,点击菜单的Registry->Local Services,工具会加载一个导出COM对象的系统服务列表。如果此时发现 “Microsoft (R) Diagnostics Hub Standard Collector Service”服务(在这里可以进行一个过滤),会在列表中看到入口。如果打开服务树节点会看到一个子节点,子节点名字是“Diagnostics Hub Standard Collector Service”,DCOM对象就在这里面。当你打开树上的节点时,工具会创建这个对象,并请求所有远程可访问的COM接口来向你提供一个对象支持的接口列表。如下图:
在这里取查看一下访问DCOM对象需要的安全需要很重要。右键树节点,选择ViewAccessPermissions或者ViewLaunchPermissions,会弹出一个窗口显示需要的权限。本例中,DCOM对象需要在IE保护模式、Edge的APP容器沙箱和LPAC中访问。
接口列表显示,我们只需关注标准接口。有时,在会有感兴趣的工厂接口。标准接口中我们关心两个:IStandardCollectorAuthorizationService和IStandardCollectorService。假设我们已经知道IStandardCollectorService是我们关心的,随着后续分析进行我们先选择哪一个没有关系。右键接口的树节点选择属性,可以看到注册的接口的部分信息。
这里没有许多有用信息,但可以看到这个接口有8个方法。在诸多COM注册信息中,这个值可能不正确,但在本例中,我们假设它是正确的。要理解方法到底是做什么的,我们需要追踪到COM服务中IStandardCollectorService的实现里。现有知识使得我们可以有目标地进行逆向正确的方法。对一个进程内的COM对象做这些工作相对容易,因为我们可以通过解析一些指针直接请求对象的VTable指针。然而,对于进程外的COM对象就复杂一些。因为我们在进程中调用的真正对象其实是一个远程对象的代理,如下图:
我们任然可以通过提取存储在服务进程中的对象信息找到OOP对象的VTable。右键Diagnostics Hub Standard Collector Service对象树节点选择Create Instance。将建立一个新的COM对象实例,如下图:
创建的实例会提供给你基本信息,如对象的CLSID和支持的接口。现在我们要和接口建立起一个连接。在下面的列表选择IStandardCollectorService,在底部的Operation菜单选择Marshal->View Properties。如果成功,你将看到下图:
这里面有许多信息,我们感兴趣的是两个:宿主服务的PID和结构指针标识符(IPID)。本例中,PID是服务运行在其中的进程,但不总是如此。有时你创建了一个COM对象,无法知道它居于哪个进程中,此时这个信息没有价值。IPID是宿主进程中唯一的,我们可以使用PID和IPID找到找到服务,并在其中找到实现了COM方法的VTable。IPID中的PID最大尺寸是16bit,现在Windows版本大多有更大的PID,所以你需要手动找到进程或者重启服务多次指导得到一个合适的PID。
现在我们要使用OVDN的一个功能,到达服务进程的内存并找到IPID信息。你可以通过主菜单Object->Processes得到所有进程的信息,但是我们可以通过PID旁的View按钮直接定位到服务进程。点击后,OVND会请求配置符号支持。符号配置类似下图:(需要以管理器启动)
如果一切正确,你会看到IPID更详细地信息,如下图:
两个最有用的信息是接口指针(分配对象的堆位置)和接口的VTable指针。VTable地址告诉我们COM服务的实际实现加载位置。在这里我们看到,VTable在主可执行文件(DiagnosticsHub.StandardCollector.Server)外的一个不同的模块(DiagnosticsHub.StandardCollector.Runtime)中。我们可以通过使用windbg附加到服务进程并转储VTable地址处的符号来验证。在之前我们还知道了有8个方法,所以我们可以将这个也考虑在内:
dqs DiagnosticsHub_StandardCollector_Runtime+0x36C78 L8
注意,windbg将模块名加下划线。如果成功,结果如下图:
通过提取这些信息,我们获得了方法的名字和在二进制中的地址。我们可以设置断点查看在正常操作时哪些被调用,或者开始逆向过程。
ATL::CComObject<StandardCollectorService>::QueryInterface
ATL::CComObjectCached<StandardCollectorService>::AddRef
ATL::CComObjectCached<StandardCollectorService>::Release
StandardCollectorService::CreateSession
StandardCollectorService::GetSession
StandardCollectorService::DestroySession
StandardCollectorService::DestroySessionAsync
StandardCollectorService::AddLifetimeMonitorProcessIdForSession
方法列表看上去是正确的:他们开始于3个COM对象的标准方法,本例中他们由ATL库实现。后面的5个由StandardCollectorService类实现。根据公开的符号,我们无法知道我们要传给COM服务什么参数。根据C++名字包含一些类型信息,IDA可能提取那些信息,但并不是必须知道所有传给函数的结构的格式。幸运的是,根据COM代理使用NDR(Network Data Representation)解析器执行编组的实现代码,可以逆向NDR字节码到我们可以理解的形式。在本例中,回到最初的服务信息,右键IStandardCollectorService树节点选择View Proxy Definition。将会让OVDN取解析NDR代理信息并显示在一个新视图,如下图:
观察代理的定义会得到代理库实现的其它接口。这对于之后的逆向很有用。反编译的代理定义以像C#的伪代码形式显示,在需要时能方便地转化为C#或C++。注意代理定义不包含方法的名字,还好我们之前已经提取得到了。进行简单整理后结合方法名我们获得如下形式定义:
[uuid("0d8af6b7-efd5-4f6d-a834-314740ab8caa")]
struct IStandardCollectorService : IUnknown {
HRESULT CreateSession(_In_ struct Struct_24* p0,
_In_ IStandardCollectorClientDelegate* p1,
_Out_ ICollectionSession** p2);
HRESULT GetSession(_In_ GUID* p0, _Out_ ICollectionSession** p1);
HRESULT DestroySession(_In_ GUID* p0);
HRESULT DestroySessionAsync(_In_ GUID* p0);
HRESULT AddLifetimeMonitorProcessIdForSession(_In_ GUID* p0, [In] int p1);
}
还差最后一个东西,我们不知道Struct_24结构的定义。这可以通过逆向过程提取出来,幸运地是,本例中我们不需要通过逆向来提取。NDR字节码必须知道如何整理这个结构,所以OVDN自动为我们提取出结构定义。选择Structures标签页找到Struct_24。
随着你进行逆向过程,你可以在需要的时候重复这个过程直到你理解所有事情如何工作。现在来实际利用DiagHub服务。
通过逆向工程,我们发现为了加载system32里的DLL,我们需要进行以下步骤:
ICollectionSession::AddAgent的简化加载代码如下:
void EtwCollectionSession::AddAgent(LPWCSTR dll_path,
REFGUID guid) {
WCHAR valid_path[MAX_PATH];
if ( !GetValidAgentPath(dll_path, valid_path)) {
return E_INVALID_AGENT_PATH;
HMODULE mod = LoadLibraryExW(valid_path,
nullptr, LOAD_WITH_ALTERED_SEARCH_PATH);
dll_get_class_obj = GetProcAddress(hModule, "DllGetClassObject");
return dll_get_class_obj(guid);
}
它首先检查代理路径是有效的,并返回完整路径(这是之前Eop bug存在的地方,不完整的检测)。这个路径被使用LoadLibraryEx加载,然后请求DLL的导出方法DllGetClassObject。所以要让代码执行只需要实现这个方法并将DLL放入system32目录。实现的DllGetClassObject方法将在加载锁外被调用,所以我们可以为所欲为。下面的代码满足加载一个叫dummy.dll的DLL。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课