Ler em inglês

Compartilhar via


Metadados de PE

Este artigo fornece detalhes adicionais para metadados do CFG (Control Flow Guard) em imagens PE. A familiaridade com a estrutura para metadados cfg em imagens PE é assumida. Consulte o tópico formato PE para obter documentação de alto nível para metadados cfg em imagens PE.

  • As funções que são destinos de chamadas indiretos válidas são listadas no GuardCFFunctionTable anexado ao diretório de configuração de carga, às vezes chamado de tabela GFIDS para brevidade. Essa é uma lista classificada de endereços virtuais relativos (RVA) que contêm informações sobre destinos de chamadas CFG válidos. Estes são, em geral, símbolos de função tomadas por endereço. Uma imagem que deseja que a imposição do CFG deve enumerar todos os símbolos de função obtidos em sua tabela GFIDS . A lista RVA na tabela GFIDS deve ser classificada corretamente ou a imagem não será carregada. A tabela GFIDS é uma matriz de 4 + n bytes, em que n é dado por ((GuardFlags & IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK) >> IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_SHIFT). "GuardFlags" é o campo GuardFlags do diretório de configuração de carga. Isso permite que metadados extras sejam anexados a destinos de chamadas CFG no futuro. Os únicos metadados definidos no momento são um campo opcional de sinalizadores extras de 1 byte ("sinalizadores GFIDS") que é anexado a cada entrada GFIDS se quaisquer destinos de chamada tiverem metadados. Há dois sinalizadores GFIDS definidos:

       
    IMAGE_GUARD_FLAG_FID_SUPPRESSED/0x1 O destino de chamada é suprimido explicitamente (não o trate como válido para fins de CFG)
    IMAGE_GUARD_FLAG_EXPORT_SUPPRESSED/0x2 O destino de chamada é a exportação suprimida. Consulte Exportar supressão para obter mais detalhes

    Para compatibilidade futura, as ferramentas não devem definir sinalizadores GFIDS que ainda não foram definidos e não devem incluir bytes de metadados adicionais do GFIDS adicionais além do 1 byte definido atualmente, pois os significados para outros sinalizadores ou metadados adicionais ainda não foram atribuídos. Você pode encontrar exemplos de imagens que incluem bytes de metadados extras despejando a tabela GFIDS de binários, como Ntdll.dll em uma versão moderna do sistema operacional Windows 10.

    As ferramentas só devem declarar símbolos de função como destinos de chamada válidos, o que pode merecer consideração adicional para o código do assembler em que os rótulos podem ser tratados. Por motivos históricos, o código assembler pode depender de rótulos de código diferentes de PROC ou .altentry, pois não está sendo convertido em destinos de chamada CFG pelo vinculador.

    Também por motivos históricos, o código pode declarar deliberadamente o código como dados para evitar a inclusão na tabela GFIDS . Por exemplo, um arquivo de objeto pode implementar um símbolo como código, enquanto outro pode declará-lo como dados para usar o endereço do símbolo sem gerar um registro de destino CFG válido. Para compatibilidade, é recomendável que os conjuntos de ferramentas ofereçam suporte a essa prática.

  • As imagens que dão suporte ao CFG e que desejam ou executam verificações CFG devem definir os bits IMAGE_GUARD_CF_INSTRUMENTED e IMAGE_GUARD_CF_FUNCTION_TABLE_PRESENT GuardFlags e devem definir o bit IMAGE_DLLCHARACTERISTICS_GUARD_CF DllCharacteristics nos cabeçalhos da imagem.

  • O diretório de configuração de carga anuncia dois ponteiros de função: GuardCFCheckFunctionPointer e GuardCFDispatchFunctionPointer (este último só tem suporte para determinadas arquiteturas, como AMD64). Esses ponteiros de função devem apontar para a memória somente leitura para que a segurança do CFG seja eficaz; O carregador de DLL do sistema operacional protegerá a memória transitóriamente durante o carregamento de imagens para armazenar os ponteiros de função. O uso típico pode ser mesclar na mesma seção que contém a Tabela de Endereços de Importação (IAT). O GuardCFCheckFunctionPointer fornece o endereço de um símbolo fornecido pelo carregador de so que pode ser chamado com um ponteiro de função no primeiro registro de argumento inteiro (ECX no x86) que retornará com êxito ou anulará o processo se o destino da chamada não for um destino CFG válido. O GuardCFDispatchFunctionPointer fornece o endereço de um símbolo fornecido pelo carregador de so que usa um destino de chamada no registro RAX e executa uma chamada combinada de verificação CFG e branch final otimizado para o destino da chamada (os registros R10/R11 são reservados para uso pelo GuardCFDispatchFunctionPointer e os registros de argumento inteiro são reservados para uso pelo destino de chamada final). O endereço padrão dos símbolos CFG em uma imagem deve apontar para uma função que retorna apenas (GuardCFCheckFunctionPointer) ou que retorna um símbolo suprimido por proteção (ou é preferencialmente totalmente omitido do símbolo de tabela GFIDS ) que executa uma instrução "jmp rax". Para AMD64 GuardCFDispatchFunctionPointer, quando uma imagem é carregada em um sistema operacional com reconhecimento CFG e o CFG está habilitado, o carregador de DLL do sistema operacional instalará ponteiros de função apropriados, o que facilita a compatibilidade com versões anteriores. Uma imagem poderá fornecer 0 para o GuardCFDispatchFunctionPointer na configuração de carga se não pretender usar a instalação de expedição cfg. Isso deve ser feito para arquiteturas não AMD64 para compatibilidade futura, caso essas arquiteturas eventualmente dêem suporte ao mecanismo de expedição cfg de alguma forma. Observe que Windows 8.1 AMD64 não era compatível com a expedição cfg e deixaria o ponteiro de função padrão em vigor para GuardCFDispatchFunctionPointer. A expedição cfg só tem suporte em sistemas operacionais Windows 10 e posteriores.

  • O CFG do modo de usuário só pode ser imposto para imagens marcadas como compatíveis com ASLR (randomização de layout de espaço de endereço) (especificada pela opção /DYNAMICBASE com o vinculador da Microsoft). Isso ocorre devido à forma como o sistema operacional lida internamente com o CFG em que ele é essencialmente conectado à infraestrutura ASLR. Em geral, os usuários do CFG devem habilitar o ASLR para suas imagens como uma primeira etapa. As ferramentas não devem assumir que o sistema operacional sempre ignorará o CFG sem o conjunto ASLR, mas geralmente deve definir ambos ao mesmo tempo.

