O hack do compilador de Ken Thompson ainda é uma ameaça?

156

Ken Thompson Hack (1984)

Ken Thompson delineou um método para corromper um binário do compilador (e outro software compilado, como um script de login em um sistema * nix) em 1984. Fiquei curioso para saber se a compilação moderna solucionou ou não essa falha de segurança.

Pequena descrição:

Reescreva o código do compilador para conter 2 falhas:

  • Ao compilar seu próprio binário, o compilador deve compilar essas falhas
  • Ao compilar algum outro código pré-selecionado (função de login), ele deve compilar algum backdoor arbitrário

Portanto, o compilador funciona normalmente - quando compila um script de login ou similar, pode criar um backdoor de segurança e, quando compila versões mais recentes de si mesmo no futuro, retém as falhas anteriores - e as falhas só existirão no compilador binário, por isso são extremamente difíceis de detectar.

Questões:

Não encontrei respostas para essas perguntas na web:

  • Como isso se relaciona com a compilação just-in-time?
  • Funções como o programa que gerencia logins em um sistema * nix são compiladas quando executadas?
  • Isso ainda é uma ameaça válida ou houve desenvolvimentos na segurança da compilação desde 1984 que impedem que isso seja um problema significativo?
  • Isso afeta todos os idiomas?

Por que eu quero saber?

Eu me deparei com isso enquanto fazia alguns trabalhos de casa, e parecia interessante, mas não tenho o histórico necessário para entender de maneira concreta se esse é um problema atual ou um problema resolvido.

Material de referência

svick
fonte
6
A estratégia Diverse Double Compiling é uma maneira razoavelmente confiável de detectar a presença de um compilador rigido RoTT.
dmckee
3
Imagino que a NSA tenha trabalhado muito nesse tipo de ataque.
Paul M

Respostas:

110

Esse truque deve ser entendido no contexto. Foi publicado em uma época e em uma cultura em que o Unix rodando em todos os tipos de hardware diferente era o sistema dominante.

O que tornou o ataque tão assustador foi o fato de o compilador C ser o software central desses sistemas. Quase tudo no sistema passou pelo compilador quando foi instalado pela primeira vez (distribuições binárias eram raras devido ao hardware heterogêneo). Todo mundo compilava coisas o tempo todo. As pessoas inspecionavam regularmente o código-fonte (eles geralmente precisavam fazer ajustes para compilar), portanto, fazer o compilador injetar backdoors parecia um tipo de cenário de "crime perfeito", onde você não podia ser pego.

Atualmente, o hardware é muito mais compatível e, portanto, os compiladores têm um papel muito menor na operação diária de um sistema. Um compilador comprometido não é mais o cenário mais assustador - rootkits e um BIOS comprometido são ainda mais difíceis de detectar e se livrar.

Michael Borgwardt
fonte
27
Ou, já que a maioria das pessoas não compilar qualquer coisa de fonte (por exemplo, no Windows) o trojan média será suficiente :) (Eu estou concordando que um compilador comprometido é a maneira exagero)
Andres F.
16
@ArjunShankar: Um compilador binário somente proprietário não-livre não precisa e não pode ter esse backdoor. Esse backdoor se aplica somente a compiladores que você mesmo compila a partir do código-fonte.
Ruakh
12
Exceto pela área de trabalho, o Unix e todas as suas variantes ainda são o sistema operacional dominante.
26713 Rob
7
@ruakh: talvez eu não entenda sua ênfase em 'isso', mas eu discordo. Se esse backdoor foi introduzido na empresa que possui o compilador proprietário não-livre e o usa para compilar novas versões do mesmo compilador, esse backdoor teria um impacto muito pior do que no cenário original. Você precisará apenas de um vetor de ataque para infectar todos.
precisa saber é
8
Imagine que alguém comprometa um servidor de compilação do ubuntu e substitua o compilador sem alterar nenhuma fonte. Pode demorar um pouco para que isso seja descoberto, e nesse momento as imagens do ubuntu seriam enviadas para as pessoas com o compilador comprometido incorporado a elas (junto com assemblies de login comprometidos ou o que você tem). Eu acho que isso ainda é uma preocupação perfeitamente válida.
Jimmy Hoffa
74

O objetivo desse discurso não era destacar uma vulnerabilidade que precisa ser tratada, ou mesmo propor uma vulnerabilidade teórica da qual precisamos estar cientes.

