domingo, 19 de outubro de 2008

ShellCoding parte 3 - Primeiros passos rumo ao shellcoding > 3.1 primeiro programa assembly

Essa seção apresenta duas ferramentas importantíssimas para quem quer encontrar vulnerabilidades; o assembler e o debugger. 

Nessa seção também, nós aprofundaremos o conhecimento que você adquiriu da linguagem assembly, só que dessa vez, usando a prática para entender como, por exemplo, a stack funciona.

De começo, usaremos o MASM32 como assembler.

O MASM32 pode ser encontrado em:
http://www.masm32.com/
E o ollydbg é o primeiro debugger que vamos usar e pode ser encontrado em:
http://www.ollydbg.de/download.htm

Um assembler é uma espécie de compilador da linguagem assembly que passa as instruções asm para o código de máquina enquanto que um debugger também chamado de depurador, roda um programa deixando-o ver cada instrução executada pelo programa passo-a-passo.
 



3.1 Primeiro programa assembly

A partir de agora, as práticas serão mescladas com a teoria e a primeira parte da prática é bem simples e nos ensinará o funcionamento da stack.

Para começar, abra seu MASM32, e insira o seguinte código:
.386
.model flat, stdcall
option casemap:none

.code
start:

 push 8

 mov ECX, 0FFFFFFFFh
 
 lg:
  dec ECX
 jnz lg

 pop EAX
 
 ret 
end start



Vamos entende-lo por partes.

.386 indica qual intruction set¹ iremos usar, nesse caso, o do 80386 mesmo.

¹: podemos encontrar o instruction set da família x86 em:
http://en.wikipedia.org/wiki/X86_instruction_listings 
http://home.comcast.net/~fbui/intel.html 
http://www.x86.org/intel.doc/386manuals.htm




.model flat, stdcall: você entenderá mais tarde...

 


.code: indica o início do código do programa...


start: é a label que indica todo o corpo do programa.


end start: é aonde ele termina, tudo o que iremos escrever ficará entre esses dois.

Na verdade, todas as funções que chamamos, são labels que indicam o offset(deslocamento) da próxima instrução a ser executada..

Agora clique em assemble & link como na imagem->



Agora o que nós vamos fazer, é abrir o ollydbg, clicar em File>Open e no binário gerado(tuto.exe no meu caso) e poderemos ver o código gerado por ele em ação passo a passo.(apesar de estar usando ele, eu recomendo o SoftIce, Windbg ¹ e o IDA Pro como disassembler no entanto não vou ter tempo de lhes ensinar como usar todos... na verdade, em breve mostrarei como criar suas próprias ferramentas, o que é bem melhor do que contar com softwares que quase sempre deixam a desejar diante das suas necessidades e tem bugs que são largamente explorados por aplicações cujos donos não querem que sejam ‘debugadas’)


¹: Recomendo a leitura desse blog> http://www.1bit.com.br/content.1bit/windbg1
O que termos é uma imagem parecida com a seguinte:


A área marcada com o número um contém o disassembly do programa, que é o código assembly gerado por um compilador comum ou assembler, como nós fizemos o programa em assembly o código é igual ao que nós fizemos, no entanto se fosse outra linguagem de programação, nós veríamos um código assembly gerado muito maior, principalmente depois do processo de “linkagem”.
A coluna “address” indica o endereço virtual de cada instrução asm.
A coluna hex dump nada mais é do que a coluna disassembly traduzida para os códigos operacionais que são o que o compilador realmente entende.

Comment, é a coluna aonde os comentários ficam, muitas vezes o próprio olly vai lhe fornecer comentários para dizer por exemplo, que certa operação é a passagem de parâmetros para uma função determinada.





A área 2 é basicamente a janela da CPU aonde você pode ver o estado de diferentes registradores.




A área 3 é a stack.
A stack é basicamente uma área da memória para acesso rápido.
O processador é otimizado para acesso à pilha, portanto ela não é como o resto da memória virtual de um processo.
Ela cresce de 32 bits em 32 bits.
No Windows ela tem a peculiaridade de estar armazenada em endereços baixos o que é prejudicial(não tanto) para construirmos nossos shellcodes.

Ela tem outra peculiaridade que é crescer de cima para baixo e quando removemos algo dela, removemos primeiro o que está em cima.

Veremos isso daqui a pouco na prática.




A área 4 é a RAM do processo, nesse tuto não veremos muito sobre essa janela.



Agora voltemos ao nosso ollydbg.
Ele é um debugger o que significa que podemos executar cada instrução de um determinado programa e ver o que acontece na íntegra com os registradores, a stack e etc.

Então o que vamos fazer é apertar F7 para que a primeira instrução seja executada.(push 8 )
O que essa instrução faz, é colocar um valor na stack, no nosso caso o 8.



