Engenharia Reversa #4— Utilizando OllyDgb (parte 2)

Introdução:

(Download dos matérias utilizados neste post, caso utilize Windows 8 ou superior: execute o arquivo install.cmd como administrador para instalar o Win32help que iremos utilizar)

Neste tutorial, continuaremos aprendendo a usar o Olly. Usaremos a mesma versão utilizada no último tutorial (não viu meu post anterior? clique aqui).

Help: Para instalar o API Help você deve copiar o arquivo até a pasta do Olly e depois selecionar no Olly -> ‘Help’ -> Select API Help Files in olly e depois selecionar o arquivo :)

DLL:

Como eu disse em um tutorial anterior, as DLLs são carregadas pelo carregador do sistema(PE Header) quando você inicia seu aplicativo. Deixe-me ser mais específico desta vez. DLL (Dynamic Link Libraries) são coleções de função, geralmente fornecidas pelo Windows (embora possam ser fornecidos por qualquer pessoa) que contenham funções que são utilizadas por muitos programas no Windows. Elas também são funções que tornam mais fácil para os programadores executar o que de outra forma seria tedioso, tarefas repetitivas.
Por exemplo, converter uma string em todas as maiúsculas é algo que precisa ser feito em muitas aplicações. Você tem três opções se seu aplicativo usar essa funcionalidade várias vezes em sua aplicação;

  1. você pode codificá-lo e colocá-lo em seu aplicativo. O problema é, e se você souber que seu próximo aplicativo também usará essa mesma função várias vezes? Você precisaria cortá-lo e colá-lo em cada aplicativo que você faz, isso é chato né?
  2. Você pode criar sua própria biblioteca que qualquer aplicativo que você faça consiga acessar. Nesse caso, você criaria uma DLL que você inclua em cada aplicativo, e essa DLL teria convertToUpper, bem como outras funções comuns, que suas aplicações podem ligar, apenas tendo que codificá-la uma vez. Outra coisa boa disso é, digamos que você tenha uma ótima otimização para converter uma string em todas as maiúsculas. No primeiro exemplo, você precisaria copiar esse novo código para cada aplicativo que o usa, mas, no caso de uma DLL comum, você simplesmente alteraria o código na DLL e cada aplicativo que usasse essa DLL obteria o benefício Do código mais rápido. Doce. Esta foi realmente a razão pela qual as DLLs vieram para ser.
  3. A última opção é usar uma das milhares de funções que o Windows incluiu no próprio conjunto de DLLs. Há muitos benefícios para isso. A primeira é que os programadores da Microsoft passaram anos otimizando suas funções, então as chances são de que eles são melhores que os seus é grande. Em segundo lugar, você não precisa incluir nenhuma das suas próprias DLLs com sua aplicação, já que todos os sistemas Windows possuem essas DLLs. E, finalmente, se o Windows decidir mudar seu sistema operacional, suas DLL personalizadas podem não ser compatíveis com o novo sistema operacional , Enquanto você usa DLLs do Windows, elas são garantidas para serem compatíveis.

Como as DLL’s são usadas

Agora que você sabe o que é uma DLL, vamos falar sobre como eles são usados. Uma DLL é basicamente apenas uma biblioteca de funções que o seu aplicativo pode chamar. Quando você carrega o aplicativo pela primeira vez, o Windows Loader verifica uma seção especial do cabeçalho PE (lembre-se do PE Header) E verifica quais as funções que o seu aplicativo chama e de quais funções da DLL residem. Depois de carregar seu aplicativo na memória, Então intera através destas DLLs e carrega cada um no espaço de memória do seu aplicativo. Em seguida, passa por todo o código do seu aplicativo e injeta o endereço correto de onde ele coloca essas funções de DLL em seu programa em todo o seu programa que chama essa função. Por exemplo, se uma de suas primeiras chamadas for converter um buffer de letras em maiúsculas, ligando para o StrToUpper na DLL do kernel32 (apenas um exemplo), o carregador encontrará o local onde carregou a DLL kernel32, localize o endereço da função StrToUpper , E injete esse endereço nas linhas de código em seu aplicativo que chamam essa função. Seu aplicativo chamará o espaço DLL do kernel32 na memória, executará a função StrToUpper e retornará ao seu programa. Vamos ver isso em ação. Carregue o programa FirstProgram.exe incluído neste tutorial no Olly. Olly vai se quebrar na primeira linha de código (de agora em diante chamado de Entry Point— isso é importante, pois é isso que o PE Header chama)

Olly!