O objetivo era que, quando se trata de segurança, gostaríamos de não precisar confiar em ninguém, mas infelizmente isso é impossível. Você sempre tem que confiar em alguém (daí o título: "Reflexões sobre confiar em confiança")


Mesmo se você for do tipo paranóico que criptografa o disco rígido do seu desktop e se recusa a executar qualquer software que não tenha sido compilado, ainda precisa confiar no seu sistema operacional. E mesmo se você mesmo compilar o sistema operacional, ainda precisará confiar no compilador que usou. E mesmo se você compilar seu próprio compilador, ainda precisará confiar nesse compilador! E isso nem menciona os fabricantes de hardware!

Você simplesmente não pode fugir de confiar em ninguém . Esse é o ponto que ele estava tentando entender.

BlueRaja - Danny Pflughoeft
fonte
2
Se alguém tiver um compilador de código aberto cujo comportamento não dependa de nenhum comportamento definido ou não especificado pela implementação, compile-o usando uma variedade de compiladores desenvolvidos de forma independente (confiáveis ​​ou não) e compile um programa usando todas as diferentes versões compiladas do aquele de código aberto, todo compilador deve produzir exatamente a mesma saída. Se o fizerem, isso sugeriria que a única maneira de um cavalo de Troia estar em um seria se fosse idêntico em todos. Isso parece bastante improvável. Um que me irrita com muito do .net, porém, ...
supercat
9
@ supercat: Você parece estar perdendo o objetivo. Você está dizendo que o truque apresentado por Ken Thompson pode ser contornado. Estou dizendo que o truque específico que ele escolheu não importa; foi apenas um exemplo, para demonstrar seu argumento mais amplo de que você sempre deve confiar em alguém . É por isso que essa pergunta é um tanto sem sentido - ela perde completamente a floresta pelas árvores.
precisa saber é o seguinte
9
@ supercat: É altamente improvável que diferentes compiladores produzam o mesmo bytecode para qualquer programa não trivial devido a diferentes decisões de projeto, otimizações etc. Isso levanta a questão - como você saberia que os binários são idênticos?
Ankit Soni
1
@AnkitSoni: Minha resposta entra em mais detalhes. A alimentação de um compilador / vinculador de código-fonte aberto escrito adequadamente através de diferentes compiladores deve gerar diferentes executáveis ​​que se comportarão de forma idêntica . Se os executáveis ​​realmente se comportarem de forma idêntica, eles produzirão a mesma saída se o código do compilador / vinculador de código aberto for passado por eles. Para comparar os arquivos, é possível copiá-los em um disquete e usar um computador antigo para compará-los.
Supercat
2
Algumas dessas conversas não significariam apenas que, para as coisas que você testou, os binários / hardware se comportaram conforme o esperado? Ainda pode haver algo que você não testou e não tem conhecimento.
Bart Silverstrim
53

Não

O ataque, como descrito originalmente, nunca foi uma ameaça. Embora um compilador possa teoricamente fazer isso, na verdade, realizar o ataque exigiria a programação do compilador para

  • Reconheça quando o código-fonte que está sendo compilado é de um compilador e
  • Descubra como modificar o código-fonte arbitrário para inserir o hack nele.

Isso implica descobrir como o compilador funciona a partir do seu código-fonte, para poder modificá-lo sem interrupção.

Por exemplo, imagine que o formato de vinculação armazene os comprimentos dos dados ou o deslocamento do código de máquina compilado em algum lugar do executável. O compilador precisaria descobrir por si mesmo quais deles precisam ser atualizados e onde, ao inserir a carga útil de exploração. As versões subseqüentes do compilador (versão inócua) podem alterar arbitrariamente esse formato, portanto o código de exploração precisaria efetivamente entender esses conceitos.

Trata-se de programação autodirecionada de alto nível, um difícil problema de IA (a última vez que verifiquei, o estado da arte estava gerando código praticamente determinado por seus tipos). Veja: poucos humanos conseguem fazer isso; você precisaria aprender a linguagem de programação e entender primeiro a base de código.

Mesmo que o problema da IA ​​seja resolvido, as pessoas notariam se a compilação de seu minúsculo compilador resultasse em um binário com uma enorme biblioteca de IA vinculada a ele.

Ataque análogo: confiança de autoinicialização

