Março de 2017

Volume 32 - Número 3

Visual Studio - Hash de arquivos de código-fonte com o Visual Studio para garantir a integridade do arquivo

Por Mike Lai | Março de 2017

A transformação do código legível por humanos para o código legível por máquina apresenta um desafio à confiabilidade dos softwares de todas as linguagens já compiladas: como um usuário pode ter certeza de que um programa de software executado em seu computador foi elaborado com o arquivo do código-fonte criado por seu desenvolvedor? Isso não pode ser comprovado, mesmo se os arquivos de código-fonte forem revisados por especialistas da área, o que pode acontecer com os softwares livres. Um fator importante para a confiabilidade do software é saber que os arquivos de código-fonte revisados são os mesmos que foram inseridos nos arquivos executáveis. 

Durante os processos de compilação e vinculação, um conjunto de arquivos de código-fonte escritos em uma determinada linguagem de programação (C#, C++, Objective C, Java, etc.) é transformado em um arquivo executável binário que será executado em um computador com uma arquitetura específica (x86, x64 e ARM, por exemplo). Mas essa transformação pode não ser determinante. É possível que dois conjuntos de arquivos de código-fonte resultem em dois arquivos executáveis idênticos bit a bit. Às vezes, isso é intencional. Ter mais ou menos espaços em branco ou comentários em texto dentro de arquivos de código-fonte não afeta o código binário emitido pelo compilador. Por outro lado, também é possível que um único conjunto de arquivos de código-fonte resulte em arquivos executáveis diferentes criados com processos de compilação distintos. Em ambos os casos, o problema é a certeza, é poder confirmar que você tem o arquivo que deseja.

Para resolver esse problema, você pode usar um compilador do Visual Studio para criar um hash dos arquivos de código-fonte durante a compilação. Fazer a correspondência entre os valores de hash do compilador com os valores de hash gerados a partir dos arquivos de código-fonte examinados permite que você verifique se o código do executável realmente resultou de um determinado conjunto de arquivos de código-fonte. Claramente, isso é bom para os usuários, que se beneficiariam mais se os fornecedores de outros compiladores também adotassem uma abordagem semelhante. Este artigo descreve o novo controle do Visual Studio para escolher um algoritmo de hash, cenários em que esses tipos de hash são comprovadamente úteis e como usar o Visual Studio para gerar hashes de código-fonte.

Gerando hashes fortes durante a compilação

Um arquivo de banco de dados de programa (PDB) é um arquivo de dados separado que armazena as informações usadas para depurar um arquivo executável binário. Recentemente, a Microsoft atualizou suas várias operações de criação de hash de arquivo (como os hashes de origem incorporados em arquivos PDB) para usar algoritmos de criptografia fortes.   

Compilador de código nativo O compilador nativo C/C++ do Visual Studio 2015, o cl.exe, vem com um novo controle para selecionar um algoritmo de hash diferente para o compilador criar um hash para arquivos de código-fonte: /ZH:{MD5|SHA_256}. O padrão é o MD5, que é conhecido por sua propensão a colisões, mas que continua sendo o padrão porque seus valores de hash são computacionalmente mais baratos de gerar. Com o novo controle, o compilador implementa a opção SHA-256, que é criptograficamente mais forte do que o MD5.

Se o hash SHA-256 para um arquivo de código-fonte corresponder ao hash SHA-256 armazenado no arquivo PDB de um executável binário, você pode ter certeza de que o mesmo arquivo de código-fonte foi compilado no executável e todos poderão confiar no arquivo executável binário. Efetivamente, os valores do hash SHA-256 armazenados no arquivo PDB do arquivo executável binário coletivamente se transformam na “certidão de nascimento” do arquivo executável binário, porque esses identificadores são registrados pelo compilador que “dá vida” ao arquivo executável binário.  

Com o SDK de Acesso à Interface de Depuração (bit.ly/2gBqKDo), é fácil criar uma ferramenta simples, como o Debugging Information Dumper (cvdump.exe), que está disponível junto com seu código fonte em bit.ly/2hAUhyy. Você pode usar o controle -sf do arquivo cvdump.exe para exibir a lista de módulos (usando seus nomes de caminhos completos na máquina local com o build) com seus hashes MD5 ou SHA-256, como mostrado na janela de comando na Figura 1.

Usando o arquivo cvdump.exe para exibir módulos e seus hashes
Figura 1: Usando o arquivo cvdump.exe para exibir módulos e seus hashes

Quando use uma versão anterior do arquivo cvdump.exe para visualizar o mesmo arquivo PDB, vi o texto “0x3”, em vez de “SHA_256”. O valor 0x3 é o valor de enum para SHA_256, e o arquivo cvdump.exe atualizado sabe como interpretá-lo. Esse é o mesmo valor de enum retornado pelo método IDiaSourceFile::get_checksumType do SDK de Acesso à Interface de Depuração.

Compilador de código gerenciado Por padrão, o compilador de código gerenciado C# do Visual Studio 2015 (csc.exe) usa o algoritmo de criptografia SHA-1 para calcular os valores de soma de verificação do hash do arquivo de origem para armazená-los nos arquivos PDB. No entanto, o csc.exe agora oferece suporte ao novo controle opcional “/checksumalgorithm”, que serve para especificar o algoritmo SHA-256. Se você quiser mudar para o algoritmo SHA-256, use essa opção para compilar todos os arquivos C# no diretório atual e incluir as informações de depuração, como a lista de arquivos de origem e os valores do hash SHA-256, em um arquivo PDB:

csc /checksumalgorithm:SHA256 /debug+ *.cs

Como parte do projeto da .NET Compiler Platform (“Roslyn”) livre, o csc.exe está disponível em github.com/dotnet/roslyn. Você encontrará suporte para o seletor de linha de comando do algoritmo de soma de verificação de depuração do arquivo fonte do SHA-256 no arquivo em bit.ly/2hd3rF3.

O csc.exe do Visual Studio 2015 é compatível apenas com os arquivos executáveis do Microsoft .NET Framework 4 ou posterior. O outro compilador do .NET Framework do Visual Studio 2015, usado para criar arquivos executáveis, anterior à versão 4 não oferece suporte ao controle /checksumalgorithm.

Os arquivos PDB com código gerenciado armazenam dados diferentemente dos arquivos PDB com código nativo. Em vez de usar o SDK de Acesso à Interface de Depuração, as interfaces interopercionais e os utilitários do Microsoft DiaSymReader podem ser usados para ler arquivos PDB com código gerenciado. O Microsoft DiaSymReader está disponível como um pacote NuGet em bit.ly/2hrLZJb.   

O projeto Roslyn inclui um utilitário chamado pdb2xml.exe, que é fornecido com suas fontes em bit.ly/2h2h596. Esse utilitário mostra o conteúdo de um PDB no formato XML. Por exemplo, o segmento na Figura 2 mostra a lista dos arquivos de código-fonte C# que foram usados para compilar um executável de código gerenciado.  

Exibindo um PDB com código gerenciado no formato XML
Figura 2: Exibindo um PDB com código gerenciado no formato XML

O GUID “8829d00f-11b8-4213-878b-770e8597ac16” no campo checkSumAlgorithmId indica que o valor no campo checksum é um valor de hash SHA-256 para o arquivo mencionado no campo name. Esse GUID é definido com a especificação de formato Portable PDB v0.1 (bit.ly/2hVYfEX).  

Suporte do compilador para SHA-256

Os compiladores do Visual Studio 2015 listados abaixo oferecem suporte ao hash SHA-256 de arquivos de código-fonte:      

  • cl.exe /ZH:SHA_256
  • ml.exe /ZH:SHA_256
  • ml64.exe /ZH:SHA_256
  • armasm.exe -gh:SHA_256
  • armasm64.exe -gh:SHA_256
  • csc.exe /checksumalgorithm:SHA256

Esses compiladores estão disponíveis na janela de comando “Prompt de Comando do Desenvolvedor para VS2015” do Visual Studio 2015.

Os compiladores que não foram criados para plataformas Windows geralmente não usam arquivos PDB para armazenar suas informações de depuração. Esses compiladores normalmente produzem dois arquivos executáveis simultaneamente durante a execução de uma compilação, um arquivo unstripped (detalhado) e outro stripped (simples) (bit.ly/2hIfvx6). Todas as informações da depuração são armazenadas no executável unstripped, enquanto o executável stripped não contém informações detalhadas da depuração. O executável unstripped pode ser usado para armazenar os hashes SHA-256 dos arquivos de código-fonte processados para o executável. Planejamos entrar em contato com os criadores desses outros compiladores para descobrir as abordagens mais adequadas aos seus produtos. Assim, os softwares não baseados no Windows e que usam esses compiladores, como o Office para Android, o Office para iOS ou o Office para Mac, podem ter benefícios semelhantes aos obtidos pelos softwares baseados no Windows.

Cenários de caso de uso   

Agora, analisaremos alguns cenários em que os valores de hash do arquivo de origem podem ser úteis.       

Recuperando arquivos de origem indexados de um arquivo binário PE (executável portátil) O script Ssindex.cmd (bit.ly/2haI0D6) é um utilitário que cria a lista dos arquivos de origem (indexados) que são verificados no controle do código-fonte, junto com as informações de versão de cada arquivo, e armazenados nos arquivos PDB. Se um arquivo PDB tiver essas informações do controle de versões, você poderá usar o utilitário srctool (bit.ly/2hs3WXY) com sua opção -h para exibir as informações. Como os arquivos de origem indexados também têm seus valores de hash incorporados no arquivo PDB, esses valores de hash podem ser usados para verificar a autenticidade dos arquivos de origem durante sua recuperação, como explica o artigo 3195907 da Base de Dados de Conhecimento (bit.ly/2hs8q0u), “Como recuperar arquivos de origem indexados de um arquivo binário executável portátil”. Se os valores de hash não corresponderem, algo pode ter dado errado durante a geração do par PE/PDB ou no sistema de controle do código-fonte. Talvez valha a pena investigar o que houve. Por outro lado, se os valores de hash corresponderem, isso é um forte indicativo de que os arquivos de origem indexados recuperados foram usados para compilar o par PE/PDB.        

Correspondendo os valores de hash produzidos por um analisador estático de arquivo de origem Hoje em dia, é comum usar ferramentas automáticas para avaliar a qualidade de um software, conforme recomendado pelo Microsoft Security Development Lifecycle (SDL), para a fase de implementação (bit.ly/­29qEfVd). Os analisadores estáticos de arquivos de origem são usados para examinar os arquivos do código-fonte de destino para avaliar os vários aspectos diferentes que definem a qualidade do software. Esses analisadores estáticos geralmente produzem os resultados em tempo real correspondentes ao examinar os arquivos do código-fonte de destino. Enquanto um analisador estático examina os arquivos de código-fonte individualmente, ele também oferece uma excelente oportunidade para gerar um hash forte (SHA-256) para cada um dos arquivos de código-fonte que estão sendo examinados. Na verdade, o SARIF (Static Analysis Results Interchange Format), proposto no projeto de software livre em bit.ly/2ibkbwz, fornece locais específicos nos resultados de análise estáticos para um analisador estático produzir os arquivos de código-fonte de destino examinados e seus respectivos valores de hash SHA-256. 

Em um arquivo PE, digamos que o seguinte esteja disponível:

  1. A listagem de hash do arquivo de origem que foi compilada do arquivo PDB correspondente, conforme produzido por um compilador.
  2. A listagem de hash do arquivo de origem examinada no resultado da análise estática correspondente, conforme produzido por um analisados estático.

Nesse cenário, você pode analisar e verificar se os dois arquivos de hash têm listagens correspondentes. Se tiverem, os arquivos de origem terão sido examinados para a avaliação de suas qualidades por um analisador estático, e você não precisará reexaminar os arquivos de origem. Anteriormente, se as listagens de hash não estivessem disponíveis, você precisaria reexaminar os arquivos para ter certeza de que o analisador estático fez uma avaliação adequada.  

Uma verificação de integridade mais rápida na atualização do software ou no processo de desenvolvimento de correções Quando você precisa lançar uma atualização de software para corrigir um problema de qualidade encontrado por um analisador estático em um produto já lançado, o analisador estático deve informar a ausência desse problema de qualidade nos arquivos de código-fonte da atualização pendente. No mínimo, esse relatório deve confirmar que a atualização é eficaz na correção do problema de qualidade original. Em outras palavras, ele deve validar a finalidade da atualização do software. Se desejar, você ou um revisor de segurança pode seguir estas etapas para realizar uma validação rápida: 

  1. Confirme se o relatório do analisador estático original identifica o problema de qualidade em questão.
  2. Confirme se o relatório original do analisador estático inclui os valores de hash dos arquivos de origem que apresentam o problema de qualidade.
  3. Corresponda os valores de hash do arquivo no relatório original do analisador estático com os valores de hash dos arquivos de origem na versão lançada do produto.
  4. Obtenha o relatório atualizado do analisador estático produzido pela verificação dos arquivos de código-fonte da atualização usando o mesmo analisador estático.
  5. Confirme que o problema de qualidade encontrado anteriormente não está presente no relatório do analisador estático para a atualização.
  6. Corresponda os valores de hash do arquivo no relatório original do analisador estático com os valores de hash dos arquivos de origem da versão lançada do produto.

Durante as etapas de validação, você não precisa ter acesso aos arquivos de código-fonte reais do produto original lançado ou da atualização. 

Criando o código-fonte delta entre duas versões de software Revisar todos os códigos-fonte pode ser uma tarefa demorada. No entanto, em alguns casos, é possível não revisar o código-fonte inteiro se ele tiver sido alterado. Para isso, você pode ser solicitado a informar apenas o código-fonte delta. Essa solicitação faz sentido, visto que é desnecessário repetir a análise das partes que não foram alteradas desde a última revisão.      

Antes, sem os valores de hash criptograficamente fortes dos arquivos de código-fonte, seria difícil criar o subconjunto delta com precisão. Mesmo se você pudesse oferecer um subconjunto delta, os especialistas no assunto não confiariam em sua habilidade de criar um subconjunto delta corretamente. Esse não é mais o caso. Com os valores de hash criptograficamente fortes, você pode usar as seguintes etapas para criar o subconjunto delta:

  1. Obtenha o pool (o Pool X, por exemplo) dos valores de hash de todos os arquivos de código-fonte da versão original do produto.
  2. Faça uma cópia exata do diretório de arquivos (Dir A, por exemplo) que contenha a inscrição do código-fonte da versão seguinte do produto, a partir da qual o conjunto delta será criado.
  3. Prepare uma pasta de destino para os arquivos finais (Dir B, por exemplo) para armazenar o conjunto de arquivos delta.
  4. Analise cada um dos arquivos em Dir A:
  5.         a. Se o valor de hash de um arquivo corresponder a um valor de hash no Pool X, não faça nada e prossiga para o próximo arquivo.
  6.         b. Se o valor de hash de um arquivo não corresponder a um valor de hash no Pool X, copie o arquivo para o Dir B antes de prosseguir para o próximo arquivo.
  7. Certifique-se de que todos os arquivos em Dir B tenham valores de hash que correspondam aos valores de hash nos arquivos de origem da versão seguinte do produto.  
  8. Transforme o conteúdo do Dir B no subconjunto de arquivos de origem delta da versão seguinte do produto.     

Gerando o hash

Agora, veremos como gerar um hash de arquivo com os compiladores do Visual Studio. Para fazer isso, usarei o exemplo da criação do aplicativo “Hello, World” descrito na documentação online do Visual Studio (bit.ly/2haPupF) para:

  1. Mostrar onde no arquivo PDB você pode encontrar os valores de hash dos arquivos de origem compilados.
  2. Usar a ferramenta certutil (bit.ly/2hIrnPR) para calcular os valores de hash do arquivo de origem e correspondê-los com os do arquivo PDB.       

Para começar, crio um novo projeto de aplicativo Win32HelloWorld na pasta Visual Studio 2015\Projects. Neste projeto Win32HelloWorld, existe apenas um arquivo de origem C++, o arquivo Win32HelloWorld.cpp, como mostra a Figura 3.

Win32HelloWorld.cpp
Figura 3: Win32HelloWorld.cpp

Como você pode ver, o arquivo Win32HelloWorld.cpp inclui a função principal que exibe o texto “Hello”.

Depois de criar uma compilação para o meu projeto Win32HelloWorld, tenho os arquivos W32HelloWorld.exe e W32HelloWorld.pdb na pasta Visual Studio 2015\Projects\W32HelloWorld\x64\Debug.

A ferramenta cvdump, usada com sua opção -sf no arquivo W32Hello­World.pdb, mostra o arquivo Win32HelloWorld.cpp e seu valor de hash MD5 na saída mostrada na Figura 4.

Saída da ferramenta cvdump que mostra o arquivo Win32HelloWorld.cpp e seu valor de hash MD5
Figura 4: Saída da ferramenta cvdump que mostra o arquivo Win32HelloWorld.cpp e seu valor de hash MD5

O valor do hash é MD5 porque MD5 é o algoritmo padrão do compilador cl.exe do Visual Studio 2015. Para mudar o algoritmo de hash do arquivo de origem para SHA-256, preciso fornecer a opção /ZH:SHA_256 ao cl.exe. Posso fazer isso inserindo “/ZH:SHA_256” na caixa Opções Adicionais em Páginas de Propriedades do projeto Win32HelloWorld, como mostra a Figura 5.

Mudando o algoritmo de hash do arquivo de origem para SHA-256
Figura 5: Mudando o algoritmo de hash do arquivo de origem para SHA-256

Depois de fazer essa mudança no Visual Studio, terei um novo par de arquivos PE/PDB W32HelloWorld.exe e W32HelloWorld.pdb na pasta Visual Studio 2015\Projects\W32HelloWorld\x64\Debug. Agora, usar a ferramenta cvdump, usada com sua opção -sf no arquivo W32HelloWorld­World.pdb mostra o arquivo Win32HelloWorld.cpp e seu valor de hash SHA-256 na saída, como mostra a Figura 6.

Saída da ferramenta cvdump que mostra o arquivo Win32HelloWorld.cpp e seu valor de hash SHA-256
Figura 6: Saída da ferramenta cvdump que mostra o arquivo Win32HelloWorld.cpp e seu valor de hash SHA-256

Agora, posso voltar ao arquivo W32HelloWorld.cpp file na pasta Visual Studio 2015\Projects\W32HelloWorld\W32HelloWorld pata verificar seu valor hash SHA-256. Usando a ferramenta certutil com seu verbo -hashfile no arquivo Win32HelloWorld.cpp para SHA-256, obtenho o valor de hash SHA-256 mostrado na Figura 7.

Obtendo o valor de hash SHA-256 com a ferramenta certutil
Figura 7: Obtendo o valor de hash SHA-256 com a ferramenta certutil

Claramente, ele corresponde ao valor SHA-256 registrado no arquivo W32Hello­World.pdb. Isso indica que o arquivo Win32HelloWorld.cpp realmente foi usado para compilar o aplicativo W32HelloWorld.exe, conforme o esperado.

Para obter mais detalhes sobre as ferramentas públicas disponíveis para trabalhar com os pares de arquivos PE/PDB que incluem o código nativo e o código gerenciado, consulte o artigo 3195907 da Base de Dados de Conhecimento “Como recuperar arquivos de origem indexados de um arquivo binário executável portátil” (bit.ly/2hs8q0u).

Conclusão

Espero que este artigo tenha mostrado alguns benefícios da criação de um vínculo mais forte entre os arquivos de código e o arquivo PE compilado com eles. Para criar um vínculo mais forte, você pode fazer com que o compilador gere um hash para os arquivos de código-fonte durante a compilação com o algoritmo de hash mais forte disponível, o SHA-256. Os valores reais de hash dos arquivos de código produzidos pelo compilador tornam-se literalmente identificadores exclusivos dos arquivos de código-fonte que são usados para compilar um executável.

Depois de entender o valor desses identificadores exclusivos, você poderá usá-los em diferentes esquemas de ciclo de vida de desenvolvimento de software para monitorar, processar e controlar os arquivos de código-fonte que têm um vínculo forte com determinados arquivos executáveis, proporcionando uma confiança maior dos usuários finais nos arquivos executáveis.


Mike Lai acaba de completar 20 anos na Microsoft. Ele é grato à empresa por lhe oferecer diversas oportunidades de contribuir para os aspectos de funcionalidade e engenharia de muitos de seus produtos. Mike gostaria de agradecer seus gerentes de Computação Confiável por sua paciência de esperar que suas ideias amadurecessem e fossem gradualmente incorporadas aos produtos lançados e por apoiar sua participação em organizações de padrões de segurança de Tecnologia de Informação e Comunicação.

Agradecemos aos seguintes especialistas técnicos da Microsoft pela revisão deste artigo: Scott Field, Mike Grimm, Sue Hotelling, Ariel Netz, Richard Ward e Roy Williams