Se você olhar para a segunda linha do código, você verá uma chamada para uma função chamada kernel32.GetModuleHandleA. Primeiro, vejamos o que essa função faz. No caso de você não ter obtido no último tutorial, incluí nos downloads para este tutorial um arquivo chamado WIN32.HLP, bem como um documento de texto que explica como instalá-lo em Olly. Este arquivo permite que você clique com o botão direito do mouse em uma API do Windows que você não conhece e que irá mostrar o que a API faz. Talvez seja necessário reiniciar Olly depois de copiá-lo. Clique com o botão direito em GetModuleHandleA e selecione “ Help on Symbolic Name”.O Olly apresentará uma folha de suporte desta API:

Documentação da API que selecionamos no FirstProgram.exe

Então, basicamente, essas funções recebem um controle para uma janela que vamos criar. No Windows, se você quiser fazer QUALQUER COISA para uma janela (ou praticamente qualquer outro objeto para esse assunto), você deve lidar com isso. Este é basicamente apenas um identificador exclusivo para que o Windows saiba qual objeto você está se referindo.

Feche a janela de ajuda e vejamos exatamente onde essa chamada está sendo executada. Como Olly tentou ajudar e substituiu o endereço real do GetModuleHandleA com o nome da função, vejamos em que endereço reside. Clique uma vez na linha de chamada GetModuleHandleA e aperte a barra de espaço. Isso abrirá a janela dissasembly:

Esta janela tem dois propósitos; Em primeiro lugar, você mostra as instruções de linguagem de montagem exatas que estão sendo computadas (no caso de Olly substituir de forma útil o endereço) e, em segundo lugar, nos permite editar o assembly do programa. Não vamos fazer qualquer edição até o próximo tutorial, então, por essa altura, apenas olhe para o endereço: 4012D6.Há duas maneiras de saltar para este endereço (sem realmente executar o código) para ver o que está lá. Você pode destacar a linha “CALL GetModuleHandleA” e aperte “Enter”. Você também pode pressionar Ctrl-G e digitar o endereço manualmente. Vamos tentar o primeiro caminho — selecione a linha no endereço 401002 (na terceira coluna com as instruções reais nele) e clique de um enter, você será levado para o código que essa chamada invocou:

Agora isso é interessante: com certeza não parece código que executaria GetModuleHandleA. Parece mais uma série de jumps. Há um bom motivo para isso, mas, infelizmente, é preciso uma pequena explicação.

A tabela de salto de endereço:

A primeira coisa a saber é que as DLLs nem sempre são carregadas na memória no mesmo ponto. O carregador do Windows, que é responsável por carregar seu aplicativo e todas as DLLs de suporte que seu programa precisa, é permitido alterar onde na memória essas DLLs podem ser carregadas (e, francamente, pode até mudar a localização do seu aplicativo, mas nós Chegarei a isso mais tarde). O motivo para isso é dizer que uma DLL do Windows, uma das primeiras carregadas, está mapeada no endereço 80000000. Bem, digamos que você também incluiu uma DLL em seu aplicativo que quer ser carregado nesse mesmo endereço. Uma vez que ambas as DLLs não podem ser carregadas no mesmo endereço, o carregador deve mover uma dessas DLL para outro endereço. Isso acontece o tempo todo, e é chamado de deslocalização.

Aqui está o problema: quando você codificou sua aplicação pela primeira vez e escreveu uma instrução chamada GetModuleHandleA, o compilador sabia exatamente onde estava a DLL apropriada, então colocou um endereço nessa instrução, algo como “CALL 800000000”. Agora, quando o seu programa é carregado na memória, ele ainda tem essa chamada para 800000000 (estou sendo muito simples aqui), e se o carregador decidiu mover essa DLL para 80000E300? Sua chamada chamará a função errada!

Viu a oportunidade né? como seria legal injetar nossas funções e o programa a executar normalmente :) continuando…

A forma como o arquivo PE e, portanto, o arquivo do Windows, contornaram esse problema, criando uma jump table.
O que isso significa é que, quando o seu código foi compilado pela primeira vez, cada chamada para GetModuleHandleA apontou para um único local em seu aplicativo, e este único local imediatamente pula para um endereço arbitrário (que eventualmente se tornará o endereço apropriado). De fato, todas as chamadas de função em DLLs usam essa mesma técnica; 
Cada uma chama um endereço específico que imediatamente salta para um endereço arbitrário. Quando o loader é carregado em todas as DLLs, ele passa por esta ‘jump table’ e substitui todo o endereço arbitrário pelo endereço real das funções na memória. Esta é a aparência de uma tabela de salto depois de todos os endereços reais terem sido preenchidos:

JMP Significa Jump, ele repassa o endereço de memoria e depois a função

Assim como esta é uma espécie de idéia complicada, deixe-me dar um exemplo. Vamos escrever um programa curto, usando informações completamente arbitrárias (apenas para provar nosso ponto) que chama uma função no kernel32.dll chamado ShowMarioBrosPicture. Aqui está o nosso programa (sem uma linguagem específica):

main() {
call ShowMarioBrosPicture();
call ShowDoYouLikeDialog()
exit(); }
ShowDoYouLikeDialog() {
If ( user clicks yes ) {
call ShowMarioBrosPicture();
Call ShowMessage( “It’s me Mario!”) }
else {
call showMessage( “Voce obviamente nunca jogou Mario, seu sem infancia.”); } }

Depois de compilar isso, as chamadas para funções serão substituídas pelo endereço real e parecerão algo assim (novamente, não em nenhuma linguagem específica):

401000 call 402000 // Call ChowMarioBrosPicture
401002 call 401006 // Call showDoYouLikeDialog
401004 call ExitProcess
401006 Code for “Do You like It” dialog
.
40109A if (user clicks yes)
40109C call 402000 // call showMarioBrosPicture
40109E call 4010FE // call show message
4010a1 call ExitProcess
4010a3 if (user clicks no)
4010a5 call 4010FE // call show message
4010a7 call ExitProcess
4010FE code for show message …
40110A retn

E abaixo nossa seria nossa tabela de salto(jump table)(neste caso, apenas o showMarioBrosPicture está nele):

402000 JMP XXXXXXXX

Agora, uma vez que o programa não tem idéia de onde o showMarioBrosPicture vai ser (ou onde o arquivo kernel32DLL será para esse assunto), o compilador do nosso programa apenas vai preencher os X para o endereço real (não exatamente, mas você consegue ter uma ideia).

Agora, quando o carregador do Windows carrega nosso aplicativo, primeiro carrega nosso binário na memória, completa com a tabela de salto, mas a tabela de salto ainda não tem nenhum endereço real. Em seguida, ele começa a carregar DLLs em nosso espaço de memória, e finalmente começa a descobrir onde todas as funções residem. Uma vez que ele encontra o endereço do showMarioBrosPicture, ele entrará na nossa tabela de salto e substituirá os Xs pelo endereço real dessa função. Digamos que o endereço showMarioBrosPicture foi em 77CE550A. Nosso código de tabela de salto seria então substituído por:

402000 JMP 77CE550A.

Agora, já que o Olly pode descobrir que isso está apontando para showMarioBrosPicture, ele entrará na nossa jump list table de forma útil e, de fato, mostra como:

402000 JMP DWORD PTR DS:[<&Kernel32.showMarioBrosPicture>]

Agora, vamos voltar e olhar para a tabela de salto em nosso aplicativo FirstProgram:

Quando este programa foi codificado pela primeira vez, todas essas funções foram chamadas em várias DLLs, mas o compilador não sabia onde essas funções deveriam ser colocadas na memória quando o nosso programa foi executado, então isso teria criado algo que parecia (embora não Exatamente) assim:

40124C JMP XXXXX // gdi32.DeleteObject

401252 JMP XXXXX // user32.CreateDialogParamA

401258 JMP XXXXX // user32.DefWindowProcA

40125E JMP XXXXX // user32.DestroyWindow

Depois que o carregador carregou o nosso aplicativo e depois carregou todas as DLLs e encontrou o endereço dessas funções, então passaria por cada uma delas e as substituiu pelo endereço real em que essas funções agora residem, ou seja, o que você viu no anterior cenário. Se você pensa sobre isso, esta é uma linda
Maneira inteligente de lidar com isso. Se não for feito desta forma, o carregador seria forçado a passar por todo o nosso aplicativo e substituir CADA chamada a CADA função em CADA DLL e substituir esse endereço pelo endereço verdadeiro. Isso seria muito trabalho. Desta forma, o carregador só tem que substituir o endereço em um local por chamada de função, a saber, a linha de funções na jump table.

Vejamos por nós mesmos. Recarregue o aplicativo no Olly e pressione F7. Clique nas instruções na linha 401002 (como fizemos antes) e aperte espaço ou spacebar tanto faz(como fizemos antes):

Eu só queria que você notasse o endereço novamente, 4012D6. Agora, clique em F7 para entrar na chamada e você notará que vamos pousar no endereço 4012D6, que se você rolar um pouco, você também notará que chamamos o meio da jump table:

Agora, clique em F7 novamente e seremos levados para o endereço REAL do GetModuleHandleA no 7780B741. Você pode dizer que agora estamos no módulo kernel32 de duas maneiras, ambas as quais você usará em momentos diferentes. O primeiro nome do título da janela da CPU do Olly:

Você pode ver que ele diz “módulo kernel32”. E a segunda maneira que você pode dizer é entrar na janela da memória e procurar o endereço:

Você pode ver que o endereço em que estamos (7780B741) cai no espaço do endereço da seção de código do kernel32.

Agora vamos voltar e ver algumas das outras chamadas de função. Re-iniciar o aplicativo e F8 até o endereço 40100C, a linha que tem a chamada para GetCommandLineA sobre ele. Clique nas instruções e aperte a barra de espaço para que possamos ver o endereço que ele está apontando, neste caso 4012D0:

Agora, vamos tentar manualmente para este endereço, pois isso é algo que você usará com freqüência. aperte Ctrl+G ou clique no ícone GOTO e digite o endereço para o qual queremos ir:

Observe que sua janela “GOTO” pode parecer um pouco diferente, mas isso será corrigido em breve. Agora, clique em OK e iremos para a jump table para a função GetCommandLineA:

Agora, clique em F7 para fazer o salto e pousaremos no início da função GetCommandLineA em kernel32.dll. Esta função começa em 7C812FBD:

Jumping in and out of DLLs

À medida que avançamos em torno de um programa, várias vezes você terminará em DLLs. Isso geralmente não é onde você quer estar se você estiver tentando superar algum tipo de esquema de proteção, pois as DLL do Windows realmente não contêm nenhum. A única ressalva disso é se o programa que você está tentando engenharia reversa vem com suas próprias DLLs e você deseja reverter essas também (ou o esquema de proteção está realmente em uma DLL). Há algumas maneiras de voltar ao nosso código de programas de uma DLL. Uma maneira é simplesmente passar por todo o código de função da DLL até você finalmente retornar ao seu programa, embora isso possa levar bastante tempo (e, em alguns casos, como Visual Basic, para sempre). A segunda opção é entrar na opção de menu “Debug” em Olly e escolher “Executar até o código do usuário” ou pressionar Alt-F9. Isso significa executar o código nesta DLL até retornarmos ao nosso próprio código de programas. Tenha em mente que às vezes isso não funciona porque, se a DLL acessa um buffer ou uma variável que esteja no espaço de trabalho de nossos programas, Olly irá parar por aí, então você pode acabar chamando o Alt-F9 várias vezes antes de você finalmente voltar.
Vamos tentar essa opção agora. Nós deveríamos estar pausados ​​no endereço 7C812FBD, o início do GetCommandLineA. Agora pressione Alt-F9. Isso nos levará de volta às instruções em nosso programa após a chamada que fizemos para o kernel32 (se você rolar até uma linha, você verá a chamada). Agora vamos tentar outra opção para voltar ao nosso código. Re-iniciar o programa, passar por cima (F8) até a chamada para GetCommandLineA (40100C), entrar na chamada (F7) e entrar no salto na tabela de salto (F7). Nós
Agora estão de volta no início do GetCommandLineA:

Agora, abra a janela da memória e role até que você possa ver as seções com nosso código de programas nele (começando no endereço 400000 com o PE Header):

Agora, clique na linha no endereço 401000, a linha que tem nossa seção .text sobre ela. Agora, pressione F2 para alternar um ponto de interrupção no acesso para esta seção de memória (ou clique com o botão direito e selecione Ponto de interrupção no acesso):

Agora, execute o aplicativo. Olly vai se quebrar na mesma linha acima no endereço 401011, a linha um depois da nossa ligação à DLL !!! Agora remova o ponto de interrupção da memória e você se perguntará por que cada vez que você executar o aplicativo quebra na próxima linha haha

Mais sobre a Stack(pilha)

A pilha é uma parte muito importante da engenharia reversa, e sem uma compreensão muito clara disso, você nunca será um ótimo engenheiro reverso. Vamos nos aprofundar um pouco nela

Primeiro, dê uma olhada na janela dos registros (depois de reiniciar o aplicativo) e veja o registro ESP. Este registro aponta para o endereço do “topo” da pilha. Neste caso, o valor de ESP é 12FFC4. Agora, olhe para baixo na janela da pilha e você notará que o endereço superior na lista corresponde a este endereço:

Agora, pressione F8 (ou F7) uma vez para empurrar o valor zero para a pilha e veja a janela da pilha:

