Escrevendo Aplicações Nativas para Windows - Parte 2
Por Davi Chaves
Até o momento, temos uma aplicação nativa, mas não conseguimos printar nada. Uma forma de construirmos essa funcionalidade seria fazer nosso processo native escrever no standard output do processo pai. Entretanto, eu acabei encontrando algumas dificuldades nessa solução. Dessa forma, vamos utilizar uma outra alternativa: criar um arquivo output.txt e escrever todos as saidas do nosso programa nele.
Criando e Escrevendo em um Arquivo
Para isso, vamos ter que utilizar duas APIs: NtCreateFile e NtWriteFile.
NTSYSCALLAPI
NTSTATUS
NTAPI
NtCreateFile(
_Out_ PHANDLE FileHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_opt_ PLARGE_INTEGER AllocationSize,
_In_ ULONG FileAttributes,
_In_ ULONG ShareAccess,
_In_ ULONG CreateDisposition,
_In_ ULONG CreateOptions,
_In_reads_bytes_opt_(EaLength) PVOID EaBuffer,
_In_ ULONG EaLength
);
NTSYSCALLAPI
NTSTATUS
NTAPI
NtWriteFile(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE Event,
_In_opt_ PIO_APC_ROUTINE ApcRoutine,
_In_opt_ PVOID ApcContext,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_reads_bytes_(Length) PVOID Buffer,
_In_ ULONG Length,
_In_opt_ PLARGE_INTEGER ByteOffset,
_In_opt_ PULONG Key
);
As documentações dessas APIs podem ser encontradas na página da Microsoft.
#include <phnt_windows.h>
#include <phnt.h>
#pragma comment(lib, "ntdll")
NTSTATUS NtProcessStartup(PPEB _peb) {
// Cria o caminho do arquivo
WCHAR _path[] = L"\\??\\C:\\Users\\IEUser\\Desktop\\output.txt";
UNICODE_STRING path;
RtlInitUnicodeString(&path, _path);
// Cria os atributos
OBJECT_ATTRIBUTES fileAttr = RTL_CONSTANT_OBJECT_ATTRIBUTES(&path, OBJ_CASE_INSENSITIVE);
IO_STATUS_BLOCK ioStatus;
// hFile vai receber o HANDLE do arquivo criado
HANDLE hFile;
NtCreateFile(&hFile, GENERIC_WRITE | SYNCHRONIZE, &fileAttr, &ioStatus, nullptr, 0, 0, FILE_SUPERSEDE, FILE_SYNCHRONOUS_IO_NONALERT, nullptr, 0);
// Escreve no arquivo utilizando o hFile
char buffer[] = "Hello World";
NtWriteFile(hFile, nullptr, nullptr, nullptr, &ioStatus, (PVOID)buffer, (ULONG)strlen(buffer), nullptr, nullptr);
NtClose(hFile);
return STATUS_SUCCESS;
}
Se lembre de alterar o _path para o valor desejado. Esse código vai criar um arquivo na área de trabalho e depois escrever o que tiver no buffer nele. Já daria para utilizar isso como um “printf”. Mas podemos fazer melhor.
Simulando o printf
Para isso, vamos utilizar a função vsprintf_s
, que é basicamente um versão da sprintf_s
que recebe uma lista de argumentos. Observe que tivemos que declarar a função vsprintf_s
pois por algum motivo ela não está declarada nos headers. Além disso, para termos uma sintáxe mais limpa, estamos acessando o HANDLE hFile de forma global.
#define PAGE_SIZE 0x1000
HANDLE hFile;
extern "C" int vsprintf_s(
char* buffer,
size_t numberOfElements,
const char* format,
va_list argptr
);
NTSTATUS printf(const char* format, ...) {
va_list args;
va_start(args, format);
char buffer[PAGE_SIZE] = { 0 };
vsprintf_s(buffer, PAGE_SIZE, format, args);
va_end(args);
IO_STATUS_BLOCK ioStatus;
return NtWriteFile(hFile, nullptr, nullptr, nullptr, &ioStatus, (PVOID)buffer, (ULONG)strnlen(buffer, PAGE_SIZE), nullptr, nullptr);
}
Podemos, também, colocar o resto do código em funções.
NTSTATUS OpenOutputFile(PHANDLE phFile) {
WCHAR _path[MAX_PATH] = { 0 };
swprintf_s(_path, ARRAYSIZE(_path), L"\\??\\%ls%ls", peb->ProcessParameters->CurrentDirectory.DosPath.Buffer, L"output.txt");
UNICODE_STRING path;
RtlInitUnicodeString(&path, _path);
IO_STATUS_BLOCK ioStatus;
OBJECT_ATTRIBUTES fileAttr = RTL_CONSTANT_OBJECT_ATTRIBUTES(&path, OBJ_CASE_INSENSITIVE);
return NtCreateFile(phFile, GENERIC_WRITE | SYNCHRONIZE, &fileAttr, &ioStatus, nullptr, 0, 0, FILE_SUPERSEDE, FILE_SYNCHRONOUS_IO_NONALERT, nullptr, 0);
}
NTSTATUS CloseOutputFile() {
return NtClose(hFile);
}
Perceba que a primeira função pega um HANDLE para o arquivo de saída. Diante disso, temos que chama-lá no início da execução do nosso programa e, ao final, devemos fechar esse HANDLE.
NTSTATUS NtProcessStartup(PPEB peb) {
if (!NT_SUCCESS(OpenOutputFile(&hFile))) return 1;
printf("Hello World! %s %d\n", "exemple", 42);
CloseOutputFile();
return STATUS_SUCCESS;
}
É importante notar que não estamos fazendo muito as tratativas de possíveis erros por simplicidade.
Conclusão
Podemos simular a funcionalide do printf
escrevendo em um arquivo de texto qualquer. Em artigos futuros vamos fazer com que o output do nosso programa nativo seja mostrado pelo console.