sexta-feira, 26 de maio de 2017

Curso de Assembly Aula 13



+---------------+
¦ ASSEMBLY XIII ¦
+---------------+

    Algumas pessoas,  depois  de  verem  o  código-exemplo  do texto
anterior, desenvolvido  para  ser  compilado  em  TURBO  PASCAL,  me
perguntaram:  "E quanto ao C?!".  Well...  aqui väo algumas técnicas
para codificaçäo mixta em C...

    Antes de começarmos a dar uma olhada nas técnicas, quero  avisar
que  meu  compilador preferido é o BORLAND C++ 3.1.  Ele tem algumas
características que näo estäo presentes  do  MicroSoft C++ 7.0 ou no
MS Visual C++!  Por  exemplo,  O  MSC++  ou  o MS-Visual C++ näo tem
"pseudo"-registradores (que ajudam um  bocado  na mixagem de código,
evitando os "avisos" do compilador).

    Mesmo com algumas diferenças, você poderá usar as técnicas  aqui
descritas...  As regras podem ser usadas  para  qualquer  compilador
que näo gere aplicaçöes em modo protegido para o MS-DOS.


 ¦ Regras para a boa codigificaçäo assembly em C

    Assim como no TURBO PASCAL, devemos:

        * Nunca alterar CS, DS, SS, SP, BP e IP.
        * Podemos alterar com muito cuidado ES, SI e DI
        * Podemos alterar sempre que quisermos AX, BX, CX, DX

    O registrador DS  sempre  aponta  para  o  segmento  de dados do
programa...  Se a sua funçäo assembly acessa alguma variável global,
e você tiver alterado DS, a  variável  que  você  pretendia  acessar
näo estará disponível!  Os registradores SS, SP e BP säo usados pela
linguagem  para  empilhar  e  desempilhar  os parametros e variáveis
locais da funçäo na  pilha...   altera-los pode causar problemas!  O
par de registradores CS:IP nao deve ser  alterado  porque  indica  a
próxima  posiçäo  da  memória  que contém uma instruçäo assembly que
será executada...  Em qualquer  programa "normal" esses últimos dois
registradores säo deixados em paz.

    No caso dos registradores ES, SI e DI, o compilador  os  usa  na
manipulaçäo  de  pointers  e  quando precisa manter uma variável num
registrador  (quando  se  usa   a  palavra-reservada  "register"  na
declaraçäo de uma variável, por exemplo!).   Dentro  de  uma  funçäo
escrita puramente em assembly, SI e DI podem ser alterados a vontade
porque  o compilador trata de salva-las na pilha (via PUSH SI e PUSH
DI) e, ao término da funçäo, as  restaura  (via POP DI e POP SI).  A
melhor  forma  de  se  saber  se  podemos  ou  näo  usar  um  desses
registradores em um código mixto é compilando o programa  e  gerando
uma listagem assembly (no BORLAND C++ isso é feito usando-se a chave
-S  na  linha  de comando!)...  faça a análize da funçäo e veja se o
uso  desses  registradores  vai  prejudicar  alguma  outra  parte do
código!

    Se você näo quer ter essa dor de cabeça,  simplesmente  salve-os
antes de usar e restaure-os depois que os usou!


 ¦ Modelamento de memória:

    O  mais  chato  dos  compiladores   C/C++  para  o  MS-DOS  é  o
modelamento de memória, coisa que näo existe no TURBO PASCAL!   Digo
"chato"  porque esse recurso, QUE É MUITO UTIL, nos dá algumas dores
de cabeça de vez em quando...

    Os modelos COMPACT, LARGE e  HUGE usam, por default, pointers do
tipo  FAR  (segmento:offset).  Os modelos TINY, SMALL e MEDIUM usam,
por default, pointers do  tipo  NEAR  (apenas  offset, o segmento de
dados é assumido!).

    A "chatisse" está  em  criarmos  códigos  que  compilem  bem  em
qualquer  modelo  de  memória.  Felizmente isso é possível graças ao
pre-processador:

+------------------------------------------------------------------+
¦#if defined(__TYNY__) || defined(__SMALL__) || defined(__MEDIUM__)¦
¦/* Processamento de pointers NEAR */                              ¦
¦#else                                                             ¦
¦/* Processamento dos mesmos pointers... mas, FAR! */              ¦
¦#endif                                                            ¦
+------------------------------------------------------------------+

    Concorda comigo que é  meio  chato  ficar enchendo a listagem de
diretivas do pré-processador?... C'est la vie!

 ¦ C + ASM

    Os compiladores da BORLAND possuem a  palavra  reservada  "asm".
