Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic mem_event does not trigger #1044

Open
UrmelAusDemEis opened this issue Aug 23, 2022 · 8 comments
Open

Generic mem_event does not trigger #1044

UrmelAusDemEis opened this issue Aug 23, 2022 · 8 comments

Comments

@UrmelAusDemEis
Copy link

Hello everyone,

thanks to everyone for the great work!

I want to setup a callback which registers every memory access with its corresponding PID. For a first prototype I have modified the code in the mem-event-example.c
I have pasted the modified (hacked) script below.

I have modified the line
SETUP_MEM_EVENT(&mem_event, gfn, VMI_MEMACCESS_X, mem_cb, false);
to be
SETUP_MEM_EVENT(&mem_event, ~0UL, VMI_MEMACCESS_RWX, mem_cb, true);
However, the callback does not trigger in this scenario.

The unmodified version of this script works. I am using lib-kvmi and I used https://kvm-vmi.github.io/kvm-vmi/kvmi-v7/setup.html#option-1-vagrant-virtual-machine-based-setup to set it up.

Best
Thorsten

#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>

static bool interrupted = false;

reg_t cr3, rip;

static void close_handler(int sig)
{
    (void)sig;
    interrupted = true;
}

event_response_t mem_cb(vmi_instance_t vmi, vmi_event_t *event)
{   
    //printf("inside the cb");
    (void)vmi;
    reg_t cr3;
    vmi_pid_t current_pid = -1;
    vmi_get_vcpureg(vmi, &cr3, CR3, 0);
    if (vmi_dtb_to_pid(vmi, cr3, &current_pid) == VMI_FAILURE){
    printf("i failed");
    //return VMI_EVENT_RESPONSE_NONE;
    }
    if (current_pid == 4){
    return 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("PID %u %s: at 0x%"PRIx64", on frame 0x%"PRIx64", permissions: %s\n",
           current_pid, __func__, event->x86_regs->rip, event->mem_event.gla, str_access);

    return VMI_EVENT_RESPONSE_NONE;
}

int main (int argc, char **argv)
{
    vmi_instance_t vmi = {0};
    vmi_mode_t mode = {0};

    struct sigaction act = {0};
    vmi_init_data_t *init_data = NULL;
    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);

    if (argc < 2) {
        fprintf(stderr, "Usage: %s <name of VM> [<socket path>]\n", argv[0]);
        return retcode;
    }

    // Arg 1 is the VM name.
    char *name = argv[1];


    // kvmi socket ?
    if (argc == 3) {
        char *path = argv[2];

        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);
    }

    if (VMI_FAILURE == vmi_get_access_mode(NULL, (void*)name, VMI_INIT_DOMAINNAME | VMI_INIT_EVENTS, init_data, &mode)) {
        fprintf(stderr, "Failed to get access mode\n");
        goto error_exit;
    }

    //if (VMI_FAILURE ==
    //        vmi_init(&vmi, mode, (void*)name, VMI_INIT_DOMAINNAME | VMI_INIT_EVENTS, init_data, NULL)) {
    //    fprintf(stderr, "Failed to init LibVMI library.\n");
    //    goto error_exit;
    //}

    if (VMI_FAILURE ==
            vmi_init_complete(&vmi, (void*)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;
    }

    vmi_init_paging(vmi, 0);
    printf("LibVMI init succeeded!\n");

    // pause vm
    if (VMI_FAILURE ==  vmi_pause_vm(vmi)) {
        fprintf(stderr, "Failed to pause vm\n");
        goto error_exit;
    }

    // get rip
    uint64_t rip;
    if (VMI_FAILURE == vmi_get_vcpureg(vmi, &rip, RIP, 0)) {
        fprintf(stderr, "Failed to get current RIP\n");
        goto error_exit;
    }

    // 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 gpa
    uint64_t paddr;
    if (VMI_FAILURE == vmi_pagetable_lookup(vmi, dtb, rip, &paddr)) {
        fprintf(stderr, "Failed to find current paddr\n");
        goto error_exit;
    }

    uint64_t gfn = paddr >> 12;
    /* register a mem event */
    vmi_event_t mem_event = {0};
    SETUP_MEM_EVENT(&mem_event, ~0UL, VMI_MEMACCESS_RWX, mem_cb, true);
    //SETUP_MEM_EVENT(&mem_event, gfn, VMI_MEMACCESS_X, mem_cb, false);

    printf("Setting X memory event at RIP 0x%"PRIx64", GPA 0x%"PRIx64", GFN 0x%"PRIx64"\n",
           rip, paddr, gfn);
    if (VMI_FAILURE == vmi_register_event(vmi, &mem_event)) {
        fprintf(stderr, "Failed to register mem event\n");
        goto error_exit;
    }

    // resuming
    if (VMI_FAILURE == vmi_resume_vm(vmi)) {
        fprintf(stderr, "Failed to resume vm\n");
        goto error_exit;
    }

    printf("Waiting for events...\n");
    while (!interrupted) {
        vmi_events_listen(vmi,500);
    }
    printf("Finished with test.\n");

    retcode = 0;
error_exit:
    vmi_clear_event(vmi, &mem_event, NULL);

    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;
}
@tklengyel
Copy link
Contributor

Because the generic handler is just that, registers a generic handler that will be called when an EPT violation happens. To trigger the EPT violations you still need to restrict the particular gfn's permission you are interested in. For non-generic handlers this happens automatically. For generic you need to use vmi_set_mem_event after the generic handler is registered.

@UrmelAusDemEis
Copy link
Author

Thank you for the swift answer!
So I would have to use vmi_set_mem_event for every gfn present on the system and when a new process is spawned, I will also have to set the permissions of the new pages? Am I correct and do you think this is feasible?

@tklengyel
Copy link
Contributor

You set permissions on GFNs, which are guest-physical addresses. As long as the VM isn't getting hot-plugged memory it doesn't matter how the guest OS maps that memory into a process or process'. Currently you have to set it one-by-one using the LibVMI API. On Xen the underlying Xen API allows you to set multiple pages via xc_set_mem_access but that isn't exposed right now through LibVMI. You can link directly with libxc and make use of it that way if you want. Here is an example: https://github.com/tklengyel/xen/blob/master/tools/misc/xen-access.c#L591

@UrmelAusDemEis
Copy link
Author

UrmelAusDemEis commented Aug 24, 2022

Thank you for the suggestion and your advice! This sounds really interesting. I do not need to have the knowledge which GFN belongs to which process, because I can get the PID, when a memory event occurs through the CR3 register.

However, I don't know how to get a list of the GFNs from LibVMI without knowing every process and querying their respective pagetables with vmi_get_va_pages. Additionally, I would have to check regularly whether new GFNs have been allocated, right? Also I am currently set on KVM.

Sorry, that I have so many questions. I am pretty new to VMI.

@tklengyel
Copy link
Contributor

You can loop from 0 to max physical memory and try to set the permission. Otherwise you have to collect the memory map from the VM itself, there is no API for that in LibVMI. Usually it is displayed in the dmesg. See https://github.com/libvmi/libvmi/blob/master/notes/memory_map.txt

@UrmelAusDemEis
Copy link
Author

Thank you again for your help! Unfortunately, it is too slow on KVM, that it halts the VM. I will try it with Xen later.

@tklengyel
Copy link
Contributor

Changing the permissions on all pages is going to be slow on Xen too. A little bit faster then doing it 1-by-1 but still slow.

@UrmelAusDemEis
Copy link
Author

Oh, I see. I guess LibVMI is not the correct tool for my particular use-case. However, it was nice to play around with it and it helped me learn a lot about VMI in general.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants