Volta e meia me perguntam sobre a possibilidade de decompilar programas .NET. Por “descompilar” entendo “produzir a partir de um executável um programa fonte em linguagem de alto-nível que, quando compilado, tem a mesma funcionalidade que o executável original”. É importante observar que qualquer programa, em qualquer linguagem de alto nível é “descompilável” pelo menos em linguagem Assembly ou similar, até porque a própria CPU do computador tem que saber executá-lo.
É então possível descompilar programas .NET, de maneira semelhante ao que era possível com descompiladores para o Clipper como “Unclip” ou “Valkyre”, que tanto transtornos causaram nos anos noventa? A resposta curta é sim. Mas continue lendo.
Uma das grandes vantagens dos “assemblies”.NET (.EXE ou .DLL) é que eles rodam em um “ambiente gerenciado”, onde os programas não podem causar danos aos outros programas rodando no computador do usuário. Este ambiente gerenciado depende basicamente do seguinte para funcionar:
- Código executável definido em uma “linguagem intermediária – IL” ao invés da linguagem do processador. Este código pode ser “verificado” em tempo de carga e execução pelo compilador “JIT”, de forma que o mesmo não possa efetuar operações não permitidas pelas configurações de segurança, como acessar um endereço de memória não permitido;
- Informação de tipo em tempo de execução (“metadata”) de forma a validar os “casts”, chamadas de funções entre diferentes DLLs e manter a integridade do sistema de tipos;
- A primeira compilação (VB ou C# para IL) não deve otimizar o código, pois as otimizações podem impedir que o compilador JIT verifique o código. A presença de otimizações tem o efeito colateral de atrapalhar os descompiladores.
Evidentemente, os fatores acima facilitam a descompilação dos programas. Note que estes recursos têm como efeito principal o aumento da segurança e integridade tanto para os desenvolvedores como para os usuários finais, o que é considerado hoje em dia uma grande vantagem. Por exemplo, a maioria dos erros no gerenciamento manual de memória dos ambientes tradicionais é eliminada. A robustez e a vulnerabilidade à descompilação também são características do outro grande ambiente gerenciado utilizado atualmente, o Java.
Dito isto, como impedir que um usuário do seu software possa descompilá-los? Existem várias maneiras:
- Impedir o acesso aos executáveis. Um aplicativo web, por exemplo, roda em um servidor e os usuários não têm acesso aos executáveis. Mesmo um aplicativo desktop pode ter parte da sua funcionalidade em WebServices, que também rodam em um servidor.
- Escrever parte do programa como código não gerenciado, quer como DLL, objeto COM ou mesmo em código não-gerenciado dentro de programas C++ .NET. Evidentemente isto elimina as vantagens do código gerenciado pelo menos em parte do seu programa.
- Escrever parte do código em Assembler IL usando recursos que dificultam a descompilação, como o uso de identificadores não suportados pelas linguagens C# ou VB.NET, por exemplo, chamando uma função com o mesmo nome de uma palavra reservada de linguagem de alto-nível.
- Usar um “ofuscator” para alterar o código de forma a impedir a descompilação.
Os ofuscadores (pesquise por “.NET obfuscators” na Web) são a maneira mais fácil e direta de proteger o seu código, pelo menos a níveis comparáveis aos de outras linguagens de alto nível como C/C++ ou Delphi. Basicamente eles processam os assemblies (.EXE/.DLL) e geram outros assemblies que tem a mesma funcionalidade, mas que são mais difíceis de descompilar.
Como isso é feito? Um dos truques é renomear os símbolos (funções, propridedades, campos etc) “private” de seus executáveis. Quando um programa .NET referencia um símbolo dentro do mesmo assembly, ele não o faz por nome e sim através de um índice em uma tabela. O nome é então supérfluo e pode ser trocado para identificadores repetidos ou mesmo palavras reservadas da linguagem, tornando o fonte descompilado impossível de ser recompilado. Alguns produtos possuem recursos bastante sofisticados e o resultado é uma proteção até maior que um programa Delphi ou C++.
Caso você deseje proteger seus programas, os ofuscadores são uma alternativa bastante prática.