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

Por: Roberto Alexis Farah

Olá!

Eis o link para o Desafio: https://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 );

https://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)

https://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)

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

Buffer Overflow Attacks (Paperback)

https://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)

https://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)

https://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.