Melhores práticas de segurança no desenvolvimento de jogos
Este artigo discute as práticas recomendadas a serem usadas no desenvolvimento de jogos.
Introdução
Um número cada vez maior de pessoas usa jogos online e com conteúdo criado pelo usuário. Combinando esse fato com o aumento da segurança do sistema operacional do Microsoft Windows, significa que os jogos são um alvo cada vez maior e mais tentador para ser explorado pelos invasores. Os desenvolvedores de jogos devem enfatizar bastante a necessidade de garantir que os jogos que são lançados não criem novas brechas de segurança para serem exploradas pelos invasores. Os desenvolvedores de jogos têm a responsabilidade e o interesse de ajudar a evitar que os computadores de seus clientes sejam invadidos por dados de rede maliciosos, modificações de usuários ou adulterações. Se uma vulnerabilidade for explorada, isso poderá resultar na perda de clientes e/ou dinheiro. Este artigo descreve e explica alguns métodos e ferramentas geralmente usados para aumentar a segurança do código sem aumentar muito o tempo de desenvolvimento.
Os três erros mais comuns cometidos por uma equipe de desenvolvimento ao lançar um produto são:
- Exigir privilégios administrativos. Os jogos não devem exigir privilégios administrativos. Para obter mais detalhes, consulte Controle de conta de usuário para desenvolvedores de jogos.
- Não usar proteção automatizada. Os desenvolvedores geralmente não estão usando /GS, /SAFESEH ou /NX. O uso desses sinalizadores de compilação/link pode detectar ou eliminar muitas falhas básicas de segurança sem aumentar significativamente a carga de trabalho. Esses sinalizadores serão discutidos posteriormente neste artigo.
- Usar APIs proibidas. Há muitas APIs (strcpy e strncpy e assim por diante), que são propensas a erros de programação e geram falhas de segurança com facilidade. Os desenvolvedores devem substituir essas APIs pelas versões seguras. O Visual Studio 2005 vem com uma ferramenta para análise de arquivos binários que pode verificar automaticamente os arquivos de objetos em busca de referências a APIs inseguras. Para obter mais informações sobre o que fazer com as informações geradas com essa ferramenta, consulte Repel Attacks on Your Code with the Visual Studio 2005 Safe C and C++ Libraries, por Martyn Lovell. Além disso, você pode obter o
banned.h
arquivo de cabeçalho que pode ajudá-lo a remover funções banidas do código (consulte Ferramentas de segurança gratuitas da Microsoft – banned.h).
Cada um dos erros listados não é apenas comum, mas é facilmente corrigível sem nenhuma alteração significativa na carga de trabalho de desenvolvimento, nos padrões de codificação ou na funcionalidade.
Exemplos de código inseguro
A seguir, um exemplo simples de tudo o que é necessário para permitir que um invasor realize um ataque de sobrecarga de buffer:
void GetPlayerName(char *pDatafromNet)
{
char playername[256];
strncpy(playername, pDatafromNet, strlen(pDatafromNet));
// ...
}
Superficialmente, esse código parece bom, afinal, ele chama uma função segura. Os dados da rede são copiados para um buffer de 256 bytes. A função strncpy depende da localização de um terminador NULL na cadeia de caracteres de origem ou é limitada pela contagem de buffer fornecida. O problema é que o tamanho do buffer está incorreto. Se os dados da rede não forem validados ou se o tamanho do buffer estiver errado (como neste exemplo), um invasor poderá simplesmente fornecer um buffer grande para substituir os dados da pilha, após o término do buffer, por qualquer dado no pacote de rede. Isso permitiria que o invasor executasse um código arbitrário sobrescrevendo o ponteiro de instruções e alterando o endereço de retorno. A lição mais básica desse exemplo é: nunca confie em uma entrada até que ela tenha sido verificada.
Mesmo que os dados não venham inicialmente da rede, ainda há risco potencial. O desenvolvimento de jogos modernos exige que muitas pessoas projetem, desenvolvam e testem a mesma base de código. Não há como saber como a função será chamada no futuro. Sempre se pergunte de onde vieram os dados e o que um invasor poderia controlar. Embora os ataques baseados em rede sejam os mais comuns, eles não são os únicos métodos de criação de falhas de segurança. Um invasor poderia criar um mod ou editar um arquivo salvo de forma a abrir uma brecha de segurança? E quanto aos arquivos de imagem e som fornecidos pelo usuário? Versões maliciosas desses arquivos podem ser publicadas na Internet e criar riscos de segurança perigosos para seus clientes.
Como observação adicional, use strsafe.h ou Safe CRT em vez de strncpy para corrigir o exemplo. O CRT seguro é uma revisão completa de segurança do runtime de C e vem com parte do Visual Studio 2005. Mais informações sobre o Safe CRT podem ser encontradas em Security Enhancements in the CRT por Michael Howard.
Maneiras de melhorar a segurança
Existem várias maneiras de melhorar a segurança no ciclo de desenvolvimento. Aqui estão algumas das melhores maneiras:
-
Ler sobre segurança
-
O livro, Writing Secure Code, Segunda Edição , de Michael Howard e David LeBlanc, fornece uma explicação detalhada e clara das estratégias e métodos de prevenção de ataques e mitigação de explorações. Desde métodos para projetar a segurança em uma versão até técnicas para proteger aplicativos de rede, o livro abrange todos os aspectos que um desenvolvedor de jogos precisa para ajudar a proteger a si mesmo, seus produtos e seus clientes contra invasores. O livro pode ser usado para implantar uma cultura de segurança em um estúdio de desenvolvimento. Não pense na segurança do código apenas como um problema do desenvolvedor ou do testador. Pense na segurança como algo em que toda a equipe (do gerente de programas ao designer, ao desenvolvedor e ao testador) deve pensar ao trabalhar em um projeto. Quanto mais olhos fizerem parte do processo de revisão, maior será a chance de detectar uma falha de segurança antes da liberação.
Writing Secure Code, Second Edition pode ser encontrado na Microsoft Press Store e informações de segurança mais gerais podem ser encontradas em Fending Off Future Attacks by Reducing Attack Surface por Michael Howard.
Michael Howard, David LeBlanc e John Viega escreveram outro livro sobre o assunto que abrange todos os sistemas operacionais e linguagens de programação comuns intitulados, 19 Deadly Sins of Software Security.
As apresentações de segurança focadas em jogos podem ser encontradas na página de download em Microsoft XNA Developer Presentations.
-
Análise de modelagem de ameaças
-
Uma análise de modelagem de ameaças é uma boa forma de avaliar a segurança do sistema, não em uma linguagem específica ou usando uma ferramenta, mas em um método amplo e completo que pode ser realizado em poucas reuniões. Quando implementado adequadamente, um modelo de thread pode identificar todos os pontos fortes e fracos de um sistema, sem adicionar uma carga de trabalho significativa ou tempo de reunião ao projeto. O método de modelagem de ameaças também enfatiza a ideia de avaliar a segurança do sistema antes e durante o processo de desenvolvimento para ajudar a garantir que uma avaliação abrangente esteja sendo feita, concentrando-se nos recursos mais arriscados. Pode ser considerado um profiler para segurança. Por não se basear em uma linguagem específica ou depender de uma ferramenta particular, a modelagem de ameaças pode ser usada em qualquer estúdio de desenvolvimento que trabalhe em projetos de qualquer gênero. A modelagem de ameaças também é um excelente método para reforçar a ideia de que a segurança é responsabilidade de todos e não um problema de outra pessoa.
Ao modelar ameaças, preste atenção especial a:
- Fontes de dados UDP
- Fontes de dados que não requerem autenticação
- Fontes de dados que são frequentemente e normalmente investigadas como parte da coleta de informações em larga escala
- Qualquer capacidade que um cliente tenha de enviar dados diretamente a outros clientes
Essas são as áreas que têm um bom potencial para falhas de segurança.
Mais informações sobre modelagem de ameaças podem ser encontradas em Modelagem de ameaças e no livro Threat Modeling de Frank Swiderski e Window Snyder.
-
Prevenção de execução de dados (/NX)
-
Uma ferramenta recente para mitigar várias violações é a prevenção de execução de dados (DEP). Se você incluir a opção /NX no comando de compilação, o Visual Studio marcará as páginas de memória com sinalizadores que indicam se o código tem o direito de ser executado ou não. Qualquer programa que tente ser executado em uma página de memória não sinalizada com a permissão EXECUTE causará um encerramento forçado do programa. A prevenção é aplicada no nível do processador e afetará os desenvolvedores que estiverem usando código de automodificação ou compiladores de linguagem JIT nativos. Atualmente, apenas os processadores Athlon64 e Opteron da AMD e Itanium e Pentium 4 mais recentes da Intel oferecem suporte à prevenção de execução, mas espera-se que todos de 32 e 64 bits ofereçam suporte à prevenção de execução no futuro. (Um esquema de proteção contra cópia usado por um desenvolvedor pode ser afetado pela prevenção de execução, mas a Microsoft tem trabalhado com fornecedores de proteção contra cópia para minimizar o impacto.) É uma boa prática usar a DEP.
Para obter mais detalhes sobre a DEP, consulte Prevenção de execução de dados.
-
Verificação de segurança do buffer (/GS) e imagem tem manipuladores de exceção seguros (/SAFESEH)
-
A verificação de segurança do buffer especificado pelo sinalizador do compilador /GS e A imagem tem manipuladores de exceções seguros, especificado pelo sinalizador do vinculador /SAFESEH (implementado pela primeira vez no Visual Studio .NET 2003), pode facilitar um pouco o trabalho do desenvolvedor de proteger o código.
O uso do sinalizador /GS faz com que o compilador construa uma verificação de algumas formas de saturação de buffer baseadas em pilha que podem ser exploradas para substituir o endereço de retorno de uma função. O uso de /GS não detectará todos os possíveis estouros de buffer e não deve ser considerado um catch-all, mas uma boa tecnologia de defesa em profundidade.
O uso do sinalizador /SAFESEH instruirá o vinculador a gerar apenas um executável ou DLL se ele também puder gerar uma tabela dos manipuladores de exceção seguros do executável ou DLL. O tratamento de exceção estruturado seguro (SafeSEH) elimina o tratamento de exceções como alvo de ataques de saturação de buffer, garantindo que, antes que uma exceção seja despachada, o manipulador de exceção seja registrado na tabela de funções localizada no arquivo de imagem. Esses benefícios de proteção são habilitados com o Windows XP SP2, Windows Server 2003, Windows Vista e Windows 7. Além disso, para que /SAFESEH funcione corretamente, ele deve ser usado em um método de tudo ou nada. Todas as bibliotecas que contêm código associado a um executável ou DLL devem ser compiladas com /SAFESEH ou a tabela não será gerada.
Para obter mais informações, consulte Verificação de segurança do buffer (/GS) e A imagem tem manipuladores de exceções seguros (/SAFESEH).
Consulte também as informações sobre o sinalizador /SDL do Microsoft Visual Studio 2012 e os aprimoramentos do sinalizador /GS do Visual Studio 2012.
-
PREfast
-
O PREfast é uma ferramenta gratuita oferecida pela Microsoft que analisa os caminhos de execução em C ou C++ compilado para ajudar a encontrar erros de tempo de execução. O PREfast opera trabalhando em todos os caminhos de execução em todas as funções e avaliando cada caminho em busca de problemas. Originalmente usada para desenvolver drivers e outros códigos do kernel, essa ferramenta pode ajudar os desenvolvedores de jogos a economizar tempo, eliminando alguns erros que são difíceis de encontrar ou que são ignorados pelo compilador. Usar o PREfast é uma excelente maneira de reduzir a carga de trabalho e concentrar os esforços da equipe de desenvolvimento e da equipe de teste. O PREfast está disponível no Visual Studio Team Suite e no Visual Studio Team Edition para desenvolvedores de software como análise de código, habilitado pela opção de compilador /analyze. (Essa opção também está disponível na versão gratuita do compilador que acompanha o Windows Software Development Kit.)
Observação
O Visual Studio 2012 oferece suporte a /analyze em todas as edições. Para obter mais informações sobre a disponibilidade da análise de código em todas as edições do Visual Studio, consulte Novidades na análise de código.
Com o uso da anotação de cabeçalho (especialmente para argumentos de ponteiro de buffer), o PREfast pode expor outros problemas, como erros de substituição de memória, uma fonte comum de falhas e possíveis vulnerabilidades de segurança. Isso é feito usando a Linguagem de Anotação Padrão (SAL), que é uma forma de marcação para protótipos de função C/C++ que fornece informações adicionais sobre a semântica esperada do argumento do ponteiro e a correlação com parâmetros de comprimento, tamanhos de buffer declarados, etc. Todos os cabeçalhos dos sistemas operacionais Windows são anotados, e a adição de marcação SAL nos cabeçalhos de APIs públicas em suas próprias bibliotecas permite que o PREfast realize verificações mais detalhadas e agressivas no código do cliente para essas APIs. Para obter uma introdução ao SAL e links para obter mais informações, consulte a publicação do blog de Michael Howard,Uma breve introdução à linguagem de anotação padrão (SAL).
-
Verificador de aplicativo do Windows
-
O Application Verifier do Windows, ou AppVerifier, pode ajudar os testadores fornecendo várias funções em uma ferramenta. O AppVerifier é uma ferramenta que foi desenvolvida para tornar os erros comuns de programação mais fáceis de serem testados. O AppVerifier pode verificar os parâmetros passados para chamadas de API, injetar entradas erradas para verificar a capacidade de tratamento de erros e registrar alterações no registro e no sistema de arquivos. O AppVerifier também pode detectar excessos de buffer no heap, verificar se uma lista de controle de acesso (ACL) foi definida corretamente e impor o uso seguro de APIs de soquete. Embora não seja exaustivo, o AppVerifier pode ser uma ferramenta na caixa de ferramentas do testador para ajudar um estúdio de desenvolvimento a lançar um produto de qualidade.
Para obter mais informações sobre o Application Verifier, consulte Application Verifier e Uso do Application Verifier em seu ciclo de vida de desenvolvimento de software.
-
Teste de fuzzing
-
O teste de fuzzing é um método semiautomatizado que pode aprimorar as metodologias de teste atuais. A ideia central por trás do teste de fuzzing é fazer uma avaliação completa de todas as entradas, inserindo dados aleatórios para ver o que quebra, incluindo todos os dados de rede, mods e jogos salvos, etc. O teste de fuzzing é bastante fácil de realizar. Basta alterar arquivos bem formados ou dados de rede inserindo bytes aleatórios, invertendo bytes adjacentes ou negando valores numéricos. 0xff, 0xffff, 0xffffffff, 0x00, 0x0000, 0x00000000 e 0x80000000 são valores bons para expor falhas de segurança durante o teste de fuzzing. Você pode observar as combinações de interação resultantes usando o AppVerifier. Embora o fuzzing não seja exaustivo, é fácil de implementar e automatizar, podendo capturar os bugs mais evasivos e imprevisíveis.
Para obter mais informações sobre testes de fuzzing, consulte a apresentação do Gamefest 2007 Etapas práticas na segurança do jogo .
-
Assinatura Authenticode
-
O Authenticode é um método para garantir que os arquivos executáveis, os arquivos DLL e os pacotes do instalador do Windows (arquivos .msi) que o usuário recebe não sejam alterados em relação ao que o desenvolvedor lançou. Usando uma combinação de princípios criptográficos, entidades confiáveis e padrões do setor, o Authenticode verifica a integridade do conteúdo executável. A Microsoft fornece uma API criptográfica, CryptoAPI, que pode ser usada para detectar automaticamente a violação do código assinado. Se ocorrer um vazamento de segurança após uma versão, um certificado poderá ser revogado e todo o código assinado com esse certificado deixará de ser autenticado. A revogação de um certificado revogará a validação de todos os títulos assinados com esse certificado. O Windows foi projetado para funcionar com a assinatura Authenticode e alertará um usuário sobre um código não assinado, em situações específicas, que pode expor o computador de um usuário a ataques.
O Authenticode não deve ser considerado um método de eliminação de problemas de segurança, e sim um método de detecção de adulteração após o lançamento de um executável. Um executável ou DLL que contém um problema de segurança explorável pode ser assinado e verificado usando o Authenticode, mas ainda introduzirá o problema de segurança no novo sistema. Somente após a verificação da segurança de um produto ou atualização é que o código deve ser assinado para garantir aos usuários que eles têm uma versão que não foi adulterada.
Mesmo que um desenvolvedor acredite que não há ameaça de que suas versões sejam modificadas, outras tecnologias e serviços dependem do Authenticode. A assinatura de código é fácil de integrar e automatizar e não há razão para os desenvolvedores não terem seus lançamentos assinados.
Para obter mais informações sobre a assinatura do Authenticode, consulte Assinatura do Authenticode para desenvolvedores de jogos.
-
Minimizar privilégios
-
Em geral, os processos devem ser executados com o conjunto mínimo de privilégios necessários para operar. No Windows Vista e no Windows 7, isso é feito usando o Controle de Conta de Usuário, permitindo que o jogo seja executado como um Usuário Padrão em vez de um administrador. Para o Windows XP, geralmente os jogos são sempre executados como administrador. Mesmo no Windows Vista e no Windows 7, às vezes é necessário elevar para direitos totais de administrador para algumas operações específicas.
Nos casos em que o processo está sendo executado com direitos administrativos totais, geralmente apenas alguns direitos além dos de um Usuário Padrão são realmente necessários. O acesso administrativo inclui muitos direitos que não são exigidos pelo código legítimo, mas que poderiam ser usados por um invasor por meio de algum ponto fraco no processo. Exemplos de tais direitos incluem SE_TAKE_OWNERSHIP, SE_DEBUG, SE_CREATE_TOKEN, SE_ASSIGNPRIMARYTOKEN, SE_TCB, SE_SECURITY, SE_LOAD_DRIVER, SE_SYSTEMTIME, SE_BACKUP, SE_RESTORE, SE_SHUTDOWN e SE_AUDIT (consulte Constantes de privilégio).
Embora um processo não possa obter mais direitos depois de iniciado, ele pode abrir mão de direitos facilmente. Na inicialização, o processo pode usar imediatamente as APIs do Win32 para remover direitos que não são necessários.
-
Utilize os recursos de segurança do Windows
-
O Windows Vista e o Windows 7 incluem vários novos recursos que melhoram a segurança do código. O Controle de Conta do Usuário é certamente o mais importante a ser entendido e adotado, mas também existem outros recursos. Além das tecnologias do Windows XP SP2, como o Firewall do Windows, a Prevenção de Execução de Dados, a Verificação de Segurança do Buffer e os Manipuladores de Exceção Segura, que também estão disponíveis no Windows Vista e no Windows 7, há três recursos de segurança mais recentes a serem considerados:
- O recurso opcional de Randomização de Layout de Espaço de Endereço. É habilitado vinculando-se à opção /DYNAMICBASE no Visual Studio 2005 Service Pack 1 ou no Visual Studio 2008. Isso faz com que o sistema randomize as posições de muitas das principais DLLs do sistema em seu espaço de processo, tornando muito mais difícil escrever programas de ataque que possam ser explorados e que se propaguem amplamente pela Internet. Esse sinalizador de vinculador é ignorado pelo Windows XP e versões mais antigas do Windows.
- A corrupção de heap pode levar a uma classe inteira de explorações de segurança. Portanto, o sistema de memória do Windows Vista e do Windows 7 agora oferece suporte a um modo que encerra o processo se for detectada corrupção de heap. Chamar HeapSetInformation com HeapEnableTermianteOnCorruption aceitará esse comportamento. Essa chamada falha no Windows XP e na versão mais antiga do Windows.
- Ao escrever serviços, eles podem ser configurados usando um novo recurso para especificar quais privilégios são realmente necessários, bem como para limitar o acesso de recursos a um SID específico. Isso é feito por meio de ChangeSeviceConfig2 e usando SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO e SERVICE_CONFIG_SERVICE_SID_INFO.
Resumo
O desenvolvimento de um jogo para o mercado atual e futuro é caro e demorado. Lançar um jogo com problemas de segurança acabará custando mais dinheiro e tempo para ser corrigido adequadamente. Portanto, é do interesse de todos os desenvolvedores de jogos integrar ferramentas e técnicas para mitigar as explorações de segurança antes do lançamento.
As informações neste artigo são apenas uma introdução ao que um estúdio de desenvolvimento pode fazer para ajudar a si mesmo e a seus clientes. Mais informações sobre práticas gerais de segurança e informações de segurança podem ser encontradas na Central de desenvolvedores de segurança da Microsoft.