Stack buffer overflow

Started by blackwinner, 26 de August , 2008, 12:46:01 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

blackwinner

1- Introdução

2- Assembly básico
-sistemas numéricos
-introdução ao assembly
-Instruções ASM

3- Shell Coding básico

4- Stack Buffer Overflow

5- Sintaxes de chamada de funções
-Cdecl
-FastCall
-Stdcall

6-Windows ShellCoding intermediário

7-Análise de programas
-Disassembling
-Criando um stack buffer overflow controlado

8- Considerações finais & Agradecimentos


Pré-requisitos: conhecimento intermediário da linguagem C

1- Introdução

Shell coding é uma arte a parte, algumas vezes, uma vulnerabilidade não é "exploitada" por que simplesmente o atacante não fez um shellcode pequeno o suficiente.
Achar vulnerabilidades é outra arte também... Saber juntar essas duas formas de arte para um objetivo principal pode e pode não ser uma tarefa difícil, mas tende a ser complexa muitas vezes.

Inicialmente, hackers e crackers viviam em mundos separados... A antítese de usuários comuns...
Hackers, historicamente vieram de sistemas de código aberto aonde as vulnerabilidades eram encontradas no código fonte liberado, não lidavam com dificuldades técnicas como unpacking e etc, no entanto conheciam a linguagem C muito bem.
Crackers vieram de um sistema de código fechado em especial(o windows) e costumeiramente tinham que "quebrar" o código de um software para conseguirem alcançar certos objetivos, que algumas vezes eram os mesmos que os dos hackers e outras vezes não eram.(como uso ilimitado de programas pagos)

De fato, a sabedoria popular é mais considerada do que a individual.
Digo isso porque a um tempo atrás a linguagem object pascal só poderia ser chamada assim. Como a maioria dos programadores novatos assimilavam o nome da linguagem com o do compilador(delphi), o nome da linguagem basicamente hoje em dia pode ser ambos.
Outro exemplo é a nossa língua em que palavras perdem acentos ou ganham de acordo com a forma que o povo mais escreve e fala.

Muitos hoje enxergam os cavaleiros como homens cheios de honra, moral e ética quando na realidade, os cavaleiros na época das cruzadas só sabiam matar e estuprar mulheres inocentes.
Assim é com hackers, na verdade a meu ver, o conceito só tem a ver com o conhecimento que você tem de hardwares e softwares... (vide o porquê do primeiro grupo de hackers terem se chamado assim)

No entanto como a sabedoria popular é a que tem a "última palavra", então hackers são seres angelicais, cheios de ética e com uma perfeita pronúncia da nossa língua nativa.

Crackers sempre foram e serão vistos como criaturas malvadas e quase sempre são confundidos com meros defacers com más intenções, principalmente em fóruns sensacionalistas.
Não que seja uma completa mentira, mas muitas vezes, um exagero.(poucos crackers praticam deface)

2- Assembly Básico

2.1 Sistemas numéricos

Não tem, ou melhor, não deveria ter outra forma de introduzir alguém a uma linguagem sem passar por esse tema.
Sinta-se livre para passar por ele se já o conhecer.
Vou passar bem rápido por esse tema, ele é simples demais para perdermos tempo nele.
 Um sistema numérico, é uma representação visual de uma contagem seqüencial.
Cada símbolo representa o número seguinte a ser contado, quando não há mais símbolos nós repetimos o segundo da seqüência ao lado esquerdo do número que estamos escrevendo e voltamos o número inicial ao seu primeiro símbolo.(famoso esquema do "vai um")
A base que normalmente usamos para representar números é a decimal, logo nós temos:
1, 2, 3, 4, 5, 6, 7, 8, 9 e 10
Certo?
Errado!
0, 1, 2, 3, 4, 5, 6, 7, 8 e 9
É a seqüência certa...
Quando chegamos o no número nove (9) nós voltamos ao zero (0) e colocamos um ao seu lado para representar que já ultrapassamos da nossa seqüência uma vez e assim em diante.
Isso é fácil, mas é a base para qualquer base numérica, como a binária.
A base binária só tem dois números, o zero e o um e subseqüentemente segue a mesma lógica que a decimal para representar os mesmos números...
Vamos ver algumas representações em binário
Binário   |  Decimal
0      |         0
1      |         1
10     |          2
11     |          3
100     |          4
101     |          5
110     |          6
111     |          7
1000    |          8
1001    |          9
1010    |          10
Como podemos ver, cada vez que passamos do número de símbolos que temos, aumentamos um número a esquerda tanto na base decimal quanto na binária.
Parece bobeira de tão fácil, e é. Lolz
Geralmente nós temos que trocar de uma base para outra... Para trocar da base decimal para a binária e vice-versa eu uso uma tabela comum para técnicos e engenheiros eletrônicos, mas pouco comum no mundo da informática.
Ela é lógica e bem simples.
Vou fazer um exemplo da conversão do número 9 para binário utilizando ela.

  16 | 8 | 4 | 2 | 1 <- cabeçalho
  0   | 1 | 0 | 0 | 1

Essa tabela é composta por um cabeçalho, esse cabeçalho define colunas baseando-se em potencias de dois:
2 ^ 0 (^ significa "elevado a") = 1
2 ^ 1 = 2
2 ^ 2 = 4
2 ^ 3 = 8

