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