A stack cresce de cima para baixo como eu disse, um novo valor é sempre posto no topo dela e o que vemos em vermelho, é que em primeiro lugar, o valor antigo da stack ficou abaixo do novo que nós colocamos(00000008) e em segundo lugar, ainda em vermelho, podemos notar que o endereço do valor da stack desce 4 bytes(de 0012FFA4 para 0012FFA0), isso se deve pelo fato de que o maior endereço que a stack pode alcançar, é zero.(na verdade não é assim, mas vamos adotar o topo como sendo o zero para que seja mais fácil de entender como os maiores valores positivos sempre estão em baixo) 

Ou seja, ela crescerá para baixo e seu topo será o zero.. quando você usar o suficiente dela para chegar no 0, seria hipoteticamente falando, a hora que o Windows diria que falta memória.(na verdade, o que acontecia nos tempos do 9x é que se podia ultrapassar os limites da stack e o que acontecia realmente era que o windows normalmente "quebrava" por corrupção de memória) 



Vemos em roxo também(voltando para a figura anterior), que os registradores que sofrem alguma mudança, o olly os realça em uma cor vermelha(pode mudar de acordo com suas configurações).
Os registradores que mudaram com a instrução ‘push 8’, foram o EIP e o ESP.

O ESP nós sabemos que aponta para o local aonde nós queremos que seja o topo da stack.
E como podemos ver, o seu valor na janela da CPU é 0012FFA0, exatamente o endereço da área onde está o nosso 00000008.
Quando PUSHamos um valor para a stack, o ESP é decrementado em 4.

O outro registrador que mudou de valor, foi o EIP.
EIP é o registrador que aponta para a próxima instrução a ser executada.
Como podemos ver, a próxima instrução está em abóbora e seu endereço é o mesmo que o do EIP.
O EIP é automaticamente atualizado pela CPU para apontar para a próxima instrução e ele não pode ser mudado por instruções como mov.
EX:
mov EIP, 00401002h -> errado
(reparem também que quando eu quero escrever algum número hexadecimal, eu coloco o terminador ‘h’, caso o número já comece com alguma letra, eu tenho que por um zero antes para que o assembler possa distingui-lo de uma variável.(ex: 0DEADBEEFh)



O que vamos fazer é apertar f7 novamente para seguirmos um passo à frente.
Vemos que ele move o valor 0FFFFFFFFh para ECX(como nós já vimos, -1 corresponde ao maior número hexadecimal que uma variável pode alcançar)
Podemos ver que ECX e EIP são realçados por terem sido mudados, ECX com o valor movido e EIP com o endereço da próxima instrução.

Se você também olhar para o hexdump da instrução mov ECX, -1... poderá ver o valor 0FFFFFFFFh sendo movido para ECX(B9 <- código operacional, FFFFFFFF <- valor)


A próxima instrução, “DEC ECX” decrementa ECX em um. Não tem nenhum mistério nisso.


Mais um passo e damos de cara com um JNZ. JNZ significa jump if not zero, ou seja, pule se não for zero.

Ele vai pular para um determinado endereço se a operação anterior não resultou em zero.

Ou seja, o que esse bloco vai fazer(DEC ECX && JNZ 401007), é decrementar ECX até ele chegar a zero.

Quando ele chegar, o jnz não será executado e poderemos passar para a próxima instrução.

Bem, você pode segurar o F7 até ECX chegar a zero e ficar olhando o EIP mudar freneticamente... ou pode simplesmente clicar duas vezes no ECX, mudar seu valor para zero, dar enter e seguir para a penúltima das nossas instruções.(faça isso quando estiver para executar o jnz, não antes ou o valor voltará para -1)


A penúltima das nossas instruções é a pop EAX. POP simplesmente pega o valor para o qual ESP está apontando(valor da stack), e põe no registrador indicado. Se lembra que nós tínhamos posto o valor 8 na stack com push?

Push decrementou ESP 4 bytes e colocou o 8 na pilha(stack). Pop vai realizar justamente o contrário... ele incrementará ESP em 4 bytes e vai colocar o valor 8 em EAX. Aperte F7 e veja isso acontecer... EAX passa a conter o valor 8.

No entanto, o valor 8 não é apagado da stack, ele continua lá muito embora ESP não aponte mais para ele... isso se deve ao fato de que pop não grava na stack, ele simplesmente à lê.


E finalmente chegamos a nossa última instrução... Retn.

Lembra-se que eu falei que EIP não pode ser modificado diretamente? Mas nós já vimos uma instrução que modifica EIP, a JNZ.

Retn/ret é outra instrução que modifica EIP. O que acontece, é que sempre que uma função é chamada, o valor da próxima instrução após essa função é guardado na stack.

Retn funciona como um pop só que específico para EIP, ele pega o valor apontado por ESP e o põe em EIP. É assim que o programa sabe para onde voltar de uma determinada função. Imagine essa situação:

void Func() { }

Int main() {

 Func();  

 return 0;

}


Nesse caso, quando Func é chamada, a endereço de “return 0” é guardado na stack, assim o programa sabe para onde deve seguir após a função interna ser executada por completo.

Clique aqui para ler a continuação

Nenhum comentário:

Postar um comentário

><))).>