sábado, 18 de outubro de 2008

ShellCoding parte 2 - asm básico > 2.2 Introdução ao assembly > 2.3 instruções ASM

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 as 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 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 assemblers)

 

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.)

Clique aqui para ler a continuação

Nenhum comentário:

Postar um comentário

><))).>