Diretivas do compilador

  • Os destinos de chamada podem ser marcados como explicitamente suprimidos com o modificador __declspec(guard(suppress)) ou com a diretiva /guardsym:symname,S linker (para código asm, por exemplo). Isso faz com que o destino da chamada seja incluído na tabela GFIDS , mas marcado de forma que o sistema operacional trate o destino de chamada como não válido. Alguns cenários de não produção, como com determinada instrumentação do verificador de aplicativos habilitada em alguns sistemas operacionais mais antigos, podem permitir que destinos de chamadas suprimidos sejam tratados como válidos, mas, em geral, esses cenários não devem ser cenários de produção. Essa diretiva é útil para anotar funções "perigosas" que não devem ser consideradas como destinos de chamada válidos, embora a regra CFG normal as inclua.

  • O código pode indicar que as verificações CFG não são desejada com o modificador __declspec(guard(nocf)). Isso orienta o compilador a não inserir nenhuma verificação CFG para toda a função. O compilador deve ter cuidado para propagar essa diretiva para qualquer código contribuído por uma função embutida marcada como não querendo verificações CFG. Normalmente, essa abordagem é usada apenas com moderação em situações específicas em que o programador inseriu manualmente a proteção "equivalente a CFG". O programador sabe que está chamando por meio de alguma tabela de funções somente leitura cujo endereço é obtido por meio de referências de memória somente leitura e para as quais o índice é mascarado para o limite da tabela de funções. Essa abordagem também pode ser aplicada a pequenas funções wrapper que não estão embutidas e que não fazem nada mais do que fazer uma chamada por meio de um ponteiro de função. Como o uso incorreto dessa diretiva pode comprometer a segurança do CFG, o programador deve ter muito cuidado ao usar a diretiva. Normalmente, esse uso é limitado a funções muito pequenas que chamam apenas uma função.

