Esempio n. 1
0
event_response_t rng_single_step_callback(vmi_instance_t vmi, vmi_event_t *event) {
	//printf("Got a single-step callback!\n");

	// gameplan step 5
	//printf("Re-adding breakpoint before instruction.\n");
	uint8_t int3 = INT3_INST; // create temporary variable because we can't use an address to a static #defined int
	if (VMI_SUCCESS != vmi_write_8_va(vmi, breakpoints[break_idx].addr, 0, &int3)) {
		//printf("Couldn't write to memory... exiting.\n");
		return VMI_FAILURE;
	}

	// clear break_idx now that we've reinserted the interrupt
	break_idx = -1;

	vmi_clear_event(vmi, event, NULL);	
	return VMI_SUCCESS;
}
Esempio n. 2
0
void hijack_thread(struct injector *injector, vmi_instance_t vmi,
        unsigned int vcpu, vmi_pid_t pid) {

    printf("Ready to hijack thread of PID %u on vCPU %u!\n", pid, vcpu);

    addr_t cpa = sym2va(vmi, pid, "kernel32.dll", "CreateProcessA");

    printf("CPA @ 0x%lx\n", cpa);

    reg_t fsgs, rbp, rsp, rip, rcx, rdx, rax, r8, r9;
    addr_t stack_base, stack_limit;

    vmi_get_vcpureg(vmi, &rsp, RSP, vcpu);
    vmi_get_vcpureg(vmi, &rip, RIP, vcpu);
    vmi_get_vcpureg(vmi, &rax, RAX, vcpu);
    vmi_get_vcpureg(vmi, &rcx, RCX, vcpu);
    vmi_get_vcpureg(vmi, &rdx, RDX, vcpu);
    vmi_get_vcpureg(vmi, &r8, R8, vcpu);
    vmi_get_vcpureg(vmi, &r9, R9, vcpu);

    if (injector->pm == VMI_PM_LEGACY || injector->pm == VMI_PM_PAE) {
        vmi_get_vcpureg(vmi, &fsgs, FS_BASE, vcpu);
        vmi_get_vcpureg(vmi, &rbp, RBP, vcpu);
        printf("FS: 0x%lx RBP: 0x%lx", fsgs, rbp);
        vmi_read_addr_va(vmi, fsgs + 0x4, pid, &stack_base);
        vmi_read_addr_va(vmi, fsgs + 0x8, pid, &stack_limit);
    } else {
        vmi_get_vcpureg(vmi, &fsgs, GS_BASE, vcpu);
        printf("GS: 0x%lx ", fsgs);
        vmi_read_addr_va(vmi, fsgs + 0x8, pid, &stack_base);
        vmi_read_addr_va(vmi, fsgs + 0x10, pid, &stack_limit);
    }

    printf("RSP: 0x%lx. RIP: 0x%lx. RCX: 0x%lx\n", rsp, rip, rcx);
    printf("Stack base: 0x%lx. Limit: 0x%lx\n", stack_base, stack_limit);

    //Push input arguments on the stack
    //CreateProcess(NULL, TARGETPROC,
    //                 NULL, NULL, 0, CREATE_SUSPENDED, NULL, NULL, &si, pi))

    uint64_t nul64 = 0;
    uint32_t nul32 = 0;
    uint8_t nul8 = 0;
    size_t len = strlen(injector->target_proc);
    addr_t addr = rsp;
    injector->saved_rsp = rsp;
    injector->saved_rip = rip;
    injector->saved_rax = rax;
    injector->saved_rdx = rdx;
    injector->saved_rcx = rcx;
    injector->saved_r8 = r8;
    injector->saved_r9 = r9;

    if (injector->pm == VMI_PM_LEGACY || injector->pm == VMI_PM_PAE) {

        addr -= 0x4; // the stack has to be alligned to 0x4
                     // and we need a bit of extra buffer before the string for \0
        // we just going to null out that extra space fully
        vmi_write_32_va(vmi, addr, pid, &nul32);

        // this string has to be aligned as well!
        addr -= len + 0x4 - (len % 0x4);
        addr_t str_addr = addr;
        vmi_write_va(vmi, addr, pid, (void*) injector->target_proc, len);
        // add null termination
        vmi_write_8_va(vmi, addr + len, pid, &nul8);
        printf("%s @ 0x%lx.\n", injector->target_proc, str_addr);

        //struct startup_info_32 si = {.wShowWindow = SW_SHOWDEFAULT };
        struct startup_info_32 si;
        memset(&si, 0, sizeof(struct startup_info_32));
        struct process_information_32 pi;
        memset(&pi, 0, sizeof(struct process_information_32));

        addr -= sizeof(struct process_information_32);
        injector->process_info = addr;
        vmi_write_va(vmi, addr, pid, &pi,
                sizeof(struct process_information_32));
        printf("pip @ 0x%lx\n", addr);

        addr -= sizeof(struct startup_info_32);
        addr_t sip = addr;
        vmi_write_va(vmi, addr, pid, &si, sizeof(struct startup_info_32));
        printf("sip @ 0x%lx\n", addr);

        //p10
        addr -= 0x4;
        vmi_write_32_va(vmi, addr, pid, (uint32_t *) &injector->process_info);
        //p9
        addr -= 0x4;
        vmi_write_32_va(vmi, addr, pid, (uint32_t *) &sip);
        //p8
        addr -= 0x4;
        vmi_write_32_va(vmi, addr, pid, &nul32);
        //p7
        addr -= 0x4;
        vmi_write_32_va(vmi, addr, pid, &nul32);
        //p6
        addr -= 0x4;
        vmi_write_32_va(vmi, addr, pid, &nul32);
        //p5
        addr -= 0x4;
        vmi_write_32_va(vmi, addr, pid, &nul32);
        //p4
        addr -= 0x4;
        vmi_write_32_va(vmi, addr, pid, &nul32);
        //p3
        addr -= 0x4;
        vmi_write_32_va(vmi, addr, pid, &nul32);
        //p2
        addr -= 0x4;
        vmi_write_32_va(vmi, addr, pid, (uint32_t *) &str_addr);
        //p1
        addr -= 0x4;
        vmi_write_32_va(vmi, addr, pid, &nul32);

        // save the return address (RIP)
        addr -= 0x4;
        vmi_write_32_va(vmi, addr, pid, (uint32_t *) &rip);

    } else {

        addr -= 0x8; // the stack has to be alligned to 0x8
                     // and we need a bit of extra buffer before the string for \0

        // we just going to null out that extra space fully
        vmi_write_64_va(vmi, addr, pid, &nul64);

        // this string has to be aligned as well!
        addr -= len + 0x8 - (len % 0x8);
        addr_t str_addr = addr;
        vmi_write_va(vmi, addr, pid, (void*) injector->target_proc, len);
        // add null termination
        vmi_write_8_va(vmi, addr + len, pid, &nul8);
        printf("%s @ 0x%lx.\n", injector->target_proc, str_addr);

        struct startup_info_64 si;
        memset(&si, 0, sizeof(struct startup_info_64));
        struct process_information_64 pi;
        memset(&pi, 0, sizeof(struct process_information_64));

        addr -= sizeof(struct process_information_64);
        injector->process_info = addr;
        vmi_write_va(vmi, addr, pid, &pi,
                sizeof(struct process_information_64));
        printf("pip @ 0x%lx\n", addr);

        addr -= sizeof(struct startup_info_64);
        addr_t sip = addr;
        vmi_write_va(vmi, addr, pid, &si, sizeof(struct startup_info_64));
        printf("sip @ 0x%lx\n", addr);

        //http://www.codemachine.com/presentations/GES2010.TRoy.Slides.pdf
        //
        //First 4 parameters to functions are always passed in registers
        //P1=rcx, P2=rdx, P3=r8, P4=r9
        //5th parameter onwards (if any) passed via the stack

        //p10
        addr -= 0x8;
        vmi_write_64_va(vmi, addr, pid, &injector->process_info);
        //p9
        addr -= 0x8;
        vmi_write_64_va(vmi, addr, pid, &sip);
        //p8
        addr -= 0x8;
        vmi_write_64_va(vmi, addr, pid, &nul64);
        //p7
        addr -= 0x8;
        vmi_write_64_va(vmi, addr, pid, &nul64);
        //p6
        addr -= 0x8;
        vmi_write_64_va(vmi, addr, pid, &nul64);
        //p5
        addr -= 0x8;
        vmi_write_64_va(vmi, addr, pid, &nul64);

        // allocate 0x20 "homing space"
        addr -= 0x8;
        vmi_write_64_va(vmi, addr, pid, &nul64);
        addr -= 0x8;
        vmi_write_64_va(vmi, addr, pid, &nul64);
        addr -= 0x8;
        vmi_write_64_va(vmi, addr, pid, &nul64);
        addr -= 0x8;
        vmi_write_64_va(vmi, addr, pid, &nul64);

        //p1
        vmi_set_vcpureg(vmi, 0, RCX, vcpu);
        //p2
        vmi_set_vcpureg(vmi, str_addr, RDX, vcpu);
        //p3
        vmi_set_vcpureg(vmi, 0, R8, vcpu);
        //p4
        vmi_set_vcpureg(vmi, 0, R9, vcpu);

        // save the return address (RIP)
        addr -= 0x8;
        vmi_write_64_va(vmi, addr, pid, &rip);
    }

    printf("Return address @ 0x%lx -> 0x%lx. Setting RSP: 0x%lx.\n", addr, rip,
            addr);

    // Grow the stack and switch execution
    vmi_set_vcpureg(vmi, addr, RSP, vcpu);
    vmi_set_vcpureg(vmi, cpa, RIP, vcpu);

    printf("Done with hijack routine\n");
}
Esempio n. 3
0
int main (int argc, char **argv)
{
	// local variables
	vmi_instance_t vmi;
	int ret_val = 0; // return code for after goto
	struct sigaction signal_action;

	// process list vars
	vmi_pid_t pid = 0;
	char *procname = NULL;
	addr_t list_head = 0;
	addr_t next_list_entry = 0;
	addr_t current_process = 0;
	addr_t tmp_next = 0;
	status_t status;

	// breakpoint vars
	char*			*sym;
	uint16_t	*off;
	vmi_pid_t *bpid;
	addr_t		*add;
	uint8_t		*byt;

	addr_t lib_map_addr = 0;
	addr_t BN_rand_addr = 0;
	int bnrand_jump_offset = 0;
	addr_t bnrand_addr = 0;

	// this is the VM or file that we are looking at
	if (argc < 2) {
		printf("Usage: %s <vmname>\n", argv[0]);
		return 1;
	}
	
	char *name = argv[1];

	////////////////////
	// Initialization // 
	////////////////////

	// initialize the libvmi library
	printf("Initializing libvmi for VM \"%s\"\n", name);
	if (vmi_init(&vmi, VMI_XEN|VMI_INIT_COMPLETE|VMI_INIT_EVENTS, name) == VMI_FAILURE) {
		printf("Failed to init LibVMI library.\n");
		ret_val = 2;
		goto error_exit;
	}

	// verify OS is Linux
	printf("Verifying the VM is running Linux...");
	// TODO: verify that the VM is running a *supported* Linux kernel
	// if kernel is not one we recognize, don't run because we'll be mucking around in memory we don't understand
	if (VMI_OS_LINUX != vmi_get_ostype(vmi)) { // this only checks if /etc/libvmi.conf says it's "Linux"
		printf("\nVM is running %s, exiting...\n", vmi_get_ostype(vmi));
		ret_val = 3;
		goto error_exit;
	}
	printf(" Yup. Good to go.\n");

	// pause the vm for consistent memory access
	printf("Pausing the VM\n");
	if (vmi_pause_vm(vmi) != VMI_SUCCESS) {
		printf("Failed to pause VM\n");
		ret_val = 4;
		goto error_exit; // don't return directly, do cleanup first
	}

	tasks_offset					= vmi_get_offset(vmi, "linux_tasks");
	name_offset						= vmi_get_offset(vmi, "linux_name");
	pid_offset						= vmi_get_offset(vmi, "linux_pid");
	mm_offset							= vmi_get_offset(vmi, "linux_mm");
	// hardcoded because config_parser doesn't support dynamic config vars
	mmap_offset						= 0x0;
	vm_area_file_offset		= 0xa0;
	vm_area_next_offset		= 0x10;
	vm_area_start_offset	= 0x0;
	file_path_offset			= 0x10;
	dentry_offset					= 0x8;
	iname_offset					= 0x38;
	//mmap_offset = vmi_get_offset(vmi, "linux_mmap");
	//vm_area_file_offset = vmi_get_offset(vmi, "linux_vm_file");
	//vm_area_next_offset = vmi_get_offset(vmi, "linux_vm_next");
	//vm_area_start_offset = vmi_get_offset(vmi, "linux_vm_start");
	//file_path_offset = vmi_get_offset(vmi, "linux_f_path");
	//dentry_offset = vmi_get_offset(vmi, "linux_dentry");
	//iname_offset = vmi_get_offset(vmi, "linux_d_iname");

	if (0 == tasks_offset) {
		printf("Failed to find tasks_offset\n");
		goto error_exit;
	}   
	if (0 == pid_offset) {
		printf("Failed to find pid_offset\n");
		goto error_exit;
	}   
	if (0 == name_offset) {
		printf("Failed to find name_offset\n");
		goto error_exit;
	}   
	if (0 == mm_offset) {
		printf("Failed to find mm_offset\n");
		goto error_exit;
	}   
	//if (0 == mmap_offset) {
	//	printf("Failed to find mmap_offset\n");
	//	goto error_exit;
	//}   
	//if (0 == vm_area_file_offset) {
	//	printf("Failed to find vm_area_file_offset\n");
	//	goto error_exit;
	//}   
	//if (0 == vm_area_next_offset) {
	//	printf("Failed to find vm_area_next_offset\n");
	//	goto error_exit;
	//}   
	//if (0 == vm_area_start_offset) {
	//	printf("Failed to find vm_area_start_offset\n");
	//	goto error_exit;
	//}   
	//if (0 == file_path_offset) {
	//	printf("Failed to find file_path_offset\n");
	//	goto error_exit;
	//}   
	//if (0 == dentry_offset) {
	//	printf("Failed to find dentry_offset\n");
	//	goto error_exit;
	//}   
	//if (0 == iname_offset) {
	//	printf("Failed to find iname_offset\n");
	//	goto error_exit;
	//}   

	// Set up breakpoints
	breakpoints = (breakpoint_t*)calloc(MAX_BREAKPOINTS, sizeof(breakpoint_t)); // allocate space for each breakpoint, zero memory
	//add_breakpoint("extract_entropy_user", 155, 0, 0xe8, before_extract_buf);
	//add_breakpoint("extract_entropy_user", 160, 0, 0x83, after_extract_buf);
	// new breakpoints created below

	//////////////////////////
	// Find apache2 process //
	//////////////////////////

	// find pid of apache2 processes
	// for each process, read symbol table and find BN_rand function
	// at offset +13 bytes from BN_rand, we find an offset from the instruction at BN_rand+17 (probably around -700 bytes)
	// +570 bytes from the offset above, we find the instruction at which we want a breakpoint
	// so, breakpoint at BN_rand+17+[BN_rand+13]+570
	// then, at callback, read r13 for address of buffer, overwrite with TODO bytes

	// find pid of apache2 processes
	list_head = vmi_translate_ksym2v(vmi, "init_task") + tasks_offset; // find init_task struct and move to first linked list entry
	next_list_entry = list_head; // iterator
	do {
		current_process = next_list_entry - tasks_offset; // subtract tasks_offset back off to get to head of struct
		vmi_read_32_va(vmi, current_process + pid_offset, 0, (uint32_t*)&pid); // get pid of this process
		procname = vmi_read_str_va(vmi, current_process + name_offset, 0); // get process name of this process
		if (strncmp(procname,"apache2",sizeof("apache2")) == 0) {
			printf("Finding library address in %s [pid %d]\n",procname,pid);
			lib_map_addr = walk_vmmap_for_lib(vmi, current_process, "libcrypto.so.1.0.2");
			if (lib_map_addr == 0) { // if failed to find lib
				printf("Failed to find library in %s\n",procname);
				ret_val = 9;
				goto error_exit;
			}
			printf("Found library address: 0x%llx\n", lib_map_addr);
			// for each process, read symbol table and find BN_rand function
			BN_rand_addr = lib_map_addr + 0xd5a50; // static offset for BN_rand function
			// at offset +13 bytes from BN_rand, we find an offset from the instruction at BN_rand+17 (probably around -700 bytes)
			vmi_read_32_va(vmi, BN_rand_addr+13, pid, &bnrand_jump_offset); // get jump offset to bnrand function
			//printf("jump offset: %d\n",bnrand_jump_offset);
			bnrand_addr = BN_rand_addr+17+bnrand_jump_offset; // get address of bnrand function
			//printf("bnrand: 0x%llx\n", bnrand_addr);
			// +570 bytes from the offset above, we find the instruction at which we want a breakpoint
			add_breakpoint_addr(bnrand_addr + 570, pid, 0x31, bnrand_callback);
			//printf("Added breakpoint at 0x%llx\n",bnrand_addr+570);
		}
		status = vmi_read_addr_va(vmi, next_list_entry, 0, &next_list_entry); // follow linked-list->next to next element
		if (status == VMI_FAILURE) {
			printf("Failed to read next pointer in loop at %"PRIx64"\n", next_list_entry);
			goto error_exit;
		}

	} while(next_list_entry != list_head);




	for (int i = 0; i < num_breakpoints; i++) { // iterate over breakpoints and find the right addresses for them

		////////////////////////////////////////////
		// Find memory location to put breakpoint //
		////////////////////////////////////////////

		// assign short names (note: modifying these modifies the breakpoint struct)
		sym = &breakpoints[i].symbol;
		off = &breakpoints[i].offset;
		bpid = &breakpoints[i].pid;
		add = &breakpoints[i].addr;
		byt = &breakpoints[i].inst_byte; // remember that if this is not set above, it should be zeroed from calloc

		if (breakpoints[i].addr == 0) { // if don't have address, find symbol
			// find address to break on
			printf("Accessing System Map for %s symbol\n", *sym);
			*add = vmi_translate_ksym2v(vmi, *sym) + *off;
			printf("%s + %u is at 0x%llx\n", *sym, *off, *add);
		}

		// either verify the byte there is correct, or record which byte is there for later replacing
		if (*byt == 0) { // if this byte was not set, we need to get it
			vmi_read_8_va(vmi, *add, *bpid, byt); // read it directly into byt
			printf("[pid %d] Saving byte at address 0x%llx: %x\n", *bpid, *add, *byt);
		} else { // if the byte was set, verify that it's currently set to that value
			uint8_t temp_byte = 0;
			vmi_read_8_va(vmi, *add, *bpid, &temp_byte); // read it temporarily
			printf("[pid %d] Checking byte at address 0x%llx is set to %x: %x\n", *bpid, *add, *byt, temp_byte);
			if (*byt != temp_byte) { // uh oh, we have an error
				ret_val = 8;
				goto error_exit;
			}
		}
	} // end first for loop after breakpoints are constructed properly

	///////////////////
	// Main gameplan //
	//               //
	// https://groups.google.com/forum/#!topic/vmitools/jNGxM0LBEDM
	// Based on the google groups discussion above (which I wish I found earlier, meh), it looks like the way people trap on instructions is to:
	//	1) actually *modify* the memory to have the 0xcc (interrupt 3, aka breakpoint) instruction in place of the instruction it would have executed
	//	2) register an event on receiving the INT3 signal and receive the callback
	//	3) at the end of the callback, fix the memory to its original instruction,
	//	4) single-step one instruction forward, executing the one instruction, then getting another callback
	//	5) replace the previous instruction with to 0xcc, "resetting" the breakpoint, then clearing the event and continuing
	//               //
	///////////////////

	for (int i = 0; i < num_breakpoints; i++) { // iterate over breakpoints and insert them all

		// assign short names (note: modifying these modifies the breakpoint struct)
		add = &breakpoints[i].addr;
		bpid = &breakpoints[i].pid;
		byt = &breakpoints[i].inst_byte;

		// Step 1: modify memory in the VM with an INT3 instruction (0xcc)
		printf("[pid %d] Setting breakpoint at address 0x%llx.\n", *bpid, *add);
		uint8_t int3 = INT3_INST; // create temporary variable because we can't use an address to a static #defined int
		if (VMI_SUCCESS != vmi_write_8_va(vmi, *add, *bpid, &int3)) {
			printf("[pid %d] Couldn't write INT3 instruction to memory... exiting.\n", *bpid);
			ret_val = 5;
			goto error_exit;
		}

		// debug: check memory is now an INT3 instruction
		uint8_t temp_byte = 0;
		vmi_read_8_va(vmi, *add, 0, &temp_byte);
		printf("[pid %d] This should be an INT3 instruction (0xcc): 0x%x\n", *bpid, temp_byte);


	} // end second for loop after breakpoints are all inserted and callback is registered

	// Step 2: register an event on receiving INT3 signal
	printf("Creating event for callback when breakpoint is reached.\n");
	memset(&rng_event, 0, sizeof(vmi_event_t)); // clear rng_event so we can set everything fresh
	rng_event.type = VMI_EVENT_INTERRUPT; // interrupt event -- trigger when interrupt occurs
	rng_event.interrupt_event.intr = INT3; // trigger on INT3 instruction
	rng_event.interrupt_event.reinject = 0; // swallow interrupt silently without passing it on to guest
	rng_event.callback = rng_int3_event_callback; // reference to our callback function
	printf("Registering event...\n");
	if (VMI_SUCCESS == vmi_register_event(vmi, &rng_event)) {; // register the event!
		printf("Event Registered!\n");
	} else { // uh oh, event failed
		printf("Problem registering event... exiting.\n");
		ret_val = 6;
		goto error_exit; // don't return directly, do cleanup first
	}

	// resume the VM
	printf("Resuming the VM\n");
	vmi_resume_vm(vmi);

	//////////////////////////////////////
	// Spin and wait for event callback // 
	//////////////////////////////////////

	// for a clean exit, catch signals (from host, not VM), set "interrupted" to non-zero, exit while loop at end of main()
	signal_action.sa_handler = close_handler;
	signal_action.sa_flags = 0;
	sigemptyset(&signal_action.sa_mask);
	sigaction(SIGHUP,  &signal_action, NULL);
	sigaction(SIGTERM, &signal_action, NULL);
	sigaction(SIGINT,  &signal_action, NULL);
	sigaction(SIGALRM, &signal_action, NULL);

	while(!interrupted) { // until an interrupt happens
		printf("Waiting for events...\n");
		if (VMI_SUCCESS != vmi_events_listen(vmi, 500)) { // listen for events for 500ms (no event = VMI_SUCCESS)
			printf("Error waiting for events... exiting.\n");
			interrupted = -1;
		}
	}
	printf("Finished with test.\n");

	//////////////////
	// Exit cleanly // 
	//////////////////

error_exit:
	// attempt to remove breakpoints
	for (int i = 0; i < num_breakpoints; i++) { // iterate over breakpoints and insert them all

		// assign short names (note: modifying these modifies the breakpoint struct)
		add = &breakpoints[i].addr;
		bpid = &breakpoints[i].pid;
		byt = &breakpoints[i].inst_byte;

		printf("[pid %d] Removing breakpoint %d at 0x%llx.\n", *bpid, i, *add);
		if (VMI_SUCCESS != vmi_write_8_va(vmi, *add, *bpid, byt)) {
			printf("Couldn't write to memory... exiting.\n");
			ret_val = 7;
		}
	}

	// resume the vm
	printf("Resuming the VM\n");
	vmi_resume_vm(vmi);

	// cleanup any memory associated with the LibVMI instance
	printf("Cleaning up\n");
	vmi_destroy(vmi);

	return ret_val;
}
Esempio n. 4
0
event_response_t rng_int3_event_callback(vmi_instance_t vmi, vmi_event_t* event) {

	break_idx = -1;

	//printf("Got an interrupt callback!\n");

	// clear reinject
	//printf("Current reinject state (1 to deliver to guest, 0 to silence): %d\n", event->interrupt_event.reinject);
	if (event->interrupt_event.reinject == -1) { // if we need to set this
		//printf("Setting reinject state to 0\n");
		event->interrupt_event.reinject = 0; // set it to silent
		//printf("Updated reinject state: %d\n", event->interrupt_event.reinject);
	}

	// iterate over breakpoints until we find the one we're at
	//printf("Looking for the breakpoint for address 0x%llx\n", event->interrupt_event.gla);
	for (int i = 0; i < num_breakpoints; i++) { 
		if (event->interrupt_event.gla == breakpoints[i].addr) { // if we've found the correct breakpoint
			break_idx = i;
			//printf("Found it: %d!\n", i);
			break;
		}
	}
	if (break_idx == -1) {
		printf("Can't find breakpoint for this instruction: 0x%llx\n",event->interrupt_event.gla);
		return VMI_FAILURE;
	}

	// call the appropriate callback
	if (VMI_SUCCESS != breakpoints[break_idx].callback(vmi, event)) {
		printf("Callback failed.\n");
		return VMI_FAILURE;
	}

	// see "Main gameplan" comment section below for context
	//	3) at the end of the callback, fix the memory to its original instruction,
	//	4) single-step one instruction forward, executing the one instruction, then getting another callback
	//	5) replace the previous instruction with to 0xcc, "resetting" the breakpoint, then clearing the event and continuing

	// gameplan step 3
	//printf("Removing breakpoint before instruction.\n");
	if (VMI_SUCCESS != vmi_write_8_va(vmi, breakpoints[break_idx].addr, 0, &(breakpoints[break_idx].inst_byte))) {
		printf("Couldn't write to memory... exiting.\n");
		return VMI_FAILURE;
	}

	// gameplan step 4
	// create singlestep event and register it
	//printf("Creating singlestep event to replace breakpoint\n");
	memset(&rng_ss_event, 0, sizeof(vmi_event_t));
	rng_ss_event.type = VMI_EVENT_SINGLESTEP;
	rng_ss_event.callback = rng_single_step_callback;
	rng_ss_event.ss_event.enable = 1;
	SET_VCPU_SINGLESTEP(rng_ss_event.ss_event, event->vcpu_id);
	//printf("Registering event...\n");
	if (VMI_SUCCESS == vmi_register_event(vmi, &rng_ss_event)) {; // register the event!
		//printf("Event Registered!\n");
	} else { // uh oh, event failed
		printf("Problem registering singlestep event... exiting.\n");
		return VMI_FAILURE;
	}

	// we don't appear to need to clear the event (clearing event for memory, register, and single-step events)
	return VMI_SUCCESS;
}