Proteção de software em ambientes hostis: além da criptografia e da esteganografia

Por Raphael Machado.

Desde os tempos antigos, o homem percebeu a conveniência de se possuir técnicas para proteção à informação. Entre os egípcios, a codificação de mensagens de modo a torná-las incompreensíveis a cidadãos comuns conferia a estas mensagens um ar “sagrado”. Tal característica tornava técnicas de codificação ideais para serem aplicadas a hieróglifos presentes em tumbas de faraós e outros egípcios elevadas castas. De fato, as inscrições na tumba de Khnumhotep II são consideradas por muitos o primeiro exemplo de criptografia, pelo fato de apresentarem substituições de símbolos que, ao mesmo tempo, dificultam a leitura e tornam a escrita (supostamente) mais estética. Já o grego Heródoto relata o episódio em que Histieus raspa a cabeça de seu mais confiável escravo e tatua uma mensagem destinada a Aristágoras convocando um levante contra os persas. Uma vez crescido o cabelo do escravo, este pôde ser enviado ao seu destino e a mensagem, transmitida despercebidamente.

Foto: Logo do curso Fundamentos de Criptografia por Clavis Segurança da Informação, CC-BY-NC-ND.

  1. Introdução
  2. Modelos de Ameaças
  3. Técnicas para proteção de software
    1. Ferramentas Criptográficas
    2. Ofuscação
    3. Diversidade de software
    4. Marca d’água
    5. Resistência à corrupção
  4. Exemplos de proteção de software
    1. Proteção de Algoritmo Secreto
    2. Teste de integridade de software crítico
    3. Garantindo a origem do software
    4. Proteção anti-pirataria
    5. Criptografia de caixa-branca
  5. Conclusão

Introdução

Desde os tempos antigos, o homem percebeu a conveniência de se possuir técnicas para proteção à informação. Entre os egípcios, a codificação de mensagens de modo a torná-las incompreensíveis a cidadãos comuns conferia a estas mensagens um ar “sagrado”. Tal característica tornava técnicas de codificação ideais para serem aplicadas a hieróglifos presentes em tumbas de faraós e outros egípcios elevadas castas. De fato, as inscrições na tumba de Khnumhotep II são consideradas por muitos o primeiro exemplo de criptografia [4], pelo fato de apresentarem substituições de símbolos que, ao mesmo tempo, dificultam a leitura e tornam a escrita (supostamente) mais estética. Já o grego Heródoto [3] relata o episódio em que Histieus raspa a cabeça de seu mais confiável escravo e tatua uma mensagem destinada a Aristágoras convocando um levante contra os persas. Uma vez crescido o cabelo do escravo, este pôde ser enviado ao seu destino e a mensagem, transmitida despercebidamente.

Os dois exemplos anteriores são emblemáticos porque exemplificam as duas abordagens clássicas desenvolvidas com o intuito de proteger informações. A primeira abordagem baseia-se no uso de técnicas de codificação pelas quais determinadas regras de “embaralhamento” de uma mensagem são mantidas ocultas. Apenas quem conhece a regra de “desembaralhamento” correspondente é capaz de recuperar a mensagem original. A segunda abordagem baseia-se no ocultamento da própria existência de uma mensagem – ou seja, tal mensagem passa despercebida, embarcada em um meio de comunicação qualquer. Tais abordagens evoluíram para duas técnicas distintas de proteção de informações, a saber, denominadas Criptografia e Esteganografia. Embora a criptografia, como ferramenta de proteção da confidencialidade, seja bem mais usada, atualmente, do que a Esteganografia, esta última encontra aplicação no cenário em que a simples existência de uma mensagem é uma informação a ser protegida.

As técnicas de criptografia e esteganografia refletem bem os requisitos de segurança da informação em “cenários clássicos”, nos quais o principal objetivo é proteger a informação enquanto ela está em trânsito ou armazenada – assumindo-se que os usuários finais de tal informação são confiáveis. O surgimento da computação leva à necessidade de modelos particulares para a avaliação da segurança da informação. A grande mudança de cenário é determinada pelo fato de que o “destinatário” de uma informação não é mais um indivíduo ao qual se pode atribuir um “grau de confiança”, mas um host qualquer, possivelmente, em ambiente hostil. Quando a informação a ser protegida está presente em software executado neste host hostil então o cenário é o mais crítico possível.

Proteger software armazenado em ambiente hostil é uma tarefa cuja solução guarda relações com a esteganografia e com a criptografia, mas que vai muito além destas técnicas clássicas. Guarda relação com a esteganografia porque o adversário – aquele que busca compreender o software e dele retirar informações sensíveis – não sabe, a priori, que tipo de informação está procurando, embora ele saiba que toda informação que possa interessar está contida naquele código binário. Guarda relação com a criptografia porque, para dificultar a tarefa do adversário de compreender o software, o proprietário do software deverá aplicar uma série de transformações sobre o código original que dificultem a tarefa de compreender este software – ainda que tais transformações não possam ser arbitrárias, sob pena de o código final simplesmente deixar de ser executável no host a que se destina.

Modelos de Ameaças

Um modelo de ameaça identifica as ameaças aos quais determinado sistema deve ser resistente. Um modelo de ameaças leva em consideração as classes relevantes de adversários – considerando seus recursos computacionais, financeiros e até mesmo intelectuais – assim outras hipóteses e condições ambientais. Um modelo de ameaças deve ser construído após uma adequada avaliação de riscos que determinará as ameaças ao sistema, as perdas potenciais e as probabilidades de determinados ataques e eventos. Ou seja, um modelo de ameaças é construído sobre um conjunto de hipóteses que, espera-se, são o mais próximo possível da realidade.

O modelo clássico de ameaças para comunicações em rede [6] assume a confiança total nos hosts finais da comunicação e vulnerabilidade de toda a cadeia de comunicação entre eles (incluídos, por exemplo, roteadores e meios físicos de comunicação). A adequação deste modelo a uma determinada aplicação dependerá das características do ambiente onde os hosts finais estão localizados – por exemplo, o modelo poderá ser inadequado se algum destes hosts é vulnerável a malwares ou se diversos indivíduos tem acesso físico a este host.

Descrevemos três modelos básicos [5] de ataques com grau crescente de exigência.

  1. Modelo de ameaças em rede.
    Um número crescente de aplicações de software é conectada em rede – browsers, clientes de email, processadores de texto, planilhas eletrônicas etc. Tais aplicações fornecem funcionalidades baseadas na conexão a outros serviços e aplicações acessíveis por rede. Portanto, tais aplicações são vulneráveis a ataques externos remotos.
  2. Modelo de ameaça “insider”.Aqui o atacante possui algum nível de acesso ao ambiente onde aplicativo é executado (por exemplo, está na mesma rede local) ou sobre o hardware que executa o aplicativo (podendo analisar, por exemplo, informações como consumo de tempo, energia etc.).
  3. Modelo do host não-confiável.
    Assumindo que a aplicação está sendo executada em um host não-confiável, ela passa a ser sujeita a ataques originados do próprio host – ou seja, do sistema operacional, kernel, outras aplicações locais etc. O usuário possui privilégios locais totais, incluindo acesso ao código em execução e visualização dos recursos utilizados em tempo de execução.

A tabela abaixo resume as principais diferenças entre os modelos de ameaça. Outras diferenças incluem: os tipos de ferramentas disponíveis e os objetivos dos adversários.

Modelo “em rede”Modelo “insiderModelo “host não-confiável”

privilégios do atacante nenhum algum total
local do atacante remoto mesmo host mesmo host
host confiável? sim sim não

Técnicas para proteção de software

Ferramentas criptográficas

As primitivas criptográficas continuam sendo estrelas também na proteção de software em hosts hostis. Cifras podem ser usadas para criptografar trechos críticos de programa – os quais podem ser decriptografados on the fly por software ou hardware. Hashes podem ser usados para verificar a integridade de um software. MAC e assinaturas digitais podem ser usados para identificar a origem de um módulo de software. No modelo de host não-confiável, no entanto, tais ferramentas criptográficas são utilizadas em conjunto com outras técnicas para atingir objetivos de segurança mais sofisticados.

Ofuscação