Tratamento de importação

  • As chamadas por meio da IAT não devem usar a proteção cfg. O IAT é lido somente em imagens modernas (supondo que o IAT seja declarado nos cabeçalhos pe, nesse caso, ele deve estar em suas próprias páginas). O IAT pode ser usado para alcançar funções que são suprimidas por proteção, portanto, esse é um requisito de correção. Somente leitura de proteção de memória por meio da IAT substitui a do CFG, uma vez que a associação de destino de chamada é imutável depois que os snaps de importação de imagem são resolvidos e a resolução de associação é refinada.

  • Carga de atraso protegida: as chamadas por meio da IAT de carga de atraso não devem usar a proteção cfg, pelos mesmos motivos que o IAT padrão. O IAT de carga de atraso deve estar em sua própria seção e a imagem deve definir o bit IMAGE_GUARD_CF_PROTECT_DELAYLOAD_IAT GuardFlags. Isso indica que o carregador de DLL do sistema operacional deve alterar as proteções para a IAT de carga de atraso durante a resolução de exportação se usar o suporte de carga de atraso do sistema operacional nativo para sistemas operacionais Windows 8 e posteriores. A sincronização dessa etapa é gerenciada pelo carregador de DLL do sistema operacional se o suporte à carga de atraso do sistema operacional nativo estiver em uso (por exemplo, ResolveDelayLoadedAPI), portanto, nenhum outro componente deve proteger novamente as páginas que abrangem a IAT de carga de atraso declarada. Para compatibilidade com versões anteriores com sistemas operacionais pré-CFG mais antigos, as ferramentas podem habilitar a opção de mover o IAT de carga de atraso para sua própria seção (canonicamente ".didat"), protegido como leitura/gravação nos cabeçalhos de imagem e, além disso, definir o sinalizador IMAGE_GUARD_CF_DELAYLOAD_IAT_IN_ITS_OWN_SECTION. Essa configuração fará com que carregadores DLL do sistema operacional com reconhecimento CFG protejam novamente toda a seção que contém a tabela IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT para ler apenas a memória durante o carregamento da imagem. A opção de colocar a IAT de carga de atraso em sua própria seção pode não ser necessária se você não se importar em executar uma imagem em sistemas operacionais que antecedem o suporte ao CFG, mas as ferramentas devem tomar essa decisão com base no suporte mínimo do sistema operacional necessário para uma imagem.

    Se uma imagem não usar o suporte de carga de atraso nativo do sistema operacional, ela ainda poderá definir os bits GuardFlags relacionados à carga de atraso protegido. Nessa configuração, o carregador do sistema operacional apenas fornecerá suporte para proteger o IAT de carga de atraso como lido somente em runtime se tiver suporte na plataforma e se tornará responsabilidade dos stubs de resolução de carga de atraso interno da imagem sincronizar e gerenciar a proteção da IAT de carga de atraso. Desde que a tabela de configuração de carga seja armazenada na memória somente leitura (o que é recomendado), a presença ou a ausência do bit IAT de carga de atraso protegido no campo GuardFlags da imagem pode ser útil como uma dica interna para os stubs de resolução de carga de atraso interno da imagem para indicar se ela deve ou não proteger a IAT de carga de atraso.

    É recomendável que a carga de atraso protegida seja habilitada por padrão se o CFG estiver habilitado. As imagens executadas em versões mais antigas do sistema operacional e que usam o suporte de carga de atraso nativo do sistema operacional, conforme observado, podem usar o IAT de carga de atraso em sua própria seção de suporte para compatibilidade com versões anteriores. Isso se opõe a marcar a IAT de carga de atraso como somente leitura e mesclar com outra seção, o que interromperia os sistemas operacionais mais antigos que não entendem as cargas de atraso protegidas e que fornecem suporte à resolução de carga de atraso nativa. Todas as versões Windows 10 e as primeiras compilações de Windows 8.1/Windows Server 2012 R2 com suporte para CFG (o que significa que a atualização de novembro de 2014) apresentam suporte para carga de atraso protegida no sistema operacional.

