https://github.com/libvmi/libvmi
Tip revision: df8547ff075d4352db2eb802775b7fa7a92756db authored by Petr Beneš on 05 February 2024, 16:53:32 UTC
Set mem-range access, read wstrings, get last PF (#1095)
Set mem-range access, read wstrings, get last PF (#1095)
Tip revision: df8547f
fool-patchguard.c
/* The LibVMI Library is an introspection library that simplifies access to
* memory in a target virtual machine or in a file containing a dump of
* a system's physical memory. LibVMI is based on the XenAccess Library.
*
* Author: Mathieu Tarral (mathieu.tarral@ssi.gouv.fr)
*
* This file is part of LibVMI.
*
* LibVMI is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* LibVMI is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with LibVMI. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <stdio.h>
#include <inttypes.h>
#include <signal.h>
#include <libvmi/libvmi.h>
#include <libvmi/events.h>
typedef struct cb_data {
addr_t ntload_driver_entry_addr;
emul_read_t emul_read;
} cb_data_t;
static int interrupted = 0;
static void close_handler(int sig)
{
interrupted = sig;
}
event_response_t read_callback(vmi_instance_t vmi, vmi_event_t *event)
{
(void)vmi;
cb_data_t *cb_data = (cb_data_t*)event->data;
event_response_t rsp = VMI_EVENT_RESPONSE_NONE;
char str_access[4] = {'_', '_', '_', '\0'};
if (event->mem_event.out_access & VMI_MEMACCESS_R) str_access[0] = 'R';
if (event->mem_event.out_access & VMI_MEMACCESS_W) str_access[1] = 'W';
if (event->mem_event.out_access & VMI_MEMACCESS_X) str_access[2] = 'X';
printf("%s: at 0x%"PRIx64", on frame 0x%"PRIx64", permissions: %s\n",
__func__, event->x86_regs->rip, event->mem_event.gfn, str_access);
if (event->x86_regs->rip == cb_data->ntload_driver_entry_addr) {
printf("READ attempt on NtLoadDriver SSDT entry !\n");
// set data to emulate
event->emul_read = &cb_data->emul_read;
// set response to emulate read data
rsp |= VMI_EVENT_RESPONSE_SET_EMUL_READ_DATA;
}
return rsp;
}
int main (int argc, char **argv)
{
vmi_instance_t vmi = {0};
struct sigaction act = {0};
vmi_init_data_t *init_data = NULL;
bool is_corrupted = false;
addr_t ntload_driver_entry_addr = 0;
int retcode = 1;
act.sa_handler = close_handler;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGHUP, &act, NULL);
sigaction(SIGTERM, &act, NULL);
sigaction(SIGINT, &act, NULL);
sigaction(SIGALRM, &act, NULL);
char *name = NULL;
if (argc < 2) {
fprintf(stderr, "Usage: %s <name of VM> [<socket>]\n", argv[0]);
return retcode;
}
// Arg 1 is the VM name.
name = argv[1];
if (argc == 3) {
char *path = argv[2];
// fill init_data
init_data = malloc(sizeof(vmi_init_data_t) + sizeof(vmi_init_data_entry_t));
init_data->count = 1;
init_data->entry[0].type = VMI_INIT_DATA_KVMI_SOCKET;
init_data->entry[0].data = strdup(path);
}
// Initialize the libvmi library.
if (VMI_FAILURE ==
vmi_init_complete(&vmi, name, VMI_INIT_DOMAINNAME | VMI_INIT_EVENTS, init_data,
VMI_CONFIG_GLOBAL_FILE_ENTRY, NULL, NULL)) {
printf("Failed to init LibVMI library.\n");
goto error_exit;
}
printf("LibVMI init succeeded!\n");
uint8_t addr_width = vmi_get_address_width(vmi);
// pause
printf("Pausing VM\n");
if (VMI_FAILURE == vmi_pause_vm(vmi)) {
fprintf(stderr, "Failed to pause vm\n");
goto error_exit;
}
// read nt!KeServiceDescriptorTable
addr_t ke_sd_table_addr = 0;
if (VMI_FAILURE == vmi_translate_ksym2v(vmi, "KeServiceDescriptorTable", &ke_sd_table_addr)) {
fprintf(stderr, "Failed to translate KeServiceDescriptorTable symbol\n");
goto error_exit;
}
printf("nt!KeServiceDescriptorTable: 0x%" PRIx64 "\n", ke_sd_table_addr);
// read nt!KiServiceTable
addr_t ki_sv_table_addr = 0;
if (VMI_FAILURE == vmi_translate_ksym2v(vmi, "KiServiceTable", &ki_sv_table_addr)) {
fprintf(stderr, "Failed to translate KiServiceTable symbol\n");
goto error_exit;
}
printf("nt!KiServiceTable: 0x%" PRIx64 "\n", ki_sv_table_addr);
// read nt!NtLoadDriver
addr_t ntload_driver_addr = 0;
if (VMI_FAILURE == vmi_translate_ksym2v(vmi, "NtLoadDriver", &ntload_driver_addr)) {
fprintf(stderr, "Failed to translate NtAddDriverEntry symbol\n");
goto error_exit;
}
printf("nt!NtLoadDriver: 0x%" PRIx64 "\n", ntload_driver_addr);
/*
* Table's structure looks like the following
* (source: https://m0uk4.gitbook.io/notebooks/mouka/windowsinternal/ssdt-hook)
struct SSDTStruct
{
LONG* pServiceTable;
PVOID pCounterTable;
#ifdef _WIN64
ULONGLONG NumberOfServices;
#else
ULONG NumberOfServices;
#endif
PCHAR pArgumentTable;
};
*/
// read NumberOfServices
addr_t nb_services_addr = ke_sd_table_addr + (addr_width * 2);
addr_t nb_services = 0;
if (VMI_FAILURE == vmi_read_addr_va(vmi, nb_services_addr, 0, &nb_services)) {
fprintf(stderr, "Failed to read SSDT.NumberOfServices field\n");
goto error_exit;
}
printf("SSDT.NumberOfServices: 0x%" PRIx64 "\n", nb_services);
// find NtAddrDriverEntry index in SSDT
int ntload_driver_index = -1;
for (int i = 0; i < (int)nb_services; i++) {
addr_t syscall_addr_entry = ki_sv_table_addr + (addr_width * i);
addr_t syscall_addr = 0;
if (VMI_FAILURE == vmi_read_addr_va(vmi, syscall_addr_entry, 0, &syscall_addr)) {
fprintf(stderr, "Failed to read syscall address\n");
goto error_exit;
}
// Debug
// printf("Syscall[%d]: 0x%" PRIx64 "\n", i, syscall_addr);
// find NtLoadDriver
if (syscall_addr == ntload_driver_addr) {
printf("Found NtLoadDriver SSDT entry: %d\n", i);
ntload_driver_index = i;
break;
}
}
if (ntload_driver_index == -1) {
fprintf(stderr, "Failed to find NtLoadDriver SSDT entry\n");
goto error_exit;
}
// corrupting pointer
printf("Corrupting NtLoadDriver SSDT entry\n");
ntload_driver_entry_addr = ki_sv_table_addr + (addr_width * ntload_driver_index);
addr_t corrupted_value = 0;
if (VMI_FAILURE == vmi_write_addr_va(vmi, ntload_driver_entry_addr, 0, &corrupted_value)) {
fprintf(stderr, "Failed to corrupt NtLoadDriver SSDT entry\n");
goto error_exit;
}
is_corrupted = true;
// flush page cache after write
vmi_pagecache_flush(vmi);
// reread NtLoadDriver SSDT entry
if (VMI_FAILURE == vmi_read_addr_va(vmi, ntload_driver_entry_addr, 0, &corrupted_value)) {
fprintf(stderr, "Failed to read NtLoadDriver SSDT entry\n");
goto error_exit;
}
printf("New NtLoadDriver SSDT entry value: 0x%" PRIx64 "\n", corrupted_value);
// protect corrupted SSDT entry using memory access event
// get dtb
uint64_t cr3;
if (VMI_FAILURE == vmi_get_vcpureg(vmi, &cr3, CR3, 0)) {
fprintf(stderr, "Failed to get current CR3\n");
goto error_exit;
}
uint64_t dtb = cr3 & ~(0xfff);
// get paddr
uint64_t syscall_entry_paddr;
if (VMI_FAILURE == vmi_pagetable_lookup(vmi, dtb, ntload_driver_entry_addr, &syscall_entry_paddr)) {
fprintf(stderr, "Failed to find current paddr\n");
goto error_exit;
}
// get Guest Frame Number (gfn)
uint64_t syscall_entry_gfn = syscall_entry_paddr >> 12;
vmi_event_t read_event = {0};
SETUP_MEM_EVENT(&read_event, syscall_entry_gfn, VMI_MEMACCESS_R, read_callback, 0);
// add cb_data
cb_data_t cb_data = {0};
cb_data.ntload_driver_entry_addr = ntload_driver_entry_addr;
cb_data.emul_read.dont_free = 1;
cb_data.emul_read.size = sizeof(ntload_driver_addr);
memcpy(&cb_data.emul_read.data, &ntload_driver_addr, cb_data.emul_read.size);
// set event callback data
read_event.data = (void*)&cb_data;
printf("Registering read event on GFN 0x%" PRIx64 "\n", syscall_entry_gfn);
if (VMI_FAILURE == vmi_register_event(vmi, &read_event)) {
fprintf(stderr, "Failed to register event\n");
goto error_exit;
}
// resume
printf("Resuming VM\n");
if (VMI_FAILURE == vmi_resume_vm(vmi)) {
fprintf(stderr, "Failed to continue VM\n");
goto error_exit;
}
printf("Waiting for events...\n");
while (!interrupted) {
if (VMI_FAILURE == vmi_events_listen(vmi,500)) {
fprintf(stderr, "Failed to listen on VMI events\n");
goto error_exit;
}
}
printf("Finished with test.\n");
retcode = 0;
error_exit:
if (is_corrupted) {
printf("Restoring NtLoadDriver SSDT entry\n");
// restore SSDT entry
if (VMI_FAILURE == vmi_write_addr_va(vmi, ntload_driver_entry_addr, 0, &ntload_driver_addr)) {
fprintf(stderr, "Failed to restore SSDT entry\n");
}
}
vmi_resume_vm(vmi);
// cleanup any memory associated with the libvmi instance
vmi_destroy(vmi);
if (init_data) {
free(init_data->entry[0].data);
free(init_data);
}
return retcode;
}