Shellcode

Em hacking, um código sh​​ell (shellcode) é um pequeno trecho de código usado como carga útil na exploração de uma vulnerabilidade de software. É chamado de "código shell" porque normalmente inicia um shell de comando a partir do qual o invasor pode controlar a máquina comprometida, mas qualquer pedaço de código que execute uma tarefa semelhante pode ser chamado de código shell. Como a função de uma carga útil não se limita a meramente gerar um shell, alguns sugeriram que o nome código shell é insuficiente.[1] No entanto, as tentativas de substituir o termo não tiveram ampla aceitação. Código shell é comumente escrito em código de máquina.

Ao criar código shell, geralmente é desejável o tornar pequeno e executável, o que permite que seja usado na maior variedade de situações possível.[2] Escrever um bom código shell pode ser tanto uma arte quanto uma ciência.[3] No código assembly, a mesma função pode ser executada de várias maneiras e há alguma variedade nos comprimentos dos opcodes que podem ser usados para esse propósito. Bons escritores de código shell podem colocar esses pequenos opcodes em uso para criar um código shell mais compacto.[4] Alguns alcançaram o menor tamanho possível, mantendo a estabilidade.[5]

Tipos de código shellEditar

O código shell pode ser local ou remoto, dependendo se ele dá (ao invasor) controle sobre a máquina local ou sobre outra máquina por meio de uma rede (remota).

LocalEditar

O código shell local é usado por um invasor que tem acesso limitado à uma máquina mas pode explorar uma vulnerabilidade (um estouro de buffer, por exemplo) em um processo com privilégios mais altos nessa máquina. Se executado com sucesso, o código shell fornecerá (ao invasor) acesso à máquina com os mesmos privilégios mais elevados do processo alvo ao invasor.

RemotoEditar

O código shell remoto é usado quando um invasor deseja atingir um processo vulnerável em execução em outra máquina em uma rede local, intranet ou rede remota. Se executado com sucesso, o código shell pode fornecer ao invasor acesso à máquina alvo na rede. Os códigos shell remotos normalmente usam conexões de soquetes TCP/IP padrõe para permitir que o invasor acesse o shell na máquina de destino. Esse código shell pode ser categorizado com base em como essa conexão é configurada: se o código shell estabelece a conexão, é chamado de "shell reverso" ou código shell de conexão porque o código shell se conecta de volta à máquina do invasor. Por outro lado, se o invasor estabelecer a conexão, o código shell é chamado de bindshell porque o código shell se liga à uma determinada porta na máquina da vítima. Há um código shell peculiar chamado bindshell de porta aleatória que ignora a parte de ligação e escuta em uma porta aleatória disponibilizada pelo sistema operacional. Por causa disso, o bindshell de porta aleatória se tornou o menor e mais estável código shell bindshell para x86_64 disponível até hoje. Um terceiro tipo, muito menos comum, é o código shell de reutilização de soquete. Este tipo de código shell é algumas vezes usado quando um exploit estabelece uma conexão com o processo vulnerável que não é fechado antes que o código shell seja executado. O código shell pode então reutilizar essa conexão para se comunicar com o invasor. O código shell de reutilização de soquete é mais elaborado, uma vez que o código shell precisa descobrir qual conexão reutilizar e a máquina pode ter muitas conexões abertas.[6]

Um firewall pode ser usado para detectar conexões de saída feitas por código shell de conexão, bem como conexões de entrada feitas por bindshells. Eles podem, portanto, oferecer alguma proteção contra um invasor, mesmo se o sistema estiver vulnerável, evitando que o invasor se conecte ao shell criado pelo código shell. Esta é uma das razões pelas quais o código shell de reutilização de soquete às vezes é usado: ele não cria novas conexões e, portanto, é mais difícil de detectar e bloquear.

Baixar e executarEditar