Alinhamento da função

  • As funções que são endereços tomadas e, portanto, incluídas na tabela GFIDS devem ser alinhadas com 16 bytes, se possível. Isso pode nem sempre ser possível. Por exemplo, para funções não COMDAT que fazem parte de arquivos de objeto reunidos como uma unidade por ferramentas sem reconhecimento CFG, que alguns assemblers podem produzir, o usuário da ferramenta que produziu os arquivos deve definir adequadamente o alinhamento. As ferramentas podem optar por emitir um aviso de diagnóstico nessa situação para que o usuário possa tomar as medidas corretivas apropriadas. A razão para isso é que o CFG marca metas de chamada como válidas ou não válidas em limites de 16 bytes para eficiência de verificações de CFG rápidas. Se uma função não estiver alinhada a 16 bytes, todo o slot de 16 bytes deverá ser marcado como válido, o que não é tão seguro, pois você pode chamar desalinhado no código que não está no início de uma função. Esse cenário tem suporte para facilitar a interoperabilidade ao criar o CFG pela primeira vez para um projeto. Imagens sem reconhecimento CFG são marcadas da mesma forma como válidas para qualquer alinhamento de destino de chamada para compatibilidade. Como antes, ter destinos de chamada desalinhados reduz os benefícios de segurança do CFG, portanto, as ferramentas devem se alinhar automaticamente a um limite de 16 bytes para qualquer coisa na tabela GFIDS quando o CFG é desejado para uma imagem. Os símbolos que não estão na tabela GFIDS não precisam ter alinhamentos específicos para CFG.

