¿El truco del compilador de Ken Thompson sigue siendo una amenaza?


156

Ken Thompson Hack (1984)

Ken Thompson describió un método para corromper un compilador binario (y otro software compilado, como un script de inicio de sesión en un sistema * nix) en 1984. Tenía curiosidad por saber si la compilación moderna ha abordado esta falla de seguridad o no.

Breve descripción:

Vuelva a escribir el código del compilador para contener 2 fallas:

  • Al compilar su propio binario, el compilador debe compilar estos defectos
  • Al compilar algún otro código preseleccionado (función de inicio de sesión) debe compilar alguna puerta trasera arbitraria

Por lo tanto, el compilador funciona normalmente: cuando compila un script de inicio de sesión o similar, puede crear una puerta trasera de seguridad, y cuando compila versiones más nuevas de sí mismo en el futuro, conserva las fallas anteriores, y las fallas solo existirán en el compilador binario, por lo que son extremadamente difíciles de detectar.

Preguntas:

No pude encontrar ninguna respuesta a estas en la web:

  • ¿Cómo se relaciona esto con la compilación justo a tiempo?
  • ¿Se compilan funciones como el programa que maneja los inicios de sesión en un sistema * nix cuando se ejecutan?
  • ¿Sigue siendo una amenaza válida o ha habido avances en la seguridad de la compilación desde 1984 que evitan que esto sea un problema importante?
  • ¿Esto afecta a todos los idiomas?

¿Por qué quiero saberlo?

Me encontré con esto mientras hacía algunos deberes, y me pareció interesante, pero me falta el fondo para comprender de manera concreta si se trata de un problema actual o un problema resuelto.

Material de referencia


66
La estrategia Diverse Double Compiling es una forma razonablemente confiable de detectar la presencia de un compilador aparejado RoTT.
dmckee

3
Me imagino que la NSA ha puesto mucho trabajo en este tipo de ataque.
Paul M

Respuestas:


110

Este truco tiene que ser entendido en contexto. Fue publicado en un momento y en una cultura en la que Unix se ejecutaba en todo tipo de hardware diferente era el sistema dominante.

Lo que hizo que el ataque fuera tan aterrador fue que el compilador de C era la pieza central de software para estos sistemas. Casi todo en el sistema pasó por el compilador cuando se instaló por primera vez (las distribuciones binarias eran raras debido al hardware heterogéneo). Todos compilamos cosas todo el tiempo. La gente inspeccionaba regularmente el código fuente (a menudo tenían que hacer ajustes para que compilara), por lo que hacer que el compilador inyectara puertas traseras parecía ser una especie de escenario de "crimen perfecto" en el que no podía ser atrapado.

Hoy en día, el hardware es mucho más compatible y, por lo tanto, los compiladores tienen un papel mucho más pequeño en el funcionamiento diario de un sistema. Un compilador comprometido ya no es el escenario más aterrador: los rootkits y un BIOS comprometido son aún más difíciles de detectar y eliminar.


27
O, dado que la mayoría de las personas no compilan nada de la fuente (digamos, en Windows), su troyano promedio será suficiente :) (Estoy de acuerdo en que un compilador comprometido es demasiado exagerado)
Andres F.

16
@ArjunShankar: un compilador binario propietario no libre no necesita, y no puede tener, esta puerta trasera. Esta puerta trasera solo se aplica a los compiladores que usted mismo compila a partir del código fuente.
ruakh

12
A excepción del escritorio, Unix, y todas sus variantes, sigue siendo el sistema operativo dominante.
Rob

77
@ruakh: tal vez no entiendo tu énfasis en 'esto', pero no estoy de acuerdo. Si esta puerta trasera se ha introducido en la empresa que posee el compilador propietario no libre y usa este compilador para compilar nuevas versiones del mismo compilador, esta puerta trasera tendría un impacto mucho peor que en el escenario original. Solo necesitarás un vector de ataque para infectar a todos.
orithena

8
Imagine que alguien compromete un servidor de compilación ubuntu y reemplaza el compilador sin cambiar ninguna fuente. Esto podría tomar un poco de tiempo para que esto se descubra, y para ese momento las imágenes de ubuntu se enviarán a personas de todas partes con el compilador comprometido incorporado en ellas (junto con ensamblados de inicio de sesión comprometidos o lo que sea). Creo que esto sigue siendo una preocupación perfectamente válida.
Jimmy Hoffa

