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.
Respostas:
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.
fonte
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.
fonte
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
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:
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.
fonte
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.
fonte
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.
fonte
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.
fonte
Como um ataque específico, é a maior ameaça que já foi, o que praticamente não é uma ameaça.
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.
Isso não é realmente relevante.
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.
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.
fonte
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.
fonte
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.
fonte
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".
fonte