O baixar e executar é um tipo de código shell remoto que baixa e executa alguma forma de malware no sistema alvo (de destino). Este tipo de código shell não gera um shell, mas instrui a máquina a baixar um certo arquivo executável da rede, o salvar no disco e o executar. Hoje em dia, é comumente usado em ataques drive-by download, em que a vítima visita uma página web mal intencionada que, por sua vez, tenta executar esse download e executar o código shell para instalar software na máquina da vítima. Uma variação deste tipo de código shell baixa e carrega uma biblioteca.[7][8] As vantagens desta técnica são que o código pode ser menor, não requer gerar um novo processo no sistema alvo e não precisa de código para limpar o processo de destino, pois isso pode ser feito pelo biblioteca carregada no processo.

Em estágiosEditar

Quando a quantidade de dados que um invasor pode injetar no processo de destino é muito limitada para executar o shellcode útil diretamente, pode ser possível o executar em estágios. Primeiro, um pequeno código shell (estágio 1) é executado. Este código então baixa um pedaço maior de código shell (estágio 2) na memória do processo e o executa.

Caça ao ovoEditar

Esta é outra forma de código shell em estágios, que é usado se um invasor puder injetar um código shell maior no processo, mas não puder determinar onde ele irá terminar no processo. Um pequeno código shell de "caça ao ovo" é injetado no processo em um local previsível e executado. Este código então pesquisa o espaço de endereço do processo para o código shell maior (o "ovo") e o executa.[9]

OmeleteEditar

Esse tipo de código shell é semelhante ao código shell de caça ao ovo, mas procura vários pequenos blocos de dados (ovos) e os recombina em um bloco maior (o omelete) que é subsequentemente executado. Isso é usado quando um invasor só pode injetar uma série de pequenos blocos de dados no processo.[10]

Estratégia de execução de código shellEditar

Um exploit normalmente injeta código shell no processo alvo (de destino) antes ou ao mesmo tempo em que explora uma vulnerabilidade para obter controle sobre o contador do programa. O contador do programa é ajustado para apontar para o código shell, após o qual ele é executado e realiza sua tarefa. A injeção de código shell geralmente é feita armazenando o código shell nos dados enviados pela rede para o processo vulnerável, o fornecendo em um arquivo que é lido pelo processo vulnerável ou por meio da linha de comando ou ambiente no caso de explorações locais.

Codificação de código shellEditar

Como a maioria dos processos filtra ou restringe os dados que podem ser injetados, o código shell geralmente precisa ser escrito para permitir essas restrições. Isso inclui tornar o código pequeno, livre de nulos ou alfanumérico. Várias soluções foram encontradas para contornar essas restrições, incluindo:

  • Otimizações de design e implementação para diminuir o tamanho do código shell.
  • Modificações de implementação para contornar as limitações no intervalo de bytes usado no código shell.
  • Código de auto modificação que modifica vários bytes de seu próprio código antes de o executar para recriar bytes que normalmente são impossíveis de injetar no processo.

Uma vez que a detecção de intrusão pode detectar assinaturas de códigos shell simples enviados pela rede, muitas vezes é codificado, feito com auto descriptografia ou polimórfico para evitar a detecção.

Codificação percentualEditar

Exploits que visam os navegadores geralmente codificam o código shell em uma string JavaScript usando codificação percentual, codificação de sequência de escape "\uXXXX" ou codificação de entidade.[11] Alguns exploits também ofuscam a string codificada do código shell para evitar a detecção pelo IDS.

Por exemplo, na arquitetura IA-32, é assim que ficariam duas instruções NOP (sem operação), primeiro não codificadas:

90             NOP
90             NOP
NOPs duplos codificados:
codificação percentual unescape("%u9090")
literal unicode "\u9090"
Entidade HTML/XML "邐" ou "邐"

Esta instrução é usada em slides NOP.

Código shell livre de nulosEditar

A maioria dos códigos shell são gravados sem o uso de bytes nulos porque se destinam a ser injetados em um processo alvo (de destino) por meio de strings terminadas em nulos. Quando uma string terminada em nulo é copiada, ela será copiada até e incluindo o primeiro nulo, mas os bytes subsequentes do código shell não serão processados. Quando o código shell que contém nulos é injetado dessa forma, apenas parte do código shell é injetado, o tornando incapaz de ser executado com êxito.