74

El propósito de ese discurso no era resaltar una vulnerabilidad que necesita ser abordada, o incluso proponer una vulnerabilidad teórica de la que debemos ser conscientes.

El propósito era que, cuando se trata de seguridad, nos gustaría no tener que confiar en nadie, pero desafortunadamente eso es imposible. Siempre debes confiar en alguien (de ahí el título: "Reflexiones sobre la confianza en la confianza")


Incluso si eres del tipo paranoico que encripta su disco duro de escritorio y se niega a ejecutar cualquier software que no hayas compilado, aún debes confiar en tu sistema operativo. E incluso si compila el sistema operativo usted mismo, aún necesita confiar en el compilador que utilizó. E incluso si compila su propio compilador, ¡ aún necesita confiar en ese compilador! ¡Y eso sin mencionar a los fabricantes de hardware!

Simplemente no puede salirse con la suya confiando en nadie . Ese era el punto que estaba tratando de cruzar.


2
Si uno tiene un compilador de código abierto cuyo comportamiento no depende de ningún comportamiento definido o no especificado por la implementación, lo compila usando una variedad de compiladores desarrollados independientemente (confiables o no), y luego compila un programa usando todas las diferentes versiones compiladas de ese de código abierto, cada compilador debe producir exactamente la misma salida. Si lo hacen, eso sugeriría que la única forma en que un troyano podría estar en uno sería si fuera idéntico en todos. Eso parecería bastante improbable. Sin embargo, una de mis molestias con gran parte de .net ...
supercat

99
@supercat: Parece que te estás perdiendo el punto. Estás diciendo que el truco que presentó Ken Thompson se puede solucionar. Estoy diciendo que el truco particular que eligió no importa; fue solo un ejemplo, para demostrar su punto más amplio de que siempre debes confiar en alguien . Es por eso que esta pregunta carece de sentido: se pierde completamente el bosque por los árboles.
BlueRaja - Danny Pflughoeft

99
@supercat: es muy poco probable que diferentes compiladores produzcan el mismo bytecode para cualquier programa no trivial debido a diferentes decisiones de diseño, optimizaciones, etc. Esto plantea la pregunta: ¿cómo podría saber si los binarios son idénticos?
Ankit Soni

1
@AnkitSoni: Mi respuesta entra en más detalles. Alimentar un compilador / enlazador de código abierto adecuadamente escrito a través de diferentes compiladores debería producir diferentes ejecutables que se comportarán de manera idéntica . Si los ejecutables se comportan de manera idéntica, producirán la misma salida si el código para el compilador / enlazador de código abierto se pasa a través de ellos. Para comparar los archivos, uno podría copiarlos en un disquete y usar una computadora antigua para compararlos.
supercat

2
¿No significaría que parte de esta conversación significa que, para las cosas que probó, los binarios / hardware se comportaron como se esperaba? Todavía podría haber algo en él que no haya probado y que desconozca.
Bart Silverstrim

53

No

El ataque, como se describió originalmente, nunca fue una amenaza. Si bien un compilador teóricamente podría hacer esto, en realidad llevar a cabo el ataque requeriría programar el compilador para

  • Reconocer cuándo el código fuente que se está compilando es de un compilador, y
  • Descubre cómo modificar el código fuente arbitrario para insertar el hack en él.

Esto implica descubrir cómo funciona el compilador a partir de su código fuente, para poder modificarlo sin romperse.

Por ejemplo, imagine que el formato de enlace almacena las longitudes de datos o el desplazamiento del código de máquina compilado en algún lugar del ejecutable. El compilador tendría que averiguar por sí mismo cuáles de estos deben actualizarse y dónde, al insertar la carga útil de explotación. Las versiones posteriores del compilador (versión inocuo) pueden cambiar arbitrariamente este formato, por lo que el código de explotación necesitaría comprender estos conceptos.

Esta es una programación autodirigida de alto nivel, un problema de IA difícil (la última vez que lo revisé, el estado de la técnica estaba generando código que está prácticamente determinado por sus tipos). Mira: pocos humanos pueden hacer esto; Tendría que aprender el lenguaje de programación y comprender primero el código base.

Incluso si se resuelve el problema de IA, las personas notarían si compilar su pequeño compilador da como resultado un binario con una enorme biblioteca de IA vinculada a él.

Ataque análogo: confianza de arranque

