status_t peparse_get_image( vmi_instance_t vmi, const access_context_t *ctx, size_t len, const uint8_t * const image) { if ( VMI_FAILURE == vmi_read(vmi, ctx, len, (void *)image, NULL) ) { dbprint(VMI_DEBUG_PEPARSE, "--PEPARSE: failed to read PE header\n"); return VMI_FAILURE; } if (VMI_SUCCESS != peparse_validate_pe_image(image, len)) { dbprint(VMI_DEBUG_PEPARSE, "--PEPARSE: failed to validate PE header(s)\n"); return VMI_FAILURE; } return VMI_SUCCESS; }
event_response_t queryobject_cb(drakvuf_t drakvuf, drakvuf_trap_info_t* info) { wrapper_t* injector = (wrapper_t*)info->trap->data; auto response = 0; uint32_t thread_id = 0; if (info->regs->cr3 != injector->target_cr3) return 0; if ( !drakvuf_get_current_thread_id(drakvuf, info, &thread_id) || !injector->target_thread_id || thread_id != injector->target_thread_id ) return 0; vmi_instance_t vmi = drakvuf_lock_and_get_vmi(drakvuf); if (info->regs->rax) goto handled; else { access_context_t ctx = { .translate_mechanism = VMI_TM_PROCESS_DTB, .dtb = info->regs->cr3, .addr = injector->ntqueryobject_info.out, }; struct FILE_FS_DEVICE_INFORMATION dev_info = { 0 }; if ((VMI_FAILURE == vmi_read(vmi, &ctx, sizeof(struct FILE_FS_DEVICE_INFORMATION), &dev_info, NULL))) { PRINT_DEBUG("[FILEDELETE2] [QueryObject] Failed to read FsDeviceInformation\n"); goto err; } if (7 != dev_info.device_type) // FILE_DEVICE_DISK goto handled; injector->ntreadfile_info.bytes_read = 0UL; addr_t pool = find_pool(injector->f->pools); if (!pool) { if (inject_allocate_pool(drakvuf, info, vmi, injector)) { response = VMI_EVENT_RESPONSE_SET_REGISTERS; goto done; } } else { injector->pool = pool; if (inject_readfile(drakvuf, info, vmi, injector)) { response = VMI_EVENT_RESPONSE_SET_REGISTERS; goto done; } } } err: PRINT_DEBUG("[FILEDELETE2] [QueryObject] Error. Stop processing (CR3 0x%lx, TID %d).\n", info->regs->cr3, thread_id); handled: response = finish_readfile(drakvuf, info, vmi, false); done: drakvuf_release_vmi(drakvuf); return response; } /* * Drakvuf must be locked/unlocked in the caller */ static bool start_readfile(drakvuf_t drakvuf, drakvuf_trap_info_t* info, vmi_instance_t vmi, handle_t handle, const char* filename, event_response_t* response) { *response = VMI_EVENT_RESPONSE_NONE; filedelete* f = (filedelete*)info->trap->data; uint64_t fo_flags = 0; if (!get_file_object_flags(drakvuf, info, vmi, f, handle, &fo_flags)) return 0; bool is_synchronous = (fo_flags & FO_SYNCHRONOUS_IO); if (!is_synchronous) return 0; if ( 0 == info->proc_data.base_addr ) { PRINT_DEBUG("[FILEDELETE2] Failed to get process base on vCPU 0x%d\n", info->vcpu); return 0; } uint32_t target_thread_id = 0; if ( !drakvuf_get_current_thread_id(drakvuf, info, &target_thread_id) || !target_thread_id ) { PRINT_DEBUG("[FILEDELETE2] Failed to get Thread ID\n"); return 0; } /* * Check if process/thread is being processed. If so skip it. Add it into * regestry otherwise. */ auto thread = std::make_pair(info->regs->cr3, target_thread_id); auto thread_it = f->closing_handles.find(thread); auto map_end = f->closing_handles.end(); if (map_end != thread_it) { bool handled = thread_it->second; if (handled) { f->files.erase(std::make_pair(info->proc_data.pid, handle)); f->closing_handles.erase(thread); } return 0; } else f->closing_handles[thread] = false; /* * Real function body. * * Now we are sure this is new call to NtClose (not result of function injection) and * the Handle have been modified in NtWriteFile. So we should save it on the host. */ wrapper_t* injector = (wrapper_t*)g_malloc0(sizeof(wrapper_t)); if (!injector) return 0; injector->bp = (drakvuf_trap_t*)g_malloc0(sizeof(drakvuf_trap_t)); if (!injector->bp) { g_free(injector); return 0; } injector->f = f; injector->bp->name = info->trap->name; injector->handle = handle; injector->fo_flags = fo_flags; injector->is32bit = (f->pm != VMI_PM_IA32E); injector->target_cr3 = info->regs->cr3; injector->curr_sequence_number = -1; injector->eprocess_base = info->proc_data.base_addr; injector->target_thread_id = target_thread_id; memcpy(&injector->saved_regs, info->regs, sizeof(x86_registers_t)); if (inject_queryobject(drakvuf, info, vmi, injector)) { *response = VMI_EVENT_RESPONSE_SET_REGISTERS; return 1; } memcpy(info->regs, &injector->saved_regs, sizeof(x86_registers_t)); return 0; }
event_response_t readfile_cb(drakvuf_t drakvuf, drakvuf_trap_info_t* info) { wrapper_t* injector = (wrapper_t*)info->trap->data; filedelete* f = injector->f; if (info->regs->cr3 != injector->target_cr3) return 0; uint32_t thread_id = 0; if (!drakvuf_get_current_thread_id(drakvuf, info, &thread_id)) return 0; if (thread_id != injector->target_thread_id) return 0; auto response = 0; access_context_t ctx = { .translate_mechanism = VMI_TM_PROCESS_DTB, .dtb = info->regs->cr3, .addr = injector->ntreadfile_info.io_status_block, }; vmi_instance_t vmi = drakvuf_lock_and_get_vmi(drakvuf); const uint32_t status = info->regs->rax; uint32_t isb_status = 0; // `isb` is `IO_STATUS_BLOCK` size_t isb_size = 0; bool is_success = false; auto filename = f->files[std::make_pair(info->proc_data.pid, injector->handle)]; if (injector->is32bit) { struct IO_STATUS_BLOCK_32 io_status_block; if ((VMI_SUCCESS != vmi_read(vmi, &ctx, sizeof(io_status_block), &io_status_block, NULL))) goto err; isb_status = io_status_block.status; isb_size = io_status_block.info; } else { struct IO_STATUS_BLOCK_64 io_status_block; if ((VMI_SUCCESS != vmi_read(vmi, &ctx, sizeof(io_status_block), &io_status_block, NULL))) goto err; isb_status = io_status_block.status; isb_size = io_status_block.info; } if ( STATUS_SUCCESS == status && isb_size ) { if (injector->curr_sequence_number < 0) injector->curr_sequence_number = ++f->sequence_number; const int curr_sequence_number = injector->curr_sequence_number; void* buffer = g_malloc0(isb_size); ctx.addr = injector->ntreadfile_info.out; if (VMI_FAILURE == vmi_read(vmi, &ctx, isb_size, buffer, NULL)) { g_free(buffer); goto err; } bool success = save_file_chunk(f, curr_sequence_number, buffer, isb_size); g_free(buffer); if (!success) goto err; injector->ntreadfile_info.bytes_read += isb_size; if (BYTES_TO_READ == isb_size) { if (inject_readfile(drakvuf, info, vmi, injector)) { response = VMI_EVENT_RESPONSE_SET_REGISTERS; goto done; } else { goto err; } } else { auto filesize = injector->ntreadfile_info.bytes_read; print_extraction_information(f, drakvuf, info, filename.c_str(), filesize, injector->fo_flags, curr_sequence_number); save_file_metadata(f, info, curr_sequence_number, 0, filename.c_str(), filesize, injector->fo_flags); } } else if (STATUS_END_OF_FILE != status) { if (injector->ntreadfile_info.bytes_read) save_file_metadata(f, info, injector->curr_sequence_number, 0, filename.c_str(), injector->ntreadfile_info.bytes_read, injector->fo_flags, status); PRINT_DEBUG("[FILEDELETE2] [ReadFile] Failed to read %s with status 0x%lx and IO_STATUS_BLOCK = { Status 0x%x; Size 0x%lx} \n", f->files[std::make_pair(info->proc_data.pid, injector->handle)].c_str(), info->regs->rax, isb_status, isb_size); goto err; } is_success = true; goto handled; err: PRINT_DEBUG("[FILEDELETE2] [ReadFile] Error. Stop processing (CR3 0x%lx, TID %d, FileName '%s', status 0x%lx).\n", info->regs->cr3, thread_id, f->files[std::make_pair(info->proc_data.pid, injector->handle)].c_str(), info->regs->rax); handled: response = finish_readfile(drakvuf, info, vmi, is_success); done: drakvuf_release_vmi(drakvuf); return response; } event_response_t exallocatepool_cb(drakvuf_t drakvuf, drakvuf_trap_info_t* info) { wrapper_t* injector = (wrapper_t*)info->trap->data; auto response = 0; uint32_t thread_id = 0; vmi_instance_t vmi = drakvuf_lock_and_get_vmi(drakvuf); if (info->regs->cr3 != injector->target_cr3) goto done; if ( !drakvuf_get_current_thread_id(drakvuf, info, &thread_id) || !injector->target_thread_id || thread_id != injector->target_thread_id ) goto done; if (info->regs->rax) { injector->f->pools[info->regs->rax] = true; injector->pool = info->regs->rax; if (inject_readfile(drakvuf, info, vmi, injector)) { response = VMI_EVENT_RESPONSE_SET_REGISTERS; goto done; } else { goto err; } } err: PRINT_DEBUG("[FILEDELETE2] [ExAllocatePoolWithTag] Error. Stop processing (CR3 0x%lx, TID %d).\n", info->regs->cr3, thread_id); response = finish_readfile(drakvuf, info, vmi, false); done: drakvuf_release_vmi(drakvuf); return response; }
static void extract_ca_file(filedelete* f, drakvuf_t drakvuf, const drakvuf_trap_info_t* info, vmi_instance_t vmi, addr_t control_area, access_context_t* ctx, const char* filename, uint64_t fo_flags) { addr_t subsection = control_area + f->control_area_size; addr_t segment = 0; addr_t test = 0; addr_t test2 = 0; size_t filesize = 0; /* Check whether subsection points back to the control area */ ctx->addr = control_area + f->offsets[CONTROL_AREA_SEGMENT]; if ( VMI_FAILURE == vmi_read_addr(vmi, ctx, &segment) ) return; ctx->addr = segment + f->offsets[SEGMENT_CONTROLAREA]; if ( VMI_FAILURE == vmi_read_addr(vmi, ctx, &test) || test != control_area ) return; ctx->addr = segment + f->offsets[SEGMENT_SIZEOFSEGMENT]; if ( VMI_FAILURE == vmi_read_64(vmi, ctx, &test) ) return; ctx->addr = segment + f->offsets[SEGMENT_TOTALNUMBEROFPTES]; if ( VMI_FAILURE == vmi_read_32(vmi, ctx, (uint32_t*)&test2) ) return; if ( test != (test2 * 4096) ) return; const int curr_sequence_number = ++f->sequence_number; char* file = NULL; if ( asprintf(&file, "%s/file.%06d.mm", f->dump_folder, curr_sequence_number) < 0 ) return; FILE* fp = fopen(file, "w"); free(file); if (!fp) return; while (subsection) { /* Check whether subsection points back to the control area */ ctx->addr = subsection + f->offsets[SUBSECTION_CONTROLAREA]; if ( VMI_FAILURE == vmi_read_addr(vmi, ctx, &test) || test != control_area ) break; addr_t base = 0; addr_t start = 0; uint32_t ptes = 0; ctx->addr = subsection + f->offsets[SUBSECTION_SUBSECTIONBASE]; if ( VMI_FAILURE == vmi_read_addr(vmi, ctx, &base) ) break; ctx->addr = subsection + f->offsets[SUBSECTION_PTESINSUBSECTION]; if ( VMI_FAILURE == vmi_read_32(vmi, ctx, &ptes) ) break; ctx->addr = subsection + f->offsets[SUBSECTION_STARTINGSECTOR]; if ( VMI_FAILURE == vmi_read_32(vmi, ctx, (uint32_t*)&start) ) break; /* * The offset into the file is stored implicitely * based on the PTE's location within the Subsection. */ addr_t subsection_offset = start * 0x200; addr_t ptecount; for (ptecount=0; ptecount < ptes; ptecount++) { addr_t pteoffset = base + f->mmpte_size * ptecount; addr_t fileoffset = subsection_offset + ptecount * 0x1000; addr_t pte = 0; ctx->addr = pteoffset; if ( VMI_FAILURE == vmi_read(vmi, ctx, f->mmpte_size, &pte, NULL) ) break; if ( ENTRY_PRESENT(1, pte) ) { uint8_t page[4096]; if ( VMI_FAILURE == vmi_read_pa(vmi, VMI_BIT_MASK(12,48) & pte, 4096, &page, NULL) ) continue; if ( !fseek ( fp, fileoffset, SEEK_SET ) ) { if ( fwrite(page, 4096, 1, fp) ) filesize = MAX(filesize, fileoffset + 4096); } } } ctx->addr = subsection + f->offsets[SUBSECTION_NEXTSUBSECTION]; if ( !vmi_read_addr(vmi, ctx, &subsection) ) break; } fclose(fp); print_extraction_information(f, drakvuf, info, filename, filesize, fo_flags, curr_sequence_number); save_file_metadata(f, info, curr_sequence_number, control_area, filename, filesize, fo_flags); }
unicode_string_t * windows_read_unicode_struct( vmi_instance_t vmi, const access_context_t *ctx) { access_context_t _ctx = *ctx; unicode_string_t *us = 0; // return val size_t struct_size = 0; size_t read = 0; addr_t buffer_va = 0; uint16_t buffer_len = 0; if (VMI_PM_IA32E == vmi_get_page_mode(vmi)) { // 64 bit guest win64_unicode_string_t us64 = { 0 }; struct_size = sizeof(us64); // read the UNICODE_STRING struct read = vmi_read(vmi, ctx, &us64, struct_size); if (read != struct_size) { dbprint(VMI_DEBUG_READ, "--%s: failed to read UNICODE_STRING\n",__FUNCTION__); goto out_error; } // if buffer_va = us64.pBuffer; buffer_len = us64.length; } else { win32_unicode_string_t us32 = { 0 }; struct_size = sizeof(us32); // read the UNICODE_STRING struct read = vmi_read(vmi, ctx, &us32, struct_size); if (read != struct_size) { dbprint(VMI_DEBUG_READ, "--%s: failed to read UNICODE_STRING\n",__FUNCTION__); goto out_error; } // if buffer_va = us32.pBuffer; buffer_len = us32.length; } // if-else // allocate the return value us = safe_malloc(sizeof(unicode_string_t)); us->length = buffer_len; us->contents = safe_malloc(sizeof(uint8_t) * (buffer_len + 2)); _ctx.addr = buffer_va; read = vmi_read(vmi, &_ctx, us->contents, us->length); if (read != us->length) { dbprint (VMI_DEBUG_READ, "--%s: failed to read UNICODE_STRING buffer\n",__FUNCTION__); goto out_error; } // if // end with NULL (needed?) us->contents[buffer_len] = 0; us->contents[buffer_len + 1] = 0; us->encoding = "UTF-16"; return us; out_error: if (us) { if (us->contents) { free(us->contents); } free(us); } return 0; }
status_t peparse_get_export_table( vmi_instance_t vmi, const access_context_t *ctx, struct export_table *et, addr_t *export_table_rva, size_t *export_table_size) { // Note: this function assumes a "normal" PE where all the headers are in // the first page of the PE and the field DosHeader.OffsetToPE points to // an address in the first page. access_context_t _ctx = *ctx; addr_t export_header_rva = 0; size_t export_header_size = 0; #define MAX_HEADER_BYTES 1024 // keep under 1 page uint8_t image[MAX_HEADER_BYTES]; if (VMI_FAILURE == peparse_get_image(vmi, ctx, MAX_HEADER_BYTES, image)) { return VMI_FAILURE; } void *optional_header = NULL; uint16_t magic = 0; peparse_assign_headers(image, NULL, NULL, &magic, &optional_header, NULL, NULL); export_header_rva = peparse_get_idd_rva(IMAGE_DIRECTORY_ENTRY_EXPORT, &magic, optional_header, NULL, NULL); export_header_size = peparse_get_idd_size(IMAGE_DIRECTORY_ENTRY_EXPORT, &magic, optional_header, NULL, NULL); if (export_table_rva) { *export_table_rva=export_header_rva; } if (export_table_size) { *export_table_size=export_header_size; } dbprint(VMI_DEBUG_PEPARSE, "--PEParse: DLL base 0x%.16"PRIx64". Export header [RVA] 0x%.16"PRIx64". Size %" PRIu64 ".\n", ctx->addr, export_header_rva, export_header_size); _ctx.addr = ctx->addr + export_header_rva; if ( VMI_FAILURE == vmi_read(vmi, &_ctx, sizeof(struct export_table), et, NULL) ) { dbprint(VMI_DEBUG_PEPARSE, "--PEParse: failed to map export header\n"); /* * Sometimes Windows maps the export table on page-boundaries, * such that the first export_flags field (which is reserved) is. * not actually accessible (the page is not mapped). See Issue #260. */ if (!((_ctx.addr+4) & 0xfff)) { dbprint(VMI_DEBUG_PEPARSE, "--PEParse: export table is mapped on page boundary\n"); _ctx.addr += 4; if ( VMI_FAILURE == vmi_read(vmi, &_ctx, sizeof(struct export_table)-4, (void*)((char*)et+4), NULL) ) { dbprint(VMI_DEBUG_PEPARSE, "--PEParse: still failed to map export header\n"); return VMI_FAILURE; } // Manually set the reserved field to zero in this case et->export_flags = 0; } else { return VMI_FAILURE; } } /* sanity check */ if (et->export_flags || !et->name) { dbprint(VMI_DEBUG_PEPARSE, "--PEParse: bad export directory table\n"); return VMI_FAILURE; } return VMI_SUCCESS; }