Esse cabeçalho pode ter quantos números você desejar.
Depois o que fazemos é pegar o número do cabeçalho que mais se aproxima do número que queremos transformar pra binário( 9 ), no nosso caso pegamos o oito.
Depois nós pegamos um número que possamos somar com o oito para se tornar nove.( o um/1)
Nas colunas dos números que nós pegamos nós colocamos o número um (1), em todas as outras, zero.

Vamos transformar o número 512 em binário para treinarmos. (PS: essa tabela é ótima para fazer em concursos porque ela é rápida e prática, melhor do que dividir milhares de vezes por dois e pegar o resto)
512 | 256 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1
  1  |  0   |   0  |  0   |  0  |  0  | 0 | 0 | 0 | 0

Isso mesmo!
Como já achamos o 512 de primeira, o resto é tudo zero... Acreditem, da outra maneira vocês iriam gastar muito tempo dividindo por dois até o final de suas vidas. Lolz
E se o número fosse 511.. bem como temos um número antes de uma potencia de dois, é só marcar 1 em todas as colunas anteriores.. vejam:
512 | 256 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1   
0    |   1 |   1    |  1  |  1  |  1  | 1 | 1 | 1 | 1

111111111 é a resposta... Se quiserem somar todos os números anteriores com a calculadora de seus computadores verão que eu estou certo.
E para fazer o processo inverso?! Isso é, transformar um número binário em decimal?
Usamos nossa tabela novamente.
Digamos que queiramos converter o número 111 para decimal...
8 | 4 | 2 | 1 <- cabeçalho
0 | 1 | 1 | 1

Agora é só somarmos os números que estão acima de cada 1 binário...
4+2 = 6
6 + 1 = 7
E pronto.
Nós também temos a base hexadecimal, que  tem a seguinte seqüência simbólica:
0 1 2 3 4 5 6 7 8 9 A B C D E F
Com 'A' representando o '10', 'B' o '11', 'C' o '12' e assim por diante...

Converter binário para hexadecimal e vice-versa é muito simples.
O maior número individual hexadecimal é igual ao maior número contido em um nibble(4 casas binárias/ 1 1 1 1)
Então, cada número hexadecimal, nós convertemos para um nibble.. por exemplo "FF"(255)
O primeiro F é o mesmo que 15 em decimal e corresponde ao nibble 1 1 1 1 e o segundo  outra sequencia de quatro 1's.
Logo, 1 1 1 1 1 1 1 1.
Só mais uma para termos um melhor idéia da operação.. o número A1 para binário vira
  A       1
1010  0001
Eu normalmente faço a transformação de hexadecimal para decimal usando o deslocamento extra para não ter que ficar multiplicando fatores de 16.
Por exemplo o FF.
F = 15
E um dos F's está uma casa a frente do primeiro.
150 < uma casa acrescentada 
  15