Exportar supressão

  • A CFG ES (supressão de exportação cfg) é um modo opcional que permite que um processo indique que os destinos de chamada que só eram válidos porque eram símbolos dllexport, e que ainda não foram resolvidos dinamicamente pelo GetProcAddress, serão considerados como não válidos para fins de CFG. Isso reduz a área de superfície do CFG das exportações de DLL do sistema. A supressão de exportação envolve comunicar destinos de chamada dllexport qualificados "suprimidos" marcando-os com os sinalizadores IMAGE_GUARD_FLAG_EXPORT_SUPPRESSED GFIDS . Símbolos Dllexport e o ponto de entrada de imagem PE devem ser implicitamente considerados endereços obtidos por ferramentas para fins de geração da tabela GFIDS . Se um símbolo de exportação estiver alinhado a 16 bytes e for usado por nenhum outro motivo além de ser um dllexport, ele poderá ser marcado com o sinalizador GFIDS suprimido de exportação na tabela de funções. Os destinos de chamada que não estão alinhados a 16 bytes não devem ser marcados com o sinalizador IMAGE_GUARD_FLAG_EXPORT_SUPPRESSED GFIDS e não podem ser restritos apenas a serem habilitados dinamicamente como destinos de chamada válidos no horário de GetProcAddress.

    Uma imagem que dá suporte ao CFG ES inclui um GuardAddressTakenIatEntryTable cuja contagem é fornecida pelo GuardAddressTakenIatEntryCount como parte de seu diretório de configuração de carga. Essa tabela é formatada estruturalmente da mesma forma que a tabela GFIDS . Ele usa o mesmo mecanismo guardflags IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK para codificar bytes de metadados opcionais extras na tabela IAT de endereço tomada, embora todos os bytes de metadados devem ser zero para a tabela IAT tomada pelo endereço e são reservados. A tabela IAT do endereço tomada indica uma matriz classificada de RVAs de thunks de importação que têm o importado como um endereço de símbolo direcionado ao destino de chamada. Esse constructo dá suporte a símbolos de endereços que existem em um módulo remoto e que são dllexports, com CFG ES em uso. Um exemplo de tal construção de código seria:

    mov rcx, [__imp_DefWindowProc]
    call foo ; where foo takes the actual address of DefWindowProc.
    

    Todos esses endereços necessários devem ser enumerados para que o carregador do sistema operacional possa encontrá-los e tornar os destinos de chamada apropriados válidos ao carregar uma imagem e ajustar suas importações. A tabela e a contagem poderão ser 0 se não houver nenhum thunks de importação que tenha sido feito.

    Um módulo define o bit IMAGE_GUARD_CF_EXPORT_SUPPRESSION_INFO_PRESENT GuardFlags para indicar que ele enumerou todos os endereços obtidos em sua tabela IAT tomada de endereço e que todas as exportações qualificadas para CFG ES são marcadas com o sinalizador IMAGE_GUARD_FLAG_EXPORT_SUPPRESSED GFIDS . Observe que pode não haver tais thunks e que também pode haver zero símbolos de dllexport. A falha na manutenção da tabela IAT tomada pelo endereço pode ser um problema de correção, pois alguns destinos de chamada podem não ser válidos quando devem estar no tempo de carregamento da DLL.

    Um módulo define o bit IMAGE_GUARD_CF_ENABLE_EXPORT_SUPPRESSION GuardFlags para indicar que ele deseja habilitar o CFG ES para o processo. Na prática, isso só é significativo para EXEs atualmente. Um processo que habilita o CFG ES não deve carregar DLLs não criadas com CFG ES ou falhas de runtime pode ocorrer devido a símbolos de IAT não atribuídos. O suporte para habilitar o CFG ES deve ser uma opção de aceitação separada da habilitação do CFG. Fornecer metadados cfg es é seguro e recomendado por padrão com CFG, embora os conjuntos de ferramentas devem ter cuidado para garantir que eles produzam metadados corretos. Caso contrário, suas imagens geradas podem não ser executadas corretamente em um processo de CFG ES. Esse suporte deve ser testado minuciosamente em um processo de teste que impõe o CFG ES. As DLLs internas do sistema operacional dão suporte a metadados CFG ES para versões modernas do sistema operacional Windows 10 que entendem o CFG ES. As versões do sistema operacional anteriores a esse suporte não entendem o CFG ES e ignorarão quaisquer diretivas relacionadas ao CFG ES na imagem. Essas imagens ainda são compatíveis com versões anteriores do sistema operacional.

    O suporte ao CFG ES é opcional do ponto de vista do conjunto de ferramentas, mas é recomendável que os conjuntos de ferramentas pelo menos incluam suporte para enumerar informações suficientes para que as imagens sejam executadas em um processo que deseja o CFG ES. Conforme mencionado, é fundamental que o suporte ao conjunto de ferramentas seja testado minuciosamente para garantir que ele seja compatível com o CFG ES, pois a maioria dos processos ainda não habilita o CFG ES.