Quando passamos no último tutorial, isso empurra zero (nulo) para a pilha. Agora, olhe para o nosso registro ESP:

Mudou para 12FFc0, porque, depois de colocar um byte na pilha, este é o novo topo da pilha. Agora pressione F8 uma vez, pisando a chamada para GetModuleHandleA e veja a janela da pilha:

Você notará que nossa pilha voltou para baixo por um (e nosso registro de ESP está de volta ao que era).
Isso ocorre porque a função GetModuleHandleA usou esse zero que foi empurrado para a pilha como um argumento e, em seguida, “apareceu na pilha, já que não era mais necessário. Como foi abordado no último tutorial, esta é uma maneira de o nosso programa transmitir os argumentos às funções: eles empurrá-los na pilha, a função chamada os exibe da pilha, usa-os e, em seguida, retorna, geralmente com qualquer informação em que precisamos Registra (como veremos em breve). Vamos ver um par de mais … Se você pressionar F8 duas vezes para passar da chamada para GetCommandLineA você notará que a pilha não mudou. Isso porque não empurramos nada para a pilha para que essa função seja usada. Em seguida, chegamos a uma instrução PUSH 0A. Este é o primeiro argumento que vamos passar para a próxima função chamada. Passando por isso, você notará que 0A estará no topo da pilha e o registro ESP diminuiu 2 (quando você empurra um valor para a pilha, o registro ESP diminui à medida que a pilha ‘cresce’ na memória) . Agora, pressione F8 novamente e o registro ESP volte para baixo novamente em 4. Isso ocorre porque nós empurramos um valor de 4 bytes para a pilha. Se você olhar para o topo da pilha você notará que empurrou 00000000 para a pilha. Por quê?

Vejamos a linha que realmente fez o empurrão em 401013:

PUSH DWORD PTR DS:[40302c]

O que significa esta linha (como tenho certeza de que você conhece porque estudou linguagem assembly): p) pegue o conteúdo dos quatro bytes no endereço 40302C e pressione-os na pilha. O que está em 40302C? Bem, 00000000, claro! (Apenas brincando. Procuremos por nós mesmos. Clique direito nas instruções no endereço 401013 e selecione “Follow in Dump” -> “Memory Address”. Isso irá carregar a janela de despejo com o conteúdo da memória começando em 40302C:

Obviamente, não há muito lá! Mas pelo menos agora você sabe de onde o zero está vindo. Se você quiser saber mais detalhes sobre o que está acontecendo aqui, esse espaço de memória está sendo configurado para variáveis e será eventualmente preenchido com essas variáveis, mas, por enquanto, todas as variáveis foram inicializadas para 0.

Agora pressione F8 uma vez e estaremos em outro PUSH, mas desta vez do endereço 403028. Se você rolar para cima na janela de despejo, você pode ver que neste endereço ainda há mais zeros (logo após a string que mudamos no último tutorial). Então, o que esta seção está fazendo é empurrar ponteiros para endereços de memória, atualmente configurados em zero, que nosso código usará como variáveis. Passar o último PUSH e entrar na chamada para endereço 40101C. A primeira coisa que você deve notar é que algo novo foi empurrado para a pilha: o endereço de retorno para nossa ligação, 401026.

Quando qualquer código usa uma instrução CALL, o endereço da próxima instrução que teria sido executada se não fizessemos a chamada é automaticamente empurrado para a pilha. A razão para isso é que, após a função que chamamos, fez tudo o que precisa fazer, precisa saber para onde retornar. Este endereço que foi
Automaticamente empurrado para a pilha é aquele endereço de retorno. Olhe para o topo da janela da pilha:

Você verá que Olly descobriu que é um endereço de retorno e ele retorna ao nosso programa (FirstPro) e que o endereço que precisa ser retornado é 40102C (uma instrução após a chamada).

Agora, no final desta função, uma instrução RETN será usada (e é claro que você sabe que isso significa “retorno” porque estava no início do seu livro de linguagem assembly). Esta instrução de retorno realmente significa “POP o endereço do topo da pilha e apontar nosso código em execução para este endereço” (ele basicamente substitui o registro EIP — o registro da linha atual de código que estamos executando — com este valor pop-ed ).
Então, agora, a função chamada sabe exatamente onde ele precisa retornar quando está pronto! Na verdade, se você rolar para baixo um pouco, você verá a instrução RETN no endereço 4011A3 que exibirá este endereço da pilha e começará a executar o código nesse endereço:

Bom, por hoje é só isso!


Creditos:

Leonardo Marciano

R4ndom