Para produzir código shell livre de nulos a partir de código shell que contém bytes nulos, se pode substituir as instruções de máquina que contêm zeros por instruções que têm o mesmo efeito, mas não contêm nulos. Por exemplo, na arquitetura IA-32, se pode substituir esta instrução:

B8 01000000    MOV EAX,1          // Configurar o registro EAX para 0x000000001

que contém zeros como parte do literal (1 se expande para 0x00000001) por essas instruções:

33C0           XOR EAX,EAX        // Configurar o registro EAX para 0x000000000,
40             INC EAX            // Aumentar o EAX para 0x00000001

que têm o mesmo efeito, mas levam menos bytes para codificar e estão livres de nulos.

Código shell alfanumérico e imprimívelEditar

Em certas circunstâncias, um processo de destino filtrará qualquer byte do código shell injetado que não seja um caractere imprimível ou alfanumérico. Sob tais circunstâncias, a gama de instruções que podem ser usadas para escrever um código shell se torna muito limitada. Uma solução para este problema foi publicada por Rix na Phrack 57[12] em que ele mostrou que era possível transformar qualquer código em código alfanumérico. Uma técnica frequentemente usada é criar código auto modificador, porque isso permite que o código modifique seus próprios bytes para incluir bytes fora do intervalo normalmente permitido, expandindo assim o intervalo de instruções que pode usar. Usando este truque, um decodificador auto modificador que inicialmente usa apenas bytes no intervalo permitido pode ser criado. O código principal do código shell é codificado, usando apenas bytes na faixa permitida também. Quando o código shell de saída é executado, o decodificador pode modificar seu próprio código para poder usar qualquer instrução necessária para funcionar corretamente e, em seguida, continuar a decodificar o código shell original. Após decodificar o código shell, o decodificador transfere o controle para ele, para que possa ser executado normalmente. Foi demonstrado que é possível criar um código shell arbitrariamente complexo que se parece com um texto normal em inglês.[13]

Código shell à prova de UnicodeEditar

Os programas modernos usam strings Unicode para permitir a internacionalização do texto. Frequentemente, esses programas convertem as strings ASCII de entrada em Unicode antes de as processar. As strings Unicode codificadas em UTF-16 usam dois bytes para codificar cada caractere (ou quatro bytes para alguns caracteres especiais). Quando uma string ASCII (ISO/IEC 8859-1 em geral) é transformada em UTF-16, um byte zero é inserido após cada byte na string original. Obscou provou na Phrack 61[14] que é possível escrever um código shell que pode ser executado com sucesso após esta transformação. Existem programas que podem codificar automaticamente qualquer código shell em código shell alfanumérico à prova de UTF-16, com base no mesmo princípio de um pequeno decodificador auto modificador que decodifica o código shell original.

PlatformasEditar

A maior parte do código shell é escrita em código de máquina por causa do baixo nível em que a vulnerabilidade que está sendo explorada dá a um invasor acesso ao processo. O código shell é, portanto, frequentemente criado para atingir uma combinação específica de processador, sistema operacional e pacote de serviços, chamada de plataforma. Para alguns exploits, devido às restrições colocadas no código shell pelo processo alvo (de destino), um código shell muito específico deve ser criado. No entanto, não é impossível para um código shell funcionar para vários exploits, pacotes de serviço, sistemas operacionais e até mesmo processadores.[15] Essa versatilidade é comumente alcançada criando várias versões do código shell que têm como alvo as várias plataformas e criando um cabeçalho que se ramifica para a versão correta para a plataforma em que o código está sendo executado. Quando executado, o código se comporta de maneira diferente para diferentes plataformas e executa a parte certa do código shell para a plataforma em que está sendo executado.

Análise de código shellEditar