______
165
O deslocamento é 90(15 * 6).
Logo, 90 + 165 = 255
Eu gosto dessa maneira porque me permite fazer de cabeça.. no entanto meus amigos acham ela de uma dificuldade absurda, talvez porque eu tenha criado ela de cabeça a um bom tempo atrás e acaba ficando mais fácil pra mim que visualizei a lógica por trás da conta do que para quem não visualizou a mesma. \=
Então não vou me ater a ela tanto assim.
Vamos a técnica mais popular.
Cada casa hexadecimal corresponde a uma quantidade de vezes que nós passamos dos 16 símbolos.
Logo, em FF
Nós temos
F x 16^0
+
F x 16^1
O que dá 255.
O procedimento contrário(transformar números decimais em hexadecimais se dá através de divisões consecutivas do número por 16 até não podermos mais dividir o resultado.

Let's see it.
255 / 16 =  15,9375
15 é igual a F.
Ai nós multiplicamos o resto
0,9375 x 16 = 15
15 é F também, logo 'FF'.

Para quem não entendeu, é porque eu realmente estou correndo... Infelizmente nem sempre a minha explicação é o suficiente, então se sinta à vontade para procurar em outros lugares a explicação sobre essa mesma matéria, eu pessoalmente recomendo esse link>
http://www.numaboa.com.br/informatica/o ... exPort.php


2.2 Introdução ao assembly

Muitas linguagens nascem e morrem.
Object pascal há um tempo, era uma linguagem exemplarmente fácil e perfeita para o ambiente industrial o que a disseminou pelos quatro cantos do mundo.
Hoje ela se torna ofuscada por linguagens relativamente novas como o JAVA.
Isso se dá, porque a facilidade de programar em alto nível aumenta de forma vertiginosa... obj pascal perde para esse novo tipo de linguagem na facilidade, na portabilidade e até na segurança(já que o tipo do qual estamos falando, são os type safe)... e se Java não puder fazer algo que o pascal possa, certamente o C Sharp poderá com a vantagem de também ser type safe.
No entanto, a dificuldade de se programar em baixo nível nunca mudou e nós sempre iremos precisar nos comunicar com a máquina na linguagem dela.(depois do ASM, é claro que antes era de outra forma)
Sempre precisarão de alguém que ponha a "mão na massa" e "faça acontecer".(duas figuras de linguagem em uma única frase curta o0 )
As linguagens de alto nível vão morrer, talvez nem todas, mas sofrerão mudanças... no entanto nosso bom e velho assembly continua ai, com seus 58 anos de idade e eu realmente duvido que seja um dia substituído simplesmente porque "panela velha é que faz comida boa"... Boa e rápida, diga-se de passagem.
Assembly é a linguagem mais rápida e não é à toa...
Assembly é uma simples representação dos códigos operacionais.
Esses opcode's(códigos operacionais) são a verdadeira linguagem dos microprocessadores.. no entanto como programar usando números seria algo desumano o assembly foi criado para suprir nossas necessidades.


Bem, para aprender assembly , existem alguns conceitos básicos pelos quais devemos passar.
O primeiro deles é o tamanho básico que alguns dados ocupam na memória.
O computador sabidamente trabalha com o sistema binário, o sistema binário varia de 1 a 0.
Essa comunicação dá-se pela tensão(força que impulsiona os elétrons).
Como eu não consegui achar o esquema na internet; os dados que eu irei passar são imprecisos já que se baseiam na minha memória. (ela só armazena 12kb =P)
De 0 volts a 1,4 é a zona morta e seu valor lógico é zero.
De 1,5 a 3,0 é a "zona de indecisão" em que basicamente o valor lógico pode ser um ou zero dependendo de que parte do circuito nós estamos.
E de 3,0 a 5,0 é a zona ativa aonde o valor lógico é um.
Para definir um padrão, nós adotamos que o valor da zona morta é sempre com a tensão zero e o da zona ativa sempre com a tensão em 5,0 v.(a zona de indecisão não é importante para nós)
O importante é perceber que apesar da tensão poder ter pequenas variações, o importante é o valor lógico ao qual ela está atrelada.

 

Bit significa binary digit ou dígito binário e por ser binário pode variar entre um ou zero.
Quando nós temos quatro bits juntos, nós temos um nibble.( de 0000 a 1111 )
Oito bits juntos são um byte.(de 00000000 a 11111111)
Dezesseis bits juntos ou 2 bytes são um Word(de 0000000000000000 a 1111111111111111)
E um Double Word são 32 bits ou 4 bytes.(não me peça pra escrever 64 bits aqui ¬¬)



O segundo conceito básico são as operações lógicas.
A maioria das operações lógicas tem dois operandos como as matemáticas(1 + 1)
A diferença fica por conta do resultado gerado.


A primeira operação lógica que nós iremos ver é a operação denominada AND.
And na língua portuguesa significa 'E', e ela compara o primeiro operando com o segundo... se ambos forem 1 logo sua saída/resposta será um, qualquer coisa diferente disse recebe o valor zero.
Vejamos sua utilização:
AND 1, 1 ; saída 1
AND  1, 0 ; saída 0
AND  0, 1 ; saída 0
AND  0, 0 ;saída 0
No mundo da eletrônica em geral(obviamente incluindo a informática), existe o que chamamos de tabela lógica, nada mais é do que uma representação do que vimos aqui mais arrumado e graficamente.

|  B   |   A  | S |
  0    |  0   | 0
  0    |  1   | 0
  1    |  0   | 0
  1    |  1   | 1


Nós temos os operandos( A & B ) e o resultado da operação/saída( S ) e abaixo, todas as possibilidades de valores que esses operandos podem assumir e as saídas que eles irão produzir.

Apesar das portas lógicas físicamente poderem ter mais do que duas variáveis, no assembly nós quase sempre a veremos com dois operandos.(pelo fato de quase sempre estarmos trabalhando com 4 bytes de informação)

A operação lógica OR, tem a saída 1 se uma das variáveis for também um.
|  B   |   A  | S |
  0    |  0   | 0
  0    |  1   | 1
  1    |  0   | 1
  1    |  1   | 1

A operação lógica XOR opera como a OR com a exceção de que ela só retorna um na saída se apenas uma das variáveis for um.

|  B   |   A  | S |
  0    |  0   | 0
  0    |  1   | 1
  1    |  0   | 1
  1    |  1   | 0  <- reparem no zero quando as duas variáveis são um


Agora vamos entender o complemento de dois, muito usado em flip-flop's  pra que vem da eletrônica.



Nós já aprendemos como os números decimais podem ser representados na base binária.
Agora vamos entender como máquinas representam números decimais negativos binariamente(já que não existe – 0001 e + 0001)

Imaginemos um nibble, ele pode representar até o número 15(1111) em decimal.
No entanto, para que ele represente negativos e positivos, ele deverá ser dividido ao meio.
Como temos um nibble, números de 0000 a 0111(7) serão positivos... Os números negativos possuem o último bit como o valor 1.
Fazendo uma tabela:
Dec  |Bin
-8   > 1000
-7  >  1001
-6  >  1010
-5 >  1011
-4 >  1100
-3  > 1101
-2  > 1110
-1 > 1111
0 >  0000
1 > 0001
2 > 0010
Isso segue até o sete(0111).
Para se adquirir um número negativo correspondente ao seu positivo, usa-se o complemento de dois, ele possui uma fórmula mas na verdade tudo o que você tem que fazer é inverter todos os bits de um número e somar 1 à ele.
Ex:
0111(sete)
1000(-8) + 1
1001(-7)
Como um nibble só tem 4 bits, então:
0111 (7)
+
1001(-7)
____
 10000

Repare que 1+1 = 10(dois em binário) e você faz aquela velha operação do "vai um" decimal.
A resposta é 10000, mas como tem 5 bits a resposta e estamos falando de uma variável que só possui quatro, o último bit é descartado e ficamos com a resposta 0000 que é o resultado correto.
Simples né? =]



