Informática Numaboa - Tutoriais e Programação
O folgado (masm)
Sex 15 Dez 2006 21:23 |
- Detalhes
- Categoria: Assembly Numaboa (antigo oiciliS)
- Atualização: Terça, 16 Fevereiro 2010 22:29
- Autor: vovó Vicki
- Acessos: 17870
Aprender fazendo, este é o segredo. Neste tutorial ainda tem muita teoria e o primeiro resultado é, no mínimo, frustrante. Mas não desanime: veja como fazer a API do Windows trabalhar para você.
Projeto
Vamos realizar um projeto extremamente simples, copiado descaradamente do tutorial 3 do Iczelion. A idéia é genial: um programa cuja única função é retornar ao Windows, ou seja, dá a impressão de que não faz nada - um folgado. Apesar disso, é um programa! Na verdade, o que interessa realmente são os conceitos necessários para programar "o folgado". Sei que ainda é muita teoria para pouca prática, mas o começo é assim mesmo.
Planejando o folgado
Vamos escrever um programa que tem apenas uma linha de código. Através deste projeto teremos a oportunidade de rever a estrutura de um programa, além de ampliar nossos conhecimentos. Então vamos planejar:
- Nosso sistema operacional é o Windows de 32 bits (ou 64, se você é dos apressadinhos).
- Não precisamos mais do que o conjunto de instruções do processador 386.
- Temos o MASM à disposição para fazer o trabalho pesado.
- O objetivo do programa é voltar para o Windows assim que for executado. Nada mais
Botando a mão na massa
Chame o QEditor do MASM e digite o seguinte:
.386
.MODEL FLAT,STDCALL
.CODE
inicio:
end inicio
O sistema operacional win32 possui uma quantidade muito grande de funções que ele utiliza - nada impede que a gente pegue uma carona e também as usemos. Esta imensa coleção de funções do win32 é chamada de API (Application Programming Interface). As funções estão organizadas em bibliotecas denominadas bibliotecas de vínculo dinâmico (dynamic-linked libraries) ou DLL. Três delas são as mais importantes e as mais utilizadas: kernel32.dll, user32.dll e gdi32.dll. A kernel32.dll contém funções API que lidam com a memória e com a administração de processos. A user32.dll possui funções que controlam a aparência da interface com o usuário e a gdi32.dll tem funções responsáveis por operações gráficas. Caso exista uma função na API win32 que execute exatamente o trabalho que pretendemos realizar, podemos usá-la diretamente no nosso programa ao invés de escrever todo o procedimento.
A função que precisamos chama-se ExitProcess e está localizada na biblioteca kernel32.lib. Dando uma olhada na referência da API do Windows, encontramos o seguinte:
VOID ExitProcess(
UINT uExitCode // código de saída para todos as linhas de execução (threads)
);
Esta função não tem valor de retorno (é VOID) e exige um parâmetro (uExitCode) do tipo UINT. UINT é apenas um dos muitos nomes que o Windows usa para uma DWORD (double word - palavra de 32 bits).
Para poder usar esta função, é preciso passar algumas informações para o assembler e para o linker: o nome da função e a biblioteca onde ela está. Com estas informações, o assembler/linker indicará ao executável onde ele deve buscar a função que deve ser executada. O código da função NÃO é adicionado ao nosso executável, apenas as informações de qual função (ExitProcess) deve ser executada e onde encontrá-la (na kernel32.dll) em tempo de execução. De posse dessas informações, nosso programa será capaz de executar uma operação de chamada, ou seja, um call ExitProcess. A instrução call faz parte do conjunto de instruções do 386 e funciona da seguinte maneira:
Antes de incluirmos a instrução call ExitProcess no nosso código, precisamos colocar os parâmetros correspondentes na pilha. A pilha é um registrador especial da CPU, cujo conteúdo indica o endereço da memória onde se deposita uma informação que deva ser temporariamente guardada no curso do processamento. No nosso exemplo, é apenas o parâmetro uExitCode, o valor que o Windows recebe quando o nosso programa termina. Para isto usamos a instrução push (empurre para a pilha) e o fragmento de código fica assim:
push 0
call ExitProcess
Se esquecermos de "pushar" o valor do parâmetro para a pilha, o assembler/linker não irá notar a falta. Usamos uma instrução do conjunto de instruções do 386 e o assembler não tem como checar os parâmetros (quem usa a instrução deve saber o que está fazendo...) ao produzir o executável. Você só vai notar o erro quando executar o programa que, logicamente, dá pau.
Existe um modo mais seguro e cômodo de fazer chamadas. É através do INVOKE, uma sintaxe de chamada de alto nível. A sintaxe de INVOKE é a seguinte:
INVOKE expressão[,argumentos]
onde expressão pode ser o nome de uma função (ou um ponteiro para a função) e os argumentos (parâmetros) são separados por vírgulas. Neste caso o assembler/linker tem condições de verificar a sintaxe porque os parâmetros são citados explicitamente (e não apenas "pushados" para a pilha). Dá para perceber que um pouquinho de alto nível no assembly não faz mal a ninguém. Só tem um porém... para poder utilizar o INVOKE é necessário fornecer um protótipo da função que se quer usar.
O protótipo de uma função informa os atributos desta função para que o assembler (e o linker) possam fazer uma checagem dos tipos. O formato de um protótipo é o nome da função seguido da palavra-chave PROTO, e esta seguida da lista de parâmetros formando pares de nome:tipo de dado separados por vírgulas.
NomeDaFunção PROTO [NomeDoParâmetro]:TipoDeDado,[NomeDoParâmetro]:TipoDeDado...
Tendo essas informações podemos construir o protótipo, que nada mais faz do que definir ExitProcess como uma função que usa apenas um parâmetro do tipo DWORD:
ExitProcess PROTO uExitCode:DWORD
Uau! Agora podemos mostrar o protótipo da ExitProcess ao construtor (assembler). É claro que o construtor precisa ser apresentado à função ANTES de fazer uso da mesma no código. A apresentação consiste no protótipo e na biblioteca que contém a função. Colocamos então este par junto com as outras solicitações:
.386
.MODEL FLAT,STDCALL
includelib \masm32\lib\kernel32.lib
ExitProcess PROTO uExitCode:DWORD
.CODE
inicio:
end inicio
Mas que negócio é este de includelib? A diretiva includelib é apenas uma maneira de indicar ao assembler quais as bibliotecas de importação que o programa usa. Quando o assembler encontra este tipo de diretiva, ele põe um comando para o linker no arquivo objeto para que o linker saiba quais bibliotecas de importação precisam ser vinculadas ao programa. É o caminho das pedras...
Como eu disse na introdução, é muita teoria. Espero que, até este ponto, tudo tenha ficado claro para que possamos criar nosso fantástico programa que não faz nada :blush:
Criando o folgado
Está tudo em riba? Se você seguiu o tutorial, o arquivo texto que você digitou no editor de texto do MASM deve estar com esta cara:
.386
.MODEL FLAT,STDCALL
includelib \masm32\lib\kernel32.lib
ExitProcess PROTO uExitCode:DWORD
.CODE
inicio:
invoke ExitProcess,0
end inicio
E isso é tudo. O arquivo texto está pronto. Agora o trabalho vai ser o de clicar no menu do editor do MASM:
- Clique em [File / Save As] e salve o arquivo texto como folgado.asm
- Clique em [Project / Assemble ASM file] e o construtor do MASM irá produzir o arquivo folgado.obj
- Clique em [Project / Link OBJ File] e o linker do MASM irá produzir o arquivo folgado.exe
Se por acaso esquecermos o parâmetro da função (neste caso seria invoke ExitProcess ou invés de invoke ExitProcess,0), na etapa 2 o construtor (assembler) dará a seguinte mensagem de erro:
Assembling E:\masm32\Projetos\Folgado\folgado.asm
C:\masm32\Projetos\Folgado\folgado.asm (6) : error A2137 : too few arguments to INVOKE
Introduza o erro no texto e faça o teste só para ir pegando o jeito. E não se esqueça de SEMPRE salvar o arquivo texto antes de assemblar ou linkar.
Se tudo correu bem após a estapa 2, o que significa que texto está sem erros que o construtor possa detectar, temos dois arquivos no diretório indicado: o folgado.asm, de 163 bytes, e o folgado.obj, de 463 bytes. E se tudo continuou a correr bem, após a etapa 3 tem mais um arquivo no diretório: folgado.exe, de 1.536 bytes. Enxutinho, não é mesmo? Tá certo que o programa não faz grande coisa, mas mesmo assim...
Execute o folgado.exe e... surpresa! Tá pensando que eu vou dizer novamente que dá a impressão de que o programa não faz nada? Nananinanão! A surpresa é que o programa fez exatamente o que se esperava dele e que NÃO DEU ERRO!
Mais um pouco de teoria
Folgado é o programa, nóis não! Só mais uma coisinha...
Já pensou ficar montando protótipos de todas as funções que se queira importar de DLLs? É muito pra cabeça e vai acabar dando calo nos dedos (e detonando o teclado). Existe um modo muito mais elegante e folgado de obter o mesmo resultado: guardando os protótipos das funções em arquivos que possam ser incluídos no texto dos nossos programas. Na verdade, estes arquivos já estão prontos. Para cada biblioteca existe um arquivo de inclusão com todos os protótipos de todas as funções desta biblioteca. São os arquivos .inc. Para a kernel32.lib existe a kernel32.inc, para a user32.lib existe a user32.inc e assim por diante.
A diretiva include é que faz a mágica. Basta usar include seguido pelo nome do arquivo que você quiser inserir no local onde se encontra a diretiva. Usando este expediente, o texto ficaria assim:
.386
.MODEL FLAT,STDCALL
includelib \masm32\lib\kernel32.lib
include \masm32\include\kernel32.inc
.CODE
inicio:
invoke ExitProcess,0
end inicio
Quando o MASM32 processa a linha "include \masm32\include\kernel32.inc", ele abre o arquivo kernel32.inc, que se encontra no diretório \masm32\include, e processa o conteúdo do arquivo como se o conteúdo do kernel32.inc estivesse presente no seu código. Desta forma, pode-se substituir um caminhão de linhas com protótipos de funções com umas poucas linhas de include.
Se você tiver curiosidade, abra num editor de texto um arquivo include qualquer. Estes arquivos estão em texto ASCII raso, o que significa que você pode lê-los com facilidade além de poder fazer seus próprios arquivos include e incorporá-los aos seus programas quando necessário.
Os arquivos .inc podem conter outras definições (constantes, estruturas, etc) além de protótipos de funções. Um dos mais completos (e constantemente atualizado) é o arquivo windows.inc do Iczelion & Hutch. É pau pra toda obra. Você encontra este arquivo no pacote do MASM32 versão 7 do Hutch no site do Iczelion e no site do hutch.
Conferindo o trabalho do MASM
Existem diversos desassembladores muito bons, como o WDasm e o ADA. Eu trabalho muito com o OllyDbg, um debugger gratuito para Windows com um desassemblador excelente. Você pode encontrá-lo no site do autor em http://home.t-online.de/home/Ollydbg/. Abrindo o executável com o OllyDbg encontramos o seguinte:
00401000 > $ 6A 00 PUSH 0 ; /ExitCode = 0
00401002 . E8 01000000 CALL ; \ExitProcess
00401007 . CC INT3
00401008 $- FF25 00204000 JMP DWORD PTR DS:[<&KERNEL32.ExitProcess>
0040100E 00 DB 00
0040100F 00 DB 00
... ...
00401FFF 00 DB 00
No endereço 401000, ponto de entrada do executável (module entrypoint), já encontramos o push do parâmetro da função KERNEL32.ExitProcess. Logo depois vem o call para a função. O MASM trabalhou direitinho. Note que o nosso programa ocupa 4096 bytes (ou 4 Kb) de memória - vai do endereço 401000 até 401FFF - apesar de só ocupar 14 bytes com o código. O restante é preenchido com zeros.
Facilite a sua vida
Para facilitar a sua vida coloquei um arquivo zipado com o tutorial e o código fonte na seção de downloads da Aldeia. Procure na categoria tutoriais/Assembly Numaboa.