Fantásticas interfaces RPC e como encontrá-las
Contribuições editoriais e adicionais por Tricia Howard
Introdução
Nos últimos meses, nossa equipe tem se esforçado muito na pesquisa de MS-RPC devido à sua complexidade e, em grande parte, ao baixo número de pesquisas sobre o assunto. Você deve ter visto as inúmeras publicações sobre vulnerabilidades que descobrimos com este trabalho, como srvsvc e Wininit.exe, por exemplo. Com a enorme quantidade de dados e ferramentas que acumulamos ao longo desta pesquisa, o melhor lugar para manter era um só: o nosso Kit de ferramentas RPC.
Esse repositório de código aberto tem tudo o que você precisa para começar ou avançar sua jornada rumo ao RPC: ferramentas, artigos, publicações de blog e palestras em conferência, bem como informações de vulnerabilidade de RPC e provas de conceito de exploração. Esse repositório foi criado com o intuito de tornar o conhecimento sobre RPC mais acessível para defensores e pesquisadores, já que todos nós ficamos mais seguros quando trabalhamos juntos. Para qualquer pessoa que tenha lido, usado uma ferramenta ou compartilhado algum de nosso trabalho: Obrigado! Estamos felizes de que você esteja fazendo bom uso dele.
Uma das ferramentas do conjunto de ferramentas é o nosso RPC Interface Analyzer, que ajuda profissionais de segurança como você a identificar de forma rápida e fácil leads para interfaces potencialmente vulneráveis. Esta publicação de blog tem como objetivo orientar você em relação aos seus objetivos e descobertas, bem como oferecer uma visão geral do RPC e alguns de seus mecanismos de segurança para quem ainda não está familiarizado com eles.
O que é um RPC e quais são seus mecanismos de segurança?
Um RPC (Remote Procedure Call, chamada de procedimento remoto) é uma forma de IPC (Inter-process communication, comunicação entre processos) que permite que um cliente invoque um procedimento exposto por um servidor RPC. O cliente chama a função como se fosse uma chamada de procedimento normal, quase sem necessidade de codificar os detalhes da interação remota. O servidor pode ser hospedado em um processo diferente, na mesma máquina ou em uma máquina remota.
O MS-RPC, a implementação de RPC da Microsoft, é muito usado pelo Windows para vários serviços diferentes, como agendamento de tarefas, criação de serviços, configurações e compartilhamentos de impressoras e gerenciamento de dados criptografados armazenados remotamente. Este amplo escopo de uso e a natureza remota do RPC como vetor são exatamente o motivo pelo qual estamos discutindo isso hoje e por que investimos tanto em pesquisas sobre RPC. Devido à grande funcionalidade desse recurso, ele recebe muita atenção do ponto de vista da segurança.
Nas seções a seguir, vamos nos aprofundar nos retornos de chamada de segurança de RPC e como você pode usar a automação para analisá-los e gerar potencialmente novos leads para pesquisa de segurança e vulnerabilidade.
O que é um retorno de chamada de segurança de RPC e como funciona?
Em resumo, um retorno de chamada de segurança é uma das várias maneiras de proteger uma interface RPC. Trata-se de um retorno de chamada de segurança personalizado, implementado pelo desenvolvedor do servidor RPC. Sua lógica depende da decisão do desenvolvedor, e permite que o desenvolvedor aplique controle de acesso baseado no usuário, autenticação, tipos de transporte ou impeça o acesso a funções específicas expostas pelo servidor.
Eventualmente, o retorno de chamada retorna RPC_S_OK para permitir a comunicação do cliente com o servidor, ou um dos códigos de erro RPC, como RPC_S_ACCESS_DENIED, para negá-lo.
A decisão de aceitar ou rejeitar a solicitação de um cliente geralmente dependerá de um atributo (ou mais de um) que analisaremos abaixo.
Sequência de protocolos
O cliente pode se comunicar com o servidor por meio de vários transportes: TCP, pipes nomeados, ALPC e muito mais. O retorno de chamada de segurança pode verificar esse atributo para filtrar somente solicitações de conexão local, somente solicitações remotas, comunicação TCP etc.
Embora os valores de sequência de protocolo não sejam documentados, mapeamos as sequências de protocolos que estão sendo passadas para o retorno de chamada de segurança dentro da estrutura RpcCallAttributes para os seguintes valores:
#define ncacn_ip_tcp 1
#define ncacn_np 2
#define ncalrpc 3
#define ncacn_http 4
Outras sequências de protocolos (como ncacn_hvsocket) podem ser testadas pelo retorno de chamada de segurança por meio de uma análise da vinculação da string.
Nível de autenticação
Verificar o nível de autenticação do cliente é muito comum em retornos de chamada de segurança. Dessa forma, o servidor pode definir o nível mínimo de autenticação esperado de seus clientes.
Há vários níveis de autenticação, cada um amplia seu nível anterior:
#define RPC_C_AUTHN_LEVEL_DEFAULT 0
#define RPC_C_AUTHN_LEVEL_NONE 1
#define RPC_C_AUTHN_LEVEL_CONNECT 2
#define RPC_C_AUTHN_LEVEL_CALL 3
#define RPC_C_AUTHN_LEVEL_PKT 4
#define RPC_C_AUTHN_LEVEL_PKT_INTEGRITY 5
#define RPC_C_AUTHN_LEVEL_PKT_PRIVACY 6
Por exemplo, um servidor esperaria um nível de autenticação de RPC_C_AUTHN_LEVEL_PKT_PRIVACY para garantir que os dados de comunicação só estejam visíveis para o cliente e o servidor, ou seria esperado o nível de autenticação de RPC_C_AUTHN_LEVEL_NONE para indicar que não há autenticação.
Serviço de autenticação
O protocolo serviço de autenticação especifica o serviço responsável pela validação da política de autenticação fornecida pelo nível de autenticação.
Os valores constantes do serviço de autenticação são:
#define RPC_C_AUTHN_NONE 0
#define RPC_C_AUTHN_DCE_PRIVATE 1
#define RPC_C_AUTHN_DCE_PUBLIC 2
#define RPC_C_AUTHN_DEC_PUBLIC 4
#define RPC_C_AUTHN_GSS_NEGOTIATE 9
#define RPC_C_AUTHN_WINNT 10
#define RPC_C_AUTHN_GSS_SCHANNEL 14
#define RPC_C_AUTHN_GSS_KERBEROS 16
#define RPC_C_AUTHN_DPA 17
#define RPC_C_AUTHN_MSN 18
#define RPC_C_AUTHN_DIGEST 21
#define RPC_C_AUTHN_NEGO_EXTENDER 30
#define RPC_C_NETLOGON 68 (não documentado)
#define RPC_C_AUTHN_MQ 100
#define RPC_C_AUTHN_DEFAULT 0xffffffff
RPC_C_AUTHN_NONE, por exemplo, desativaria a autenticação, enquanto RPC_C_AUTHN_WINNT usará o protocolo NTLM.
A lista completa de serviços de autenticação e seus valores pode ser encontrada na página do GitHub.
Sessão NULL
Uma sessão NULL é uma conexão anônima. Nesse caso, o cliente se comunica com o servidor sem autenticação, ou seja, sem nome de usuário ou senha.
Na maioria dos casos, se um retorno de chamada de segurança for registrado, as sessões NULL serão bloqueadas por padrão, a menos que o sinalizador RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH seja fornecido no registro do servidor (leia sobre outros casos aqui). Retornos de chamada de segurança também podem verificar sessões NULL.
Ao bloquear o acesso a essas sessões, o retorno de chamada de segurança protege a interface RPC contra usuários não autenticados.
Número da operação (opnum)
O opnum representa a função de interface solicitada pelo cliente para execução. Mais precisamente, o opnum é o índice na tabela de funções do servidor RPC.
Ao verificar o valor opnum, o servidor pode limitar ou bloquear o acesso do cliente a funções específicas, como funções que lidam com dados confidenciais de clientes remotos, que acessam a memória do kernel e/ou a funcionalidade de clientes no modo de usuário etc.
A Pesquisa da Akamai sobre segurança fez uma publicação no blog com um exemplo de um retorno de chamada de segurança interessante que depende dessa verificação.
Outras verificações de retorno de chamada de segurança podem ser:
Origem do chamador: para verificar se o chamador vem do modo usuário ou do modo kernel
PID do cliente: para permitir apenas processos específicos
Vinculação da string: para validar atributos de conexão RPC, como sequência de protocolos, endereço de rede, informações de ponto de extremidade etc.
Representação: para garantir que o servidor possa executar o código no contexto de segurança do cliente
Também há verificações complexas. Por exemplo, no retorno de chamada LsaCapRpcIfCallbackFn em lsasrv.dll, se o serviço de autenticação for netlogon, o nível de autenticação deve ser menor que RPC_C_AUTHN_LEVEL_PKT_INTEGRITY.
RPC Interface Analyzer: um passo a passo prático
O RPC Interface Analyzer é uma ferramenta de automação para pesquisar interfaces RPC. Ele permite que os pesquisadores encontrem e analisem interfaces RPC de duas fontes diferentes: arquivos IDL (interface definition, definição de interface) ou arquivos PE.
Arquivos IDL
Arquivos IDL são os arquivos que definem uma interface RPC e suas funções. Ao analisar arquivos IDL disponíveis ao público, podemos ver informações sobre uma interface RPC e as funções expostas pelo seu servidor, juntamente com seus parâmetros e seus tipos de valor de retorno. Com essas informações, um pesquisador pode procurar funções potencialmente vulneráveis; por exemplo, funções que recebem um argumento de caminho, como no caso do PetitPotam.
Para executar nosso analisador IDL, execute os comandos a seguir:
1. Baixe todos os arquivos IDL do site da Microsoft na sua máquina usando o script idl_scraper:
idl_scraper.py [-h] [-o OUTPUT] [-p PROTOCOL]
2. Em seguida, execute o idl_parser para analisar estes arquivos IDL:
idl_parser.py [-h] [-r] input_path [output_path]
A saída que você receberá de retorno é um arquivo CSV que contém os nomes de interface RPC, identificadores universalmente exclusivos (UUIDs), nomes de funções expostas e assinaturas.
Arquivos PE
A análise dos arquivos IDL pode ser útil, mas podemos perder algumas interfaces RPC, pois só temos acesso aos arquivos IDL disponíveis ao público. Outra abordagem é procurar interfaces RPC em nosso sistema de arquivos local (em arquivos PE, que são os arquivos .exe ou .dll). Achamos essa abordagem preferível à verificação de processos em tempo real, pois dessa forma não podemos perder servidores RPC que não estão em operação ou que estão sendo executados em processos protegidos.
O protocolo RPC PE Analyzer procurará interfaces RPC registradas pela função RpcServerRegisterIf (e suas variantes) e analisará os argumentos passados a ela, caso um desmontador tenha sido fornecido. Ao executar sem ela, no modo padrão, o analisador encontraria interfaces RPC usando o regex. Essa palestra se aprofunda no processo de pesquisa.
Para usar o RPC PE Analyzer no modo padrão, execute o seguinte comando:
pe_rpc_scraper.py <scrape_path> <output_path>
Esse comando fornecerá a saída básica que inclui o UUID da interface, sua função (cliente/servidor) e os nomes e endereços de função.
Para receber informações mais detalhadas, você pode fornecer um desmontador e seu caminho (se não for o padrão) para o script:
pe_rpc_scraper.py [-d {idapro,radare}] [-P DISASSEMBLER_PATH] <scrape_path> <output_path>
O uso da opção de desmontador adicionará também informações de registro da interface, incluindo:
Sinalizadores fornecidos no registro do servidor
Nome e endereço do retorno de chamada de segurança da interface
Descritor de segurança do servidor RPC (se houver)
Se o armazenamento em cache global está ativado para o retorno de chamada de segurança
Nosso recurso mais recente lançado junto com esta publicação também conta com uma análise do retorno de chamada de segurança.
Exemplo de uso
Digamos que queremos verificar todas as interfaces RPC disponíveis em nossa máquina. Podemos executar o RPC PE Analyzer e fornecer uma cópia do C:\Windows\System32 como nosso scrape_path e passar para a saída.
pe_rpc_scraper.py -d idapro “C:\Users\User\Documents\System32_Copy”
Como a saída está no formato JSON, é fácil iterar nela e procurar informações específicas. Por exemplo:
Localizar todos os clientes/servidores RPC em um arquivo DLL
Localizar todos os clientes/servidores RPC em uma máquina
Localizar os clientes de uma interface RPC específica
Localizar o retorno de chamada de segurança RPC de um determinado servidor RPC
Localizar quais interfaces RPC usam armazenamento em cache no nível da interface (leia esta publicação de blog para ver mais informações sobre por que esse tipo de armazenamento em cache é problemático)
Estes são apenas alguns dos muitos usos possíveis para esta saída. Ficaremos felizes em saber sobre mais usos e ideias.
Novo recurso: informações de retorno de chamada de segurança
Estrutura RpcCallAttributes
RPC_CALL_ATTRIBUTES é uma estrutura que contém dados relacionados à solicitação do cliente. O retorno de chamada de segurança da interface no lado do servidor pode acessar essas informações chamando a função RpcServerInqCallAttributes .
typedef struct tagRPC_CALL_ATTRIBUTES_V3_W
{
Versão de int não assinada;
Sinalizadores longos não assinados;
ServerPrincipalNameBufferLength longo não assinado;
*ServerPrincipalName curto não assinado;
ClientPrincipalNameBufferLength longo não assinado;
*ClientPrincipalName curto não assinado;
AuthenticationLevel longo não assinado;
AuthenticationService longo não assinado;
BOOL NullSession;
BOOL KernelModeCaller;
ProtocolSequence longo não assinado;
RpcCallClientLocality IsClientLocal;
HANDLE ClientPID;
CallStatus longo não assinado;
RpcCallType CallType;
RPC_CALL_LOCAL_ADDRESS_V1 *CallLocalAddress;
OpNum curto não assinado;
UUID InterfaceUuid;
ClientIdentifierBufferLength longo não assinado;
*ClientIdentifier char não assinado;
} RPC_CALL_ATTRIBUTES_V3_W;
Já mencionamos os testes executados pelos retornos de chamada de segurança. Eles podem consultar alguns desses valores individualmente (por exemplo, RpcStringBindingParseW para receber a sequência de protocolos, RpcBindingInqAuthClient para acessar informações de autenticação e muito mais), ou usar essa estrutura que mantém tudo e requer apenas uma chamada de função. Na verdade, na maioria dos retornos de chamada de segurança que analisamos, eles chamam RpcServerInqCallAttributes e usam a estrutura RPC_CALL_ATTRIBUTES para consultar todos os atributos simultaneamente. Isso torna essa estrutura muito interessante se você quiser entender a lógica do retorno de chamada de segurança.
A estrutura atualmente tem três versões diferentes (1, 2 e 3), e cada uma delas é uma extensão de sua versão anterior e tem versões ANSI e Unicode. Você pode encontrar as diferentes versões e seus membros na página do GitHub.
Informações de retorno de chamada de segurança
A nova adição ao nosso kit de ferramentas RPC são as informações de retorno de chamada de segurança, que fazem parte do RPC PE Analyzer. Com elas, você fica de olho nas verificações que o retorno de chamada de segurança faz antes de aprovar/negar solicitações de clientes.
Analisar o retorno de chamada de segurança de uma interface RPC e, especificamente, seu acesso à estrutura RPC_CALL_ATTRIBUTES pode trazer mais informações sobre a interface. Dessa forma, você pode procurar retornos de chamada de segurança que verificam o atributo de serviço de autenticação se quiser filtrar interfaces RPC que usam (ou não usam) um protocolo de autenticação específico. Você também pode consultar interfaces RPC que não verificam sessões NULL antes de permitir solicitações de clientes e antes de seus sinalizadores de registro de servidor permitirem sessões NULL, para descobrir interfaces RPC potencialmente vulneráveis.
Como isso funciona?
Para cada retorno de chamada de segurança, o analisador irá:
Encontrar qual versão da estrutura RPC_CALL_ATTRIBUTES está sendo usada e definir a estrutura relevante nos tipos de locais de IDA
Localizar a variável local RpcCallAttribute e aplicar a estrutura RPC_CALL_ATTRIBUTES como seu tipo
Analisar as verificações executadas pelo retorno de chamada de segurança usando essa estrutura e exibir qual membro está sendo testado, o valor ao qual ele está sendo comparado e com qual operador (== / != / > / < / etc.).
Como posso usá-la?
O uso não mudou: Sempre que você executar o RPC PE Analyzer com o sinalizador de desmontador do IDA, a saída para cada interface RPC incluirá agora suas informações de retorno de chamada de segurança: se ele acessou membros da estrutura RpcCallAttributes e o que ele testou.
Nota: no momento, esta adição está disponível apenas para arquivos IDA, a execução do analisador com a opção Radare não incluirá informações de retorno de chamada de segurança.
Quando tiver a saída, você pode usá-la para encontrar interfaces RPC possivelmente vulneráveis e filtrá-las de acordo com suas necessidades. Alguns exemplos incluem:
Obter uma interface RPC que use apenas o nível de autenticação de RPC_C_AUTHN_LEVEL_PKT_PRIVACY
Obter as interfaces RPC que esperam conexões locais
Obter as interfaces RPC que esperam apenas chamadores do modo kernel
Obter interfaces RPC que verificam o opnum, como no caso de vulnerabilidade de cache
Resumo
Ficou claro que o RPC é terra fértil para a pesquisa, especialmente considerando o quanto ele é antigo e integrado em tantos processos importantes. Temos reunido recursos em nosso repositório há quase um ano, e ainda há muito mais a fazer para entender totalmente as possíveis ameaças do RPC. Ainda há poucas pesquisas sobre ele e, embora tenha conquistado mais atenção recentemente, ainda há muito a descobrir sobre a infinidade de maneiras que os invasores podem fazer mal uso desse recurso.
A natureza intrínseca do RPC garante pesquisas sobre todos os ângulos potencialmente perigosos. Esperamos que a nossa pesquisa contínua e as ferramentas em nosso repositório tragam mais informações sobre esse vetor e inspirem outros pesquisadores a se aprofundarem nelas também. Se você é um defensor responsável pela segurança do RPC ou um pesquisador que procura seu próximo alvo, o RPC tem muito potencial para conhecimento e insights.
Tudo pronto para começar? Confira o repositórioe não deixe de nos contar no Twitter o que você encontrar!