No entanto, uma generalização do ataque é relevante. A questão básica é que sua cadeia de confiança precisa começar em algum lugar e, em muitos domínios, sua origem pode subverter toda a cadeia de uma maneira difícil de detectar.

Um exemplo que poderia ser facilmente realizado na vida real

Seu sistema operacional, por exemplo, o Ubuntu Linux, garante a segurança (integridade) das atualizações verificando os pacotes de atualização baixados na chave de assinatura do repositório (usando criptografia de chave pública). Mas isso garante apenas a autenticidade das atualizações se você puder provar que a chave de assinatura pertence a uma fonte legítima.

Onde você conseguiu a chave de assinatura? Quando você baixou a distribuição do sistema operacional pela primeira vez.

Você precisa confiar que a fonte de sua cadeia de confiança, essa chave de assinatura, não é má.

Qualquer um que possa MITM a conexão à Internet entre você e o servidor de download do Ubuntu - este pode ser o seu ISP, um governo que controla o acesso à Internet (por exemplo, China) ou o provedor de hospedagem do Ubuntu - poderia ter invadido esse processo:

  • Detecte que você está baixando a imagem do CD do Ubuntu. Isso é simples: veja se a solicitação está indo para qualquer um dos espelhos do Ubuntu (listados publicamente) e solicita o nome do arquivo da imagem ISO.
  • Atenda a solicitação em seu próprio servidor, fornecendo uma imagem de CD contendo a chave pública do atacante e o local do repositório, em vez da do Ubuntu.

A partir de agora, você receberá suas atualizações com segurança do servidor do invasor. As atualizações são executadas como raiz, para que o invasor tenha controle total.

Você pode impedir o ataque, certificando-se de que o original seja autêntico. Mas isso requer que você valide a imagem do CD baixada usando um hash ( poucas pessoas realmente fazem isso ) - e o próprio hash deve ser baixado com segurança, por exemplo, por HTTPS. E se o invasor puder adicionar um certificado ao seu computador (comum em um ambiente corporativo) ou controlar uma autoridade de certificação (por exemplo, China), mesmo o HTTPS não fornecerá proteção.

Caracol mecânico
fonte
47
Isto é falso. O compilador só precisa determinar quando está compilando um arquivo de origem muito específico a partir de seu próprio código-fonte com conteúdo muito específico, não quando está compilando qualquer compilador, seja qual for !!!
Kaz
14
@Kaz - Em algum momento, as modificações anteriores no compilador ou no programa de login podem chegar ao ponto em que anulam o compilador-reconhecedor / logon-reconhecedor do backdoor, e as iterações subsequentes perdem o backdoor. Isso é análogo a uma mutação biológica aleatória que concede imunidade a certas doenças.
Russell Borogove
12
A primeira metade da sua resposta tem o problema que Kaz descreve, mas a segunda metade é tão boa que eu estou marcando o marcador de qualquer maneira!
Ruakh
7
Um compilador maligno que apenas reconhece sua própria fonte é fácil de construir, mas na prática é relativamente inútil - poucas pessoas que já possuem um binário desse compilador o usariam para recriar o referido binário. Para que o ataque seja bem-sucedido por um período mais longo, o compilador precisaria de mais inteligência, para corrigir novas versões de sua própria fonte, encontrando os problemas descritos na snswer.
precisa saber é o seguinte
5
Um reconhecedor para um compilador específico pode ser bastante geral e improvável de se intrometer na nova versão. Tomemos, por exemplo, o gcc - muitas linhas de código no gcc são muito antigas e não mudaram muito. Coisas simples como o nome quase nunca mudam. Antes que o reconhecimento dê errado, é provável que o código injetado o faria. E, na realidade, esses dois problemas são amplamente teóricos - na prática, um autor de malware não teria problemas para acompanhar o ritmo (lento) do desenvolvimento do compilador.
Eamon Nerbonne
25

Primeiro, meu artigo favorito sobre esse hack é chamado Strange Loops .