Como eu estou correndo MESMO, vou pular alguns temas muito importantes para que você aprenda definitivamente essa linguagem tão impressionante.
A CPU trabalha através de certos registradores, esse registradores são como as variáveis que nós usamos nos nossos exemplos de portas lógicas, eles armazenam uma certa quantidade de bits(8, 16, 32 e estamos partindo para 64)..
Vamos conhecer os registradores que nós mais veremos durante a vida útil desse tutorial.
A maioria dos registradores podem ser divididos em 4 partes.
Por exemplo o EAX(o nome do registrador é AX, o 'E' antes dele significa 'extended')
Um registrador Extended tem 32 bits no entanto ele também pode ser acessado como um registrador de 16 e 8 bits.
Veja por esse lado, EAX tem 32 bits
EAX pode ser dividido em EAL('L' de LOW/Baixo) 16 bits e EAH('H' de high/alto).
Ou seja, os 16 bits mais baixos e os 16 mais altos.
Eles ainda fazem parte de EAX, mas você pode acessar as partes de EAX separadamente se quiser.
Por sua vez, EAX pode ser acessado como AX como um registrador de 16 bits que também pode ser dividido em AL e AH cada um tendo 8 bits.(note que EAL é igual a AX)
EAX é um registrador de propósito geral o que quer dizer que ele logicamente pode ser usado de diversas maneiras para guardar qualquer tipo de dado... No entanto a prática não é assim. (devido á certas leis que não são imutáveis mas que são seguidas a risca por muitos compiladores)   
EAX é um registrador importante, ele é usado para passar dados para uma função ou retornar esses dados de certa função, as APIs do Windows retornam HANDLES, ponteiros, estruturas e etc.
Tudo por ele.
Ele também é usado para ler e receber dados da memória(por ser um pouco mais rápido para isso que os outros)

EBX é muito usado para endereços de memória.

ECX como contador para instruções de loop.(não é regra e ás vezes é até usado como auxiliar do EAX o0)

EDX é o auxiliar do EAX oficial, se o EAX estiver em uso, então esse registrador armazenará o os dados por EAX.

Um resumão>
AX- acumulador
BX- Base
CX- contador
DX- dados
Todos esses apresentados podem ser divididos como registradores de 8, 16 e 32 bits...
Os próximos registradores que vou lhes apresentar,  também são de uso geral no entanto como esses, vocês irão perceber que não são de uso "tão geral assim".



ESI & EDI: esses dois registradores são quase sempre usados como source e index(fonte e destino) para operações envolvendo strings mas também são usados sempre que se precisa ler e escrever em uma área da memória.


ESP: Mantém o endereço do topo da stack(mais tarde veremos um pouco sobre ela)


EBP: Normalmente armazena o endereço da base de um stack frame, nós o veremos muito em prólogos e epílogos de funções.(não se assuste, o nome é estranho mas logo não será mais)

Esses registradores podem ser divididos entre 16 e 32 bits, sendo os de 32 bits os que possuem o prefixo "E"(ESP) e sem o prefixo os de 16(SP).


2.3 Instruções ASM
Todas as operações do assembly são processor-dependent¹... não importa se você está programando para um micro-controlador ou em um pc comum, o set de instruções que um micro-processador aceita é designado de fábrica e você pode ler o "instruction set"² no site do fabricante.

¹: dependem diretamente do processador
²:Também referido como instructions table e por ai vai



A primeira função que veremos é a mov.
Mov, movimenta dados da origem(primeiro operador) para o destino(segundo operador).(isso muda na sintaxe AT&T, mais tarde eu farei um pequeno tuto sobre ela, no momento, nos focaremos na intel)

É como se fizéssemos isso:
"int EDX, EAX = 1;
 EDX = EAX;"

Por exemplo, vamos usá-la com os registradores edx e eax.
 mov EAX, 1  ;EAX = 1
mov EDX, EAX  ; EDX = EAX

Reparem que tudo depois do caracter ';' é uma linha de comentário diferentemente do C que usa os caracteres "/*" e "*/" ou "//"


Outro conjunto de instruções, são as portas lógicas que lhes ensinei.

XOR, faz a operação XOR(exclusive or, ou exclusivo) bit a bit em dois operandos e o resultado é guardado no primeiro operando.

Ex:mov AL, 1 ; AL = 0000 0001
mov AH, 2  ; AH = 0000 0010
xor AL, AH  ; AL = 0000 0011

xor é muitas vezes usado para zerar um registrador, por exemplo:

mov AX, 512 ; AX = 0000 0010 0000 0000
xor AX, AX   ;  AX = 0000 0000 0000 0000

Como o xor é o "ou exclusivo" e só retorna um se somente um dos bits for 1, a operação entre dois valores exatamente iguais sempre dará zero.

Lembre-se que ax, contém 16 bits e AL e AH somente 8.



OR faz a operação OR(ou) bit a bit em dois operandos.
mov AX, 512 ; AX = 0000 0010 0000 0000
or AX, AX   ;  AX = 0000 0010 0000 0000