Ofuscar um programa refere-se a transformar seu código em um código de mais difícil compreensão mantendo, porém, as funcionalidades originais do programa [2]. Tais transformações podem ocorrer tanto no código-fonte – que posteriormente é compilado de maneira a gerar um binário mais complexo – como diretamente no código objeto. Alguns trabalhos propõem tranformações a serem aplicadas sobre “código intermediário”, visando, por exemplo, a proteção de Java bytecodes. Como sabemos, os Java bytecodes são uma espécie de código intermediário. Ou seja, embora prontos para serem executados em uma máquina virtual, não estão propriamente escritos em “linguagem de máquina”, o que torna sua compreensão bem mais simples que a compreensão de códigos binários. Neste cenário, a proteção de software torna-se ainda mais crítica, e o uso de técnicas de ofuscação é obrigatório para quem não quer ver as suas “classes” sendo chamadas em programas alheios.

Transformações de ofuscação variam desde as mais simples – como a simples insersão de código “inútil” com o objetivo de confundir a análise do programa – até as mais complexas – como transformações que redefinem fluxos de dados ou camuflam chamadas de procedimentos.

Diversidade de Software

A idéia da diversidade de software é bastante natural a partir da observação de sistemas biológicos: a variabilidade genética dos indivíduos de determinada espécie reduz as chances de toda a espécie ser dizimada por uma determinada ameaça biológica. Quanto maior a variabilidade genética, maiores as chances de haver indivíduos resistentes a cada ameaça.

A idéia é a mesma para software, especialmente num mundo em que proliferam softwares maliciosos. Se todos os computadores do planeta utilizassem a mesma versão de sistema operacional então, certamente, todos os softwares maliciosos do planeta seriam projetados para ela. E mais, uma vez que um software malicioso realmente efetivo fosse produzido, então todos os computadores do planeta estariam vulneráveis.

A idéia de diversidade software, dessa maneira quase “ideológica”, foi mencionada explicitamente por Cohen [1] em 1992, embora a idéia seja, certamente, muito mais antiga.

Atualmente, a idéia de diversidade ganha caráter mais objetivo. Considere o caso em que determinada informação secreta deve ser protegida em um software em execução – digamos, uma chave privada. Suponha que, após sofisticadas operações de transformação de código, o algoritmo criptográfico está tão ofuscado que a operação de recuperação de chave privada (para fins de assinatura de mensagem), busca os seu (digamos) 1024 bits em 1024 posições de memória distintas. Além disso, tais posições não são fixas, mas determinadas pelos bits de outras posições de memória – essas sim, fixas. Embora extremamente complexo, uma vez compreendido este algoritmo de recuperação da chave privada, o adversário pode reproduzir o algoritmo em todos os outros hosts cujo software foi ofuscado de maneira similar.

A idéia da diversidade de software se aplica, portanto, através da aplicação de transformações de ofuscação efetivamente distintas, de forma a criar diversas versões de software difíceis de compreender em absoluto e que, uma vez compreendidas, não fornecem informação que ajude a compreender outa versão do mesmo software gerada através de transformações de ofuscação distintas.

Marca d’água

Marcas d’áqua são informações embarcadas em arquivos de tal forma que se torna muito difícil removê-las através de pequenas modificações no arquivo. Idealmente, a marca d’água estaria tão ligada ao arquivo que qualquer região suficientemente do arquivo possuiria tal informação. Uma marca d’água poderia conter, por exemplo, o nome do desenvolvedor do software. Dessa forma, embora a marca d’água, por si só, não impeça um ato de violação de software, ela, no mínimo, desestimula tal ato e favorece a localização do violador do software. Além disso, marcas d’água podem conter informações de integridade de software, como por exemplo, um resumo criptográfico. Dessa forma, o software pode inserir, em rotinas críticas, “travas” que verificam a marca d’água de maneira a verificar se o software em execução está íntegro. Neste caso, a marca d’água é parte de um sistema de resistência à corrupção.

Resistência à corrupção

A ofuscação de um software busca proteção contra a engenharia reversa, cujo objetivo é a compreensão de determinado programa. A engenharia reversa é um primeiro passo anterior à modificação deste software com o objetivo de obter alguma espécie de vantagem. Detectar violações à integridade do software – possivelmente impedindo a execução de software corrompido – é objeto das técnicas de resistência à corrupção.