Ela  diz  ao compilador que o que a segue deve ser interpretado como
uma instruçäo  assembly.   Os  compiladores  da  MicroSoft possuem o
"_asm" ou o "__asm".  A  BORLAND  ainda  tem  uma  diretiva  para  o
pré-processador  que é usada para indicar ao compilador que o código
deve ser montado pelo TURBO ASSEMBLER ao invés do compilador C/C++:

+------------------------------------------------------------------+
¦ #pragma inline                                                   ¦
+------------------------------------------------------------------+

    Você pode usar isto ou entäo  a  chave -B da linha de comando do
BCC...  funciona da mesma forma!  Você  deve  estar  se  perguntando
porque  usar  o  TURBO  ASSEMBLER se o próprio compilador C/C++ pode
compilar o código...   Ahhhhh,  por  motivos de COMPATIBILIDADE!  Se
você pretende que o seu código seja compilável no TURBO C  2.0,  por
exemplo,  deve  incluir a diretiva acima!!  Além do mais, o TASM faz
uma checagem mais detalhada do código assembly do que o BCC...

    Eis um exemplo de uma funçäozinha escrita em assembly:

 +----------------------------------------------------------------+
 ¦  int f(int X)                                                  ¦
 ¦  {                                                             ¦
 ¦      asm mov     ax,X    /* AX = parametro X */                ¦
 ¦      asm add     ax,ax   /* AX = 2 * AX */                     ¦
 ¦      return _AX;         /* retorna AX */                      ¦
 ¦  }                                                             ¦
 +----------------------------------------------------------------+

    Aqui segue mais uma regra:

    ¦ Se a sua funçäo pretende  devolver  um valor do tipo "char" ou
      "unsigned char", coloque o valor  no  registrador  AL  e  (nos
      compiladores da BORLAND) use "return _AL;"

    ¦  Se  a  sua funçäo pretende devolver um valor do tipo "int" ou
      "unsigned int", coloque o  valor  no  registrador AX e (também
      nos compiladores da BORLAND) use "return _AX;"

    A última linha da funçäo acima ("return _AX;") näo é necessária,
mas  se  näo  a colocarmos teremos um aviso do compilador, indicando
que "a funçäo precisa retornar  um  'int'".  Se você omitir a última
linha   (é  o  caso  dos  compiladores  da  MicroSoft  que  näo  tem
pseudo-registradores) e näo ligar  pros  avisos, a coisa funciona do
mesmo jeito.

    Agora você deve estar querendo  saber  como  devolver  os  tipos
