利用USO服务将特权文件写入武器化

 

James Forshaw发现的DiagHub DLL loading技术已经非常有名了。每当你在Windows或一些第三方软件中发现SYSTEM权限的任意文件写漏洞时,你就可以用这一招来造成任意代码执行,而且不用重启。不幸的是这种方法在Windows 10 build 1903中被微软禁止。Andrea Pierini在Twitter上简单地提到了这一点。在这里,我想和大家分享一下我在最新版本的Windows上寻找DLL劫持弱点时发现的另一种加载dll的方法。

 

前言

在本文的开头,我想澄清一下:

这不算是一个漏洞。正如我们所看到的,为了能够使用这个技巧,必须先将一个专门制作的DLL植入C:\Windows\System32文件夹,当然只有 “SYSTEM” 权限的用户才能做到这一点。

 

写在开始

作为出发点,我决定寻找一些简单的例子,例如在以NT AUTHORITY/System运行的服务中的DLL劫持。我的想法是监控那些可以被普通用户启动或至少是 “影响 “的服务。为此,我做了一个非常简单的PowerShell脚本,每秒钟检查一个服务是否被启动或停止。

在Windows操作系统的后台,运行Process Monitor来记录文件系统操作。我简单地配置了一个过滤器,只显示涉及*.dll文件的操作,如果找不到的话就返回NAME NOT FOUND错误代码。然后,我试着在没有管理员权限的情况下,一次一次地启动所有能启动的服务。不幸的是,我没有用这个方法发现任何东西。不过我发现了以下内容。

当 “Process Monitor “在后台运行时,它捕捉到一些定期打开 “windowscoredeviceinfo.dll “文件的操作。频率不一,可能每小时发生一次,也可能每30分钟发生一次。事件的属性显示了该进程的命令行。通过查看事件的属性能够发现这个操作是由命令行”C:WINDOWS/System32/svchost.exe -k utcsvc -p”造成的。

知道了这一点,就可以很容易地通过以下PowerShell命令找到相应的服务,例如。在这种情况下,可以用DiagTrack命令。

注:我也可以使用进程的PID,例如在任务管理器中查找它。这种方法的缺点是,你不知道它什么时候启动,在你检查的时候,进程可能并不在运行。

 

我们的第一个目标

“Process Monitor “中的事件属性显示了一些关于DLL如何被加载的信息。Stack选项卡显示了导致这次操作的调用列表。在这里,我们可以看到,初始调用是从diagtrack.dll进行的。DiagTrack服务加载了FlightSettings.dll文件,它又使用了dcntel.dll中的GetCensusRegistryLocation()方法,最后,使用标准的LoadLibraryEx()WinApi调用加载了windowscoredeviceinfo.dll

为了确定我的方向是正确的,我在IDA中打开最后一个DLL,并查找windowscoredeviceinfo.dll的出现。Strings标签页中能够清楚的找到这个dll的名字。

注意:你必须配置视图以包含unicode字符串,这不是IDA的默认设置……。

然后,我们可以直接到它在.rdata部分的位置,寻找Xrefs。在这种情况下,只有一个。这个字符串确实是在QueryWCOSDeviceInformation()方法中使用的。好吧,说明我们的路线是对的!

IDA生成的伪代码非常清晰。我们发现前面看到的Process MonitorLoadLibraryEx("windowscoredeviceinfo.dll")调用。然后,如果库被成功加载,就会进行下面的调用。GetProcAddress("QueryDeviceInformation"),也就是说这个函数应该是windowscoredeviceinfo.dll的导出函数。

我们来总结一下情况。目前,我们知道以下几点。

  • DiagTrack服务定期(每30分钟或每小时)运行一个未知任务。
  • 每次,它都会尝试加载一个名为 “windowscoredeviceinfo.dll “的DLL,而这个DLL默认情况下并不存在。
  • 如果成功加载,则导入 “QueryDeviceInformation “函数。

这是一个好的开始,但我缺少一些关键要素。例如,我不知道这个 “任务 “是如何运行的。我甚至不知道作为一个普通用户是否能够触发它。所以,与其在不知道自己在寻找什么的情况下就开始对服务进行逆向工程,我决定创建一个PoC DLL,并验证我是否真的能以NT AUTHORITYSystem的方式获得任意代码执行。

 

制作一个PoC DLL

这个PoC DLL的目标非常简单。我想记录一些关于加载它的进程的关键信息命令行当前用户名PIDPPID。所有这些都会被记录到C:tempdll.log中。

首先,我想出了以下代码。TrackCall()函数负责收集和记录信息。它会被DllMain()QueryDeviceInformation()执行,用来跟踪它的调用者。

#include <Windows.h>
#include <iostream>
#include <Lmcons.h> // UNLEN + GetUserName
#include <tlhelp32.h> // CreateToolhelp32Snapshot()