AND, uso:
mov AL, 15  ; AL = 0000 1111
AND AL, 4 ; AL = 0000 0100



INC, incrementa um operando em um:

mov AL, 0  ; AL = 0000 0000
inc AL  ; AL = 0000 0001
inc AL ; AL =  0000 0010



DEC, decrementa um operando em um:
mov AL, 2  ; AL = 0000 0010
dec AL  ; AL = 0000 00001
dec AL ; AL =  0000 0000
 



IMUL, multiplica dois operandos guardando o resultado no primeiro:
mov AL, 2 ;AL = 0000 0010
mov AH, 2 ; AH = 0000 0010
IMUL  AH, AL ; AH = 0000 0100
                        ; AX = 0000 0100 0000 0010
                                   AH        AL   


Você ainda não conhece assembly o suficiente após ter lido isso, mas essa parte de assembly básico já está concluída, no entanto nós sempre veremos um pouco de assembly no decorrer do tutorial.
Eu escolhi essa organização porque por experiência própria, eu sei que uma aprendizagem dinâmica é melhor do que algo estático.(é sempre bom quando seu professor de matemática usa um pouco de outra matéria para desviar do assunto principal e lhe ensinar matemática de uma forma mais alternativa sem uma abordagem tão direta.)



3- Primeiros passos rumo ao Shell coding

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.. o MASM32 não entende isso muito bem já o NASM sim mas deixemos isso de lado por enquanto.

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.

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.



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)
sergaralho.blogspot.com --> a informação como ela deve ser.. pura!

blackwinner

#1
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.
sergaralho.blogspot.com --> a informação como ela deve ser.. pura!

Alucard

good...

aguardando continuação..
:)

blackwinner

Omg, esse edit é uma mão na roda =]

Deu pra edita a primeira msg e colocar mais umas coisas e editar a segunda. =P
sergaralho.blogspot.com --> a informação como ela deve ser.. pura!

blackwinner

3.2 Um novo debugger, um novo ponto de vista... o mesmo programa.

O que me levou ao ollydbg foi a sua simplicidade... eu sou mais familiarizado com outro debugger, o windbg.
Apesar dele ser "mais complexo", foi o primeiro que eu aprendi a mexer por influencia de alguns amigos, uma ótima influência.

O ollydbg é bem simples, mas nós somos machos certo?*coça o saco*
Então vamos mostrar como somos machos e que não temos medo de cara/interface feia e vamos aprender a mexer um pouco no windbg =]
Eu não me sentiria bem se passasse esse tutorial todo sem pelo menos fazer um "overview" do windbg... e aqui está ele.


O windbg é muito poderoso e é usado pelo próprio time de programadores da Microsoft entre outras coisas, para desenvolver o Windows!

Sim, ele permite a depuração de drivers e afins.

Algumas afirmações que eu fiz para vocês, foram bem mastigadas, muito mesmo.
Agora vamos usar esse debugger poderoso para complementar essas informações.

Ele é gratuito e você não terá que gastar nenhum centavo, apenas alguns minutos fazendo o download do pacote de debugging da Microsoft.
Onde encontrá-lo?
Aqui:
http://www.microsoft.com/whdc/DevTools/ ... fault.mspx

Escolha a instalação com base na versão do SO que você tem(32 ou 64 bits).
Instale.

O pacote vem com entre outros aplicativos, o logger que é usado para identificar quais APIs um programa está usando, logview utilizado para visualizar os logs gerados pelo logger.exe ,KD que é um kernel debbuger e etc... por último mas não menos importante o windbg, o pacote também vem com outros debuggers user-mode application como o CDB mas não entraremos em detalhes.



Agora, abra o windbg na pasta que você instalou o pacote de debugging tools for Windows.
Você vai ter uma tela meio feinha de começo com somente a command bar à mostra.

O que você vai fazer é pressionar "CTRL + E" e abrir aquele assembly program que nós debugamos com o olly, se lembra?

