Vamos programar um pouco. Neste momento é importante, se ainda não o fez, que você instale o Visual Studio Community.
Abra o Visual Studio e crie um novo projeto do tipo Console App, conforme a imagem abaixo mostra:
Nomeie o projeto como "Mensagem" (sem aspas) e após criá-lo, substitua o conteúdo do arquivo Mensagens.cpp que o Visual Studio criará automaticamente por este:
Tecle F5 para rodar o programa e você deve ver uma janela como esta:
Há vários conceitos escondidos neste código de propósito, de forma que dedeiquemos alguns minutos ao estudo deles. Acompanhe:
Na linha 1, como o Windows utiliza sistemas de arquivos que não são sensíveis ao caso, ou seja, não diferenciam maiúsculas de minúsculas, tanto faz escrever Windows.h
, windows.h
ou mesmo WINDOWS.H
. Vai funcionar.
Na linha 4 chamei a função MessageBox
, mas ela na verdade não existe: é uma macro, substituída pelo pré-processador pelas funções MessageBoxW
(mais comum) ou MessageBoxA
(caso a macro UNICODE
não esteja definida).
Ainda na linha 4 introduzi um conceito novo, de nullptr
ao invés de NULL
, aproveitando que o compilador utilizado é de C++. Acho melhor de digitar.
Nas linhas 5 e 6 (sim, não há o menor problema em colocar os outros parâmetros da função em outras linhas para facilitar a leitura) eu passo para a função o texto e o título, respectivamente. Impossível não notar o L
colado com as aspas duplas que abrem uma string em C não é mesmo? Ele serve para transformar a string subsequente em uma wide string (Unicode), que já estudamos. Este L
é necessário porque a função MessageBox
vai expandir, por padrão, para a MessageBoxW
(perceba o W
no final) que é a versão da MessageBox
que trabalha com strings Unicode. Também usamos o caractere de nova linha duas vezes para dividir a mensagem em três linhas, sendo a segunda vazia.
Na linha 7 eu utilizo uma combinação de duas flags: MB_OK
e MB_ICONINFORMATION
. Esta última configura este ícone de um "i" numa bolinha azul.
Agora vamos criar um programa um pouco maior afim de estudar mais conceitos da API do Windows. Compila aí:
Vamos analisar os conceitos novos aqui, como fizemos com o programa anterior:
Na linha 5 declaro uma variável do tipo LPCWSTR
. A diferença de LPCSTR
, que já estudamos, é este "W", de wide, para definir uma string Unicode.
A linha 7 declara uma variável ret
do tipo int
e já a inicializa com o retorno da chamada à MessageBoxW
.
Nas linhas 12 e 15 comparo o conteúdo da variável res
, que detém o retorno da chamada à MessageBoxW
. Se for igual a IDYES
, novamente uma macro, mostra uma determinada mensagem. Se for igual a IDNO
, mostra outra.
Em relação às strings, há três maneiras de se programar com a Windows API: ASCII (CHAR)
, UNICODE (WCHAR
) ou em compatibilidade (TCHAR
), que expandirá para CHAR
ou WCHAR
, caso a macro UNICODE
esteja definida. Atualmente, é recomendado utilizar WCHAR
e textos L"assim"
.
A tabela abaixo ajuda na compreensão:
O registro do Windows é um repositório de dados utilizado normalmente para armazenar configurações de programas instalados no sistema operacional e do próprio sistema, mas na real ele não faz distinção do que pode ser armazenado lá, já que suporta vários tipos de dados, incluindo textos, números e dados binários.
A estrutura do registro é parecida com um sistema de arquivos. As chaves são como as pastas e os valores são como os arquivos. Os dados de um valor são como o conteúdo dos arquivos.
O registro tem algumas chaves especiais em sua raiz. São elas:
As quatro primeiras chaves são as mais comuns. Dentro delas, é possível criar e ler subchaves e manipular seus valores. Vamos ver como fazer isso estudando a função RegCreateKey
.
Embora a Microsoft recomende utilizar a versão mais nova dessa função chamada RegCreateKeyEx
, muitos programas ainda utilizam a versão mais antiga, que estudaremos agora. Eis o protótipo da versão ASCII desta função:
Agora vamos aos parâmetros:
Uma das chaves raíz, por exemplo: HKEY_CURRENT_USER
ou HKEY_LOCAL_MACHINE
(para essa o usuário rodando o programa precisa ter privilégios administrativos).
A subchave desejada, por exemplo, se o parâmetro hKey
HKEY_LOCAL_MACHINE
e lpSubKey
é Software\Microsoft\Windows\
, o caminho completo utilizado pela função será HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\
.
Alguns textos abreviam essas chaves raíz com as letras iniciais de seu nome. Por exemplo, HKCU
para HKEY_CURRENT_USER
, HKCR
para HKEY_CLASSES_ROOT
e HKLM
para HKEY_LOCAL_MACHINE
. Tais abreviaçõe são válidas para acesso ao registro através de programas como o Registry Editor (regedit.exe), mas não são válidas para código em C.
Um ponteiro para uma váriável do tipo HKEY
, previamente alocada, pois é aqui que a função vai escrever o handle da chave criada ou aberta por ela.
Colocando tudo junto, se quisermos criar a sub-chave HKCU\Software\Mente Binária
, basta fazer:
Perceba que, assim como um handle para arquivo, o handle para chave também precisa ser fechado depois de seu uso.
Como o nome sugere, essa função configura um valor em uma chave. Seu protótipo é:
Já sabemos o que são os parâmetros hKey
e lpSubKey
. Nos restam então os seguintes:
Um ponteiro para uma string contendo o nome do valor. Caso seja NULL
ou aponte para uma string vazia, o valor padrão da chave é considerado.
O tipo do valor. Pode ser um dos seguintes:
Os dados do valor, que deve ser casar com o tipo configurado no parâmetro dwType
.
O tamanho dos dadoos do valor.
O código abaixo cria uma chave HKCU\Software\Mente Binária
, configura um valor "Habilitado" do tipo REG_DWORD
com o dado 1
e um valor "Website" do tipo REG_SZ
com o dado textual "https://menteb.in":
É muito comum softwares trabalharem com arquivos. O mesmo vale para malware. Considero importante, do ponto de vista de engenharia reversa, saber como as funções do Windows que trabalham com arquivos são chamadas.
Vamos começar pela função CreateFile
, que tanto cria quando abre arquivos e outros objetos no Windows. O protótipo da versão Unicode dessa função é o seguinte:
Agora vamos aos parâmetros:
O caminho do arquivo que será aberto para escrita ou leitura. Se somente um nome for especificado, o diretório de onde o programa é chamado será considerado. Este parâmetro é do tipo LPCSTR na versão ASCII da função e do tipo LPCSWSTR na versão UNICODE.
Este é um campo numérico que designa o tipo de acesso desejado ao arquivo. Os valores possíveis são:
Também é possível combinar tais valores. Por exemplo, GENERIC_READ | GENERIC_WRITE
para abrir um arquivo com acesso de leitura e escrita.
O modo de compartilhamento deste arquivo com outros processos. Os valores possíveis são:
No entanto, o valor 0
é bem comum e faz com que nenhum outro processo apossa abrir o arquivo simultâneamente.
Um ponteiro para uma estrutura especial do tipo SECURITY_ATTRIBUTES
. Em geral, usamos NULL
.
Ações para tomar em relação à criação do arquivo, pode ser:
Atributos e flags especiais para os arquivos. O mais comum é passar somente FILE_ATTRIBUTE_NORMAL
, mas a documentação oficial prevê muitos outros possíveis valores.
Um handle válido para um arquivo modelo, para ter os atributos copiados. Normalmente é NULL
.
Colocando tudo junto, podemos criar um arquivo usando a API do Windows assim:
Logo após a chamada à CreateFileA
, é comum encontrar uma comparação para saber se o objeto foi aberto com sucesso. Como esta função retorna um handle para o arquivo ou o valor INVALID_HANDLE_VALUE
(0xffffffff) em caso de falha, podemos fazer na sequência:
Por fim, é importante fechar o handle obtido para o arquivo. Isso é feito com a função CloseHandle
:
O código que construímos só abre o arquivo, criando-o sempre, e depois o fecha. Nada é escrito nele. Vamos agora escrever algum texto antes de fechar, mas para isso precisamos de mais uma função.
Essa função escreve dados num objeto. Seu protótipo é o seguinte:
hFile
é o handle de um arquivo previamente aberto com a CreateFile
. O próximo parâmetro, lpBuffer
, é um ponteiro para os dados que pretendemos escrever no arquivo. A quantidade de bytes a serem escritos é informada pelo parâmetro nNumberOfBytesToWrite
e a quantidade de bytes que a função conseguiu escrever é colocada num parâmetro de saída opcional lpNumberOfBytesWritten
. Por fim, o parâmetro lpOverlapped
é um ponteiro para uma estrutura do tipo OVERLAPPED
utilizada em casos especials, mas normalmente é NULL
.
A WriteFile
retorna TRUE
se a escrita ocorreu, ou FALSE
em caso de falha.
Com tais definições, podemos completar nosso programa para fazê-lo escrever um texto no arquivo antes de fechar o arquivo com a CloseHandle
. O código final fica assim:
Ao compilar e rodar este código que produzimos, o programa deve criar o arquivo log.txt
e escrever o texto "Programando usando a API do Windows" nele.
Tipo | Expansão |
---|---|
LPSTR
char*
LPCSTR
const char*
LPWSTR
wchar_t*
LPCWSTR
const wchar_t*
LPTSTR
char or wchar_t dependendo da UNICODE
LPCTSTR
const char or const wchar_t dependendo da UNICODE
Uma API (Application Programming Interface) é uma interface para programar uma aplicação. No caso da Windows API, esta consiste num conjunto de funções expostas para serem usadas por aplicativos rodando em user mode.
Para o escopo deste livro, vamos cobrir uma pequena parte da Win32 API (outro nome para a Windows API), pois o assunto é bastante extenso.
Considere o seguinte programa em C:
A função MessageBox() está definida em windows.h. Quando compilado, o código acima gera um executável dependente da USER32.DLL
(além de outras bibliotecas, dependendo de certas opções de compilação), que provê a versão compilada de tal função. A documentação desta e de outras funções da Win32 está disponível no site da Microsoft. Copiamos seu protótipo abaixo para explicar seus parâmetros:
A Microsoft criou definições de anotações e novos tipos na linguagem C que precisam ser explicadas para o entendimento dos protótipos das funções de sua API. Para entender o protótipo da função MessageBox, é preciso conhecer o significado dos seguintes termos:
Um handle é um número que identifica um objeto (arquivo, chave de registro, diretório, etc) aberto usado por um processo. É um conceito similar ao file descriptor em ambiente Unix/Linux. Handles só são acessíveis diretamente em kernel mode, por isso os programas interagem com eles através de funções da API do Windows. Por exemplo, a função CreateFile() retorna um handle válido em caso de execução com sucesso, enquanto a função CloseHandle() o fecha.
Agora vamos explicar os parâmetros da função MessageBox:
Um handle que identifica qual janela é dona da caixa de mensagem. Isso serve para atrelar uma mensagem a uma certa janela (e impedi-la de ser fechada antes da caixa de mensagem, por exemplo). Como é opcional, este parâmetro pode ser NULL, o que faz com que a caixa de mensagem não possua uma janela dona.
Um ponteiro para um texto (uma string) que será exibido na caixa de mensagem. Se for NULL, a mensagem não terá um conteúdo, mas ainda assim aparecerá.
Um ponteiro para o texto que será o título da caixa de mensagem. Se for NULL, a caixa de mensagem terá o título padrão "Error".
Configura o tipo de caixa de mensagem. É um número inteiro que pode ser definido por macros para cada flag, definida na documentação da função. Se passada a macro MB_OKCANCEL (0x00000001L), por exemplo, faz com que a caixa de mensagem tenha dois botões: OK e Cancelar. Se passada a macro MB_ICONEXCLAMATION (0x00000030L), a janela terá um ícone de exclamação. Se quiséssemos combinar as duas características, precisaríamos passar as duas flags utilizando uma operação OU entre elas, assim:
Como macros e cálculos assim são resolvidos em C numa etapa conhecida por pré-compilação, o resultado da operação OU entre 1 e 0x30 será substituído neste código, antes de ser compilado, ficando assim:
Dizer que um parâmetro é opcional não quer dizer que você não precise passá-lo ao chamar a função, mas sim que ele pode ser NULL, ou 0, dependendo do que a documentação da função diz. Como o Visual Studio é um compilador de C++, você também pode usar nullptr.
Veremos agora algumas funções da Windows API para funções básicas, mas você encontrará informações sobre outras rotinas no apêdice Funções da API do Windows.
[in]
Define que o parâmetro é de entrada
[optional]
O parâmetro é opcional (pode ser NULL, ou 0 normalmente)
HWND
Um handle (identificador) da janela
LPCTSTR
Long Pointer to a Const TCHAR STRing
UINT
unsigned int ou DWORD (32-bits ou 4 bytes)