Tratamento e desenrolamento de exceções

  • Manipuladores específicos do idioma, como __C_specific_handler, conforme designado pelas informações do manipulador de exceção em um registro .pdata, não devem ser marcados como destinos de chamada válidos na tabela GFIDS . Em vez disso, eles são pesquisados atravessando apenas a memória de leitura. Da mesma forma, o manipulador específico da linguagem C da Microsoft usa pesquisas de memória somente leitura para localizar funclets para manipuladores de exceção e, portanto, não declara seus funclets como destinos de chamada válidos na tabela GFIDS .

  • Tratamento de salto em distância (para destinos não x86 como AMD64): conjuntos de ferramentas compilados com CFG e suporte a setjmp()/longjmp() devem implementar o salto em distância como "salto em distância seguro" que interopera com o SEH (tratamento de exceção estruturado). Isso significa que o salto em distância é implementado como uma chamada para RtlUnwindEx com STATUS_LONGJUMP como o código de status no registro de exceção fornecido e um _JUMP_BUFFER padrão apontado por ExceptionInformation[0]. O destino de desenrolamento de salto deve ser o TargetIp do desenrolamento. O buffer de salto representa o contexto de registro restaurado pelo sistema operacional após a conclusão do salto em distância. RtlUnwind(Ex) quando chamado com STATUS_LONGJUMP tem um significado especial exclusivo para o CFG. O destino de salto em distância (_JUMP_BUFFER. Rip ou _JUMP_BUFFER. O LR no ARM64) é pesquisado na lista de módulos carregados mantida pelo sistema operacional na memória somente leitura. Se o módulo que contém o destino de salto (o "módulo de destino") tiver o sinalizador IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT definido em seu campo GuardFlags, o diretório de configuração de carga terá uma contagem de elementos GuardLongJumpTargetTable especificada pelo campo GuardLongJumpTargetCount da configuração de carga. Essa tabela é formatada estruturalmente da mesma forma que a tabela GFIDS e usa o mesmo mecanismo de IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK GuardFlags para codificar bytes de metadados extras opcionais na tabela de salto em distância. Todos os bytes de metadados devem ser zero para a tabela de salto em distância e são reservados.

    A tabela de salto em distância representa uma matriz classificada de RVAs que são destinos de salto em distância válidos. Se um módulo de destino de salto em distância definir IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT em seu campo GuardFlags, todos os destinos de salto em distância deverão ser enumerados no LongJumpTargetTable. Mesmo que um módulo não tenha destinos de salto em distância, ele ainda deverá definir o sinalizador IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT se o conjunto de ferramentas der suporte à proteção de salto em distância para CFG. Isso significa explicitamente que a imagem não tem destinos de salto em distância e não é uma imagem antiga que o sistema operacional deve assumir que pode ter destinos de salto em distância válidos em locais não marcados para os quais não pode executar a verificação de destino de salto em distância.

    Recomendamos que a proteção de salto em distância seja habilitada por padrão se houver suporte para CFG. Essa é a disposição dos compiladores da Microsoft. Os sistemas operacionais que não entendem o endurecimento de salto em distância (versões de Windows 10 ou mais antigas do Windows 10) não executarão verificações de proteção de salto em distância e ignorarão os metadados de proteção de salto em distância, portanto, a proteção de salto em distância é compatível com versões mais antigas do sistema operacional.

    Para imagens do modo kernel, a tabela de destino de salto em distância de proteção não deve ser incluída em uma seção descarte. A tabela de destino de salto em distância de proteção sempre deve ser armazenada na memória somente leitura para que suas propriedades de segurança sejam eficazes.

Informações de COFF

  • Há marcações de arquivo de objeto para declarar se um arquivo de objeto está em conformidade com o CFG ou não. Um arquivo de objeto que está em conformidade com o CFG listará os destinos de chamada válidos que ele produz, explicitamente, bem como todos os metadados de IAT obtidos pelo endereço. Um arquivo de objeto que não está em conformidade com o CFG deve ter destinos de chamada inferidos examinando as realocações de COFF do arquivo obj para encontrar realocações que apontem para o início de um símbolo de função. Isso pode sobrecarregar os destinos de chamadas CFG válidos, portanto, é desejável que as ferramentas marquem seus arquivos obj que estejam cientes do CFG e incluam os metadados de arquivo obj cfg se forem compilados com CFG.

  • Há marcações de arquivo de objeto para declarar destinos de salto em distância para o salto em distância protegido por CFG que devem ser populados para o modo de compilação CFG.