Imediatamente ele nos mostra os módulos carregados e o estado dos registradores.(módulo é de certa forma só um outro nome para image file names(nome das "imagens" de arquivos /carregados/)

Existe um módulo em especial que sempre será carregado, é a DLL Kernel32.
Todos os programas que você tentar debuggar verá que essa DLL está presente.

Você quer saber por que isso te interessa?

Quando um processo é iniciado, o Windows cria um tipo de "header" para o processo.
Esse header se chama PEB(process enviroment block)¹.
Nele estão contidas todas as informações de um processo, inclusive os módulos carregados por ordem de loading.

Para nós, o conhecimento desse bloco é importante pois no Windows, nossos exploits precisam chamar APIs diferentemente de sistemas nix like que podemos chamar diretamente sys calls que estão sempre paradinhas la esperando por você, no entanto nós nem sempre sabemos o endereço em que uma DLL foi carregada e queremos que nosso exploit chame uma função da user32 por exemplo, o que fazemos, bem como o kernel32 é sempre carregado e é um dos primeiros módulos carregados, ele está sempre no mesmo lugar dentro do PEB de um processo.
E esse módulo tem duas APIs importantíssimas para podermos usar qualquer uma que quisermos... São as LoadLibrary* e GetProcessAdress* que respectivamente carregam um módulo e pegam o endereço de uma API contida nele.

Mas você ainda não vai construir um exploit agora... só que a partir de agora, as informações que antes eu disse que foram "filtradas/mastigadas" não serão mais tão mastigadas, então se prepare para uma enxurrada controlada de nomes estranhos aparecendo na sua tela. =P

¹:http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Process/PEB.html


Voltemos ao Windbg com nosso tuto.exe carregado, aperte "ALT + F7" para ver o disassembly.
E repare em uma coisa, nós estamos antes mesmo do programa começar, esse não é o código do nosso processo!
Então cadê ele?

O Windows ainda está por chamar o nosso programa mas se quisermos encontrar aonde começa o código dele é simples... vamos encontrar o entry point do módulo tuto.exe.

Entry point é basicamente aonde o código do programa começa, ele denota a parte do image file para o qual nós iremos dar um long jump, ou seja, a primeira função executada de um determinado processo.
Hoje em dia é normal que os programas só tenham um entry point, por exemplo o int WinMain() que nós vemos todo dia em nossos programas win app C++/C.

Image File ou imagem do arquivo, é a imagem que é criada na memória de um determinado arquivo.
Quando você executa um determinado programa, por exemplo, o nosso tuto.exe, o Windows copia ele todo para a memória, essa cópia ganha o nome de image file.

Mas não para por ai, não pense que memória é sinônimo de memória RAM física não.

Cada processo tem uma determinada quantidade de memória que é chamada de memória virtual.
Essa memória virtual é endereçável(lógico) mas o seu endereço é relativo, ou seja, nem sempre corresponde ao endereço REAL na memória física.
Isso quer dizer que uma variável de um programa pode estar claramente no endereço 0x0012ffaa, outro programa ter uma variável no mesmo endereço e no entanto, elas podem sim ter valores diferentes... isso porque os processos(normalmente) não compartilham sua memória virtual com outros, eles podem ter claramente o mesmo endereço virtual, sendo que o endereço virtual é mapeado(transformado) para um físico no momento de execução do processo.
Cada processo pode ter até 2GB de memória virtual, mas nem toda ela é alocável, parte é reservada pelo sistema e os endereços vão de 0x00000000e16 à 0x7FFFFFFFe16.('e16' é notação científica)

Lembra-se que eu disse que a stack poderia chegar ao endereço 0x00 e o sistema falaria que falta memória?
 Aquilo ajudou a entender que a memória cresce do maior endereço para o menor, mas eu coloquei aquele "hipoteticamente falando" justamente porque isso não é possível, o endereço virtual 0x00 é reservado para o sistema e se sobrescrito possivelmente quebra o programa.
E sim, no win32 a stack faz parte da memória virtual de um processo.

O mundo era mais bonito quando o céu só podia ser azul não é?

Mas a vida real não é assim e as coisas são mais complicadas do que isso, então se concentre e não perca o foco porque eu ainda não comecei a parte mais complicada.



Aprendemos o que é o entry point, então vamos achá-lo?

O windbg vem com várias extensões muito úteis e vamos usar uma delas.
Na command bar digite "!dlls".
Pronto!
Agora você está vendo informações específicas dos módulos carregados.



E está ai também o entry point do tuto.exe certo?
Então já sabemos seu endereço(401000), vamos colocar um breakpoint nesse endereço e mandar o windbg deixar o programa correr livre leve e solto até encontrar o que nós queremos.

Digite .cls para limpar a tela(so fresco mesmo =P)
Agora digite:
BP /1 401000 ".echo =============|| ENTRY POINT ||=================" [enter]

BP, seta um breakpoint.
/1 diz que assim que esse break point for executado uma vez, ele será deletado.
401000 é o endereço.
O que segue entre aspas é o comando que será executado quando esse breakpoint for encontrado.
No nosso caso, o echo vai exibir a string "...ENTRY P..." na tela.

Se aparecer o status "busy" ao lado da command bar, espere até o status voltar ao normal.

Agora digite 'g'(sem aspas) que  significa "oh baby, let's Go" ou  só "GO" se você preferir. =P

Ele vai rodar e vai para justamente aonde começa o nosso código tão conhecido. =]

sergaralho.blogspot.com --> a informação como ela deve ser.. pura!

blackwinner

Thks =]

Vo faze a uma nova parte dele assim que tira uns malware do pc...
É até injustiça existir malware pra esse pc aqui que eu divido com minha mãe e minha irmã o0
Elas são espiãs pq elas querem ser hackeadas e eu fico pouco tempo em casa para empedir lolz

e o outro pc é só pra trabalhos da empresa \=
sergaralho.blogspot.com --> a informação como ela deve ser.. pura!

Alucard


blackwinner

Rlx cara, todo mundo aqui contribui de alguma forma então eu também tenho que fazer alguma coisa de vez enquando... mas vlw =]

E apesar do tuto ser todo escrito por mim, a idéia de escrever sobre o assunto não foi só minha já que ela surgiu em uma conversa com uns amigos em uma conf que ocorreu no RJ sobre hacking/cracking, eles só não ajudam pq alguns não estão mais no brasil ou estão ocupados em seus próprios projetos.

Já consegui tirar os rootkits que estavam me pertubando aqui, de madruga possívelmente eu escrevo outra parte se minha mãe ou minha irmã não conseguirem ferrar esse pc aqui de novo.