Sin embargo, una generalización del ataque es relevante. El problema básico es que su cadena de confianza tiene que comenzar en alguna parte, y en muchos dominios su origen podría subvertir toda la cadena de una manera difícil de detectar.

Un ejemplo que se podría sacar fácilmente en la vida real.

Su sistema operativo, por ejemplo, Ubuntu Linux, garantiza la seguridad (integridad) de las actualizaciones al comparar los paquetes de actualización descargados con la clave de firma del repositorio (utilizando criptografía de clave pública). Pero esto solo garantiza la autenticidad de las actualizaciones si puede probar que la clave de firma es propiedad de una fuente legítima.

¿De dónde sacaste la clave de firma? Cuando descargó por primera vez la distribución del sistema operativo.

Debe confiar en que la fuente de su cadena de confianza, esta clave de firma, no es mala.

Cualquiera que pueda MITM la conexión a Internet entre usted y el servidor de descarga de Ubuntu, este podría ser su ISP, un gobierno que controla el acceso a Internet (por ejemplo, China) o el proveedor de alojamiento de Ubuntu, podría haber secuestrado este proceso:

  • Detecta que estás descargando la imagen del CD de Ubuntu. Esto es simple: vea que la solicitud vaya a cualquiera de los espejos de Ubuntu (que figuran en la lista pública) y solicite el nombre de archivo de la imagen ISO.
  • Atienda la solicitud desde su propio servidor, proporcionándole una imagen de CD que contenga la clave pública del atacante y la ubicación del repositorio en lugar de la de Ubuntu.

A partir de entonces, obtendrá sus actualizaciones de forma segura desde el servidor del atacante. Las actualizaciones se ejecutan como root, por lo que el atacante tiene control total.

Puede evitar el ataque asegurándose de que el original sea auténtico. Pero esto requiere que valide la imagen de CD descargada utilizando un hash ( pocas personas realmente hacen esto ), y el hash debe descargarse de forma segura, por ejemplo, a través de HTTPS. Y si su atacante puede agregar un certificado en su computadora (común en un entorno corporativo) o controla una autoridad de certificación (por ejemplo, China), incluso HTTPS no proporciona protección.


47
Esto es falso ¡El compilador solo tiene que determinar cuándo está compilando un archivo fuente muy específico a partir de su propio código fuente con contenidos muy específicos, no cuándo está compilando ningún compilador!
Kaz

14
@Kaz: en algún momento, las modificaciones por encima del compilador o el programa de inicio de sesión podrían llegar al punto en que vencen al compilador-reconocedor / inicio de sesión-reconocimiento de la puerta trasera, y las iteraciones posteriores perderían la puerta trasera. Esto es análogo a una mutación biológica aleatoria que otorga inmunidad a ciertas enfermedades.
Russell Borogove

12
La primera mitad de su respuesta tiene el problema que describe Kaz, pero la segunda mitad es tan buena que estoy haciendo +1 de todos modos.
ruakh

77
Un compilador maligno que solo reconoce su propia fuente es fácil de construir, pero relativamente inútil en la práctica: pocas personas que ya tienen un binario de este compilador lo usarían para recrear dicho binario. Para que el ataque tenga éxito durante un período más largo, el compilador necesitaría más inteligencia, para parchear verdions más nuevos de su propia fuente, encontrando así los problemas descritos en el snswer.
user281377

55
Un reconocedor para un compilador específico podría ser bastante general, y es poco probable que se rompa frente a una nueva versión. Tomemos como ejemplo gcc: muchas líneas de código en gcc son muy antiguas y no han cambiado mucho. Cosas simples como el nombre casi nunca cambian. Antes de que el reconocimiento salga mal, es probable que el código inyectado lo haga. Y en realidad, ambos problemas son en gran medida teóricos: en la práctica, un autor de malware no tendría problemas para mantenerse al día con el ritmo (lento) del desarrollo del compilador.
Eamon Nerbonne

25

Primero, mi escrito favorito de este truco se llama Strange Loops .