Esse hack em particular certamente (*) poderia ser feito hoje em qualquer um dos principais projetos de SO de código aberto, particularmente Linux, * BSD e similares. Eu esperaria que funcionasse quase de forma idêntica. Por exemplo, você baixa uma cópia do FreeBSD que possui um compilador explorado para modificar o openssh. A partir de então, toda vez que você atualizar o openssh ou o compilador por fonte, continuará o problema. Supondo que o invasor tenha explorado o sistema usado para empacotar o FreeBSD em primeiro lugar (provavelmente, uma vez que a própria imagem está corrompida ou o atacante é de fato o empacotador), toda vez que o sistema reconstruir os binários do FreeBSD, ele reinicia o problema. Existem várias maneiras de esse ataque falhar, mas não são fundamentalmente diferentes de como o ataque de Ken poderia ter falhado (**). O mundo realmente não mudou muito.

É claro que ataques semelhantes poderiam ser injetados com facilidade (ou mais facilmente) por seus proprietários em sistemas como Java, SDK do iOS, Windows ou qualquer outro sistema. Certos tipos de falhas de segurança podem até ser projetados no hardware (particularmente enfraquecendo a geração de números aleatórios).

(*) Mas com "certamente" quero dizer "em princípio". Você deveria esperar que esse tipo de buraco exista em algum sistema específico? Não. Eu consideraria isso improvável por várias razões práticas. Com o tempo, à medida que o código muda e muda, a probabilidade de esse tipo de hack causar erros estranhos aumenta. E isso aumenta a probabilidade de ser descoberto. Backdoors menos engenhosos exigiriam conspirações para manter. É claro que sabemos que as backdoors de "interceptação legal" foram instaladas em vários sistemas de telecomunicações e redes; portanto, em muitos casos, esse tipo de hack elaborado é desnecessário. O hack é instalado abertamente.

Então sempre, defesa em profundidade.

(**) Supondo que o ataque de Ken já existisse. Ele acabou de discutir como isso poderia ser feito. Ele não disse que realmente fez isso, tanto quanto eu sei.

Rob Napier
fonte
Em relação à sua segunda nota de rodapé, Ken disse "construa e não distribua".
8bittree
15

Isso afeta todos os idiomas?

Esse ataque afeta principalmente idiomas que são auto-hospedados. É nesses idiomas que o compilador é escrito no próprio idioma. C, Squeak Smalltalk e o interpretador PyPy Python seriam afetados por isso. Perl, JavaScript e o interpretador CPython Python não.

Como isso se relaciona com a compilação just-in-time?

Não muito. É a natureza auto-hospedada do compilador que permite que o hack seja oculto. Não conheço nenhum compilador JIT auto-hospedado. (Talvez LLVM?)

Funções como o programa que gerencia logins em um sistema * nix são compiladas quando executadas?

Normalmente não. Mas a questão não é quando é compilado, mas por qual compilador . Se o programa de login for compilado por um compilador contaminado, ele será contaminado. Se for compilado por um compilador limpo, estará limpo.

Isso ainda é uma ameaça válida ou houve desenvolvimentos na segurança da compilação desde 1984 que impedem que isso seja um problema significativo?

Ainda é uma ameaça teórica, mas não é muito provável.

Uma coisa que você pode fazer para atenuá-lo é usar vários compiladores. Por exemplo, um compilador LLVM que é ele próprio compilado pelo GCC não passa pela porta dos fundos. Da mesma forma, um GCC compilado pelo LLVM não passará pela porta dos fundos. Portanto, se você estiver preocupado com esse tipo de ataque, poderá compilar seu compilador com outra raça de compiladores. Isso significa que o hacker do mal (no fornecedor do seu sistema operacional?) Terá que manchar os dois compiladores para se reconhecerem; Um problema muito mais difícil.

Sean McMillan
fonte
Seu último parágrafo não é, a rigor, verdadeiro. Em teoria, o código pode detectar o compilador que está sendo compilado e gerar a porta traseira adequadamente. Naturalmente, isso é impraticável no mundo real, mas não há nada que o impeça inerentemente. Mas, então, a idéia original não era sobre ameaças práticas reais, mas uma lição de confiança.
Steven Burnap
Ponto justo. Afinal, o hack carrega um backdoor para login e um mod para o compilador, para que ele também possa carregar um mod para outro compilador. Mas isso se torna cada vez mais improvável.
precisa saber é o seguinte
A compilação just in time pode ser um prazer. Se algum código tiver alguma vulnerabilidade apenas quando uma parte específica é compilada pelo JIT, ela pode passar despercebida. (apenas thoery pura)
GameDeveloper
12

Há uma chance teórica para isso acontecer. Existe, no entanto, uma maneira de verificar se um compilador específico (com código fonte disponível) foi comprometido por meio da compilação dupla diversa de David A. Wheeler .