Se eu morar nessa casa por mais um ano, eu vou virar o maior especialista em malwares do mundo.. 0o lolz ;D
sergaralho.blogspot.com --> a informação como ela deve ser.. pura!

Reeves

Kra...
Demorei para ter coragem de ler tudo
e agora que arrumei tempo e li tenho o que falar

está realmente muitooo bom blackwinner
parabéns mesmo kra!
grande contribuição  :)

Abraços!
  °vº   NÃO USE DROGAS,
/(_)\  USE GNU/LINUX
^ ^

blackwinner

#9
3.3 A base dos exploits.

Novo capítulo, mas nós vamos continuar com aquele nosso debugger(windbg) e no ponto aonde paramos, mas desta vez, a idéia não é explicar o que vai acontecer com o programa e sim entender talvez o último ponto necessário para começarmos a construir nossos exploits.

Só para recapitular:



A primeira coisa que quero que você faça após encontrar o entry point de tuto.exe é apertar f10, o debugger vai dar uma "passo" a frente colocando 00000008 na stack.
Ok, isso nós já sabemos.
O windbg já mostra o estado dos registradores, a próxima instrução a ser executada e etc, normal.

Vamos agora fazer um dump ou seja, ver o valor que está em certo local da memória virtual do nosso processo.

A função 'd' do windbg é responsável por fazer o dump da memória virtual de um processo.
Ela funciona com diferentes tamanhos.
Por exemplo, se você escrever 'db *endereço*' ela vai te mostrar o dump hexadecimal em bytes(8 bits) do endereço desejado.

Bem, nós sabemos que esp aponta para o topo da stack certo?
Então como nós já sabemos que o 0~8 já deve estar na stack, vamos ver essa função em ação passando para ela o endereço que está em esp.

Digite 'dd esp' e logo você verá isso:
0012ffa0  00000008 76373833 7ffdd000 0012ffec
0012ffb0  77a5a9bd 7ffdd000 00129e72 00000000
0012ffc0  00000000 7ffdd000 00000000 00000000
0012ffd0  00000000 0012ffb8 00000000 ffffffff
0012ffe0  77a28bf2 77a69096 00000000 00000000
0012fff0  00000000 00401000 7ffdd000 00000000
00130000  78746341 00000020 00000001 00003008
00130010  000000dc 00000000 00000020 00000000

Percebe o 8 no topo?
Repare que dd faz ele exibir em Double words ou seja, de 32 em 32 bits ou 8 caracteres hexadecimais/4 bytes.

Repare também que ele exibe 5 colunas, a primeria é o endereço do valor da segunda coluna na memória vitual do processo.

Nós podemos obrigá-la a exibir apenas uma coluna, assim:

'dd /c 1 esp'
Assim nós visualizaremos como se estivéssemos no ollydbg, de quatro em quatro bytes e seus respectivos endereços ao lado.

0012ffa0  00000008
0012ffa4  76373833
0012ffa8  7ffdd000
0012ffac  0012ffec
0012ffb0  77a5a9bd
0012ffb4  7ffdd000
0012ffb8  00129e72
0012ffbc  00000000
0012ffc0  00000000
0012ffc4  7ffdd000
0012ffc8  00000000
0012ffcc  00000000
0012ffd0  00000000
0012ffd4  0012ffb8
0012ffd8  00000000
0012ffdc  ffffffff
0012ffe0  77a28bf2
0012ffe4  77a69096
0012ffe8  00000000
0012ffec  00000000
0012fff0  00000000
0012fff4  00401000
0012fff8  7ffdd000
0012fffc  00000000
00130000  78746341
00130004  00000020
00130008  00000001
0013000c  00003008
00130010  000000dc
00130014  00000000
00130018  00000020
0013001c  00000000

Nós podemos visualizar de byte em byte ao invés de 4 em 4 como antes.
Usando o db como foi dito antes e podemos ainda mais, podemos dizer apartir de qual endereço da memória nós queremos ver e até qual.
Assim:
'db esp esp+3'
0:000> db esp esp+3
0012ffa0  08 00 00 00

Ou seja, ele vai exibir esp(12ffa0) até esp+3(12ffa43) em outras palavras ele vai exibir o
12ffa0
12ffa1
12ffa2
12ffa3

Ou seja, um double Word(4 bytes) só que de byte em byte.

Agora, se lembra que eu falei que as instruções asm são representações para os opcodes¹ e que esses sim é que rodam e fazem algo?
Bem, o programa já foi compilado e esses opcodes já estão na memória virtual do processo,  o disassembly só reconverte esses opcodes para código asm novamente o que não é nada difícil.


¹: tem um tuto mto bom que explica como são feitos, ele foi feito pelo membro desse fórum(alucard) e está aqui:
http://daniloxavier.blogspot.com/2008/0 ... nando.html



Bem, se esses opcodes estão no endereço em que vemos o disassembly, então nós podemos "ripalos".
Ou seja, nós podemos ver quais são esses opcodes e colocar em outro programa.

Vamos dumpar todo o código do nosso tuto.exe.

Digite 'dd 401000 40100b'

401000 é o endereço do entry point do nosso módulo como já vimos, e 40100b é o endereço da sua última instrução(ret).
A saída desse dump é:
00401000  ffb9086a 49ffffff c358fd75
A primeira coluna é um endereço como já sabemos e as 3 seguintes são o dump dessa área da memória.