int TrackCall(const wchar_t * callingFrom)
{
    WCHAR strSt[4096], strUsername[UNLEN + 1];
    WCHAR * strComandLine;
    SYSTEMTIME st;
    HANDLE hFile, hToolhelpSnapshot;
    PROCESSENTRY32 stProcessEntry;
    DWORD dwPcbBuffer = UNLEN, dwBytesWritten, dwProcessId, dwParentProcessId;
    BOOL bResult;

    strComandLine = GetCommandLine(); // Get Command line of the current process 
    bResult = GetUserName(strUsername, &dwPcbBuffer); // Get current user name 
    dwProcessId = GetCurrentProcessId(); // Get PID

    // Get PPID 
    hToolhelpSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    stProcessEntry = { 0 };
    stProcessEntry.dwSize = sizeof(PROCESSENTRY32);
    dwParentProcessId = 0;
    if (Process32First(hToolhelpSnapshot, &stProcessEntry)) {
        do {
            if (stProcessEntry.th32ProcessID == dwProcessId) {
                dwParentProcessId = stProcessEntry.th32ParentProcessID;
                break;
            }
        } while (Process32Next(hToolhelpSnapshot, &stProcessEntry));
    }
    CloseHandle(hToolhelpSnapshot);

    // Create log entry 
    GetLocalTime(&st); 
    wsprintfW(strSt, L"[%.2u:%.2u:%.2u] - PID=%d - PPID=%d - USER='%s' - CMD='%s' - METHOD='%s'n", st.wHour, st.wMinute, st.wSecond, dwProcessId, dwParentProcessId, strUsername, strComandLine, callingFrom);

    // Save to log file 
    hFile = CreateFile(L"C:\Temp\dll.log", FILE_APPEND_DATA, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 
    if (hFile != INVALID_HANDLE_VALUE)
        bResult = WriteFile(hFile, strSt, lstrlenW(strSt)*sizeof(WCHAR), &dwBytesWritten, NULL);

    CloseHandle(hFile);

    return S_OK;
}

HRESULT __stdcall QueryDeviceInformation()
{
    TrackCall(L"QueryDeviceInformation()");

    return S_OK;
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        TrackCall(L"DllMain()");
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

然后,作为管理员,我把这个DLL复制到C:/Windows/System32,然后,等待它被调用…

过了一会儿,windowscoredeviceinfo.dll的调用终于出现在Process Monitor中。所有的CreateFile操作都成功返回。但是,在C:temp中没有创建日志文件的迹象。当然,这意味着DLL没有被正确加载,问题是:为什么?我猜测,我创建的DLL没有导出进程所需的所有函数。

此时,我不知道该如何继续,但是,当我回到Process Monitor时,我看到了一些以前没有看到的事件。

文件windowscoredeviceinfo.dll也被一个名为usocoreworker.exe的进程加载,这个进程的权限为NT AUTHORITY/System。这时,一些信息被记录到C:Tempdll.log中,这说明代码此时已经正确执行了。

这看起来更有希望,所以我决定抛开DiagTrack服务,看看这个新目标。

 

转移到新目标

我们又回到了起点。我们需要找出usocoreworker.exe进程是如何创建的。为此,我们可以寻找与写入日志文件的PPID对应的进程。根据任务管理器,它是svchost.exe的一个实例,就像大多数Windows服务一样,所以PPID对我们的帮助不大。

对应的服务是 BrokerInfrastructure,它的描述是 “处理后台任务”。好吧,这也没什么用……。

让我们来看看我们可以从Process Monitor 中找到什么。访问与这个进程相关的事件的属性,然后,进入Stack选项卡,会显示以下内容。我们可以看到,有很多对rpcrt4.dllcombase.dll的引用。这可能意味着这个进程是由一个COM相关的RPC调用触发的。如果是这样的话,根据远程对象和接口的权限,也可以作为普通用户触发。

。COM用于进程间通信(IPC)。因此,它可以为低权限的进程提供运行高权限操作的能力。

从二进制文件的属性来看,我们可以看到如下描述:USO Core Worker Process

根据前几个要素,我试图在谷歌上找到更多信息。第一个结果把我引向answsers.microsoft.com上的一个帖子。根据其中一条信息,我发现这个文件与 “Update Orchestrator Service “有关。

细细研究,我发现了这个非常有趣的关于 “USO客户端 “的非官方文档。首先,我们了解到 “USO “是 “Update Session Orchestrator “的缩写。

我们还了解到,”USO客户端”(usoclient.exe)是取代 “WUAUCLT “的工具,而WUAUCLT在以前的Windows版本中是用来管理Windows更新的。事实上,这个工具已经被Windows系统管理员所熟知,因为虽然它不被微软官方支持,但它能让他们自动完成更新管理过程。

注:他们甚至引用了TechNet上微软员工的回复,其中说你不应该直接运行这个工具。这越来越有趣了。我们喜欢做我们不应该做的事情,不是吗?

文档中列出了所有你可以使用的选项。所以,我试着玩玩usoclient命令,看看是否能触发我之前观察到的相同行为。从StartScan开始,根据描述,它会触发一个简单地获取可用更新的检查。

我像往常一样在后台运行 Process Monitor,运行命令,成了。

 

结论

通过一个简单的命令,我们能够让Update Orchestrator服务以NT AUTHORITY/System的方式运行任意代码。这种方法的另一个好处是,我们可以在DllMain之外运行我们的代码(即在加载器锁之外)。

注意:根据微软的说法,应避免在DllMain中运行代码,因为它可能会导致应用程序死锁。更多信息查看这里

然而,这种技术也有一些缺点。

  • 这需要你能够控制一个特权文件创建或移动的操作。
  • 导致ACL覆盖的漏洞等。
  • 作为一个普通用户,我们不知道DLL是否已经成功加载。

依赖于usoclient工具而不了解其工作原理也是我不喜欢这种技术的地方。所以,我对客户端和服务进行了逆向工程,以便制作一个可以在未来项目中重复使用的开源工具。UsoDllLoader。我将在本文的第二部分尝试解释这个过程。敬请期待

 

Links & Resources

(完)