"long", "double", "float", etc...  O tipo "long" (bem como "unsigned
long") é simples:

    ¦ Se a sua funçäo pretende  devolver  um valor do tipo "long" ou
      "unsigned  long", coloque os 16 bits mais significativos em DX
      e os 16 menos significativos em AX.

    Näo existe uma forma de devolvermos  DX  e  AX  ao  mesmo  tempo
usando  os pseudo-registradores da Borland, entäo prepare-se para um
"aviso" do compilador...

    Os demais tipos näo säo  inteiros...   säo  de  ponto-flutuante,
portanto, deixe que o compilador tome conta deles.


 ¦ Trabalhando com pointers e vetores:

    Dê uma olhada na listagem abaixo:

+-------------------------------------------------------------------+
¦   unsigned ArraySize(char *str)                                   ¦
¦   {                                                               ¦
¦#if defined(__TYNY__) || defined(__SMALL__) || defined(__MEDIUM__) ¦
¦       asm mov     si,str  /* STR = OFFSET apenas */               ¦
¦#else                                                              ¦
¦       asm push    ds                                              ¦
¦       asm lds     si,str  /* STR = SEGMENTO:OFFSET */             ¦
¦#endif                                                             ¦
¦                                                                   ¦
¦       asm mov     cx,-1                                           ¦
¦ContinuaProcurando:                                                ¦
¦       asm inc     cx                                              ¦
¦       asm lodsb                                                   ¦
¦       asm or      al,al                                           ¦
¦       asm jnz     ContinuaProcurando                              ¦
¦       asm mov     ax,cx                                           ¦
¦                                                                   ¦
¦#if defined(__COMPACT__) || defined(__LARGE__) || defined(__HUGE__)¦
¦       asm pop     ds          /* Restaura DS */                   ¦
¦#endif                                                             ¦
¦                                                                   ¦
¦       return _AX;                                                 ¦
¦   }                                                               ¦
+-------------------------------------------------------------------+

    A rotina acima é equivalente a funçäo strlen() de <string.h>.

    Como disse antes, nos modelos COMPACT, LARGE e HUGE  um  pointer
tem o formato SEGMENTO:OFFSET  que  é  armazenado  na memória em uma
grande variável de 32 bits (os 16 mais significativos säo o SEGMENTO
e os 16 menos significativos säo o OFFSET).  Nos modelos TINY, SMALL
e MEDIUM apenas o OFFSET é fornecido no pointer  (ele  tem  16  bits
neste  caso),  o SEGMENTO é o assumido em DS (näo devemos alterá-lo,
neste caso!).

    Se você compilar  essa  listagem  nos  modelos COMPACT, LARGE ou
HUGE o código coloca em DS:SI o pointer (lembre-se: pointer é só  um
outro  nome  para "endereço de memória!").  Senäo, precisamos apenas
colocar em SI o OFFSET (DS já está certo!).

    Ao sair da funçäo, DS deve  ser  o  mesmo de antes da funçäo ser
chamada...  Portanto, nos modelos "LARGOS" (hehe) precisamos  salvar
DS ANTES de usá-lo e restaura-lo  DEPOIS de usado!  O compilador näo
faz isso automaticamente!

    Näo se preocupe com SI  (neste caso!)...  este sim, o compilador
salva sozinho...

    Um macete com o  uso  de  vetores  pode ser mostrado no seguinte
código exemplo:

 +-----------------------------------------------------------------+
 ¦  char a[3];                                                     ¦
 ¦  int b[3], c[3];                                                ¦
 ¦  long d[3];                                                     ¦
 ¦                                                                 ¦
 ¦  void init(void)                                                ¦
 ¦  {                                                              ¦
 ¦      int i;                                                     ¦
 ¦                                                                 ¦
 ¦      for (i = 0; i < 3; i++)                                    ¦
 ¦          a[i] = b[i] = c[i] = d[i] = 0;                         ¦
 ¦  }                                                              ¦
 +-----------------------------------------------------------------+

    O compilador gera a seguinte funçäo equivalente em assembly:

 +-----------------------------------------------------------------+
 ¦  void init(void)                                                ¦
 ¦  {                                                              ¦
 ¦      asm xor     si,si           /* SI = i */                   ¦
 ¦      asm jmp     short @1@98                                    ¦
 ¦  @1@50:                                                         ¦
 ¦      asm mov     bx,si           /* BX = i */                   ¦
 ¦      asm shl     bx,1                                           ¦
 ¦      asm shl     bx,1            /* BX = BX * 4 */              ¦
 ¦      asm xor     ax,ax                                          ¦
 ¦      asm mov     word ptr [d+bx+2],0 /* ?! */                   ¦
 ¦      asm mov     word ptr [d+bx],ax                             ¦
 ¦                                                                 ¦
 ¦      asm mov     bx,si                                          ¦
 ¦      asm shl     bx,1                                           ¦
 ¦      asm mov     [c+bx],ax                                      ¦
 ¦                                                                 ¦
 ¦      asm mov     bx,si       /* ?! */                           ¦
 ¦      asm shl     bx,1        /* ?! */                           ¦
 ¦      asm mov     [b+bx],ax                                      ¦
 ¦                                                                 ¦
 ¦      asm mov     [a+si],al                                      ¦
 ¦      asm inc     si                                             ¦
 ¦  @1@98:                                                         ¦
 ¦      asm cmp     si,3                                           ¦
 ¦      asm jl      short @1@50                                    ¦
 ¦  }                                                              ¦
 +-----------------------------------------------------------------+

    Quando poderiamos ter:

 +-----------------------------------------------------------------+
 ¦  void init(void)                                                ¦
 ¦  {                                                              ¦
 ¦      asm xor     si,si           /* SI = i = 0 */               ¦
 ¦      asm jmp     short @1@98                                    ¦
 ¦  @1@50:                                                         ¦
 ¦      asm mov     bx,si           /* BX = i */                   ¦
 ¦      asm shl     bx,1                                           ¦
 ¦      asm shl     bx,1            /* BX = BX * 4 */              ¦
 ¦      asm xor     ax,ax           /* AX = 0 */                   ¦
 ¦      asm mov     word ptr [d+bx+2],ax /* modificado! */         ¦
 ¦      asm mov     word ptr [d+bx],ax                             ¦
 ¦                                                                 ¦
 ¦      asm shr     bx,1            /* BX = BX / 2 */              ¦
 ¦      asm mov     [c+bx],ax                                      ¦
 ¦      asm mov     [b+bx],ax                                      ¦
 ¦                                                                 ¦
 ¦      asm mov     [a+si],al                                      ¦
 ¦      asm inc     si                                             ¦
 ¦  @1@98:                                                         ¦
 ¦      asm cmp     si,3                                           ¦
 ¦      asm jl      short @1@50                                    ¦
 ¦  }                                                              ¦
 +-----------------------------------------------------------------+

    Note  que  economizamos  3  instruçöes  em  assembly   e   ainda
aceleramos um tiquinho, retirando o movimento de um  valor  imediato
para memória (o 0 de  "mov  word  ptr [d+bx+2],0"), colocando em seu
lugar o registrador AX, que foi zerado previamente.

    Isso  parece  besteira neste código, e eu concordo...  mas, e se
tivessemos:

 +-----------------------------------------------------------------+
 ¦  void init(void)                                                ¦
 ¦  {                                                              ¦
 ¦      for (i = 0; i < 32000; i++)                                ¦
 ¦          a[i] = b[i] = c[i] = d[i] =                            ¦
 ¦          e[i] = f[i] = g[i] = h[i] =                            ¦
 ¦          I[i] = j[i] = k[i] = l[i] =                            ¦
 ¦          m[i] = n[i] = o[i] = p[i] =                            ¦
 ¦          r[i] = s[i] = t[i] = u[i] =                            ¦
 ¦          v[i] = x[i] = y[i] = z[i] =                            ¦
 ¦          /* ... mais um monte de membros de vetores... */       ¦
 ¦          = _XYZ[i] = 0;                                         ¦
 ¦  }                                                              ¦
 +-----------------------------------------------------------------+

    A perda de eficiência  e  o  ganho  de  tamanho do código seriam
enormes por causa da quantidade de  vezes  que  o  loop  é  exeutado
(32000)  e  por  causa  do numero de movimentos de valores imediatos
para memória, "SHL"s  e  "MOV  BX,SI"  que  teriamos!  Conclusäo: Em
alguns casos é mais conveniente manipular VARIOS vetores com funçöes
escritas em assembly...

   EXEMPLO de codificaçäo: ** O swap() aditivado :)

    Alguns  códigos  em  C  que  precisam  trocar  o conteúdo de uma