O código shell não pode ser executado diretamente. Para analisar o que um código shell tenta fazer, ele deve ser carregado em outro processo. Uma técnica de análise comum é escrever um pequeno programa em C que mantém o código shell como um buffer de bytes e, em seguida, usar um ponteiro de função ou usar um assembler embutido para transferir a execução para ele. Outra técnica é usar uma ferramenta online, como o shellcode_2_exe, para incorporar o código shell em uma casca executável pré fabricada que pode então ser analisada em um depurador padrão. Também existem ferramentas de análise de código shell especializadas, como o projeto sclog iDefense, que foi originalmente lançado em 2005 como parte do Malcode analyst pack. O sclog é projetado para carregar arquivos de código shell externos e os executar dentro de uma estrutura de registro de API. Também existem ferramentas de análise de código shell baseadas em emulação, como o aplicativo sctest, que faz parte do pacote libemu de plataforma cruzada. Outra ferramenta de análise de código shell baseada em emulação, construída em torno da biblioteca libemu, é o scdbg, que inclui um shell de depuração básico e recursos de relatório integrados.

Ver tambémEditar

ReferênciasEditar

  1. Foster, James C.; Price, Mike (12 de abril de 2005). Sockets, shellcode, porting e coding: exploits de engenharia reversa e codificação de ferramentas para profissionais de segurança (em inglês). [S.l.]: Livros de ciência e tecnologia da Elsevier. ISBN 1-59749-005-9 
  2. Chris Anley; Jack Koziol (2007). O manual do codificador de shell: descobrindo e explorando falhas de segurança (em inglês) 2ª ed. Indianapolis, IN: Wiley. ISBN 978-0-470-19882-7. OCLC 173682537 
  3. Gupta, Sunil (2019), «Ataque de estouro de buffer», ISBN 978-1-4842-4340-4, Berkeley, CA: Apress, Hacking ético - Orquestrando ataques (em inglês), doi:10.1007/978-1-4842-4340-4_12 
  4. Foster, James C. (2005). Ataques de estouro de buffer: detectar, explorar, prevenir (em inglês). Rockland, MA: Syngress. ISBN 1-59749-022-9. OCLC 57566682 
  5. «Tiny Execve sh - Linguagem Assembly - Linux/x86». GitHub (em inglês). Consultado em 1 de fevereiro de 2021 
  6. «Código shell/Reutilização de soquete» (em inglês). BHA. 6 de junho de 2013. Consultado em 7 de junho de 2013 
  7. SkyLined (11 de janeiro de 2010). «Código shell "Download and LoadLibrary" lançado» (em inglês). Consultado em 19 de janeiro de 2010. Arquivado do original em 23 de janeiro de 2010 
  8. SkyLined (11 de janeiro de 2010). «Código shell "Download and LoadLibrary" para Windows x86» (em inglês). Consultado em 19 de janeiro de 2010 
  9. Skape (9 de março de 2004). «Procurando o espaço de endereço virtual do processo com segurança» (PDF) (em inglês). nologin. Consultado em 19 de março de 2009 
  10. SkyLined (16 de março de 2009). «Código shell omelete w32 SEH» (em inglês). Skypher.com. Consultado em 19 de março de 2009. Arquivado do original em 23 de março de 2009 
  11. «Grande número de padrões de não escape em JavaScript Sistemas de segurança de internet IBM» (em inglês). Arquivado do original em 20 de março de 2015 
  12. Rix (8 de novembro de 2001). «Escrevendo códigos shell alfanuméricos ia32» (em inglês). Phrack. Consultado em 29 de fevereiro de 2008 
  13. Mason, Joshua; Small, Sam; Monrose, Fabian; MacManus, Greg (novembro de 2009). «Código shell em inglês» (PDF) (em inglês). Consultado em 10 de janeiro de 2010 
  14. Obscou (13 de agosto de 2003). «Criação de códigos shell IA32 à prova de Unicode» (em inglês). Phrack. Consultado em 29 de fevereiro de 2008 
  15. Eugene (11 de agosto de 2001). «Código shell de abrangência de arquitetura» (em inglês). Phrack. Consultado em 29 de fevereiro de 2008 

Ligações externasEditar

Banco de dados de códigos shell multi plataforma (em inglês)