/*
Off-Topic: Está disponível no Shell-Storm versão do
Shell Bind TCP
usando o método GetPC (GetEIP). Os demais shellcodes apresentados neste post
também já foram disponibilizados no mesmo repositório. Tks again, Salwan.
*/
Enquanto que em muita área por aí o que importa é ter ou fazer algo grande, quando falamos em shellcodes sempre os queremos minúsculos, correto? E mesmo que isso não seja tão importante para as mais recentes técnicas de exploração, você não gostaria que o seu fosse menor? =D
Bom, como sou teimoso, aproveitei o tempo de viagem de onde estou trabalhando até minha casa (10h de ônibus) para brincar um pouco com as versões anteriores que disponibilizei e consequentemente aprender mais. Meu notebook, graças ao Jupiter, aguentou 7h de pdfs, chrome (em offline), editores, compilação e muito debugging.
O que fazer para se diminuir o tamanho de um shellcode?
Com a questão em mente, condensei nas regras abaixo o que aprendi.
Regra n. 01 - Certificar-se da “pureza” dos registers antes de serem preenchidos com valores menores que o da arquitetura
Num ambiente de desenvolvimento e exploração, ao se testar um shellcode projetado, os registers se apresentam sem lixo, o que pode mascarar o seu real funcionamento.
Temos que ter em mente que um shellcode é um pedaço de código injetado em um programa já em execução com suas STACK e registers já em utilização. Se começarmos a simplesmente utilizar esses registers ou a STACK sem as devidas providências o shellcode não vai servir ao seu propósito.
Na arquitetura 32 bits, se eu movimentar (não gosto de usar o termo movimentar; mesmo a instrução se chamando MOV o que ela faz, em verdade, é copiar) um valor para um register [mov eax, 10], a instrução preenche totalmente o EAX com o valor imediato.
Se antes da cópia EAX tiver 0xffffffff, após ele ficará com 0x0000000a:
b8 0a 00 00 00 mov eax,0xa
Até então sem problemas, certo? Não, para um shellcode! O opcode B8 que movimenta o valor imediato 0xa (10) preenche todos os 32 bits de EAX, e você deve se recordar que não podemos ter null bytes para que o payload seja funcional.
Contornando a situação com [mov al, 10]:
b0 0a mov al,0xa
Like a charm? Copiamos agora apenas o byte necessário 0xa para o register AL de 8 bits.
Contudo, se EAX já estiver, hipoteticamente, com 0xffffffff, ao copiarmos apenas para AL, o valor final será 0xffffff0a (-246). Qualquer syscall ao usar EAX retornará erro.
Dando a volta por cima
Eu estava usando um truque com a instrução CDQ - convert double to quad - para zerar EDX, após preparar EAX com algum valor inicial, ex: PUSH 10 (6A, 0A), POP EAX (58). A CDQ é de apenas um opcode (99), por isso me encantou. Em suma, com ela eu podia despoluir EDX e já deixar EAX preparada com apenas 3 bytes.
Sendo que o zero é essencial para o preenchimento dos argumentos das syscalls mas não pode estar presente no shellcode, essa técnica permite termos o zero em EDX para utilização posterior.
E eu não poderia fazer isso usando XOR? Sim, mas a custo de mais um byte.
31 d2 xor edx,edx
...
99 cdq
E para limpar os demais registers necessários no início do shellcode sempre serão utilizados dois opcodes, seja com a técnica do XOR, com a do PUSH reg, POP reg ou com a do MOV reg, reg.
31 d2 xor edx,edx
...
52 push edx #que já será zero após o cdq
5b pop ebx
...
89 dd mov ebp,ebx
De tanto garimpar, descobri um truque com o uso da instrução MUL - unsigned multiply - que permite despoluir três registers de uma só vez (EAX, EBX [poderia ser outro: ECX, ESI, …] e EDX).
31 db xor ebx,ebx
f7 e3 mul ebx
Ele gera um byte a mais, entretanto um a menos se usarmos um XOR, para zerar EBX logo após o uso do CDQ.
Temos também o mágico XCHG que permuta os valores de dois registers a custo de apenas um byte se um dos envolvidos na troca for o EAX; qualquer outro tipo de permuta terá dois opcodes.
95 xchg ebp,eax
96 xchg esi,eax
87 d9 xchg ecx,ebx
De toda sorte, essas formas (CDQ, MUL, MOV, XOR e XCHG) são boas e devem, se possível, ser utilizadas em conjunto. O sucesso na redução do tamanho irá depender de quais registers serão necessários às syscalls do shellcode e de que valores já estarão contidos neles, dando vantagem a uma forma em detrimento às demais.
Regra n. 02 - Reutilizar dados já inseridos na STACK
Eu estava simplesmente fazendo PUSH de todos os argumentos necessários a cada syscall, desprezando o que eu já havia inserido na STACK. Obviamente os primeiros valores a serem utilizados precisam ser inseridos por nós mesmos, pois não sabemos o que há na pilha (o contrário pode ser dito se se tratar de uma aplicação em específico, cujo exploit fora desenvolvido por nós mesmos).
Socket
52 push edx
53 push ebx
6a 02 push 0x2
89 e1 mov ecx,esp
Bind
b3 02 mov bl,0x2
52 push edx
66 68 2b 67 pushw 0x672b
66 6a 02 pushw 0x2
89 e1 mov ecx,esp
No exemplo acima, sem reutilizar os dados da STACK, ao criar a estrutura sockaddr_in para fazer o Bind, são gerados 12 bytes. Todavia, vejam a diferença fazendo uso dos dados já existentes.
5b pop ebx
5e pop esi
52 push edx
66 68 2b 67 pushw 0x672b
Fantasticamente geramos apenas 7 bytes (cinco a menos). É importante ressaltar que a instrução POP apenas copia o valor da pilha para algum destino, modificando o ponteiro (ESP) do topo da pilha e deixando o valor intacto na memória, contrariamente à PUSH que o substitui. Atente para o fato de que ECX já contém o ponteiro para os dados que estamos utilizando, portanto, não o alteramos.
Se algum dado distante na STACK precisar ser modificado para reutilização correta dos demais, tiramos proveito fazendo a modificação com a MOV e um ponteiro, gerando apenas 3 bytes.
89 51 04 mov DWORD PTR [ecx+0x4],edx
Mais uma vez, devemos ponderar qual método é mais eficaz em cada caso.
Regra n. 03 - Usar instruções com menor número de opcodes
Mesmo esta regra sendo óbvia e estar implícita nas anteriores, faço questão de exemplificar com algumas instruções, para que haja melhor entendimento.
43 inc ebx
b3 02 mov bl,0x2
No caso acima, se EBX já contivesse 1, mais inteligente seria apenas o incrementarmos com INC, em vez de o atribuirmos 2 com MOV. Abaixo, segue exemplo de redução usando a LEA - load effective address - para uso aritmético com vários operandos em vez da ADD (tks Pedro Fausto).
01 d8 add eax,ebx
83 c0 02 add eax,0x2
8d 44 18 02 lea eax,[eax+ebx*1+0x2]
Regra n. 04 - GetPC (GetEIP) - Adendo
Esqueci-me de mencionar, na postagem original, a técnica GetPC que utilizei como demonstração no shellcode Shell Bind TCP - GetPC (github: assembly, fluxograma).
Se o shellcode tiver um tamanho considerável e houver uma certa quantidade de instruções que se repetem, esse método é válido para se diminuir o payload usando as CALL e RET.
Analisem os códigos! Vale à pena.
Os menores do mundo? Não sei, diga-me!
O resultado de tanto mexido foi o engendramento dos três, aparentemente, menores shellcodes do mundo nas suas respectivas especificidades.
Shellcode Improvements (github com os concernentes fluxogramas e shellcode launchers)
Tiny Shell Bind TCP - 73 bytes
Tiny Shell Bind TCP Random Port - 57 bytes
Tiny Shell Reverse TCP - 67 bytes
Eu desafio vocês!
Pensando nisso estou lançando um concurso para redução dos shellcodes apresentados. Para concorrer basta criar um shellcode dos tipos acima com menor tamanho em bytes e enviar para geyslan@gmail.com. Para redução podem ser utilizados, se preferirem, os que disponibilizei.
Premiação
Aprendizado! E menos bytes! ;D
Vemo-nos em breve!
Mais Informações
aiuto: Java, C++ & ASM Ref
Basic Assembly Language (x86)
Shell-Storm
Intel 64 and IA-32 Manuals