Este truco en particular ciertamente podría (*) hacerse hoy en cualquiera de los principales proyectos de SO de código abierto, particularmente Linux, * BSD y similares. Esperaría que funcionara casi de manera idéntica. Por ejemplo, descarga una copia de FreeBSD que tiene un compilador explotado para modificar openssh. A partir de ese momento, cada vez que actualice openssh o el compilador por fuente, continuará con el problema. Suponiendo que el atacante ha explotado el sistema utilizado para empaquetar FreeBSD en primer lugar (probablemente, dado que la imagen en sí está dañada o el atacante es el empacador), cada vez que el sistema reconstruya los binarios de FreeBSD, reinyectará el problema. Hay muchas maneras de que este ataque falle, pero no son fundamentalmente diferentes de cómo el ataque de Ken podría haber fallado (**). El mundo realmente no ha cambiado tanto.

Por supuesto, sus propietarios podrían inyectar ataques similares (o más fácilmente) en sistemas como Java, el SDK de iOS, Windows o cualquier otro sistema. Ciertos tipos de fallas de seguridad incluso se pueden diseñar en el hardware (particularmente debilitando la generación de números aleatorios).

(*) Pero por "ciertamente" quiero decir "en principio". ¿Debería esperar que este tipo de agujero exista en algún sistema en particular? No. Lo consideraría bastante improbable por varias razones prácticas. Con el tiempo, a medida que el código cambia y cambia, aumenta la probabilidad de que este tipo de pirateo cause errores extraños. Y eso aumenta la probabilidad de que se descubra. Las puertas traseras menos ingeniosas requerirían conspiraciones para mantenerse. Por supuesto, sabemos con certeza que se han instalado puertas traseras de "intercepción legal" en varios sistemas de telecomunicaciones y redes, por lo que en muchos casos este tipo de intrusión es innecesario. El truco se instala abiertamente.

Así que siempre, defensa en profundidad.

(**) Suponiendo que el ataque de Ken haya existido realmente. Él solo discutió cómo podría hacerse. No dijo que realmente lo hizo hasta donde yo sé.


Con respecto a su segunda nota al pie, Ken dijo "construir y no distribuir".
8bittree

15

¿Esto afecta a todos los idiomas?

Este ataque afecta principalmente a los idiomas que se autohospedan. Es en los idiomas donde el compilador está escrito en el idioma mismo. C, Squeak Smalltalk y el intérprete PyPy Python se verían afectados por esto. Perl, JavaScript y el intérprete CPython Python no lo harían.

¿Cómo se relaciona esto con la compilación justo a tiempo?

No mucho. Es la naturaleza de alojamiento propio del compilador lo que permite ocultar el hack. No conozco ningún compilador JIT de alojamiento propio. (¿Quizás LLVM?)

¿Se compilan funciones como el programa que maneja los inicios de sesión en un sistema * nix cuando se ejecutan?

No Usualmente. Pero la pregunta no es cuándo se compila, sino por qué compilador . Si el programa de inicio de sesión es compilado por un compilador contaminado, será contaminado. Si es compilado por un compilador limpio, estará limpio.

¿Sigue siendo una amenaza válida o ha habido avances en la seguridad de la compilación desde 1984 que evitan que esto sea un problema importante?

Esto sigue siendo una amenaza teórica, pero no es muy probable.

Una cosa que podría hacer para mitigarlo es usar múltiples compiladores. Por ejemplo, un compilador LLVM que es, en sí, compilado por GCC no pasará por una puerta trasera. Del mismo modo, un GCC compilado por LLVM no pasará por una puerta trasera. Entonces, si le preocupa este tipo de ataque, entonces podría compilar su compilador con otra generación de compiladores. Eso significa que el hacker maligno (¿en el proveedor de su sistema operativo?) Tendrá que contaminar a ambos compiladores para reconocerse entre sí; Un problema mucho más difícil.


Su último párrafo no es, estrictamente hablando, verdadero. En teoría, el código podría detectar el compilador que se está compilando y generar la puerta trasera de manera adecuada. Por supuesto, esto no es práctico en el mundo real, pero no hay nada que lo impida inherentemente. Pero entonces, la idea original no era sobre amenazas prácticas reales sino más bien una lección de confianza.
Steven Burnap

Punto justo. Después de todo, el hack lleva una puerta trasera para iniciar sesión y un mod para el compilador, por lo que también puede llevar un mod para otro compilador. Pero se vuelve cada vez más improbable.
Sean McMillan

La compilación justo a tiempo podría ser un placer. Si algún código tiene alguna vulnerabilidad solo cuando una pieza en particular está compilada en JIT, puede pasar desapercibida. (simplemente puro juego)
GameDeveloper

12

