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":
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:
[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)
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.
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:
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
É 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.