Se lembra que nosso programa põe o 8 na stack e depois o coloca em eax?
Se lembra que eu também disse que eax é usado para retornar valores de funções.

Logo, imagine esse código:

int soma()
{
 int eax = 8, ecx = 0xFFFFFFFF;
 for(;ecx>0;ecx--);
 return eax;
}

Pois bem, o nosso programa é a completa representação desse código C.

Ele põe 8 na stack, cria um loop de 0xFFFFFFFF até 0x00(uma espécie de delay hardcoded) põe 8 em eax e ret retorna para o ponto aonde o processo chamador parou tirando da stack o endereço pra próxima instrução e colocando em eip, como eax é usado para retornar valores e resultados de funções então logo a nossa função retorna um número, o 8.

Bem, nós já temos os opcodes, que tal colocarmos ele em um programa e ver se roda?

Bem, vamos primeiro visualizá-lo como um programa C completo:

#include <stdio.h>


int soma()
{
    int eax = 8, ecx = 0xFFFFFFFF;

    for (;ecx > 0; ecx--);

    return eax;
}

int main()
{
    printf("Valor: %d", soma() );

}

Isso nós já sabemos né?

Então que tal colocarmos aqueles opcodes e ver se tem o mesmo resultado?

Vamos fazer isso, repare que temos 3 colunas de 4 bytes de dados.
4 bytes = int

Então vamos criar um array de 3 int's e vamos forçar o nosso programa C a chamá-lo como se fosse uma função...

Primeiro vamos copiar essas 3 colunas de opcodes e transformar em um array de ints.

int OpCodes[] = {0xffb9086a, 0x49ffffff, 0xc358fd75};
Veja que eu coloco o '0x' na frente para o compilador saber que são valores hexadecimais.

Agora que nós temos o tal array vamos forçar o compilador a chamá-lo como uma função que retornar um inteiro:
((int (*) ())OpCodes)() Não se assuste, olhe por um lado, nós temos o array OpCodes.
Vamos usar um cast de uma função que retorna um inteiro n'ele:
( int (*)() ) <- esse é o cast

Envolvemos o cast e o endereço do primeiro elemento de Opcodes entre parênteses:
((int) (*) ()) OpCodes)

E agora que já o transformamos em uma função que retorna um inteiro, vamos chamar essa função:
((int) (*) ()) OpCodes) ()


Bem, vamos colocar isso tudo num printf para que ele exiba o valor 8:

#include <stdio.h>



int main()
{
  int OpCodes[] = {0xffb9086a, 0x49ffffff, 0xc358fd75};

  printf("Valor: %d", ((int (*) ())OpCodes)() );

}

Vê?!

Alguns preferem fazer a mesma coisa só que com uma string.

Strings em C são simples arrays de bytes... isso significa que nós iremos ter que colocar um '\x'¹ para cada byte(2 dígitos hex), no final o resultado é o mesmo, a diferença é quem escreve mais:

#include <stdio.h>



int main()
{
  int OpCodes[] = {0xffb9086a, 0x49ffffff, 0xc358fd75};

  char szOpCodes[] =
                    "\x6a\x08\xb9\xff"
                    "\xff\xff\xff\x49"
                    "\x75\xfd\x58\xc3";


  printf("Valor: %d", ((int (*) ())szOpCodes)() );

}

Repare que eu só adicionei um 'sz'² e troquei a ordem dos bytes.

Você já vai saber o porque dessa ordem trocada... mas por hoje é só, cya. =]


¹: indica que o próximo byte não deve ser interpretado como caracter
²: notação húngara indicando uma string terminada em zero.
sergaralho.blogspot.com --> a informação como ela deve ser.. pura!

blackwinner

Vlw reeves =]

Alucard, coloquei teu tuto como referencia no meu mas se tiver algum problema com isso é só avisa que eu tiro.. fuiz
sergaralho.blogspot.com --> a informação como ela deve ser.. pura!

Alucard

Quote from: "blackwinner"Vlw reeves =]

Alucard, coloquei teu tuto como referencia no meu mas se tiver algum problema com isso é só avisa que eu tiro.. fuiz
Olha sem problema nenhum só gostaria que mudasse o link para
http://daniloxavier.blogspot.com/2008/0 ... nando.html

É onde eu postei no meu blog.

Muito obrigado!

blackwinner

sergaralho.blogspot.com --> a informação como ela deve ser.. pura!

Alienaqtor

Ae blackwinner, estou lendo seu tutorial e to gostando, mas , tive um problema quando tentei fazer o primeiro programa em assembly. Primeiro so colei no MASM e deu error "cannot perform this operation, there is no file loaded to perform it on" consegui resolver salvando o arquivo como  teste1.asm (mas eh um genio esse guri :P) so que quando fui tentar denovo ....

"D:\masm32\teste1"
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: D:\masm32\teste1.asm
MASM : fatal error A1000: cannot open file : D:\masm32\teste1.asm
_
Assembly Error
Pressione qualquer tecla para continuar. . .

o que fazer?

ps: parabens pelo tuto

Alienaqtor

PQP, parece ate zuacao, tentei denovo, e ai pela 4 tentativa deu certo, so deus sabe porque, mas, pelo menso agora posso continuar lendo o tutorial ... flw