Existem diversas propostas de técnicas para a verificação da integridade de um software. Em determinadas abordagens, trechos críticos de um software podem ser mantidos em endereços fixos de memória, os quais seriam periodicamente verificados por módulos confiáveis. Outras abordagens baseiam-se no próprio comportamento do software para verificar se eles está íntegro, técnica denominada “reflexão de software”.

Verificação de integridade. Diz respeito a verificar se o software em execução corresponde a uma versão previamente autorizada. A forma mais simples de verificação de integridade é a leitura completa do código em execução e a comparação com as versões de software autorizadas. No entanto, tal abordagem nem sempre é possível ou prática, de maneira que outras abordagens – por exemplo, baseadas em comportamento do software – vem sendo investigadas.

Verificação de autenticidade. Uma técnica ligeiramente distinta do que chamamos verificação de integridade é a “verificação de autenticidade”. Enquanto verificação de integridade diz respeito ao processo de verificação de correspondência entre o software em execução em um host e o software previsto para estar em execução (software “íntegro”), a verificação de autenticidade diz respeito ao processo de verificação da entidade desenvolvedora de determinado software. A verificação de autenticidade tem aplicação clássica em sistemas que recebem carga remota de software. Idealmente, tais sistemas somente aceitarão carga de softwares desenvolvidos por entidades confiáveis. A verificação de autenticidade diz respeito exatamente a este processo.

Travas de software. Uma vez desenvolvida uma técnica que determina se um software está íntegro ou não, é possível desenvolver rotinas críticas que verifiquem a integridade do software antes de, efetivamente, entrarem em execução. Em tal cenário, mesmo que se determine a localização de determinada rotina ou função dentro de um binário, tal rotina/função não poderia ser reaproveitada maliciosamente em outro software, pois identificaria estar “fora” de seu software original.

Exemplos de proteção de software

Proteção de algoritmo secreto

Aplicação típica de fabricantes e desenvolvedores que querem proteger seus algoritmos – supostamente, vantagens competitivas – para que estes não possam ser reutilizados por concorrentes. A grande questão é: como protegê-las? Uma solução simples é simplesmente não distribuir tais algoritmos – ou seja, tal fabricante passa a vender o serviço gerado por seu software (possivelmente, através da Internet) e passa a distribuir simplesmente um aplicativo cliente que acessa um aplicativo servidor, este último responsável por executar o algoritmo e armazenado em um host confiável. Em certos cenários, tal solução é inviável. Considere, por exemplo, um software que deve ser embarcado em um equipamento (digamos, um osciloscópio) a ser vendido. Neste caso, se o algoritmo é realmente um diferencial competitivo, técnicas de ofuscação devem ser consideradas.

Teste de integridade de software crítico

Determinado software executa tarefa crítica e somente deve ser executado se se tem certeza de que todas as suas rotinas críticas estão íntegras. Neste caso, técnicas de resistência à corrupção devem ser empregadas de maneira a garantir que o software em execução é exatamente aquele que foi aprovado e autorizado para a execução desta tarefa.

Garantindo a origem do software

Determinado equipamento funciona através da execução de um software embarcado. Tal software deve poder ser, eventualmente, atualizado (para a correção de bugs ou inclusão de funcionalidades). No entanto, somente o fabricante de tal equipamento deve poder fazer tais atualizações. Neste caso, técnicas de verificação de autenticidade devem ser empregadas com o objetivo de garantir que nenhum adversário será capaz de alterar o software sobrescrevendo-o com uma versão maliciosa.

Proteção anti-pirataria

Um desenvolvedor de software quer simplesmente impedir que seu software ou módulos de seu software sejam reutilizados por concorrentes. Técnicas de watermarking não impedirão que o software seja utilizado por seu concorrente, mas ajudarão a rastrear o “desenvolvedor pirata”, o que, en conjunto com medidas legais, ajudam a reduzir o potencial fraudador de concorrentes. Para impedir que módulos de software (rotinas e funções) sejam reaproveitados, travas de software podem ser inseridas nas rotinas e funções críticas – acompanhadas por técnicas de ofuscação que dificultem a desativação destas travas.

Criptografia de caixa-branca

A chamada criptografia de caixa branca se refere a um cenário bastante específico: como manter a segurança de um esquema criptográfico implementado em um software embarcado em equipamento sob domínio alheio. Se o equipamento está em ambiente inseguro, aquele que o detém poderá ser capaz de analisar todo o seu software e sua memória – em particular, poderá verificar os algoritmos e as chave criptográficas.