Basicamente, use o compilador suspeito e outro compilador desenvolvido de forma independente para compilar a origem do compilador suspeito. Isto dá-lhe SC sc e SC T . Agora, compile a fonte suspeita usando esses dois binários. Se os binários resultantes forem idênticos (com exceção de várias coisas que podem variar legitimamente, como carimbos de data / hora variados), o compilador suspeito não estava abusando da confiança.

Vatine
fonte
Ou isso ou o compilador confiável não é tão confiável quanto o usuário pensava. Porém, para duas implementações independentes de uma linguagem, a probabilidade de elas conterem o mesmo backdoor é insignificante.
Damian Yerrick 10/09
Ou a ferramenta de comparação que você está usando para compará-los foi comprometida também;)
iCodeSometime
@kennycoc No entanto, escrever uma ferramenta de comparação "estes dois arquivos são idênticos" não é, considerando tudo, tão difícil (como em, dada uma referência syscall, deve ser possível em 2-16 horas no código da máquina binária).
Vatine 30/08/19
3

Como um ataque específico, é a maior ameaça que já foi, o que praticamente não é uma ameaça.

Como isso se relaciona com a compilação just-in-time?

Não sei o que quer dizer com isso. Um JITter é imune a isso? Não. É mais vulnerável? Na verdade não. Como desenvolvedor, SEU aplicativo é mais vulnerável, simplesmente porque você não pode validar que não foi feito. Observe que seu aplicativo ainda não desenvolvido é basicamente imune a essa e a todas as variações práticas, você só precisa se preocupar com um compilador mais recente que o seu código.

Funções como o programa que gerencia logins em um sistema * nix são compiladas quando executadas?

Isso não é realmente relevante.

Isso ainda é uma ameaça válida ou houve desenvolvimentos na segurança da compilação desde 1984 que impedem que isso seja um problema significativo?

Não existe uma segurança real de compilação e não pode ser. Esse foi realmente o ponto da conversa dele, que em algum momento você precisa confiar em alguém.

Isso afeta todos os idiomas?

Sim. Fundamentalmente, em algum momento ou outro, suas instruções devem ser transformadas em algo que o computador exija, e essa tradução pode ser feita incorretamente.

jmoreno
fonte
-2

David Wheeler tem um bom artigo: http://www.dwheeler.com/trusting-trust/

Eu, estou mais preocupado com ataques de hardware. Acho que precisamos de uma cadeia de ferramentas de design totalmente VLSI com código-fonte FLOSS, que possamos modificar e compilar a nós mesmos, que nos permita construir um microprocessador que não possui backdoors inseridos pelas ferramentas. As ferramentas também devem nos permitir entender a finalidade de qualquer transistor no chip. Em seguida, poderíamos abrir uma amostra dos chips prontos e inspecioná-los com um microscópio, certificando-se de que eles tivessem o mesmo circuito que as ferramentas disseram que deveriam ter.

Paulo
fonte
3
-1, a maior parte da sua resposta falha ao abordar a questão.
-3

Os sistemas nos quais os usuários finais têm acesso ao código-fonte são aqueles para os quais você precisaria ocultar esse tipo de ataque. Esses seriam sistemas de código aberto no mundo de hoje. O problema é que, embora exista uma dependência de um único compilador para todos os sistemas Linux, o ataque teria que chegar aos servidores de compilação para todas as principais distribuições Linux. Como esses arquivos não baixam os binários do compilador diretamente para cada versão do compilador, a origem do ataque deveria estar em seus servidores de compilação em pelo menos uma versão anterior do compilador. Isso ou a primeira versão do compilador que eles baixaram como um binário teria que ter sido comprometida.

Dale Gulledge
fonte
2
Sua resposta arranha a superfície da pergunta, mas realmente não aborda o que está sendo solicitado.
-4

Se alguém possui código-fonte para um sistema de compilação / compilação cuja saída não deve depender de nada além do conteúdo dos arquivos de origem fornecidos, e se possui vários outros compiladores e sabe que nem todos eles contêm o mesmo hack do compilador, pode-se certifique-se de obter um executável que não dependa de nada além do código-fonte.

