Marzo de 2017

Volumen 32, número 3

Visual Studio: uso de hash en archivos de código fuente con Visual Studio para garantizar la integridad de los archivos

Por Mike Lai | Marzo de 2017

La transformación de código en lenguaje natural en código de lectura mecánica supone un desafío de garantía de calidad del software para todos los lenguajes de software compilado: ¿Cómo puede confiar un usuario en que un programa de software que se ejecuta en el equipo está compilado a partir del mismo archivo de código fuente que creó el desarrollador? Eso no es necesariamente cierto, aunque los archivos de código fuente estén revisados por expertos en la materia, como podría ser en el caso de software de código abierto. Una parte crítica de la garantía de calidad del software es confiar en que los archivos de código fuente revisados son los mismos archivos de código fuente integrados en los archivos ejecutables. 

Durante los procesos de compilación y vinculación, un conjunto de archivos de código fuente escritos en un lenguaje de programación específico (C#, C++, Objective C, Java, etc.) se transforma en un archivo ejecutable binario para ejecutarse en un equipo de una arquitectura específica (por ejemplo, x86, x64 o ARM). No obstante, es posible que esta transformación no sea determinista. Es posible que dos conjuntos de archivos de código fuente diferentes puedan originar dos archivos ejecutables idénticos bit a bit. En ocasiones, esto es intencionado. Tener más o menos espacios en blanco o comentarios de texto dentro de los archivos de código fuente no debería afectar al código binario que emite el compilador. Por otro lado, también es posible que un solo conjunto de archivos de código fuente genere archivos ejecutables diferentes en distintos procesos de compilación. En cualquier caso, se trata de un problema de seguridad, de saber con certeza que el archivo que tiene es el que quiere.

Para abordar el problema, resulta útil usar un compilador de Visual Studio para aplicar algoritmos hash a los archivos de código fuente durante la compilación. Al hacer coincidir los valores hash del compilador con los valores hash generados a partir de los archivos de código fuente examinados, se comprueba que el código ejecutable tiene su origen efectivamente en los archivos de código fuente concretos. Esto resulta claramente beneficioso para los usuarios (que, de hecho, se beneficiarían aún más si los proveedores de otros compiladores siguieran una estrategia similar). Mike Lai describe el nuevo modificador de Visual Studio para elegir un algoritmo hash, los escenarios donde los hash pueden resultar útiles y el método para usar Visual Studio con la finalidad de generar hash de código fuente.

Generar algoritmos hash sólidos durante la compilación

Un archivo de base de datos de programa (PDB) es un archivo de datos independiente que almacena la información usada para depurar un archivo ejecutable binario. Microsoft actualizó recientemente sus distintas operaciones de hash de archivos del compilador (como algoritmos hash de origen insertados en archivos PDB) para usar algoritmos criptográficos sólidos.   

Compilador de código nativo El compilador de C/C++ nativo de Visual Studio 2015 (cl.exe) incluye un nuevo modificador para elegir un algoritmo hash diferente para que el compilador aplique algoritmos hash a los archivos de código fuente: /ZH:{MD5|SHA_256}. MD5, conocido por ser más propenso a colisiones, es el predeterminado porque sus valores hash son más económicos de generar desde el punto de vista computacional. Con el nuevo modificador, el compilador implementa la opción SHA-256, que es criptográficamente más sólida que MD5.

Si un hash SHA-256 de un archivo de código fuente coincide con un hash SHA-256 almacenado en el archivo PDB de un ejecutable binario, es cierto que el mismo archivo de código fuente se compiló en el ejecutable, de modo que cualquier parte interesada puede confiar en el archivo ejecutable binario. Efectivamente, el conjunto de valores hash SHA-256 almacenados en el archivo PDB del archivo ejecutable binario se convierte colectivamente en los identificadores del "certificado de nacimiento" del archivo ejecutable binario, ya que estos identificadores los registra el compilador que genera el archivo ejecutable binario.  

Con Debug Interface Access SDK (bit.ly/2gBqKDo), resulta sencillo crear una herramienta simple, como Debugging Information Dumper, cvdump.exe (que, junto con su código fuente, está ahora disponible en bit.ly/2hAUhyy). Puede usar el modificador -sf de cvdump.exe para ver el listado de los módulos (mediante sus nombres de ruta de acceso completa en la máquina de compilación local) con sus algoritmos hash MD5 o SHA-256, como se muestra en la ventana Comandos de la Figura 1.

Uso de cvdump.exe para ver módulos con sus hash
Figura 1 Uso de cvdump.exe para ver módulos con sus hash

Cuando usé una versión anterior de cvdump.exe para ver el mismo archivo PDB, vi el texto "0x3" en lugar de "SHA_256". El valor 0x3 es el valor de enumeración de SHA_256 y el archivo cvdump.exe actualizado sabe cómo interpretarlo. Es el mismo valor de enumeración que devuelve el método IDiaSourceFile::get_checksumType de Debug Interface Access SDK.

Compilador de código administrado De forma predeterminada, el compilador de C# de código administrado de Visual Studio 2015, csc.exe, usa el algoritmo criptográfico SHA-1 para calcular los valores hash de suma de comprobación del archivo de código fuente que se van a almacenar en los archivos PDB. No obstante, csc.exe admite ahora un nuevo modificador "/checksumalgorithm" opcional para especificar el algoritmo SHA-256. Con el fin de cambiar al algoritmo SHA-256, use esta opción para compilar todos los archivos C# del directorio actual y coloque la información de depuración, incluido el listado de archivos de código fuente y los valores hash SHA-256, en un archivo PDB:

csc /checksumalgorithm:SHA256 /debug+ *.cs

Como parte del proyecto de código abierto .NET Compiler Platform ("Roslyn"), csc.exe está disponible en github.com/dotnet/roslyn. Encontrará soporte técnico para el selector de línea de comandos del algoritmo de suma de comprobación de depuración del archivo de código fuente SHA-256 en el archivo de bit.ly/2hd3rF3.

El archivo csc.exe de Visual Studio 2015 es compatible solo con los archivos ejecutables de Microsoft .NET Framework 4 o una versión superior. El otro compilador de Visual Studio 2015 .NET Framework usado para compilar archivos ejecutables de versiones anteriores a la 4 no admite el modificador /checksumalgorithm.

Los archivos PDB de código administrado almacenan datos de manera distinta a como lo hacen los archivos PDB de código nativo. En lugar de usar Debug Interface Access SDK, se pueden emplear las utilidades e interfaces de interoperabilidad de Microsoft DiaSymReader para leer archivos PDB de código administrado. Microsoft DiaSymReader está disponible como un paquete NuGet en bit.ly/2hrLZJb.   

El proyecto Roslyn incluye una utilidad denominada pdb2xml.exe, que encontrará con sus orígenes en bit.ly/2h2h596. Esta utilidad muestra el contenido de un archivo PDB en formato XML. Por ejemplo, el segmento de la Figura 2 muestra el listado de archivos de código fuente de C# que se han usado para compilar un ejecutable de código administrado.  

Visualización de un archivo PDB de código administrado en formato XML
Figura 2 Visualización de un archivo PDB de código administrado en formato XML

El GUID "8829d00f-11b8-4213-878b-770e8597ac16" del campo checkSumAlgorithmId indica que el valor del campo de suma de comprobación es un valor hash SHA-256 del archivo al que se hace referencia en el campo de nombre. Este GUID se define en la especificación de formato PDB portátil v0.1 (bit.ly/2hVYfEX).  

Compatibilidad del compilador para SHA-256

Los siguientes compiladores de Visual Studio 2015 admiten la opción de uso de hash SHA-256 en los archivos de código fuente:      

  • 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

Estos compiladores están disponibles en la ventana Comandos "Símbolo del sistema para desarrolladores de VS2015" de Visual Studio 2015.

Los compiladores que no están orientados a plataformas Windows no suelen usar archivos PDB para almacenar la información de depuración. Estos compiladores suelen producir dos archivos ejecutables simultáneamente durante una ejecución de compilación, uno sin reducir y otro reducido (bit.ly/2hIfvx6). La información de depuración completa se almacena en el ejecutable sin reducir, mientras que el ejecutable reducido no contiene ningún dato de depuración detallado. El archivo sin reducir puede resultar adecuado para almacenar los valores hash SHA-256 de los archivos de código fuente procesados para el ejecutable. Estamos planeando contactar con los creadores de estos otros compiladores para averiguar qué estrategias les funcionan mejor, de modo que el software basado en plataformas distintas de Windows que use estos compiladores, como Office para Android, Office para iOS u Office para Mac, pueda obtener ventajas similares a las del software basado en Windows.

Escenarios de casos de uso   

Ahora echemos un vistazo a algunos escenarios en los que pueden resultar de utilidad los valores hash de los archivos de código fuente.       

Recuperación de archivos de código fuente indexados de un archivo binario portable ejecutable (PE) El script Ssindex.cmd (bit.ly/2haI0D6) es una utilidad que compila la lista de archivos de código fuente (indexados) registrados en el control de código fuente, junto con la información de versión de cada archivo, para su almacenamiento en los archivos PDB. Si un archivo PDB tiene esta información de control de versiones, puede usar la utilidad srctool (bit.ly/2hs3WXY) con su opción -h para mostrar dicha información. Dado que los archivos de código fuente indexados también tienen sus valores hash insertados en el archivo PDB, dichos valores se pueden usar para comprobar la autenticidad de los archivos de código fuente durante su recuperación, tal como se explica en el artículo de KB 3195907 (bit.ly/2hs8q0u), "How To Retrieve Indexed Source Files of a Portable Executable Binary File" (Cómo recuperar archivos de código fuente indexados de un archivo binario portable ejecutable). Específicamente, si los valores hash no coinciden, es posible que algo saliera mal durante la generación del par de archivos PE/PDB en el sistema de control de código fuente. Esto podría garantizar una investigación adicional. Por otro lado, si los valores hash coinciden, es una indicación clara de que los archivos de código fuente indexados recuperados se usaron para compilar el par de archivos PE/PDB.        

Coincidencia de valores hash producidos por un analizador estático de archivos de código fuente Actualmente, es habitual usar herramientas automáticas para evaluar la calidad del software, según recomienda el Ciclo de vida de desarrollo de seguridad (SDL) de Microsoft para la fase de implementación (bit.ly/­29qEfVd). Concretamente, los analizadores estáticos de archivos de código fuente se usan para examinar archivos de código fuente de destino con la finalidad de evaluar muchos aspectos distintos de la calidad del software. Estos analizadores estáticos suelen producir los resultados en tiempo real correspondientes al examinar los archivos de código fuente de destino. Dado que un analizador estático examina archivos de código fuente individuales, presenta una excelente oportunidad para generar también un hash sólido (SHA-256) para cada uno de los archivos de código fuente examinados. De hecho, el formato de intercambio de resultados de análisis estático (Static Analysis Results Interchange Format, SARIF), propuesto en el proyecto de código abierto de bit.ly/2ibkbwz, proporciona ubicaciones específicas en los resultados de análisis estático de un analizador estático para producir los archivos de código fuente de destino examinados y sus valores hash SHA-256. 

Dado un archivo PE, supongamos que los siguientes elementos están disponibles:

  1. El listado de hash del archivo de código fuente compilado a partir del archivo PDB correspondiente, tal como lo generó un compilador.
  2. El listado de hash del archivo de código fuente examinado desde el resultado de análisis estático correspondiente, tal como lo generó un analizador estático.

En este escenario, puede revisar y comprobar si los dos listados de hash de archivo coinciden. Si es así, un analizador estático examinó los archivos de código fuente para evaluar su calidad y no es necesario que vuelva a examinarlos. Anteriormente, al carecer de listados de hash de archivo, podría haber tenido que volver a examinarlos para comprobar que la evaluación del analizador estático era correcta.  

Comprobación de integridad más rápida en el proceso de actualización de software o desarrollo de revisiones En situaciones en las que es necesario lanzar una actualización de software para corregir un problema de calidad detectado por un analizador estático de archivos de código fuente en un producto lanzado, el analizador estático debería notificar la ausencia de ese problema de calidad en los archivos de código fuente de la actualización pendiente. Como mínimo, esta notificación confirmaría la eficacia de la actualización para resolver el problema de calidad original. En otras palabras, validaría la finalidad prevista de la actualización de software. Si lo desea, usted o un revisor de seguridad pueden realizar el procedimiento siguiente para realizar una validación rápida: 

  1. Comprobar que el informe del analizador estático original identifica el problema de calidad en cuestión.
  2. Comprobar que el informe del analizador estático original incluye los valores hash de los archivos de código fuente que presentan el problema de calidad.
  3. Haga coincidir los valores hash de archivo encontrados en el informe del analizador estático original con los valores hash de los archivos de código fuente de la versión del producto lanzado.
  4. Obtenga el informe del analizador estático actualizado producido por un examen de los archivos de código fuente de la actualización con el mismo analizador estático.
  5. Compruebe que el problema de calidad detectado anteriormente no se encuentra en el informe del analizador estático de la actualización.
  6. Haga coincidir los valores hash de archivo del informe del analizador estático actualizado con los valores hash de los archivos de código fuente de la actualización.

Durante estos pasos de validación, no necesita tener acceso a los archivos de código fuente reales del producto lanzado original ni de la actualización. 

Construcción del archivo delta de código fuente entre dos versiones de software La revisión de un conjunto de código fuente completo puede requerir tiempo. No obstante, en algunos casos, cuando se producen cambios en el código fuente, no es necesario realizar una revisión completa del código fuente. Como resultado, es posible que solo se le pida el archivo delta de código fuente. Esta solicitud es realmente razonable, ya que no existe ningún fundamento racional para repetir el análisis de ninguna parte no modificada desde la última revisión.      

Anteriormente, sin los valores hash criptográficamente sólidos de los archivos de código fuente, sería difícil construir el subconjunto delta con precisión. Aunque hubiese tenido un subconjunto delta que ofrecer, los expertos en la materia habrían mostrado poca confianza en su capacidad de crear el subconjunto delta con precisión. No obstante, este ya no es el caso. Con los sólidos valores hash criptográficos de los archivos de código fuente, puede usar el procedimiento siguiente para crear el subconjunto delta:

  1. Obtenga el grupo, Grupo X por ejemplo, de valores hash de todos los archivos de código fuente de la versión del producto original.
  2. Realice una copia exacta del directorio de archivos, Dir A por ejemplo, que contiene la inscripción de código fuente de la versión del producto posterior, a partir de la cual se construirá el subconjunto delta.
  3. Prepare un destino de carpeta de archivos final, Dir B por ejemplo, destinado a contener solo el subconjunto de archivos delta.
  4. Revise todos los archivos de Dir A:
  5.         a. Si el valor hash de un archivo coincide con un valor hash de Grupo X, no haga nada y vaya al archivo siguiente.
  6.         b. Si el valor hash de un archivo no coincide con ningún valor de Grupo X, copie el archivo en Dir B antes de pasar al archivo siguiente.
  7. Compruebe que todos los archivos de Dir B tienen valores hash que coinciden con los valores hash correspondientes de los archivos de código fuente de la versión del producto posterior.  
  8. Convierta el contenido de Dir B en el subconjunto de archivos de código fuente delta de la versión del producto posterior.     

Generación del hash

Ahora echemos un vistazo al proceso para generar hash de archivo con los compiladores de Visual Studio. Para ello, usaré el ejemplo de creación de la aplicación "Hola mundo" de la documentación en línea de Visual Studio (bit.ly/2haPupF) para:

  1. Mostrar dónde se pueden encontrar, en el archivo PDB de salida, los valores hash de los archivos de código fuente compilados.
  2. Usar la herramienta certutil (bit.ly/2hIrnPR) para calcular los valores hash de archivo de código fuente de modo que coincidan con los del archivo PDB.       

Para empezar, creo un nuevo proyecto de aplicación de Win32HelloWorld en la carpeta Visual Studio 2015\Projects. En este proyecto de Win32HelloWorld, solo existe un archivo de código fuente de C++ (Win32HelloWorld.cpp), como se muestra en la Figura 3.

Win32HelloWorld.cpp
Figura 3 Win32HelloWorld.cpp

Como puede ver, Win32HelloWorld.cpp incluye la función principal, que muestra el texto "Hello".

Después de crear una compilación para mi proyecto de Win32HelloWorld, termino con los archivos W32HelloWorld.exe y W32HelloWorld.pdb en la carpeta Visual Studio 2015\Projects\W32HelloWorld\x64\Debug.

La herramienta cvdump, usada con su opción -sf en el archivo W32Hello­World.pdb, muestra el archivo Win32HelloWorld.cpp y su valor hash MD5 en la salida que se muestra en la Figura 4.

Salida cvdump que muestra el archivo Win32HelloWorld.cpp y su valor hash MD5
Figura 4 Salida cvdump que muestra el archivo Win32HelloWorld.cpp y su valor hash MD5

El valor hash es MD5 porque MD5 es el algoritmo predeterminado para el compilador de Visual Studio 2015, cl.exe. Para cambiar el algoritmo de hash de archivo de código fuente a SHA-256, debo proporcionar la opción /ZH:SHA_256 a cl.exe. Para hacerlo, agrego "/ZH:SHA_256" en el cuadro Opciones adicionales de Páginas de propiedades del proyecto de Win32HelloWorld, como se muestra en la Figura 5.

Cambio del algoritmo de hash del archivo de código fuente a SHA-256
Figura 5 Cambio del algoritmo de hash del archivo de código fuente a SHA-256

Después de la recompilación en Visual Studio, tengo un nuevo par de archivos PE/PDB, W32HelloWorld.exe y W32HelloWorld.pdb, en la carpeta Visual Studio 2015\Projects\W32HelloWorld\x64\Debug. Ahora, mediante la herramienta cvdump y su opción -sf en el nuevo archivo W32HelloWorld.pdb, se muestra el archivo Win32HelloWorld.cpp y su valor hash SHA-256 en la salida, como se muestra en la Figura 6.

Cvdump que muestra el archivo Win32HelloWorld.cpp y su valor hash SHA-256
Figura 6 Cvdump que muestra el archivo Win32HelloWorld.cpp y su valor hash SHA-256

Ahora, puedo volver al archivo W32HelloWorld.cpp de la carpeta Visual Studio 2015\Projects\W32HelloWorld\W32HelloWorld para comprobar su valor hash SHA-256. Al usar la herramienta certutil con su verbo -hashfile en el archivo Win32HelloWorld.cpp para SHA-256, obtengo el valor hash SHA-256 que se muestra en la Figura 7.

Obtención del valor hash SHA-256 con Certutil
Figura 7 Obtención del valor hash SHA-256 con Certutil

Coincide claramente con el valor SHA-256 registrado en el archivo W32Hello­World.pdb. Esto indica con claridad que el archivo Win32HelloWorld.cpp se usó ciertamente para compilar la aplicación W32HelloWorld.exe, según lo previsto.

Para obtener más información sobre las herramientas públicas relacionadas para trabajar con pares de archivos PE/PDB de código nativo y código administrado, consulte el artículo de KB 3195907 "How To Retrieve Indexed Source Files of a Portable Executable Binary File" (Cómo recuperar archivos de código fuente indexados de un archivo binario portable ejecutable) en bit.ly/2hs8q0u.

Resumen

Espero que este artículo le haya presentado algunas ventajas potenciales de una vinculación más fuerte entre los archivos de código fuente y el archivo PE que se compila con ellos. Para crear una vinculación más fuerte, haga que el compilador aplique algoritmos hash a los archivos de código fuente durante la compilación con el algoritmo hash más sólido disponible: SHA-256. Los valores hash reales de los archivos de código fuente generados por el compilador se convierten literalmente en los identificadores únicos de los archivos de código fuente que se usan para compilar un ejecutable.

Cuando comprenda el valor de estos identificadores únicos, podrá usarlos en distintos esquemas de ciclo de vida de desarrollo de software para el seguimiento, el procesamiento y el control de archivos de código fuente con una vinculación fuerte a los archivos ejecutables específicos, lo que aumenta la confianza del usuario final en los archivos ejecutables.


Mike Lai *acaba de cumplir su vigésimo aniversario como empleado de Microsoft. Agradece a Microsoft las múltiples oportunidades de contribuir en los aspectos de funcionalidad e ingeniería de muchos de sus productos. Quiere agradecer a la administración actual de Trustworthy Computing su paciencia al permitir la maduración de sus ideas y su incorporación paulatina en los productos lanzados, así como su apoyo para participar en organizaciones de normas de seguridad de tecnologías de la información y la comunicación.  *

Gracias a los siguientes expertos técnicos de Microsoft por revisar este artículo: Scott Field, Mike Grimm, Sue Hotelling, Ariel Netz, Richard Ward y Roy Williams