Post by Gio » 30 May 2019, 12:16
Ok. Tá certo, mas olha só, esse é um tópico muito longo, e no final quase sempre a conclusão acaba sendo bem diferente do que se esperava. Mesmo assim vamos lá, vou tratar vários pontos relacionados para que possamos adquirir uma visão mais abrangente do que está envolvido, e daí quem sabe você terá mais embasamento para optar por algum dos métodos descritos.
A primeira coisa que você deve ter em mente é que a segurança da informação tem um custo. Isso significa basicamente que você vai precisar dedicar muitas horas de trabalho implementando novas rotinas com o único propósito de elevar o nível de segurança da informação. Além disso, é possível que a implementação dessas rotinas atrapalhem você também na hora de atualizar o programa ou mesmo na hora de procurar bugs no seu programa. Neste sentido, entenda que a segurança deve ser tratada como em uma analogia com o mundo real, onde o trabalho de gerar segurança é como construir um muro para impedir o acesso a uma propriedade sua.
Quanto mais alto você projetar o muro, em tese mais seguro ele se torna, mas se você ultrapassar uma determinada altura, o custo para aumentar o muro deixa de ser justificado pelo benefício de ter um muro mais alto. Além disso, o muro muito alto pode criar fraquezas estruturais (ou seja, uma rotina de segurança também pode criar espaço para exploits de hackers). Lembre-se sempre que um muro também pode ser demolido ou que o ladrão pode simplesmente cair de paraquedas: a ideia é que uma pessoa realmente motivada e com os recursos e conhecimentos necessários jamais poderá ser totalmente impedida de entrar na sua propriedade (ou de extrair a informação do seu programa). Também não faz sentido gastar 100 mil em um muro para uma propriedade que vale 50 mil certo?
Tendo dito tudo isso, vamos falar primeiro sobre o que podemos fazer para proteger o código fonte do script "compilado" inteiro.
Existem duas formas de implementar uma linguagem de programação: através de um compilador e através de um interpretador. O executável do autohotkey é um poderoso interprertador. Isso significa que ele lê os comandos de texto que você escreve no script e então executa os códigos de máquina necessários para que aquelas ações aconteçam. Um exemplo de outra linguagem interpretada é o conhecido Java. A outra forma de implementar uma linguagem de programação é através de um compilador de verdade. Um compilador, ao invés de ler os comandos escritos e executar as instruções, vai ler os comandos escritos e CRIAR NOVAS sequências de instruções em código de máquina que executem esses comandos. Isso significa que o resultado da compilação verdadeira é um código de máquina à parte, e que o código-fonte não estará mais presente neste arquivo final. Um exemplo de linguagem que faz isso é o C++, através do Visual Studio C++.
No caso do AutoHotkey, como os usuários demonstraram a necessidade de criar arquivos executáveis únicos (sem arquivos de texto na mesma pasta), foi criado um pseudo-compilador (ahk2exe), que na verdade simplesmente pega o script e insere dentro do próprio executável do AutoHotkey. Assim, quando você "compila" um script, você simplesmente copia o executável do AutoHotkey e insere dentro dele o script em forma de instruções de texto mesmo. Dessa forma, olhando dentro do arquivo do executável "cru", você poderá normalmente encontrar o script inteirinho com todos os comandos de texto de forma legível. Mas isso não é uma regra: existe uma maneira de transformar até mesmo o arquivo de texto do script em código de máquina, e isso é feito através de um packer (como por exemplo, o mpress, que se estiver presente na pasta do compilador pode ser usado clicando na flag "use mpress if present" dentro da tela do ahk2exe).
O que um packer faz é pegar um arquivo executável e com ele criar um segundo arquivo executável comprimido, e neste arquivo comprimido é inserido um código de máquina no início da cadeia de instruções que vai auto-descomprimir o executável comprimido, carregando para dentro da RAM os códigos de máquina do arquivo original, que serão então executados. A vantagem do uso do packer é que o arquivo final fica com um tamanho menor em disco do que o arquivo original. Para nosso caso, a vantagem é que o script também passa a ficar escondido dentro de código de máquina de auto-descompressão.
Mas então, essa segurança se torna igual a de um compilador verdadeiro? Talvez. Pode ser até maior (mas também pode ser menor). Essa pergunta não tem uma resposta certa, porque os métodos de extração de dados (engenharia reversa) teriam resultados diferentes em cada uma das situações conforme os casos específicos. Alguns arquivos fruto de um compilador podem ser bem fáceis de aplicar engenharia reversa, especialmente para fazer coisas como burlar uma licença (desde que você saiba o que fazer, lógico). Então a compilação verdadeira na verdade não é muito segura também. Além disso, o hacker que quisesse burlar o arquivo do AutoHotkey após o uso do packer teria que fazer o que chamamos de "unpack", que é uma coisa relativamente trabalhosa (mas é possível haver ferramentas que facilitem, por isso cada packer tem a sua dificuldade de fazer um "unpack"). O que você deve tirar deste parágrafo é o seguinte:
use um packer. Se você está preocupado que alguém que conheça à fundo tanto o AutoHotkey como também a engenharia reversa possa tentar fazer um hack no seu arquivo (e tem pouquíssimas pessoas assim, principalmente aqui no brasil), não use o mpress (use outro menos conhecido da comunidade). Mas se você achar que isso é muito improvável (e quase 100% das vezes é), pode usar o mpress numa boa. Finalmente, com relação aos packers você deve observar que tem de testar o programa inteiro depois de aplicar o packer, pois é possível que algo inesperado ocorra (ou seja, que o packer não consiga fazer um pack bom no arquivo). Outra coisa que você também deve ter em mente é que packers são usados bastante por desenvolvedores de vírus (em virtude de diminuir o tamanho em disco do arquivo final e dificultar a engenharia reversa). Dessa forma, podem ocorrer mais falso-positivos em anti-vírus com o seu arquivo final.
Outra coisa que você pode fazer é
obfuscar o seu código-fonte. A obfuscação é um trabalho que pode ser feito com ferramentas ou de forma manual, e o que ela faz é embaralhar o código fonte do seu arquivo, tornando-o tão difícil de um ser humano ler que fica muito difícil tentar modificar ou extrair alguma coisa, e isso torna inviável o uso desse código fonte para quase qualquer fim que não seja a sua execução sem alterações. A obfuscação em alto nível, eu acredito ser até mesmo muito mais segura do que a compilação do arquivo, pois o código de máquina, apesar de ilegível por um ser humano à princípio, tem um funcionamento de conhecimento geral e muitas ferramentas de análise disponíveis, mas a rotina de obfuscação que o seu programa vai usar pode ser algo que só você saiba o funcionamento, o que vai forçar o candidato a hacker a tentar deduzir o que foi que você escreveu em cada linha a partir de muitos testes diferentes. Outra dica: A obfuscação manual é bem mais trabalhosa, mas a obfuscação com ferramentas disponíveis está mais sujeita a alguém tentar obter informações direto da ferramenta obfuscadora. Você também pode usar as duas se quiser.
Agora como você ja sabe o que fazer em relação ao código fonte, vamos à questão da segurança da conexão.
A conexão que o seu programa faz com um banco de dados, através de um aplicativo ou serviço (ou mesmo via protocolo web), também pode ser protegida. Neste caso, se for uma conexão via web, eu acredito que alguma proteção já se torna algo bem mais importante. Imagine que um hacker pode tentar criar um serviço falso para tentar obter a string que o seu programa está enviado na tentativa de conexão e dela obter as credenciais de acesso. Assim, os desenvolvedores de banco de dados normalmente têm hoje ferramentas de autenticação criptografada. Busque investigar quais estão disponíveis e o que você deve fazer para conseguir implementar uma autenticação criptografada no seu mecanismo de banco de dados. Dica: normalmente isto é feito através de chave pública e chave privada, com algum elemento de segurança tipo data de acesso e consumo de seção, ou coisa parecida, de modo que a string de autenticação seja diferente a cada acesso (e assim uma string não possa ser usada para inferir outra). De qualquer forma, você ainda terá o problema de o hacker só precisar de um único acesso para tentar extrair informações, então fique esperto: no gestor do banco de dados você também tem outras opções de ferramentas de segurança, como fazer com que todas as consultas do tipo de acesso elencado ao seu programa usem somente Stored Procedures (código SQL que você criou) e também que quaisquer acessos às tabelas ativem triggers de log ou interrupção de acesso. Outra opção, no caso do acesso via web, é limitar os IPs que podem acessar o servidor.
Por fim, vamos falar de segurança de licenças.
Usando engenharia reversa, é relativamente fácil burlar uma segurança de licenças básica offline alterando somente o código de máquina de um arquivo compilado (mesmo a compilação verdadeira). Isso ocorre porque o programa normalmente usa especificamente instruções de comparação e em momentos previsíveis da execução para definir se uma chave é válida. Assim, se você quiser agregar mais segurança à rotina de verificação de licença do seu programa, pode, por exemplo, criar várias rotinas diferentes de comparação da chave para autenticação e em momentos diferentes da execução (para forçar o hacker a ir procurando e alterando uma a uma). O uso do packer também ajuda, uma vez que ele deve ser vencido antes que o código de máquina de comparação esteja visível no debugger. Uma outra coisa que você deve ter em mente é que uma rotina de verificação de licenças nunca é tão segura quanto uma rotina onde parte essencial do serviço do programa seja executada pelo seu próprio servidor. Se este último caso for possível, o hacker teria que basicamente acessar seu servidor para fazer uso efetivo daquele programa, e se isso não fosse o bastante, você ainda pode limitar quais IPs podem acessar os serviços do seu servidor (mesmo com as credenciais de autenticação corretas, um IP fora da whitelist vai ter seu acesso negado), e se for um IP da própria whitelist, você também pode logar esse acesso para fins de prova.
Pronto! acho que agora consegui dar um breve resumo sobre algumas das opções que você tem para elevar o nível de segurança dos seus programas. Mas sendo bem sincero, e é preciso que você pondere bem sobre isso, em 99% das vezes uma grande parte disso não vale nem a pena se dar ao trabalho de fazer. Quando a gente inicia na programação, a gente tem essa ideia de que aquilo que estamos fazendo vale muito dinheiro por si só ou então que interessaria muito às pessoas hackearem os nossos programas, mas a grande verdade é que isso é quase sempre coisa da nossa imaginação.
Quase qualquer programa que você desenvolva é relativamente fácil de entender a lógica só de olhar as telas e quase qualquer programador de nível comercial vai saber implementar aquilo de outra forma (e em outra linguagem) no programa dele (o que se for feito dessa forma muitas vezes sequer é um infração de direito autoral). Além disso, praticamente ninguém vai se dar ao trabalho de hackear um programa para se ver livre de pagar uma licença em massa (que normalmente é menos de 50 reais) ou então de uma licença mensal (que normalmente está atrelada a serviços como suporte e atualização). Se isso não fosse o bastante, invadir um banco de dados é um crime (Lei 12.737), gerando responsabilização penal do indivíduo, e ainda a alteração indevida de uma obra de direito autoral (seu programa) gera possibilidades de ressarcimento para o detentor do direito autoral do programa (você), além de que você pode impor ainda uma cláusula penal pesada aos seus clientes em caso de tentativa de hacking por parte deles (ou seja, uma indenização que eles teriam que pagar para você). Por fim, se você desenvolver uma solução técnica muito específica, pode também ser o caso de patenteá-la (um software não é passível de ser patenteado, ele já é defendido por direito autoral, mas a solução técnica que se opera por meio dele pode sim ser patenteada dependendo do caso).
Procure sempre ponderar que tipos de ferramentas de segurança são efetivamente importantes em cada caso e quando tiver optado por alguma ação específica, sinta-se livre para tirar suas dúvidas sobre como implementá-la (mas por favor, vamos tratar de apenas uma implementação de cada vez).
Espero ter ajudado
Ok. Tá certo, mas olha só, esse é um tópico muito longo, e no final quase sempre a conclusão acaba sendo bem diferente do que se esperava. Mesmo assim vamos lá, vou tratar vários pontos relacionados para que possamos adquirir uma visão mais abrangente do que está envolvido, e daí quem sabe você terá mais embasamento para optar por algum dos métodos descritos.
A primeira coisa que você deve ter em mente é que a segurança da informação tem um custo. Isso significa basicamente que você vai precisar dedicar muitas horas de trabalho implementando novas rotinas com o único propósito de elevar o nível de segurança da informação. Além disso, é possível que a implementação dessas rotinas atrapalhem você também na hora de atualizar o programa ou mesmo na hora de procurar bugs no seu programa. Neste sentido, entenda que a segurança deve ser tratada como em uma analogia com o mundo real, onde o trabalho de gerar segurança é como construir um muro para impedir o acesso a uma propriedade sua.
Quanto mais alto você projetar o muro, em tese mais seguro ele se torna, mas se você ultrapassar uma determinada altura, o custo para aumentar o muro deixa de ser justificado pelo benefício de ter um muro mais alto. Além disso, o muro muito alto pode criar fraquezas estruturais (ou seja, uma rotina de segurança também pode criar espaço para exploits de hackers). Lembre-se sempre que um muro também pode ser demolido ou que o ladrão pode simplesmente cair de paraquedas: a ideia é que uma pessoa realmente motivada e com os recursos e conhecimentos necessários jamais poderá ser totalmente impedida de entrar na sua propriedade (ou de extrair a informação do seu programa). Também não faz sentido gastar 100 mil em um muro para uma propriedade que vale 50 mil certo?
:arrow: Tendo dito tudo isso, vamos falar primeiro sobre o que podemos fazer para proteger o código fonte do script "compilado" inteiro.
Existem duas formas de implementar uma linguagem de programação: através de um compilador e através de um interpretador. O executável do autohotkey é um poderoso interprertador. Isso significa que ele lê os comandos de texto que você escreve no script e então executa os códigos de máquina necessários para que aquelas ações aconteçam. Um exemplo de outra linguagem interpretada é o conhecido Java. A outra forma de implementar uma linguagem de programação é através de um compilador de verdade. Um compilador, ao invés de ler os comandos escritos e executar as instruções, vai ler os comandos escritos e CRIAR NOVAS sequências de instruções em código de máquina que executem esses comandos. Isso significa que o resultado da compilação verdadeira é um código de máquina à parte, e que o código-fonte não estará mais presente neste arquivo final. Um exemplo de linguagem que faz isso é o C++, através do Visual Studio C++.
No caso do AutoHotkey, como os usuários demonstraram a necessidade de criar arquivos executáveis únicos (sem arquivos de texto na mesma pasta), foi criado um pseudo-compilador (ahk2exe), que na verdade simplesmente pega o script e insere dentro do próprio executável do AutoHotkey. Assim, quando você "compila" um script, você simplesmente copia o executável do AutoHotkey e insere dentro dele o script em forma de instruções de texto mesmo. Dessa forma, olhando dentro do arquivo do executável "cru", você poderá normalmente encontrar o script inteirinho com todos os comandos de texto de forma legível. Mas isso não é uma regra: existe uma maneira de transformar até mesmo o arquivo de texto do script em código de máquina, e isso é feito através de um packer (como por exemplo, o mpress, que se estiver presente na pasta do compilador pode ser usado clicando na flag "use mpress if present" dentro da tela do ahk2exe).
O que um packer faz é pegar um arquivo executável e com ele criar um segundo arquivo executável comprimido, e neste arquivo comprimido é inserido um código de máquina no início da cadeia de instruções que vai auto-descomprimir o executável comprimido, carregando para dentro da RAM os códigos de máquina do arquivo original, que serão então executados. A vantagem do uso do packer é que o arquivo final fica com um tamanho menor em disco do que o arquivo original. Para nosso caso, a vantagem é que o script também passa a ficar escondido dentro de código de máquina de auto-descompressão.
Mas então, essa segurança se torna igual a de um compilador verdadeiro? Talvez. Pode ser até maior (mas também pode ser menor). Essa pergunta não tem uma resposta certa, porque os métodos de extração de dados (engenharia reversa) teriam resultados diferentes em cada uma das situações conforme os casos específicos. Alguns arquivos fruto de um compilador podem ser bem fáceis de aplicar engenharia reversa, especialmente para fazer coisas como burlar uma licença (desde que você saiba o que fazer, lógico). Então a compilação verdadeira na verdade não é muito segura também. Além disso, o hacker que quisesse burlar o arquivo do AutoHotkey após o uso do packer teria que fazer o que chamamos de "unpack", que é uma coisa relativamente trabalhosa (mas é possível haver ferramentas que facilitem, por isso cada packer tem a sua dificuldade de fazer um "unpack"). O que você deve tirar deste parágrafo é o seguinte: [u]use um packer[/u]. Se você está preocupado que alguém que conheça à fundo tanto o AutoHotkey como também a engenharia reversa possa tentar fazer um hack no seu arquivo (e tem pouquíssimas pessoas assim, principalmente aqui no brasil), não use o mpress (use outro menos conhecido da comunidade). Mas se você achar que isso é muito improvável (e quase 100% das vezes é), pode usar o mpress numa boa. Finalmente, com relação aos packers você deve observar que tem de testar o programa inteiro depois de aplicar o packer, pois é possível que algo inesperado ocorra (ou seja, que o packer não consiga fazer um pack bom no arquivo). Outra coisa que você também deve ter em mente é que packers são usados bastante por desenvolvedores de vírus (em virtude de diminuir o tamanho em disco do arquivo final e dificultar a engenharia reversa). Dessa forma, podem ocorrer mais falso-positivos em anti-vírus com o seu arquivo final.
Outra coisa que você pode fazer é [u]obfuscar o seu código-fonte[/u]. A obfuscação é um trabalho que pode ser feito com ferramentas ou de forma manual, e o que ela faz é embaralhar o código fonte do seu arquivo, tornando-o tão difícil de um ser humano ler que fica muito difícil tentar modificar ou extrair alguma coisa, e isso torna inviável o uso desse código fonte para quase qualquer fim que não seja a sua execução sem alterações. A obfuscação em alto nível, eu acredito ser até mesmo muito mais segura do que a compilação do arquivo, pois o código de máquina, apesar de ilegível por um ser humano à princípio, tem um funcionamento de conhecimento geral e muitas ferramentas de análise disponíveis, mas a rotina de obfuscação que o seu programa vai usar pode ser algo que só você saiba o funcionamento, o que vai forçar o candidato a hacker a tentar deduzir o que foi que você escreveu em cada linha a partir de muitos testes diferentes. Outra dica: A obfuscação manual é bem mais trabalhosa, mas a obfuscação com ferramentas disponíveis está mais sujeita a alguém tentar obter informações direto da ferramenta obfuscadora. Você também pode usar as duas se quiser.
:arrow: Agora como você ja sabe o que fazer em relação ao código fonte, vamos à questão da segurança da conexão.
A conexão que o seu programa faz com um banco de dados, através de um aplicativo ou serviço (ou mesmo via protocolo web), também pode ser protegida. Neste caso, se for uma conexão via web, eu acredito que alguma proteção já se torna algo bem mais importante. Imagine que um hacker pode tentar criar um serviço falso para tentar obter a string que o seu programa está enviado na tentativa de conexão e dela obter as credenciais de acesso. Assim, os desenvolvedores de banco de dados normalmente têm hoje ferramentas de autenticação criptografada. Busque investigar quais estão disponíveis e o que você deve fazer para conseguir implementar uma autenticação criptografada no seu mecanismo de banco de dados. Dica: normalmente isto é feito através de chave pública e chave privada, com algum elemento de segurança tipo data de acesso e consumo de seção, ou coisa parecida, de modo que a string de autenticação seja diferente a cada acesso (e assim uma string não possa ser usada para inferir outra). De qualquer forma, você ainda terá o problema de o hacker só precisar de um único acesso para tentar extrair informações, então fique esperto: no gestor do banco de dados você também tem outras opções de ferramentas de segurança, como fazer com que todas as consultas do tipo de acesso elencado ao seu programa usem somente Stored Procedures (código SQL que você criou) e também que quaisquer acessos às tabelas ativem triggers de log ou interrupção de acesso. Outra opção, no caso do acesso via web, é limitar os IPs que podem acessar o servidor.
:arrow: Por fim, vamos falar de segurança de licenças.
Usando engenharia reversa, é relativamente fácil burlar uma segurança de licenças básica offline alterando somente o código de máquina de um arquivo compilado (mesmo a compilação verdadeira). Isso ocorre porque o programa normalmente usa especificamente instruções de comparação e em momentos previsíveis da execução para definir se uma chave é válida. Assim, se você quiser agregar mais segurança à rotina de verificação de licença do seu programa, pode, por exemplo, criar várias rotinas diferentes de comparação da chave para autenticação e em momentos diferentes da execução (para forçar o hacker a ir procurando e alterando uma a uma). O uso do packer também ajuda, uma vez que ele deve ser vencido antes que o código de máquina de comparação esteja visível no debugger. Uma outra coisa que você deve ter em mente é que uma rotina de verificação de licenças nunca é tão segura quanto uma rotina onde parte essencial do serviço do programa seja executada pelo seu próprio servidor. Se este último caso for possível, o hacker teria que basicamente acessar seu servidor para fazer uso efetivo daquele programa, e se isso não fosse o bastante, você ainda pode limitar quais IPs podem acessar os serviços do seu servidor (mesmo com as credenciais de autenticação corretas, um IP fora da whitelist vai ter seu acesso negado), e se for um IP da própria whitelist, você também pode logar esse acesso para fins de prova.
:arrow: Pronto! acho que agora consegui dar um breve resumo sobre algumas das opções que você tem para elevar o nível de segurança dos seus programas. Mas sendo bem sincero, e é preciso que você pondere bem sobre isso, em 99% das vezes uma grande parte disso não vale nem a pena se dar ao trabalho de fazer. Quando a gente inicia na programação, a gente tem essa ideia de que aquilo que estamos fazendo vale muito dinheiro por si só ou então que interessaria muito às pessoas hackearem os nossos programas, mas a grande verdade é que isso é quase sempre coisa da nossa imaginação.
Quase qualquer programa que você desenvolva é relativamente fácil de entender a lógica só de olhar as telas e quase qualquer programador de nível comercial vai saber implementar aquilo de outra forma (e em outra linguagem) no programa dele (o que se for feito dessa forma muitas vezes sequer é um infração de direito autoral). Além disso, praticamente ninguém vai se dar ao trabalho de hackear um programa para se ver livre de pagar uma licença em massa (que normalmente é menos de 50 reais) ou então de uma licença mensal (que normalmente está atrelada a serviços como suporte e atualização). Se isso não fosse o bastante, invadir um banco de dados é um crime (Lei 12.737), gerando responsabilização penal do indivíduo, e ainda a alteração indevida de uma obra de direito autoral (seu programa) gera possibilidades de ressarcimento para o detentor do direito autoral do programa (você), além de que você pode impor ainda uma cláusula penal pesada aos seus clientes em caso de tentativa de hacking por parte deles (ou seja, uma indenização que eles teriam que pagar para você). Por fim, se você desenvolver uma solução técnica muito específica, pode também ser o caso de patenteá-la (um software não é passível de ser patenteado, ele já é defendido por direito autoral, mas a solução técnica que se opera por meio dele pode sim ser patenteada dependendo do caso).
Procure sempre ponderar que tipos de ferramentas de segurança são efetivamente importantes em cada caso e quando tiver optado por alguma ação específica, sinta-se livre para tirar suas dúvidas sobre como implementá-la (mas por favor, vamos tratar de apenas uma implementação de cada vez).
Espero ter ajudado :beer: