Windows API Uma API (Application Programming Interface ) é uma interface para uma aplicação "falar" com outra. A Windows API 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 Windows API, pois o assunto é 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 Windows API 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. A partir daÃ, toda leitura e escrita neste arquivo deve ser feita a partir do handle . Por fim, a função CloseHandle() o fecha o handle quando ele não é mais necessário.
Agora vamos explicar os parâmetros da função MessageBox :
É um parâmetro de entrada, ou seja, é uma informação que a função precisa (e não quem chamou). Neste caso, é 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" (pode rir).
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 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 , que também está disponÃvel em C a partir da C23.
Veremos agora algumas funções da Windows API para funções básicas, mas você encontrará informações sobre outras rotinas no apêndice Funções da API do Windows.
Caixas de Mensagens Um "Hello, World" Conceituado
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:
![Tela de criação de projeto do Visual Studio Community 2019][image-1]
Manipulação de Arquivos É muito comum programas 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 quanto abre arquivos e outros objetos no Windows. O protótipo da versão Unicode dessa função é o seguinte:
Agora vamos aos parâmetros:
Acesso ao Registro 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:
Os valores à direita representam códigos para acessar cada chave. 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.
Copy #include <windows.h>
int main(void) {
MessageBox(NULL, "Mundo", "Olá", MB_OK);
}
Copy int MessageBox(
[in, optional] HWND hWnd,
[in, optional] LPCTSTR lpText,
[in, optional] LPCTSTR lpCaption,
[in] UINT uType
); Define que o parâmetro é de entrada
O parâmetro é opcional (pode ser NULL, ou 0 normalmente)
Um handle (identificador) da janela
L ong P ointer to a C onst T CHAR STR ing
unsigned int ou DWORD (32-bits ou 4 bytes )
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 uma das seguintes macros:
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 à CreateFile, é 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. Teste-o e se tudo funcionar, 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 especiais. Podemos usar NULL já que é um parâmetro opcional.
A WriteFile retorna TRUE se a escrita teve sucesso 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 handle dele 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 no diretório do projeto do Visual Studio e escrever o texto "Programando usando a API do Windows" nele. Vamos agora ver como fazer para acessar o registro do Windows através da API.
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ções 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 uso com a função.
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. Perceba que este é um parâmetro de saÃda, ou seja, quem chamou a função receberá algo nesta variável que pode ser útil após a chamada de função.
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 ter seu tipo compatÃvel com o tipo configurado no parâmetro dwType.
O tamanho dos dados 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":
Com este programa finalizamos esta breve introdução à Windows API. Existem, é claro, centenas de outras funções disponÃveis para uso, mas é preciso saber programar em C/C++ para utilizá-las em seus programas. Vamos agora ver como os programas ficam depois que os compilamos como você fez aqui. Para isso, vamos iniciar nossos estudos de Assembly.
Copy MessageBox(NULL, "Mundo", "Olá", MB_OKCANCEL | MB_ICONEXCLAMATION);
Copy MessageBox(NULL, "Mundo", "Olá"), 0x31);
Copy HANDLE CreateFileW(
[in] LPCWSTR lpFileName,
[in] DWORD dwDesiredAccess,
[in] DWORD dwShareMode,
[in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
[in] DWORD dwCreationDisposition,
[in] DWORD dwFlagsAndAttributes,
[in, optional] HANDLE hTemplateFile
);
Copy #define GENERIC_READ (0x80000000L)
#define GENERIC_WRITE (0x40000000L)
#define GENERIC_EXECUTE (0x20000000L)
#define GENERIC_ALL (0x10000000L)
Copy #define FILE_SHARE_READ 0x00000001
#define FILE_SHARE_WRITE 0x00000002
#define FILE_SHARE_DELETE 0x00000004
Copy #define CREATE_NEW 1
#define CREATE_ALWAYS 2
#define OPEN_EXISTING 3
#define OPEN_ALWAYS 4
#define TRUNCATE_EXISTING 5
Copy HANDLE hFile = CreateFile(L"log.txt",
GENERIC_WRITE,
0,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr);
Copy if (hFile == INVALID_HANDLE_VALUE) {
return EXIT_FAILURE;
}
Copy BOOL WriteFile(
[in] HANDLE hFile,
[in] LPCVOID lpBuffer,
[in] DWORD nNumberOfBytesToWrite,
[out, optional] LPDWORD lpNumberOfBytesWritten,
[in, out, optional] LPOVERLAPPED lpOverlapped
);
Copy #include <Windows.h>
int main() {
HANDLE hFile = CreateFile(L"log.txt",
GENERIC_WRITE,
0,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (hFile == INVALID_HANDLE_VALUE) {
return EXIT_FAILURE; // expande para 1
}
LPCSTR texto = "Programando usando a API do Windows";
size_t tam = lstrlenA(texto);
if (WriteFile(hFile, texto, tam, nullptr, nullptr) == FALSE) {
return EXIT_FAILURE;
}
CloseHandle(hFile);
}
Copy #define HKEY_CLASSES_ROOT (( HKEY ) (ULONG_PTR)((LONG)0x80000000) )
#define HKEY_CURRENT_USER (( HKEY ) (ULONG_PTR)((LONG)0x80000001) )
#define HKEY_LOCAL_MACHINE (( HKEY ) (ULONG_PTR)((LONG)0x80000002) )
#define HKEY_USERS (( HKEY ) (ULONG_PTR)((LONG)0x80000003) )
#define HKEY_PERFORMANCE_DATA (( HKEY ) (ULONG_PTR)((LONG)0x80000004) )
#define HKEY_PERFORMANCE_TEXT (( HKEY ) (ULONG_PTR)((LONG)0x80000050) )
#define HKEY_PERFORMANCE_NLSTEXT (( HKEY ) (ULONG_PTR)((LONG)0x80000060) )
#if(WINVER >= 0x0400)
#define HKEY_CURRENT_CONFIG (( HKEY ) (ULONG_PTR)((LONG)0x80000005) )
#define HKEY_DYN_DATA (( HKEY ) (ULONG_PTR)((LONG)0x80000006) )
#define HKEY_CURRENT_USER_LOCAL_SETTINGS (( HKEY ) (ULONG_PTR)((LONG)0x80000007) )
#endif
Copy LSTATUS RegCreateKeyW(
[in] HKEY hKey,
[in, optional] LPCWSTR lpSubKey,
[out] PHKEY phkResult
);
Copy HKEY hChave;
RegCreateKey(HKEY_CURRENT_USER, L"Software\\Mente Binária", &hChave);
RegCloseKey(hKey);
Copy LSTATUS RegSetKeyValueW(
[in] HKEY hKey,
[in, optional] LPCWSTR lpSubKey,
[in, optional] LPCWSTR lpValueName,
[in] DWORD dwType,
[in, optional] LPCVOID lpData,
[in] DWORD cbData
);
Copy #define REG_NONE ( 0ul ) // Nenhum tipo
#define REG_SZ ( 1ul ) // String UNICODE terminada em null
#define REG_EXPAND_SZ ( 2ul ) // String UNICODE terminada em null
// (com suporte à variáveis de ambiente)
#define REG_BINARY ( 3ul ) // Dados binários
#define REG_DWORD ( 4ul ) // Número de 32-bits em little endian
#define REG_DWORD_LITTLE_ENDIAN ( 4ul ) // Número de 32-bits (o mesmo que REG_DWORD)
#define REG_DWORD_BIG_ENDIAN ( 5ul ) // Número de 32-bits em big endian
#define REG_LINK ( 6ul ) // Um link (atalho) UNICODE
#define REG_MULTI_SZ ( 7ul ) // Várias strings UNICODE
#define REG_RESOURCE_LIST ( 8ul ) // Lista de recursos num mapa de recursos
#define REG_FULL_RESOURCE_DESCRIPTOR ( 9ul ) // Lista de recursos na descrição do hardware
#define REG_RESOURCE_REQUIREMENTS_LIST ( 10ul )
#define REG_QWORD ( 11ul ) // Número de 64-bits em little endian
#define REG_QWORD_LITTLE_ENDIAN ( 11ul ) // Número de 64-bits (o mesmo que REG_QWORD)
Copy #include <Windows.h>
int main() {
HKEY hChave;
RegCreateKey(HKEY_CURRENT_USER, L"Software\\Mente Binária", &hChave);
LPCWSTR website = L"https://menteb.in";
DWORD tamanho = (lstrlen(website) + 1) * sizeof(WCHAR); // +1 para incluir o terminador nulo
RegSetKeyValueW(hChave, nullptr, L"Website", REG_SZ, website, (DWORD)tamanho);
DWORD habilitado = 1;
RegSetKeyValueW(hChave, nullptr, L"Habilitado", REG_DWORD, &habilitado, sizeof(habilitado));
RegCloseKey(hChave);
return EXIT_SUCCESS;
} Nomeie o projeto como "Mensagem" (sem aspas) e após criá-lo, substitua o conteúdo do arquivo Mensagem.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 neste código. Vamos dedicar um tempo a eles. 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 letras maiúsculas de minúsculas, tanto faz escrever Windows.h, 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 que será 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 uma linha 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.
Lendo o Retorno da Função
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 à MessageBox.
Nas linhas 12 e 15 comparo o conteúdo da variável res, que detém o retorno da chamada à MessageBox. 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:
char or wchar\_t dependendo da UNICODE
const char or const wchar\_t dependendo da UNICODE
Vamos fazer algo um pouco mais significativo agora. Vamos pedir ao kernel do Windows que crie um arquivo para nós.
Copy #include <Windows.h>
int main() {
MessageBox(nullptr,
L"Estou estudando a Windows API\n\nGostei disso! :)",
L"Mente Binária",
MB_OK | MB_ICONINFORMATION);
}
Copy #include <Windows.h>
int main() {
LPCWSTR titulo = L"Mente Binária";
int ret = MessageBox(nullptr,
L"Você já se registrou em https://menteb.in?",
titulo,
MB_YESNO | MB_ICONQUESTION);
if (ret == IDYES) {
MessageBox(nullptr, L"Aê! Isso é ser inteligente!", titulo, MB_OK);
} else if (ret == IDNO) {
MessageBox(nullptr, L"Tá esperando o que então? Vai lá!", titulo, MB_OK);
}
}