Resposta ao Desafio da Semana #8 [Crash – Buffer Overflow em C/C++]



Por: Roberto Alexis Farah


 


Olá!


Eis o link para o Desafio: http://blogs.technet.com/latam/archive/2006/07/14/441892.aspx


 


Agora vamos a resposta…


 


Esse é um bug bastante difícil de se identificar e extremamente nocivo pois o sintoma nem sempre é um crash de aplicação! Além disso, bugs desse gênero, caracterizados como buffer overflow, podem ser explorados, por exemplo, através de shellcodes.


 


<SOBRE A COMPLEXIDADE DOS DESAFIOS>


 


Conforme venho fazendo nos desafios, o problema dessa vez também é bastante simples, embora não necessariamente fácil de se achar a primeira vista.


Os problemas simples que passam despercebido por desenvolvimento e testes se tornam problemas difíceis de serem isolados quando a aplicação está finalizada e rodando. E, por incrível que pareça, a causa raiz de complexos incidentes que nos chegam, na grande maioria das vezes são problemas simples que passaram despercebidos no início! Essa é a maior motivação pela qual insisto em colocar desafios que são simples. A beleza deles está na simplicidade! Embora simples eles continuam a ocorrer frequentemente.


 


Isolar um problema simples escondido no meio de um enorme sistema é algo complexo. Como sempre digo, a solução costuma ser a parte mais simples e isolar o problema em si é a parte mais complicada.


 


Imaginem, no caso desse desafio #8, se o problema tivesse, como numa situação real, escondido sob milhares de linhas de código fonte!


 


</SOBRE A COMPLEXIDADE DOS DESAFIOS>


 


Shellcodes são códigos de máquina usados para explorar um bug de software, de modo a permitir que um usuário não autorizado possa ter acesso ao computador. Para aqueles que desejam saber mais colocarei uma bibliografia ao final do artigo.


 


 


PROBLEMA


 


O problema ocasionando o intermitente sintoma de Crash é um buffer overflow. Ele é ocasionado sempre que o parâmetro que representa a quantidade de strings for negativo.


 


Exemplo:


 


App.exe -60 “aadlfhsfsjdhsjkhjskhggkhjghaghdjghdsjghjsgsdghghsdghdsjghdsjghdsjghdsghsdjk”


 


O que ocorre aqui é bastante interessante.


Um número negativo é menor que o tamanho do buffer interno da aplicação, logo, pela lógica da mesma, ela considera esse valor como válido.


Entretanto, na chamada de memcpy() temos:


 


void *memcpy( void *dest, const void *src, size_t count );


 


http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore98/HTML/_crt_memcpy.asp


 


O terceiro parâmetro vai tratar o valor negativo como um unsigned, ou seja, um tipo que não aceita números negativos, portanto, o número negativo se tornará um enorme número positivo!


Sendo um grande número positivo, memcpy() vai sobreescrever a área reservada para o buffer interno, que, no caso, é de 10 bytes.


Dependendo da sobreposição dessa área poderemos ter um crash de aplicação!


 


Eis o que ocorre na prática, usando o Visual C++ 6.0 e o Visual Studio 2005. Depurei a aplicação compilada como Debug para ficar mais fácil visualizar o problema.


 


Versão compilada com Visual C++ 6.0:


 


0012ff70  cccccccc cccccccc cccccccc ffffffc4        ß 0xCCCCCCCC representa a memória não inicializada de cBuffer em modo Debug.


0012ff80  0012ffc0 00401809 00000003 00321190    ß Endereço da pilha em vermelho.


0012ff90  00321258 00011970 7c9118f1 7ffdf000


0012ffa0  00000001 00000001 0012ff94 afe75d08


0012ffb0  0012ffe0 004048d0 00422180 00000000


0012ffc0  0012fff0 7c816d4f 00011970 7c9118f1


0012ffd0  7ffdf000 8054a6ed 0012ffc8 88e8c790


0012ffe0  ffffffff 7c8399f3 7c816d58 00000000


 


 


ChildEBP RetAddr                                             ß Pilha durante execução.


0012ff80 00401809 BUG1!main+0x29


0012ffc0 7c816d4f BUG1!mainCRTStartup+0xe9


0012fff0 00000000 kernel32!BaseProcessStart+0x23


 


Após algumas execuções:


 


0012ff70  73676466 73646764 73736467 67736664    ß Passou a área do buffer e sobreescreveu pilha…


0012ff80  67736467 00401809 00000003 00321190


0012ff90  00321258 00011970 7c9118f1 7ffdf000


0012ffa0  00000001 00000001 0012ff94 afe75d08


0012ffb0  0012ffe0 004048d0 00422180 00000000


0012ffc0  0012fff0 7c816d4f 00011970 7c9118f1


0012ffd0  7ffdf000 8054a6ed 0012ffc8 88e8c790


0012ffe0  ffffffff 7c8399f3 7c816d58 00000000


 


 


Versão compilada com Visual Studio 2005:


 


 