Hay una posibilidad teórica de que esto suceda. Sin embargo, hay una forma de verificar si un compilador específico (con el código fuente disponible) se ha visto comprometido, a través de la doble compilación Diverse de David A. Wheeler .

Básicamente, use tanto el compilador sospechoso como otro compilador desarrollado independientemente para compilar la fuente del compilador sospechoso. Esto le da SC SC y SC T . Ahora, compile la fuente sospechosa utilizando ambos binarios. Si los binarios resultantes son idénticos (con excepción de una variedad de cosas que pueden variar legítimamente, como varias marcas de tiempo), el compilador sospechoso no estaba abusando de la confianza.


O eso o el compilador de confianza no es tan confiable como pensaba el usuario. Pero para dos implementaciones independientes de un lenguaje, la probabilidad de que contengan la misma puerta trasera es insignificante.
Damian Yerrick

O la herramienta de diferencias que está utilizando para compararlos también se vio comprometida;)
iCodeSometime

@kennycoc Sin embargo, escribir una herramienta de comparación "son estos dos archivos idénticos" no es, a fin de cuentas, tan difícil (como en, dada una referencia de syscall, debería ser factible en 2-16 horas en código de máquina binario).
Vatine

3

Como ataque específico, es una amenaza tan grande como siempre, que prácticamente no es una amenaza.

¿Cómo se relaciona esto con la compilación justo a tiempo?

No estoy seguro de lo que quieres decir con eso. ¿Un JITter es inmune a esto? No. ¿Es más vulnerable? Realmente no. Como desarrollador, SU aplicación es más vulnerable simplemente porque no puede validar que no se haya hecho. Tenga en cuenta que su aplicación aún no desarrollada es básicamente inmune a esta y a todas las variaciones prácticas, solo tiene que preocuparse por un compilador que sea más nuevo que su código.

¿Se compilan funciones como el programa que maneja los inicios de sesión en un sistema * nix cuando se ejecutan?

Eso no es realmente relevante.

¿Sigue siendo una amenaza válida o ha habido avances en la seguridad de la compilación desde 1984 que evitan que esto sea un problema importante?

No existe una seguridad real de compilación, y no puede existir. Ese fue realmente el punto de su charla, que en algún momento tienes que confiar en alguien.

¿Esto afecta a todos los idiomas?

Si. Fundamentalmente, en algún momento u otro, sus instrucciones tienen que convertirse en algo que la computadora ejecute, y esa traducción puede hacerse incorrectamente.


-2

David Wheeler tiene un buen artículo: http://www.dwheeler.com/trusting-trust/

Yo estoy más preocupado por los ataques de hardware. Creo que necesitamos una cadena de herramientas de diseño totalmente VLSI con código fuente FLOSS, que podamos modificar y compilar nosotros mismos, que nos permita construir un microprocesador que no tenga puertas traseras insertadas por las herramientas. Las herramientas también deberían permitirnos comprender el propósito de cualquier transistor en el chip. Luego podríamos abrir una muestra de los chips terminados e inspeccionarlos con un microscopio, asegurándonos de que tuvieran el mismo circuito que las herramientas dijeron que se suponía que debían tener.


3
-1, la mayor parte de su respuesta no responde a la pregunta.

-3

Los sistemas en los que los usuarios finales tienen acceso al código fuente son aquellos en los que tendrías que ocultar este tipo de ataque. Esos serían sistemas de código abierto en el mundo de hoy. El problema es que, aunque existe una dependencia de un único compilador para todos los sistemas Linux, el ataque tendría que llegar a los servidores de compilación para todas las principales distribuciones de Linux. Como esos no descargan los binarios del compilador directamente para cada versión del compilador, la fuente del ataque debería haber estado en sus servidores de compilación en al menos una versión anterior del compilador. O eso o la primera versión del compilador que descargaron como binario tendría que haber sido comprometida.


2
Su respuesta rasca en la superficie de la pregunta, pero en realidad no aborda lo que se le pregunta.

-4

Si uno tiene código fuente para un compilador / sistema de compilación cuya salida no debe depender de otra cosa que no sea el contenido de los archivos fuente suministrados, y si tiene varios otros compiladores y sabe que no todos contienen el mismo truco del compilador, uno puede asegúrese de obtener un archivo ejecutable que dependa únicamente del código fuente.