Nesse ponto, um fato digno de menção é a facilidade de se encontrar chaves criptográficas armazenadas em memória. Uma chave criptográfica é uma ilha de aleatoriedade dentro de um mar de dados e informações altamente estruturadas. Conforme Someren e Shamir [7] nos mostram, a alta aleatoriedade e entropia das chaves criptográficas permitem que elas sejam localizadas rapidamente dentro de muitos gigabytes de dados.

O que fazer, então, no caso de um host que usa rotinas criptográficas para executar suas funções mas que está armazenado em ambiente inseguro – e, portanto, pode ter seu software e memórias analisados. Tal cenário é extremamente comum em aplicações baseadas em equipamentos com software embarcado. Frequentemente, tais equipamentos possuem um par chave pública / chave privada usado para fins de identificação / autenticação. Gostaríamos, portanto, que a chave privada fosse mantida em sigilo. O modelo de “ataques de caixa branca” é o mais severo possível, onde todos os detalhes de implementação são conhecidos, e que se contrapõe ao modelo tradicional de caixa preta, em que o “bloco de criptografia” do qual apenas entradas e saídas de dados podem ser observadas.

O ataque de caixa-branca assume que o adversário possui acesso total às implementações dos algoritmos criptográficos, pode verificar e alterar detalhes internos dos algoritmos, pode observar os aspectos dinâmicos da execução. O objetivo do adversário é recuperar a chave privada com o objetivo de utilizá-la em outra plataforma – por exemplo, para emular o comportamento do host cuja chave foi “roubada”. Fica claro que, neste cenário, o paradigma tradicional da criptografia falha em oferecer proteção. A última linha de defesa neste modelo de ataque é o uso de uma implementação complexa e ofuscada.

A criptografia de caixa branca levanta ainda outra questão interessante: uma vez que o adversário consegue ter sucesso na recuperação da chave privada de um host, quão difícil para ele será obter a chave privada de outros hosts. Idealmente, gostaríamos que o primeiro sucesso não fornecesse nenhuma informação a respeito de como obter novos sucessos, conceito algumas vezes referido como ortogonalidade. O caminho para a ortogonalidade parece ser a diversidade de software, ou seja, dados dois hosts diferentes executando o mesmo algoritmo criptográfico, as duas implementações são tão distintas que a engenharia reversa aplicada a um dos hosts em nada ajuda na engenharia reversa a ser aplicada no outro.

Conclusão

A maioria dos ataques efetivos a sistemas computacionais utiliza-se em vulnerabilidades de software. A indústria de software, por outro lado, está bastante atrasada em relação aos seus adversários no que diz respeito à proteção de software armazenado em hosts não-confiáveis. Avanços efetinos na área de proteção de software demandam um esforço interdisciplinar em Ciência da Computação, envolvendo áreas distintas tais como linguagens de programação, sistemas operacionais, engenharia de software e criptografia. O presente artigo apresenta alguns dos conceitos associados ao problema de proteção de software.

Para saber mais

  1. ^ F. Cohen. Operating System Protection Through Program Evolution. Computers
    and Security 12 (1993) 565-584.
  2. ^ C. Collberg, C. Thomborson, D. Low. A Taxonomy of Obfuscating Transformations. Technical Report 148 (1997) Department of Computer Science, University of Auckland.
  3. ^ Herodotus. The Histories. J. M. Dent & Sons (1992).
  4. ^ D. Kahn. The CodeBreakers: The Story of Secret Writing. Macmillan (1967).
  5. ^ A. Main, P.C. van Oorschot. Software Protection and Application Security: Understanding the Battleground. Procedings of the International Course on State of the Art and Evolution of Computer Security and Industrial Cryptography (2003).
  6. ^ E. Rescorla. SSL and TLS: Designing and Building Secure Systems. Addison Wesley (2001).
  7. ^ A. Shamir, N. van Someren. Playing hide and seek with stored keys. Proceedings of the Third International Conference on Financial Cryptography (1999) 118–124.

Foto: Logo do curso Fundamentos de Criptografia por Clavis Segurança da Informação, CC-BY-NC-ND.