0012ff48  cccccccc cccccccc cccccccc cccccccc           ß Área de buffer de cBuffer.


0012ff58  cccccccc 00000000 cccccccc 3574e29d


0012ff68  0012ffb8 00411a36 00000003 00352650


0012ff78  00353288 3574e24d 00011970 7c9118f1


0012ff88  7ffde000 b25fe8f0 00000000 00000000


0012ff98  00130000 00000000 0012ff7c 0000431a


0012ffa8  0012ffe0 0041107d 3527742d 00000000


0012ffb8  0012ffc0 0041187d 0012fff0 7c816d4f


 


ChildEBP RetAddr                                                      ß Início da execução…


0012ff68 00411a36 Bug!main+0x39


0012ffb8 0041187d Bug!__tmainCRTStartup+0x1a6


0012ffc0 7c816d4f Bug!mainCRTStartup+0xd


0012fff0 00000000 kernel32!BaseProcessStart+0x23


 


 


Apos algumas execuções temos:


 


0012ff48  4f535349 20274520 54204d55 45545345        ß Reparem no buffer overflow!


0012ff58  54202d20 41545345 214f444e fdfdfd00


0012ff68  abababfd abababab 00000003 00352650


0012ff78  00353288 3574e24d 00011970 7c9118f1


0012ff88  7ffde000 b25fe8f0 00000000 00000000


0012ff98  00130000 00000000 0012ff7c 0000431a


0012ffa8  0012ffe0 0041107d 3527742d 00000000


0012ffb8  0012ffc0 0041187d 0012fff0 7c816d4f


 


 


ChildEBP RetAddr                                                


0012fe04 10233732 MSVCR80D!fastcopy_I+0x41


0012fe34 10233794 MSVCR80D!_VEC_memcpy+0x52


0012fe64 0041140f MSVCR80D!_VEC_memcpy+0xb4


0012fe04 10233732 Bug!main+0x6f


0012fe34 10233794 MSVCR80D!_VEC_memcpy+0x52


0012fe64 0041140f MSVCR80D!_VEC_memcpy+0xb4


0012fe34 10233794 Bug!main+0x6f


0012fe64 0041140f MSVCR80D!_VEC_memcpy+0xb4


0012fe64 0041140f Bug!main+0x6f


0012ff68 abababab Bug!main+0x6f                ß Pilha sendo corrompida!


0012ff80 7c9118f1 0xabababab                     ß Pilha sendo corrompida!


00011970 00000000 ntdll!RtlDeleteCriticalSection+0x72   ß Pilha sendo corrompida!


 


 


 


Em seguida temos uma exceção gerada após sobreescrever boa parte da pilha:


 


eax=0012ff50 ebx=003526b0 ecx=01fffffe edx=00000000 esi=00352730 edi=0012ffd0


eip=10233811 esp=0012fdfc ebp=0012fe04 iopl=0         nv up ei pl nz na po nc


cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202


MSVCR80D!fastcopy_I+0x41:


10233811 660f7f5f30      movdqa  xmmword ptr [edi+30h],xmm3 ds:0023:00130000=00002498000000010000002078746341


 


 


E olhem a área de buffer da variável cBuffer após exceção:


 


0012ff48  4f535349 20274520 54204d55 45545345        ß Lindo né, gente? J


0012ff58  54202d20 41545345 214f444e fdfdfd00


0012ff68  abababfd abababab feeefeab feeefeee


0012ff78  00000000 00000000 00170124 00ee04ee


0012ff88  00350178 00350178 feeefeee feeefeee


0012ff98  feeefeee feeefeee feeefeee feeefeee


0012ffa8  feeefeee feeefeee feeefeee feeefeee


0012ffb8  feeefeee feeefeee feeefeee feeefeee


 


E a pilha corrompida:


 


ChildEBP RetAddr 


0012fe04 10233732 MSVCR80D!fastcopy_I+0x41


0012fe34 10233794 MSVCR80D!_VEC_memcpy+0x52


0012fe64 0041140f MSVCR80D!_VEC_memcpy+0xb4


0012fe04 10233732 Bug!main+0x6f


0012fe34 10233794 MSVCR80D!_VEC_memcpy+0x52


0012fe64 0041140f MSVCR80D!_VEC_memcpy+0xb4


0012fe34 10233794 Bug!main+0x6f


0012fe64 0041140f MSVCR80D!_VEC_memcpy+0xb4


0012fe64 0041140f Bug!main+0x6f


0012ff68 abababab Bug!main+0x6f


0012ff74 00000000 0xabababab


 


Detalhes da exceção:


 


ExceptionAddress: 10233811 (MSVCR80D!fastcopy_I+0x00000041)


   ExceptionCode: c0000005 (Access violation)


  ExceptionFlags: 00000000


NumberParameters: 2


   Parameter[0]: 00000001


   Parameter[1]: 00130000                       ß Repare nesse endereço lá em cima onde há o dump de cBuffer.


Attempt to write to address 00130000


 


 