Supongamos que uno tiene un código fuente para un paquete compilador / enlazador (digamos Groucho Suite) escrito de tal manera que su salida no dependerá de comportamientos no especificados, ni de nada más que el contenido de los archivos fuente de entrada, y uno compila / vincula ese código en una variedad de compiladores / paquetes vinculadores producidos de forma independiente (por ejemplo, Harpo Suite, Chico suite y Zeppo Suite), produciendo un conjunto diferente de ejecutables para cada uno (llámelos G-Harpo, G-Chico y G-Zeppo). No sería inesperado que estos ejecutables contengan diferentes secuencias de instrucciones, pero deberían ser funcionalmente idénticos. Sin embargo, probar que son funcionalmente idénticos en todos los casos probablemente sería un problema insoluble.

Afortunadamente, tal prueba no será necesaria si uno solo usa los ejecutables resultantes para un solo propósito: compilar nuevamente la suite Groucho. Si uno compila la suite Groucho usando G-Harpo (produciendo GG-Harpo), G-Chico (GG-Chico) y G-Zeppo (GG-Zeppo), entonces los tres archivos resultantes, GG-Harpo, GG-Chico y GG-Zeppo, todos deberían ser idénticos byte por byte. Si los archivos coinciden, eso implicaría que cualquier "virus compilador" que exista en cualquiera de ellos debe existir de manera idéntica en todos ellos (dado que los tres archivos son idénticos byte por byte, no hay forma de que sus comportamientos puedan diferir de alguna manera camino).

Dependiendo de la edad y el linaje de los otros compiladores, puede ser posible asegurar que tal virus no pueda existir en ellos. Por ejemplo, si se usa un Macintosh antiguo para alimentar un compilador que se escribió desde cero en 2007 a través de una versión de MPW que se escribió en la década de 1980, los compiladores de la década de 1980 no sabrían dónde insertar un virus en el compilador de 2007. Es posible que un compilador de hoy haga un análisis de código lo suficientemente sofisticado como para descubrirlo, pero el nivel de cómputo requerido para dicho análisis excedería con creces el nivel de cómputo requerido para simplemente compilar el código, y no podría pasar desapercibido. en un mercado donde la velocidad de compilación era un importante punto de venta.

Yo diría que si uno está trabajando con herramientas de compilación donde los bytes en un archivo ejecutable a ser producido no deberían depender de otra manera que no sea el contenido de los archivos fuente enviados, es posible lograr una inmunidad razonablemente buena de un Thompson virus de estilo. Desafortunadamente, por alguna razón, el no determinismo en la compilación parece considerarse normal en algunos entornos. Reconozco que en un sistema con múltiples CPU puede ser posible que un compilador se ejecute más rápido si se permite que ciertos aspectos de la generación de código varíen dependiendo de cuál de los dos subprocesos termine primero un trabajo.

Por otro lado, no estoy seguro de ver alguna razón por la cual los compiladores / enlazadores no deberían proporcionar un modo de "salida canónica" donde la salida depende solo de los archivos de origen y una "fecha de compilación" que el usuario puede anular. . Incluso si la compilación de código en tal modo tomara el doble de tiempo que la compilación normal, sugeriría que sería de gran valor poder recrear cualquier "compilación de lanzamiento", byte por byte, completamente a partir de materiales fuente, incluso si eso significara que las versiones de lanzamiento tardarían más que las "versiones normales".


2
-1. No veo cómo su respuesta aborda los aspectos centrales de la pregunta.

@ GlenH7: Muchas herramientas de compilación más antiguas producirían resultados idénticos a los bits cuando se les da una entrada idéntica a los bits [fuera de cosas como TIME , que podrían modificarse para informar un tiempo de compilación "oficial". Usando tales herramientas, uno podría muy bien protegerse contra los virus compiladores. El hecho de que algunos marcos de desarrollo populares no proporcionen una forma de compilar código "determinísticamente" significa que las técnicas que podrían haber protegido contra virus en herramientas más antiguas no se pueden usar de manera efectiva con las más nuevas.
supercat

¿Has probado esto? 1. Lidera con tu tesis. 2. Use párrafos más cortos. 3. Sea más explícito acerca de la diferencia entre "funcionalmente idéntico" (el resultado de la primera etapa) y "bit idéntico" (el resultado de la segunda), posiblemente con una lista de todos los binarios del compilador producidos y sus relaciones entre sí. 4. Citar el artículo de David A. Wheeler DDC.
Damian Yerrick
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.