Suponha que se tenha um código-fonte para um pacote de compilador / vinculador (digamos, o Groucho Suite) escrito de tal maneira que sua saída não dependa de comportamentos não especificados, nem de nada além do conteúdo dos arquivos de origem de entrada, e um compile / vincula esse código a uma variedade de pacotes de compiladores / vinculadores produzidos independentemente (por exemplo, o Harpo Suite, o Chico suite e o Zeppo Suite), produzindo um conjunto diferente de execuções para cada um (chame-os de G-Harpo, G-Chico e G-Zeppo). Não seria inesperado para esses executáveis ​​conter diferentes seqüências de instruções, mas eles devem ser funcionalmente idênticos. Provar que eles são funcionalmente idênticos em todos os casos, no entanto, provavelmente seria um problema intratável.

Felizmente, essa prova não será necessária se alguém usar apenas os executáveis ​​resultantes para uma única finalidade: compilar o pacote Groucho novamente. Se você compilar o conjunto Groucho usando G-Harpo (produzindo GG-Harpo), G-Chico (GG-Chico) e G-Zeppo (GG-Zeppo), todos os três arquivos resultantes, GG-Harpo, GG-Chico , e GG-Zeppo, todos os bytes por byte devem ser idênticos. Se os arquivos corresponderem, isso implicaria que qualquer "vírus de compilador" existente em qualquer um deles devesse existir de forma idêntica em todos eles (como os três arquivos são idênticos em bytes por byte, não há como seus comportamentos diferirem em nenhum). maneira).

Dependendo da idade e linhagem dos outros compiladores, pode ser possível garantir que esse vírus não exista plausivelmente neles. Por exemplo, se alguém usa um Macintosh antigo para alimentar um compilador que foi escrito do zero em 2007 por uma versão do MPW que foi escrita na década de 80, os compiladores da década de 80 não saberiam onde inserir um vírus no compilador de 2007. Pode ser possível para um compilador hoje fazer análises de código suficientes para descobrir isso, mas o nível de computação necessário para essa análise excederia em muito o nível de computação necessário para compilar o código e não poderia passar despercebido. em um mercado em que a velocidade de compilação foi um grande ponto de venda.

Eu diria que, se alguém estiver trabalhando com ferramentas de compilação em que os bytes em um arquivo executável a ser produzido não devam depender de maneira alguma de algo além do conteúdo dos arquivos de origem enviados, é possível obter uma imunidade razoavelmente boa de um Thompson vírus de estilo. Infelizmente, por alguma razão, o não determinismo na compilação parece ser considerado normal em alguns ambientes. Reconheço que, em um sistema com várias CPUs, pode ser possível que um compilador execute mais rapidamente se for permitido que certos aspectos da geração de código variem dependendo de qual dos dois threads conclua um trabalho primeiro.

Por outro lado, não tenho certeza se vejo algum motivo para os compiladores / vinculadores não fornecerem um modo de "saída canônica", em que a saída depende apenas dos arquivos de origem e de uma "data de compilação" que possa ser substituída pelo usuário . Mesmo que a compilação de código nesse modo demore duas vezes mais que a compilação normal, eu sugeriria que haveria um valor considerável em poder recriar qualquer "versão de compilação", byte por byte, inteiramente a partir de materiais de origem, mesmo que isso significasse que as versões de lançamento levariam mais tempo que as "versões normais".

supercat
fonte
2
-1. Não vejo como sua resposta aborda os principais aspectos da pergunta.
@ GlenH7: Muitas ferramentas antigas de compilação produziam consistentemente saídas idênticas a bits quando recebidas entradas idênticas a bits [fora de coisas como TIME , que poderiam ser aprimoradas para relatar um tempo de compilação "oficial"]. Usando essas ferramentas, pode-se muito bem proteger contra vírus de compilador. O fato de algumas estruturas de desenvolvimento populares não fornecerem meios de compilar "deterministicamente" significa que as técnicas que poderiam ter se protegido contra vírus em ferramentas mais antigas não podem ser efetivamente usadas com as mais novas.
Supercat
Você já tentou isso? 1. Lidere sua tese. 2. Use parágrafos mais curtos. 3. Seja mais explícito sobre a diferença entre "funcionalmente idêntico" (o resultado do primeiro estágio) e "pouco idêntico" (o resultado do segundo), possivelmente com uma lista de todos os binários do compilador produzidos e seus relacionamentos entre si. 4. Cite o documento DDC de David A. Wheeler.
Damian Yerrick