Eis o endereço:


 


    00130000 : 00130000 – 00003000


                    Type     00040000 MEM_MAPPED


                    Protect  00000002 PAGE_READONLY


                    State    00001000 MEM_COMMIT


                    Usage    RegionUsageIsVAD


 


 


Agora olhem o endereço do buffer ‘cBuffer’


 


    00030000 : 00126000 – 0000a000


                    Type     00020000 MEM_PRIVATE


                    Protect  00000004 PAGE_READWRITE


                    State    00001000 MEM_COMMIT


                    Usage    RegionUsageStack


                    Pid.Tid  1278.11d4


 


A aplicação foi sobreescrevendo a pilha até alcançar um endereço sem permissão para escrita. Entretanto, uma corrupção de pilha poderia fazer algo diferente, como apenas alterar o valor da próxima variável local. Logo, o sintoma poderia ter sido outro e bem mais difícil de diagnosticar!


 


 


SOLUÇÃO


 


Eis duas possíveis soluções…


 


1-       Mudar o tipo da variável nLen para unsigned int, portanto, em caso de número negativo o mesmo será sempre 0:


 


int main(int argc, char* argv[])


{


        if(argc < 3)


        {


                return 0;


        }


       


        unsigned int nLen = 0;  // Mudando o tipo da variavel funciona.


 


       


        // Nao assumir string com NULL terminator.


        // Por isso usei ‘cBuffer’ ao inves de ‘pszBuffer’.


        char cBuffer[BUFFER_SIZE]; 


       


        nLen = atoi(argv[1]);


       


        if(nLen < BUFFER_SIZE)


        {


                memcpy(cBuffer, argv[2], nLen);


        }


        else


        {


                printf(“Muita informacao para copiar…\n”);


        }


       


        return 0;


}


 


 


2- Fazer a verificação explícita de número negativo:


 


int main(int argc, char* argv[])


{


        if(argc < 3)


        {


                return 0;


        }


       


        int nLen = 0;


       


        // Nao assumir string com NULL terminator.


        // Por isso usei ‘cBuffer’ ao inves de ‘pszBuffer’.


        char cBuffer[BUFFER_SIZE]; 


       


        nLen = atoi(argv[1]);


       


        if( (nLen >= 0) && (nLen < BUFFER_SIZE) )


        {


                memcpy(cBuffer, argv[2], nLen);


        }


        else


        {


                printf(“Muita informacao para copiar…\n”);


        }


       


        return 0;


}


 


 


 


Legal, né? Pois é, boa parte dos meus cabelhos grisalhos (e precoces) são decorrência de trabalhar em incidentes com esse tipo de problema. J


 


Eis ótimos livros sobre o assunto:


 


Writing Secure Code, Second Edition (Paperback)


http://www.amazon.com/gp/product/0735617228/sr=8-2/qid=1152664556/ref=sr_1_2/104-0455824-0491134?ie=UTF8


 


Secure Coding in C and C++ (SEI Series in Software Engineering) (Paperback)


http://www.amazon.com/gp/product/0321335724/sr=8-1/qid=1152664556/ref=pd_bbs_1/104-0455824-0491134?ie=UTF8


 


Buffer Overflow Attacks (Paperback)


http://www.amazon.com/gp/product/1932266674/qid=1152664641/sr=2-3/ref=pd_bbs_b_2_3/104-0455824-0491134?s=books&v=glance&n=283155


 


The Shellcoder’s Handbook : Discovering and Exploiting Security Holes (Paperback)


http://www.amazon.com/gp/product/0764544683/qid=1152664641/sr=2-1/ref=pd_bbs_b_2_1/104-0455824-0491134?s=books&v=glance&n=283155


 


Shellcoder’s Programming Uncovered (Uncovered series) (Paperback)


http://www.amazon.com/gp/product/193176946X/qid=1152664725/sr=1-6/ref=sr_1_6/104-0455824-0491134?s=books&v=glance&n=283155


 


 


Até o próximo desafio.

Comments (2)

  1. LatamBlog says:

    Esse desafio foi massa! (como já dizia os meus amigos paulistas) 🙂

    Joao@MS-LasColinas 😉

  2. Alexandre Albuquerque says:

    Bom, parabens Roberto. Continue postando desafios  para que todos aprendam (de uma forma bem mais divertida que clareamento capilar).

    Nesse desafio é importante afirmar que olhando o problema com uma aplicação ou duas chamando a problemática não seria o fim do mundo, mas se tivermos uma aplicação importante que seja acessada diversas vezes, aí meu rapaz é olhar pro espelho e aguardar cabelos brancos e olheiras.

    Também é compreensível que desenvolvedores errem ao esquecerem dos números negativos (ou qualquer outro erro óbvio), os diretores querem rapidez, e pressionam de modo que ao invéz de ajudar atrapalham o processo! Logico que nem todos.

    Mas parabenizo a toda equipe da Microsoft pelo blog, e colocando oportunidades ao pessoal latino!

    Até o Desafio 9!