variável pelo de outra usam o seguinte macro:

 +-----------------------------------------------------------------+
 ¦  #define swap(a,b) { int t; t = a; a = b; b = t; }              ¦
 +-----------------------------------------------------------------+

    Bem...  a macro acima funciona  perfeitamente bem, mas vamos dar
uma olhada no código  assembly  gerado  pelo compilador pro seguinte
programinha usando o macro swap():

 +-----------------------------------------------------------------+
 ¦  #define swap(a,b) { int t; t = a; a = b; b = t; }              ¦
 ¦                                                                 ¦
 ¦  int x = 1, y = 2;                                              ¦
 ¦                                                                 ¦
 ¦  void main(void)                                                ¦
 ¦  { swap(x,y); }                                                 ¦
 +-----------------------------------------------------------------+

    O código equivalente, após ser pre-processado, ficaria:

 +-----------------------------------------------------------------+
 ¦  int x = 2, y = 1;                                              ¦
 ¦  void main(void) {                                              ¦
 ¦      int t;                                                     ¦
 ¦                                                                 ¦
 ¦      asm mov ax,x                                               ¦
 ¦      asm mov t,ax                                               ¦
 ¦      asm mov ax,y                                               ¦
 ¦      asm mov x,ax                                               ¦
 ¦      asm mov ax,t                                               ¦
 ¦      asm mov y,ax                                               ¦
 ¦  }                                                              ¦
 +-----------------------------------------------------------------+

    No máximo, o compilador usa o registrador SI ou DI como variavel
't'... Poderiamos fazer:

 +-----------------------------------------------------------------+
 ¦  int x = 2, y = 1;                                              ¦
 ¦  void main(void)                                                ¦
 ¦  {                                                              ¦
 ¦      asm mov     ax,x                                           ¦
 ¦      asm mov     bx,y                                           ¦
 ¦      asm xchg    ax,bx                                          ¦
 ¦      asm mov     x,ax                                           ¦
 ¦      asm mov     y,ax                                           ¦
 ¦  }                                                              ¦
 +-----------------------------------------------------------------+

    Repare que eliminamos  uma  instruçäo  em  assembly,  eliminando
também  um acesso à memória e uma variável local...  Tá bom...  pode
me chamar de chato, mas  eu  ADORO  diminuir  o tamanho e aumentar a
velocidade de meus programas usando esse tipo de artifício! :)


Nenhum comentário:

Postar um comentário

Curso SANS 504 Hacker Techniques, Exploits & Incident Handling

SANS SECURITY 504 - Hacker Techniques, Exploits & Incident Handling     SANS Security 504.5.pdf13 MB     SANS Security 504.1.pdf12 M...